1 #include "builtin.h"
2 #include "parse-options.h"
3 
4 #ifndef NO_UNIX_SOCKETS
5 
6 #include "credential.h"
7 #include "string-list.h"
8 #include "unix-socket.h"
9 #include "run-command.h"
10 
11 #define FLAG_SPAWN 0x1
12 #define FLAG_RELAY 0x2
13 
14 #ifdef GIT_WINDOWS_NATIVE
15 
connection_closed(int error)16 static int connection_closed(int error)
17 {
18 	return (error == EINVAL);
19 }
20 
connection_fatally_broken(int error)21 static int connection_fatally_broken(int error)
22 {
23 	return (error != ENOENT) && (error != ENETDOWN);
24 }
25 
26 #else
27 
connection_closed(int error)28 static int connection_closed(int error)
29 {
30 	return (error == ECONNRESET);
31 }
32 
connection_fatally_broken(int error)33 static int connection_fatally_broken(int error)
34 {
35 	return (error != ENOENT) && (error != ECONNREFUSED);
36 }
37 
38 #endif
39 
send_request(const char * socket,const struct strbuf * out)40 static int send_request(const char *socket, const struct strbuf *out)
41 {
42 	int got_data = 0;
43 	int fd = unix_stream_connect(socket, 0);
44 
45 	if (fd < 0)
46 		return -1;
47 
48 	if (write_in_full(fd, out->buf, out->len) < 0)
49 		die_errno("unable to write to cache daemon");
50 	shutdown(fd, SHUT_WR);
51 
52 	while (1) {
53 		char in[1024];
54 		int r;
55 
56 		r = read_in_full(fd, in, sizeof(in));
57 		if (r == 0 || (r < 0 && connection_closed(errno)))
58 			break;
59 		if (r < 0)
60 			die_errno("read error from cache daemon");
61 		write_or_die(1, in, r);
62 		got_data = 1;
63 	}
64 	close(fd);
65 	return got_data;
66 }
67 
spawn_daemon(const char * socket)68 static void spawn_daemon(const char *socket)
69 {
70 	struct child_process daemon = CHILD_PROCESS_INIT;
71 	char buf[128];
72 	int r;
73 
74 	strvec_pushl(&daemon.args,
75 		     "credential-cache--daemon", socket,
76 		     NULL);
77 	daemon.git_cmd = 1;
78 	daemon.no_stdin = 1;
79 	daemon.out = -1;
80 
81 	if (start_command(&daemon))
82 		die_errno("unable to start cache daemon");
83 	r = read_in_full(daemon.out, buf, sizeof(buf));
84 	if (r < 0)
85 		die_errno("unable to read result code from cache daemon");
86 	if (r != 3 || memcmp(buf, "ok\n", 3))
87 		die("cache daemon did not start: %.*s", r, buf);
88 	close(daemon.out);
89 }
90 
do_cache(const char * socket,const char * action,int timeout,int flags)91 static void do_cache(const char *socket, const char *action, int timeout,
92 		     int flags)
93 {
94 	struct strbuf buf = STRBUF_INIT;
95 
96 	strbuf_addf(&buf, "action=%s\n", action);
97 	strbuf_addf(&buf, "timeout=%d\n", timeout);
98 	if (flags & FLAG_RELAY) {
99 		if (strbuf_read(&buf, 0, 0) < 0)
100 			die_errno("unable to relay credential");
101 	}
102 
103 	if (send_request(socket, &buf) < 0) {
104 		if (connection_fatally_broken(errno))
105 			die_errno("unable to connect to cache daemon");
106 		if (flags & FLAG_SPAWN) {
107 			spawn_daemon(socket);
108 			if (send_request(socket, &buf) < 0)
109 				die_errno("unable to connect to cache daemon");
110 		}
111 	}
112 	strbuf_release(&buf);
113 }
114 
get_socket_path(void)115 static char *get_socket_path(void)
116 {
117 	struct stat sb;
118 	char *old_dir, *socket;
119 	old_dir = interpolate_path("~/.git-credential-cache", 0);
120 	if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode))
121 		socket = xstrfmt("%s/socket", old_dir);
122 	else
123 		socket = xdg_cache_home("credential/socket");
124 	free(old_dir);
125 	return socket;
126 }
127 
cmd_credential_cache(int argc,const char ** argv,const char * prefix)128 int cmd_credential_cache(int argc, const char **argv, const char *prefix)
129 {
130 	char *socket_path = NULL;
131 	int timeout = 900;
132 	const char *op;
133 	const char * const usage[] = {
134 		"git credential-cache [<options>] <action>",
135 		NULL
136 	};
137 	struct option options[] = {
138 		OPT_INTEGER(0, "timeout", &timeout,
139 			    "number of seconds to cache credentials"),
140 		OPT_STRING(0, "socket", &socket_path, "path",
141 			   "path of cache-daemon socket"),
142 		OPT_END()
143 	};
144 
145 	argc = parse_options(argc, argv, prefix, options, usage, 0);
146 	if (!argc)
147 		usage_with_options(usage, options);
148 	op = argv[0];
149 
150 	if (!socket_path)
151 		socket_path = get_socket_path();
152 	if (!socket_path)
153 		die("unable to find a suitable socket path; use --socket");
154 
155 	if (!strcmp(op, "exit"))
156 		do_cache(socket_path, op, timeout, 0);
157 	else if (!strcmp(op, "get") || !strcmp(op, "erase"))
158 		do_cache(socket_path, op, timeout, FLAG_RELAY);
159 	else if (!strcmp(op, "store"))
160 		do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);
161 	else
162 		; /* ignore unknown operation */
163 
164 	return 0;
165 }
166 
167 #else
168 
cmd_credential_cache(int argc,const char ** argv,const char * prefix)169 int cmd_credential_cache(int argc, const char **argv, const char *prefix)
170 {
171 	const char * const usage[] = {
172 		"git credential-cache [options] <action>",
173 		"",
174 		"credential-cache is disabled in this build of Git",
175 		NULL
176 	};
177 	struct option options[] = { OPT_END() };
178 
179 	argc = parse_options(argc, argv, prefix, options, usage, 0);
180 	die(_("credential-cache unavailable; no unix socket support"));
181 }
182 
183 #endif /* NO_UNIX_SOCKETS */
184