credentials: add "cache" helper
If you access repositories over smart-http using http authentication, then it can be annoying to have git ask you for your password repeatedly. We cache credentials in memory, of course, but git is composed of many small programs. Having to input your password for each one can be frustrating. This patch introduces a credential helper that will cache passwords in memory for a short period of time. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jeff King committed
Dec 10, 2011 at 05:34 UTC
e2770979fec968a25ac21e34f9082bc17a71a780
12 files changed
+812
-5
.gitignore
+2
@@ -30,6 +30,8 @@
30
/git-commit-tree
31
/git-config
32
/git-count-objects
33
+/git-credential-cache
34
+/git-credential-cache--daemon
35
/git-cvsexportcommit
36
/git-cvsimport
37
/git-cvsserver
Documentation/git-credential-cache--daemon.txt
new
+26
@@ -0,0 +1,26 @@
1
+git-credential-cache--daemon(1)
2
+===============================
3
+
4
+NAME
5
+----
6
+git-credential-cache--daemon - temporarily store user credentials in memory
7
+
8
+SYNOPSIS
9
+--------
10
+[verse]
11
+git credential-cache--daemon <socket>
12
+
13
+DESCRIPTION
14
+-----------
15
+
16
+NOTE: You probably don't want to invoke this command yourself; it is
17
+started automatically when you use linkgit:git-credential-cache[1].
18
+
19
+This command listens on the Unix domain socket specified by `<socket>`
20
+for `git-credential-cache` clients. Clients may store and retrieve
21
+credentials. Each credential is held for a timeout specified by the
22
+client; once no credentials are held, the daemon exits.
23
+
24
+GIT
25
+---
26
+Part of the linkgit:git[1] suite
Documentation/git-credential-cache.txt
new
+77
@@ -0,0 +1,77 @@
1
+git-credential-cache(1)
2
+=======================
3
+
4
+NAME
5
+----
6
+git-credential-cache - helper to temporarily store passwords in memory
7
+
8
+SYNOPSIS
9
+--------
10
+-----------------------------
11
+git config credential.helper 'cache [options]'
12
+-----------------------------
13
+
14
+DESCRIPTION
15
+-----------
16
+
17
+This command caches credentials in memory for use by future git
18
+programs. The stored credentials never touch the disk, and are forgotten
19
+after a configurable timeout. The cache is accessible over a Unix
20
+domain socket, restricted to the current user by filesystem permissions.
21
+
22
+You probably don't want to invoke this command directly; it is meant to
23
+be used as a credential helper by other parts of git. See
24
+linkgit:gitcredentials[7] or `EXAMPLES` below.
25
+
26
+OPTIONS
27
+-------
28
+
29
+--timeout <seconds>::
30
+
31
+ Number of seconds to cache credentials (default: 900).
32
+
33
+--socket <path>::
34
+
35
+ Use `<path>` to contact a running cache daemon (or start a new
36
+ cache daemon if one is not started). Defaults to
37
+ `~/.git-credential-cache/socket`. If your home directory is on a
38
+ network-mounted filesystem, you may need to change this to a
39
+ local filesystem.
40
+
41
+CONTROLLING THE DAEMON
42
+----------------------
43
+
44
+If you would like the daemon to exit early, forgetting all cached
45
+credentials before their timeout, you can issue an `exit` action:
46
+
47
+--------------------------------------
48
+git credential-cache exit
49
+--------------------------------------
50
+
51
+EXAMPLES
52
+--------
53
+
54
+The point of this helper is to reduce the number of times you must type
55
+your username or password. For example:
56
+
57
+------------------------------------
58
+$ git config credential.helper cache
59
+$ git push http://example.com/repo.git
60
+Username: <type your username>
61
+Password: <type your password>
62
+
63
+[work for 5 more minutes]
64
+$ git push http://example.com/repo.git
65
+[your credentials are used automatically]
66
+------------------------------------
67
+
68
+You can provide options via the credential.helper configuration
69
+variable (this example drops the cache time to 5 minutes):
70
+
71
+-------------------------------------------------------
72
+$ git config credential.helper 'cache --timeout=300'
73
+-------------------------------------------------------
74
+
75
+GIT
76
+---
77
+Part of the linkgit:git[1] suite
Documentation/gitcredentials.txt
+12
-5
@@ -63,11 +63,18 @@ Credential helpers, on the other hand, are external programs from which git can
63
request both usernames and passwords; they typically interface with secure
64
storage provided by the OS or other programs.
65
66
-To use a helper, you must first select one to use. Git does not yet
67
-include any credential helpers, but you may have third-party helpers
68
-installed; search for `credential-*` in the output of `git help -a`, and
69
-consult the documentation of individual helpers. Once you have selected
70
-a helper, you can tell git to use it by putting its name into the
66
+To use a helper, you must first select one to use. Git currently
67
+includes the following helpers:
68
+
69
+cache::
70
+
71
+ Cache credentials in memory for a short period of time. See
72
+ linkgit:git-credential-cache[1] for details.
73
+
74
+You may also have third-party helpers installed; search for
75
+`credential-*` in the output of `git help -a`, and consult the
76
+documentation of individual helpers. Once you have selected a helper,
77
+you can tell git to use it by putting its name into the
78
credential.helper variable.
79
80
1. Find a helper.
Makefile
+3
@@ -427,6 +427,8 @@ PROGRAM_OBJS += show-index.o
427
PROGRAM_OBJS += upload-pack.o
428
PROGRAM_OBJS += http-backend.o
429
PROGRAM_OBJS += sh-i18n--envsubst.o
430
+PROGRAM_OBJS += credential-cache.o
431
+PROGRAM_OBJS += credential-cache--daemon.o
432
433
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
434
@@ -699,6 +701,7 @@ LIB_OBJS += transport-helper.o
701
LIB_OBJS += tree-diff.o
702
LIB_OBJS += tree.o
703
LIB_OBJS += tree-walk.o
704
+LIB_OBJS += unix-socket.o
705
LIB_OBJS += unpack-trees.o
706
LIB_OBJS += url.o
707
LIB_OBJS += usage.o
credential-cache--daemon.c
new
+269
@@ -0,0 +1,269 @@
1
+#include "cache.h"
2
+#include "credential.h"
3
+#include "unix-socket.h"
4
+#include "sigchain.h"
5
+
6
+static const char *socket_path;
7
+
8
+static void cleanup_socket(void)
9
+{
10
+ if (socket_path)
11
+ unlink(socket_path);
12
+}
13
+
14
+static void cleanup_socket_on_signal(int sig)
15
+{
16
+ cleanup_socket();
17
+ sigchain_pop(sig);
18
+ raise(sig);
19
+}
20
+
21
+struct credential_cache_entry {
22
+ struct credential item;
23
+ unsigned long expiration;
24
+};
25
+static struct credential_cache_entry *entries;
26
+static int entries_nr;
27
+static int entries_alloc;
28
+
29
+static void cache_credential(struct credential *c, int timeout)
30
+{
31
+ struct credential_cache_entry *e;
32
+
33
+ ALLOC_GROW(entries, entries_nr + 1, entries_alloc);
34
+ e = &entries[entries_nr++];
35
+
36
+ /* take ownership of pointers */
37
+ memcpy(&e->item, c, sizeof(*c));
38
+ memset(c, 0, sizeof(*c));
39
+ e->expiration = time(NULL) + timeout;
40
+}
41
+
42
+static struct credential_cache_entry *lookup_credential(const struct credential *c)
43
+{
44
+ int i;
45
+ for (i = 0; i < entries_nr; i++) {
46
+ struct credential *e = &entries[i].item;
47
+ if (credential_match(c, e))
48
+ return &entries[i];
49
+ }
50
+ return NULL;
51
+}
52
+
53
+static void remove_credential(const struct credential *c)
54
+{
55
+ struct credential_cache_entry *e;
56
+
57
+ e = lookup_credential(c);
58
+ if (e)
59
+ e->expiration = 0;
60
+}
61
+
62
+static int check_expirations(void)
63
+{
64
+ static unsigned long wait_for_entry_until;
65
+ int i = 0;
66
+ unsigned long now = time(NULL);
67
+ unsigned long next = (unsigned long)-1;
68
+
69
+ /*
70
+ * Initially give the client 30 seconds to actually contact us
71
+ * and store a credential before we decide there's no point in
72
+ * keeping the daemon around.
73
+ */
74
+ if (!wait_for_entry_until)
75
+ wait_for_entry_until = now + 30;
76
+
77
+ while (i < entries_nr) {
78
+ if (entries[i].expiration <= now) {
79
+ entries_nr--;
80
+ credential_clear(&entries[i].item);
81
+ if (i != entries_nr)
82
+ memcpy(&entries[i], &entries[entries_nr], sizeof(*entries));
83
+ /*
84
+ * Stick around 30 seconds in case a new credential
85
+ * shows up (e.g., because we just removed a failed
86
+ * one, and we will soon get the correct one).
87
+ */
88
+ wait_for_entry_until = now + 30;
89
+ }
90
+ else {
91
+ if (entries[i].expiration < next)
92
+ next = entries[i].expiration;
93
+ i++;
94
+ }
95
+ }
96
+
97
+ if (!entries_nr) {
98
+ if (wait_for_entry_until <= now)
99
+ return 0;
100
+ next = wait_for_entry_until;
101
+ }
102
+
103
+ return next - now;
104
+}
105
+
106
+static int read_request(FILE *fh, struct credential *c,
107
+ struct strbuf *action, int *timeout) {
108
+ static struct strbuf item = STRBUF_INIT;
109
+ const char *p;
110
+
111
+ strbuf_getline(&item, fh, '\n');
112
+ p = skip_prefix(item.buf, "action=");
113
+ if (!p)
114
+ return error("client sent bogus action line: %s", item.buf);
115
+ strbuf_addstr(action, p);
116
+
117
+ strbuf_getline(&item, fh, '\n');
118
+ p = skip_prefix(item.buf, "timeout=");
119
+ if (!p)
120
+ return error("client sent bogus timeout line: %s", item.buf);
121
+ *timeout = atoi(p);
122
+
123
+ if (credential_read(c, fh) < 0)
124
+ return -1;
125
+ return 0;
126
+}
127
+
128
+static void serve_one_client(FILE *in, FILE *out)
129
+{
130
+ struct credential c = CREDENTIAL_INIT;
131
+ struct strbuf action = STRBUF_INIT;
132
+ int timeout = -1;
133
+
134
+ if (read_request(in, &c, &action, &timeout) < 0)
135
+ /* ignore error */ ;
136
+ else if (!strcmp(action.buf, "get")) {
137
+ struct credential_cache_entry *e = lookup_credential(&c);
138
+ if (e) {
139
+ fprintf(out, "username=%s\n", e->item.username);
140
+ fprintf(out, "password=%s\n", e->item.password);
141
+ }
142
+ }
143
+ else if (!strcmp(action.buf, "exit"))
144
+ exit(0);
145
+ else if (!strcmp(action.buf, "erase"))
146
+ remove_credential(&c);
147
+ else if (!strcmp(action.buf, "store")) {
148
+ if (timeout < 0)
149
+ warning("cache client didn't specify a timeout");
150
+ else if (!c.username || !c.password)
151
+ warning("cache client gave us a partial credential");
152
+ else {
153
+ remove_credential(&c);
154
+ cache_credential(&c, timeout);
155
+ }
156
+ }
157
+ else
158
+ warning("cache client sent unknown action: %s", action.buf);
159
+
160
+ credential_clear(&c);
161
+ strbuf_release(&action);
162
+}
163
+
164
+static int serve_cache_loop(int fd)
165
+{
166
+ struct pollfd pfd;
167
+ unsigned long wakeup;
168
+
169
+ wakeup = check_expirations();
170
+ if (!wakeup)
171
+ return 0;
172
+
173
+ pfd.fd = fd;
174
+ pfd.events = POLLIN;
175
+ if (poll(&pfd, 1, 1000 * wakeup) < 0) {
176
+ if (errno != EINTR)
177
+ die_errno("poll failed");
178
+ return 1;
179
+ }
180
+
181
+ if (pfd.revents & POLLIN) {
182
+ int client, client2;
183
+ FILE *in, *out;
184
+
185
+ client = accept(fd, NULL, NULL);
186
+ if (client < 0) {
187
+ warning("accept failed: %s", strerror(errno));
188
+ return 1;
189
+ }
190
+ client2 = dup(client);
191
+ if (client2 < 0) {
192
+ warning("dup failed: %s", strerror(errno));
193
+ close(client);
194
+ return 1;
195
+ }
196
+
197
+ in = xfdopen(client, "r");
198
+ out = xfdopen(client2, "w");
199
+ serve_one_client(in, out);
200
+ fclose(in);
201
+ fclose(out);
202
+ }
203
+ return 1;
204
+}
205
+
206
+static void serve_cache(const char *socket_path)
207
+{
208
+ int fd;
209
+
210
+ fd = unix_stream_listen(socket_path);
211
+ if (fd < 0)
212
+ die_errno("unable to bind to '%s'", socket_path);
213
+
214
+ printf("ok\n");
215
+ fclose(stdout);
216
+
217
+ while (serve_cache_loop(fd))
218
+ ; /* nothing */
219
+
220
+ close(fd);
221
+ unlink(socket_path);
222
+}
223
+
224
+static const char permissions_advice[] =
225
+"The permissions on your socket directory are too loose; other\n"
226
+"users may be able to read your cached credentials. Consider running:\n"
227
+"\n"
228
+" chmod 0700 %s";
229
+static void check_socket_directory(const char *path)
230
+{
231
+ struct stat st;
232
+ char *path_copy = xstrdup(path);
233
+ char *dir = dirname(path_copy);
234
+
235
+ if (!stat(dir, &st)) {
236
+ if (st.st_mode & 077)
237
+ die(permissions_advice, dir);
238
+ free(path_copy);
239
+ return;
240
+ }
241
+
242
+ /*
243
+ * We must be sure to create the directory with the correct mode,
244
+ * not just chmod it after the fact; otherwise, there is a race
245
+ * condition in which somebody can chdir to it, sleep, then try to open
246
+ * our protected socket.
247
+ */
248
+ if (safe_create_leading_directories_const(dir) < 0)
249
+ die_errno("unable to create directories for '%s'", dir);
250
+ if (mkdir(dir, 0700) < 0)
251
+ die_errno("unable to mkdir '%s'", dir);
252
+ free(path_copy);
253
+}
254
+
255
+int main(int argc, const char **argv)
256
+{
257
+ socket_path = argv[1];
258
+
259
+ if (!socket_path)
260
+ die("usage: git-credential-cache--daemon <socket_path>");
261
+ check_socket_directory(socket_path);
262
+
263
+ atexit(cleanup_socket);
264
+ sigchain_push_common(cleanup_socket_on_signal);
265
+
266
+ serve_cache(socket_path);
267
+
268
+ return 0;
269
+}
credential-cache.c
new
+120
@@ -0,0 +1,120 @@
1
+#include "cache.h"
2
+#include "credential.h"
3
+#include "string-list.h"
4
+#include "parse-options.h"
5
+#include "unix-socket.h"
6
+#include "run-command.h"
7
+
8
+#define FLAG_SPAWN 0x1
9
+#define FLAG_RELAY 0x2
10
+
11
+static int send_request(const char *socket, const struct strbuf *out)
12
+{
13
+ int got_data = 0;
14
+ int fd = unix_stream_connect(socket);
15
+
16
+ if (fd < 0)
17
+ return -1;
18
+
19
+ if (write_in_full(fd, out->buf, out->len) < 0)
20
+ die_errno("unable to write to cache daemon");
21
+ shutdown(fd, SHUT_WR);
22
+
23
+ while (1) {
24
+ char in[1024];
25
+ int r;
26
+
27
+ r = read_in_full(fd, in, sizeof(in));
28
+ if (r == 0)
29
+ break;
30
+ if (r < 0)
31
+ die_errno("read error from cache daemon");
32
+ write_or_die(1, in, r);
33
+ got_data = 1;
34
+ }
35
+ return got_data;
36
+}
37
+
38
+static void spawn_daemon(const char *socket)
39
+{
40
+ struct child_process daemon;
41
+ const char *argv[] = { NULL, NULL, NULL };
42
+ char buf[128];
43
+ int r;
44
+
45
+ memset(&daemon, 0, sizeof(daemon));
46
+ argv[0] = "git-credential-cache--daemon";
47
+ argv[1] = socket;
48
+ daemon.argv = argv;
49
+ daemon.no_stdin = 1;
50
+ daemon.out = -1;
51
+
52
+ if (start_command(&daemon))
53
+ die_errno("unable to start cache daemon");
54
+ r = read_in_full(daemon.out, buf, sizeof(buf));
55
+ if (r < 0)
56
+ die_errno("unable to read result code from cache daemon");
57
+ if (r != 3 || memcmp(buf, "ok\n", 3))
58
+ die("cache daemon did not start: %.*s", r, buf);
59
+ close(daemon.out);
60
+}
61
+
62
+static void do_cache(const char *socket, const char *action, int timeout,
63
+ int flags)
64
+{
65
+ struct strbuf buf = STRBUF_INIT;
66
+
67
+ strbuf_addf(&buf, "action=%s\n", action);
68
+ strbuf_addf(&buf, "timeout=%d\n", timeout);
69
+ if (flags & FLAG_RELAY) {
70
+ if (strbuf_read(&buf, 0, 0) < 0)
71
+ die_errno("unable to relay credential");
72
+ }
73
+
74
+ if (!send_request(socket, &buf))
75
+ return;
76
+ if (flags & FLAG_SPAWN) {
77
+ spawn_daemon(socket);
78
+ send_request(socket, &buf);
79
+ }
80
+ strbuf_release(&buf);
81
+}
82
+
83
+int main(int argc, const char **argv)
84
+{
85
+ char *socket_path = NULL;
86
+ int timeout = 900;
87
+ const char *op;
88
+ const char * const usage[] = {
89
+ "git credential-cache [options] <action>",
90
+ NULL
91
+ };
92
+ struct option options[] = {
93
+ OPT_INTEGER(0, "timeout", &timeout,
94
+ "number of seconds to cache credentials"),
95
+ OPT_STRING(0, "socket", &socket_path, "path",
96
+ "path of cache-daemon socket"),
97
+ OPT_END()
98
+ };
99
+
100
+ argc = parse_options(argc, argv, NULL, options, usage, 0);
101
+ if (!argc)
102
+ usage_with_options(usage, options);
103
+ op = argv[0];
104
+
105
+ if (!socket_path)
106
+ socket_path = expand_user_path("~/.git-credential-cache/socket");
107
+ if (!socket_path)
108
+ die("unable to find a suitable socket path; use --socket");
109
+
110
+ if (!strcmp(op, "exit"))
111
+ do_cache(socket_path, op, timeout, 0);
112
+ else if (!strcmp(op, "get") || !strcmp(op, "erase"))
113
+ do_cache(socket_path, op, timeout, FLAG_RELAY);
114
+ else if (!strcmp(op, "store"))
115
+ do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);
116
+ else
117
+ ; /* ignore unknown operation */
118
+
119
+ return 0;
120
+}
git-compat-util.h
+1
@@ -130,6 +130,7 @@
130
#include <arpa/inet.h>
131
#include <netdb.h>
132
#include <pwd.h>
133
+#include <sys/un.h>
134
#ifndef NO_INTTYPES_H
135
#include <inttypes.h>
136
#else
t/lib-credential.sh
+221
@@ -21,6 +21,227 @@ read_chunk() {
21
done
22
}
23
24
+# Clear any residual data from previous tests. We only
25
+# need this when testing third-party helpers which read and
26
+# write outside of our trash-directory sandbox.
27
+#
28
+# Don't bother checking for success here, as it is
29
+# outside the scope of tests and represents a best effort to
30
+# clean up after ourselves.
31
+helper_test_clean() {
32
+ reject $1 https example.com store-user
33
+ reject $1 https example.com user1
34
+ reject $1 https example.com user2
35
+ reject $1 http path.tld user
36
+ reject $1 https timeout.tld user
37
+}
38
+
39
+reject() {
40
+ (
41
+ echo protocol=$2
42
+ echo host=$3
43
+ echo username=$4
44
+ ) | test-credential reject $1
45
+}
46
+
47
+helper_test() {
48
+ HELPER=$1
49
+
50
+ test_expect_success "helper ($HELPER) has no existing data" '
51
+ check fill $HELPER <<-\EOF
52
+ protocol=https
53
+ host=example.com
54
+ --
55
+ username=askpass-username
56
+ password=askpass-password
57
+ --
58
+ askpass: Username for '\''https://example.com'\'':
59
+ askpass: Password for '\''https://askpass-username@example.com'\'':
60
+ EOF
61
+ '
62
+
63
+ test_expect_success "helper ($HELPER) stores password" '
64
+ check approve $HELPER <<-\EOF
65
+ protocol=https
66
+ host=example.com
67
+ username=store-user
68
+ password=store-pass
69
+ EOF
70
+ '
71
+
72
+ test_expect_success "helper ($HELPER) can retrieve password" '
73
+ check fill $HELPER <<-\EOF
74
+ protocol=https
75
+ host=example.com
76
+ --
77
+ username=store-user
78
+ password=store-pass
79
+ --
80
+ EOF
81
+ '
82
+
83
+ test_expect_success "helper ($HELPER) requires matching protocol" '
84
+ check fill $HELPER <<-\EOF
85
+ protocol=http
86
+ host=example.com
87
+ --
88
+ username=askpass-username
89
+ password=askpass-password
90
+ --
91
+ askpass: Username for '\''http://example.com'\'':
92
+ askpass: Password for '\''http://askpass-username@example.com'\'':
93
+ EOF
94
+ '
95
+
96
+ test_expect_success "helper ($HELPER) requires matching host" '
97
+ check fill $HELPER <<-\EOF
98
+ protocol=https
99
+ host=other.tld
100
+ --
101
+ username=askpass-username
102
+ password=askpass-password
103
+ --
104
+ askpass: Username for '\''https://other.tld'\'':
105
+ askpass: Password for '\''https://askpass-username@other.tld'\'':
106
+ EOF
107
+ '
108
+
109
+ test_expect_success "helper ($HELPER) requires matching username" '
110
+ check fill $HELPER <<-\EOF
111
+ protocol=https
112
+ host=example.com
113
+ username=other
114
+ --
115
+ username=other
116
+ password=askpass-password
117
+ --
118
+ askpass: Password for '\''https://other@example.com'\'':
119
+ EOF
120
+ '
121
+
122
+ test_expect_success "helper ($HELPER) requires matching path" '
123
+ test_config credential.usehttppath true &&
124
+ check approve $HELPER <<-\EOF &&
125
+ protocol=http
126
+ host=path.tld
127
+ path=foo.git
128
+ username=user
129
+ password=pass
130
+ EOF
131
+ check fill $HELPER <<-\EOF
132
+ protocol=http
133
+ host=path.tld
134
+ path=bar.git
135
+ --
136
+ username=askpass-username
137
+ password=askpass-password
138
+ --
139
+ askpass: Username for '\''http://path.tld/bar.git'\'':
140
+ askpass: Password for '\''http://askpass-username@path.tld/bar.git'\'':
141
+ EOF
142
+ '
143
+
144
+ test_expect_success "helper ($HELPER) can forget host" '
145
+ check reject $HELPER <<-\EOF &&
146
+ protocol=https
147
+ host=example.com
148
+ EOF
149
+ check fill $HELPER <<-\EOF
150
+ protocol=https
151
+ host=example.com
152
+ --
153
+ username=askpass-username
154
+ password=askpass-password
155
+ --
156
+ askpass: Username for '\''https://example.com'\'':
157
+ askpass: Password for '\''https://askpass-username@example.com'\'':
158
+ EOF
159
+ '
160
+
161
+ test_expect_success "helper ($HELPER) can store multiple users" '
162
+ check approve $HELPER <<-\EOF &&
163
+ protocol=https
164
+ host=example.com
165
+ username=user1
166
+ password=pass1
167
+ EOF
168
+ check approve $HELPER <<-\EOF &&
169
+ protocol=https
170
+ host=example.com
171
+ username=user2
172
+ password=pass2
173
+ EOF
174
+ check fill $HELPER <<-\EOF &&
175
+ protocol=https
176
+ host=example.com
177
+ username=user1
178
+ --
179
+ username=user1
180
+ password=pass1
181
+ EOF
182
+ check fill $HELPER <<-\EOF
183
+ protocol=https
184
+ host=example.com
185
+ username=user2
186
+ --
187
+ username=user2
188
+ password=pass2
189
+ EOF
190
+ '
191
+
192
+ test_expect_success "helper ($HELPER) can forget user" '
193
+ check reject $HELPER <<-\EOF &&
194
+ protocol=https
195
+ host=example.com
196
+ username=user1
197
+ EOF
198
+ check fill $HELPER <<-\EOF
199
+ protocol=https
200
+ host=example.com
201
+ username=user1
202
+ --
203
+ username=user1
204
+ password=askpass-password
205
+ --
206
+ askpass: Password for '\''https://user1@example.com'\'':
207
+ EOF
208
+ '
209
+
210
+ test_expect_success "helper ($HELPER) remembers other user" '
211
+ check fill $HELPER <<-\EOF
212
+ protocol=https
213
+ host=example.com
214
+ username=user2
215
+ --
216
+ username=user2
217
+ password=pass2
218
+ EOF
219
+ '
220
+}
221
+
222
+helper_test_timeout() {
223
+ HELPER="$*"
224
+
225
+ test_expect_success "helper ($HELPER) times out" '
226
+ check approve "$HELPER" <<-\EOF &&
227
+ protocol=https
228
+ host=timeout.tld
229
+ username=user
230
+ password=pass
231
+ EOF
232
+ sleep 2 &&
233
+ check fill "$HELPER" <<-\EOF
234
+ protocol=https
235
+ host=timeout.tld
236
+ --
237
+ username=askpass-username
238
+ password=askpass-password
239
+ --
240
+ askpass: Username for '\''https://timeout.tld'\'':
241
+ askpass: Password for '\''https://askpass-username@timeout.tld'\'':
242
+ EOF
243
+ '
244
+}
245
246
cat >askpass <<\EOF
247
#!/bin/sh
t/t0301-credential-cache.sh
new
+18
@@ -0,0 +1,18 @@
1
+#!/bin/sh
2
+
3
+test_description='credential-cache tests'
4
+. ./test-lib.sh
5
+. "$TEST_DIRECTORY"/lib-credential.sh
6
+
7
+# don't leave a stale daemon running
8
+trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT
9
+
10
+helper_test cache
11
+helper_test_timeout cache --timeout=1
12
+
13
+# we can't rely on our "trap" above working after test_done,
14
+# as test_done will delete the trash directory containing
15
+# our socket, leaving us with no way to access the daemon.
16
+git credential-cache exit
17
+
18
+test_done
unix-socket.c
new
+56
@@ -0,0 +1,56 @@
1
+#include "cache.h"
2
+#include "unix-socket.h"
3
+
4
+static int unix_stream_socket(void)
5
+{
6
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
7
+ if (fd < 0)
8
+ die_errno("unable to create socket");
9
+ return fd;
10
+}
11
+
12
+static void unix_sockaddr_init(struct sockaddr_un *sa, const char *path)
13
+{
14
+ int size = strlen(path) + 1;
15
+ if (size > sizeof(sa->sun_path))
16
+ die("socket path is too long to fit in sockaddr");
17
+ memset(sa, 0, sizeof(*sa));
18
+ sa->sun_family = AF_UNIX;
19
+ memcpy(sa->sun_path, path, size);
20
+}
21
+
22
+int unix_stream_connect(const char *path)
23
+{
24
+ int fd;
25
+ struct sockaddr_un sa;
26
+
27
+ unix_sockaddr_init(&sa, path);
28
+ fd = unix_stream_socket();
29
+ if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
30
+ close(fd);
31
+ return -1;
32
+ }
33
+ return fd;
34
+}
35
+
36
+int unix_stream_listen(const char *path)
37
+{
38
+ int fd;
39
+ struct sockaddr_un sa;
40
+
41
+ unix_sockaddr_init(&sa, path);
42
+ fd = unix_stream_socket();
43
+
44
+ unlink(path);
45
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
46
+ close(fd);
47
+ return -1;
48
+ }
49
+
50
+ if (listen(fd, 5) < 0) {
51
+ close(fd);
52
+ return -1;
53
+ }
54
+
55
+ return fd;
56
+}
unix-socket.h
new
+7
@@ -0,0 +1,7 @@
1
+#ifndef UNIX_SOCKET_H
2
+#define UNIX_SOCKET_H
3
+
4
+int unix_stream_connect(const char *path);
5
+int unix_stream_listen(const char *path);
6
+
7
+#endif /* UNIX_SOCKET_H */