1 #include "cache.h"
2 #include "config.h"
3 #include "credential.h"
4 #include "string-list.h"
5 #include "run-command.h"
6 #include "url.h"
7 #include "prompt.h"
8 #include "sigchain.h"
9 #include "urlmatch.h"
10 
credential_init(struct credential * c)11 void credential_init(struct credential *c)
12 {
13 	struct credential blank = CREDENTIAL_INIT;
14 	memcpy(c, &blank, sizeof(*c));
15 }
16 
credential_clear(struct credential * c)17 void credential_clear(struct credential *c)
18 {
19 	free(c->protocol);
20 	free(c->host);
21 	free(c->path);
22 	free(c->username);
23 	free(c->password);
24 	string_list_clear(&c->helpers, 0);
25 
26 	credential_init(c);
27 }
28 
credential_match(const struct credential * want,const struct credential * have)29 int credential_match(const struct credential *want,
30 		     const struct credential *have)
31 {
32 #define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
33 	return CHECK(protocol) &&
34 	       CHECK(host) &&
35 	       CHECK(path) &&
36 	       CHECK(username);
37 #undef CHECK
38 }
39 
40 
41 static int credential_from_potentially_partial_url(struct credential *c,
42 						   const char *url);
43 
credential_config_callback(const char * var,const char * value,void * data)44 static int credential_config_callback(const char *var, const char *value,
45 				      void *data)
46 {
47 	struct credential *c = data;
48 	const char *key;
49 
50 	if (!skip_prefix(var, "credential.", &key))
51 		return 0;
52 
53 	if (!value)
54 		return config_error_nonbool(var);
55 
56 	if (!strcmp(key, "helper")) {
57 		if (*value)
58 			string_list_append(&c->helpers, value);
59 		else
60 			string_list_clear(&c->helpers, 0);
61 	} else if (!strcmp(key, "username")) {
62 		if (!c->username_from_proto) {
63 			free(c->username);
64 			c->username = xstrdup(value);
65 		}
66 	}
67 	else if (!strcmp(key, "usehttppath"))
68 		c->use_http_path = git_config_bool(var, value);
69 
70 	return 0;
71 }
72 
proto_is_http(const char * s)73 static int proto_is_http(const char *s)
74 {
75 	if (!s)
76 		return 0;
77 	return !strcmp(s, "https") || !strcmp(s, "http");
78 }
79 
80 static void credential_describe(struct credential *c, struct strbuf *out);
81 static void credential_format(struct credential *c, struct strbuf *out);
82 
select_all(const struct urlmatch_item * a,const struct urlmatch_item * b)83 static int select_all(const struct urlmatch_item *a,
84 		      const struct urlmatch_item *b)
85 {
86 	return 0;
87 }
88 
match_partial_url(const char * url,void * cb)89 static int match_partial_url(const char *url, void *cb)
90 {
91 	struct credential *c = cb;
92 	struct credential want = CREDENTIAL_INIT;
93 	int matches = 0;
94 
95 	if (credential_from_potentially_partial_url(&want, url) < 0)
96 		warning(_("skipping credential lookup for key: credential.%s"),
97 			url);
98 	else
99 		matches = credential_match(&want, c);
100 	credential_clear(&want);
101 
102 	return matches;
103 }
104 
credential_apply_config(struct credential * c)105 static void credential_apply_config(struct credential *c)
106 {
107 	char *normalized_url;
108 	struct urlmatch_config config = URLMATCH_CONFIG_INIT;
109 	struct strbuf url = STRBUF_INIT;
110 
111 	if (!c->host)
112 		die(_("refusing to work with credential missing host field"));
113 	if (!c->protocol)
114 		die(_("refusing to work with credential missing protocol field"));
115 
116 	if (c->configured)
117 		return;
118 
119 	config.section = "credential";
120 	config.key = NULL;
121 	config.collect_fn = credential_config_callback;
122 	config.cascade_fn = NULL;
123 	config.select_fn = select_all;
124 	config.fallback_match_fn = match_partial_url;
125 	config.cb = c;
126 
127 	credential_format(c, &url);
128 	normalized_url = url_normalize(url.buf, &config.url);
129 
130 	git_config(urlmatch_config_entry, &config);
131 	string_list_clear(&config.vars, 1);
132 	free(normalized_url);
133 	strbuf_release(&url);
134 
135 	c->configured = 1;
136 
137 	if (!c->use_http_path && proto_is_http(c->protocol)) {
138 		FREE_AND_NULL(c->path);
139 	}
140 }
141 
credential_describe(struct credential * c,struct strbuf * out)142 static void credential_describe(struct credential *c, struct strbuf *out)
143 {
144 	if (!c->protocol)
145 		return;
146 	strbuf_addf(out, "%s://", c->protocol);
147 	if (c->username && *c->username)
148 		strbuf_addf(out, "%s@", c->username);
149 	if (c->host)
150 		strbuf_addstr(out, c->host);
151 	if (c->path)
152 		strbuf_addf(out, "/%s", c->path);
153 }
154 
credential_format(struct credential * c,struct strbuf * out)155 static void credential_format(struct credential *c, struct strbuf *out)
156 {
157 	if (!c->protocol)
158 		return;
159 	strbuf_addf(out, "%s://", c->protocol);
160 	if (c->username && *c->username) {
161 		strbuf_add_percentencode(out, c->username, STRBUF_ENCODE_SLASH);
162 		strbuf_addch(out, '@');
163 	}
164 	if (c->host)
165 		strbuf_addstr(out, c->host);
166 	if (c->path) {
167 		strbuf_addch(out, '/');
168 		strbuf_add_percentencode(out, c->path, 0);
169 	}
170 }
171 
credential_ask_one(const char * what,struct credential * c,int flags)172 static char *credential_ask_one(const char *what, struct credential *c,
173 				int flags)
174 {
175 	struct strbuf desc = STRBUF_INIT;
176 	struct strbuf prompt = STRBUF_INIT;
177 	char *r;
178 
179 	credential_describe(c, &desc);
180 	if (desc.len)
181 		strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
182 	else
183 		strbuf_addf(&prompt, "%s: ", what);
184 
185 	r = git_prompt(prompt.buf, flags);
186 
187 	strbuf_release(&desc);
188 	strbuf_release(&prompt);
189 	return xstrdup(r);
190 }
191 
credential_getpass(struct credential * c)192 static void credential_getpass(struct credential *c)
193 {
194 	if (!c->username)
195 		c->username = credential_ask_one("Username", c,
196 						 PROMPT_ASKPASS|PROMPT_ECHO);
197 	if (!c->password)
198 		c->password = credential_ask_one("Password", c,
199 						 PROMPT_ASKPASS);
200 }
201 
credential_read(struct credential * c,FILE * fp)202 int credential_read(struct credential *c, FILE *fp)
203 {
204 	struct strbuf line = STRBUF_INIT;
205 
206 	while (strbuf_getline(&line, fp) != EOF) {
207 		char *key = line.buf;
208 		char *value = strchr(key, '=');
209 
210 		if (!line.len)
211 			break;
212 
213 		if (!value) {
214 			warning("invalid credential line: %s", key);
215 			strbuf_release(&line);
216 			return -1;
217 		}
218 		*value++ = '\0';
219 
220 		if (!strcmp(key, "username")) {
221 			free(c->username);
222 			c->username = xstrdup(value);
223 			c->username_from_proto = 1;
224 		} else if (!strcmp(key, "password")) {
225 			free(c->password);
226 			c->password = xstrdup(value);
227 		} else if (!strcmp(key, "protocol")) {
228 			free(c->protocol);
229 			c->protocol = xstrdup(value);
230 		} else if (!strcmp(key, "host")) {
231 			free(c->host);
232 			c->host = xstrdup(value);
233 		} else if (!strcmp(key, "path")) {
234 			free(c->path);
235 			c->path = xstrdup(value);
236 		} else if (!strcmp(key, "url")) {
237 			credential_from_url(c, value);
238 		} else if (!strcmp(key, "quit")) {
239 			c->quit = !!git_config_bool("quit", value);
240 		}
241 		/*
242 		 * Ignore other lines; we don't know what they mean, but
243 		 * this future-proofs us when later versions of git do
244 		 * learn new lines, and the helpers are updated to match.
245 		 */
246 	}
247 
248 	strbuf_release(&line);
249 	return 0;
250 }
251 
credential_write_item(FILE * fp,const char * key,const char * value,int required)252 static void credential_write_item(FILE *fp, const char *key, const char *value,
253 				  int required)
254 {
255 	if (!value && required)
256 		BUG("credential value for %s is missing", key);
257 	if (!value)
258 		return;
259 	if (strchr(value, '\n'))
260 		die("credential value for %s contains newline", key);
261 	fprintf(fp, "%s=%s\n", key, value);
262 }
263 
credential_write(const struct credential * c,FILE * fp)264 void credential_write(const struct credential *c, FILE *fp)
265 {
266 	credential_write_item(fp, "protocol", c->protocol, 1);
267 	credential_write_item(fp, "host", c->host, 1);
268 	credential_write_item(fp, "path", c->path, 0);
269 	credential_write_item(fp, "username", c->username, 0);
270 	credential_write_item(fp, "password", c->password, 0);
271 }
272 
run_credential_helper(struct credential * c,const char * cmd,int want_output)273 static int run_credential_helper(struct credential *c,
274 				 const char *cmd,
275 				 int want_output)
276 {
277 	struct child_process helper = CHILD_PROCESS_INIT;
278 	FILE *fp;
279 
280 	strvec_push(&helper.args, cmd);
281 	helper.use_shell = 1;
282 	helper.in = -1;
283 	if (want_output)
284 		helper.out = -1;
285 	else
286 		helper.no_stdout = 1;
287 
288 	if (start_command(&helper) < 0)
289 		return -1;
290 
291 	fp = xfdopen(helper.in, "w");
292 	sigchain_push(SIGPIPE, SIG_IGN);
293 	credential_write(c, fp);
294 	fclose(fp);
295 	sigchain_pop(SIGPIPE);
296 
297 	if (want_output) {
298 		int r;
299 		fp = xfdopen(helper.out, "r");
300 		r = credential_read(c, fp);
301 		fclose(fp);
302 		if (r < 0) {
303 			finish_command(&helper);
304 			return -1;
305 		}
306 	}
307 
308 	if (finish_command(&helper))
309 		return -1;
310 	return 0;
311 }
312 
credential_do(struct credential * c,const char * helper,const char * operation)313 static int credential_do(struct credential *c, const char *helper,
314 			 const char *operation)
315 {
316 	struct strbuf cmd = STRBUF_INIT;
317 	int r;
318 
319 	if (helper[0] == '!')
320 		strbuf_addstr(&cmd, helper + 1);
321 	else if (is_absolute_path(helper))
322 		strbuf_addstr(&cmd, helper);
323 	else
324 		strbuf_addf(&cmd, "git credential-%s", helper);
325 
326 	strbuf_addf(&cmd, " %s", operation);
327 	r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
328 
329 	strbuf_release(&cmd);
330 	return r;
331 }
332 
credential_fill(struct credential * c)333 void credential_fill(struct credential *c)
334 {
335 	int i;
336 
337 	if (c->username && c->password)
338 		return;
339 
340 	credential_apply_config(c);
341 
342 	for (i = 0; i < c->helpers.nr; i++) {
343 		credential_do(c, c->helpers.items[i].string, "get");
344 		if (c->username && c->password)
345 			return;
346 		if (c->quit)
347 			die("credential helper '%s' told us to quit",
348 			    c->helpers.items[i].string);
349 	}
350 
351 	credential_getpass(c);
352 	if (!c->username && !c->password)
353 		die("unable to get password from user");
354 }
355 
credential_approve(struct credential * c)356 void credential_approve(struct credential *c)
357 {
358 	int i;
359 
360 	if (c->approved)
361 		return;
362 	if (!c->username || !c->password)
363 		return;
364 
365 	credential_apply_config(c);
366 
367 	for (i = 0; i < c->helpers.nr; i++)
368 		credential_do(c, c->helpers.items[i].string, "store");
369 	c->approved = 1;
370 }
371 
credential_reject(struct credential * c)372 void credential_reject(struct credential *c)
373 {
374 	int i;
375 
376 	credential_apply_config(c);
377 
378 	for (i = 0; i < c->helpers.nr; i++)
379 		credential_do(c, c->helpers.items[i].string, "erase");
380 
381 	FREE_AND_NULL(c->username);
382 	FREE_AND_NULL(c->password);
383 	c->approved = 0;
384 }
385 
check_url_component(const char * url,int quiet,const char * name,const char * value)386 static int check_url_component(const char *url, int quiet,
387 			       const char *name, const char *value)
388 {
389 	if (!value)
390 		return 0;
391 	if (!strchr(value, '\n'))
392 		return 0;
393 
394 	if (!quiet)
395 		warning(_("url contains a newline in its %s component: %s"),
396 			name, url);
397 	return -1;
398 }
399 
400 /*
401  * Potentially-partial URLs can, but do not have to, contain
402  *
403  * - a protocol (or scheme) of the form "<protocol>://"
404  *
405  * - a host name (the part after the protocol and before the first slash after
406  *   that, if any)
407  *
408  * - a user name and potentially a password (as "<user>[:<password>]@" part of
409  *   the host name)
410  *
411  * - a path (the part after the host name, if any, starting with the slash)
412  *
413  * Missing parts will be left unset in `struct credential`. Thus, `https://`
414  * will have only the `protocol` set, `example.com` only the host name, and
415  * `/git` only the path.
416  *
417  * Note that an empty host name in an otherwise fully-qualified URL (e.g.
418  * `cert:///path/to/cert.pem`) will be treated as unset if we expect the URL to
419  * be potentially partial, and only then (otherwise, the empty string is used).
420  *
421  * The credential_from_url() function does not allow partial URLs.
422  */
credential_from_url_1(struct credential * c,const char * url,int allow_partial_url,int quiet)423 static int credential_from_url_1(struct credential *c, const char *url,
424 				 int allow_partial_url, int quiet)
425 {
426 	const char *at, *colon, *cp, *slash, *host, *proto_end;
427 
428 	credential_clear(c);
429 
430 	/*
431 	 * Match one of:
432 	 *   (1) proto://<host>/...
433 	 *   (2) proto://<user>@<host>/...
434 	 *   (3) proto://<user>:<pass>@<host>/...
435 	 */
436 	proto_end = strstr(url, "://");
437 	if (!allow_partial_url && (!proto_end || proto_end == url)) {
438 		if (!quiet)
439 			warning(_("url has no scheme: %s"), url);
440 		return -1;
441 	}
442 	cp = proto_end ? proto_end + 3 : url;
443 	at = strchr(cp, '@');
444 	colon = strchr(cp, ':');
445 
446 	/*
447 	 * A query or fragment marker before the slash ends the host portion.
448 	 * We'll just continue to call this "slash" for simplicity. Notably our
449 	 * "trim leading slashes" part won't skip over this part of the path,
450 	 * but that's what we'd want.
451 	 */
452 	slash = cp + strcspn(cp, "/?#");
453 
454 	if (!at || slash <= at) {
455 		/* Case (1) */
456 		host = cp;
457 	}
458 	else if (!colon || at <= colon) {
459 		/* Case (2) */
460 		c->username = url_decode_mem(cp, at - cp);
461 		if (c->username && *c->username)
462 			c->username_from_proto = 1;
463 		host = at + 1;
464 	} else {
465 		/* Case (3) */
466 		c->username = url_decode_mem(cp, colon - cp);
467 		if (c->username && *c->username)
468 			c->username_from_proto = 1;
469 		c->password = url_decode_mem(colon + 1, at - (colon + 1));
470 		host = at + 1;
471 	}
472 
473 	if (proto_end && proto_end - url > 0)
474 		c->protocol = xmemdupz(url, proto_end - url);
475 	if (!allow_partial_url || slash - host > 0)
476 		c->host = url_decode_mem(host, slash - host);
477 	/* Trim leading and trailing slashes from path */
478 	while (*slash == '/')
479 		slash++;
480 	if (*slash) {
481 		char *p;
482 		c->path = url_decode(slash);
483 		p = c->path + strlen(c->path) - 1;
484 		while (p > c->path && *p == '/')
485 			*p-- = '\0';
486 	}
487 
488 	if (check_url_component(url, quiet, "username", c->username) < 0 ||
489 	    check_url_component(url, quiet, "password", c->password) < 0 ||
490 	    check_url_component(url, quiet, "protocol", c->protocol) < 0 ||
491 	    check_url_component(url, quiet, "host", c->host) < 0 ||
492 	    check_url_component(url, quiet, "path", c->path) < 0)
493 		return -1;
494 
495 	return 0;
496 }
497 
credential_from_potentially_partial_url(struct credential * c,const char * url)498 static int credential_from_potentially_partial_url(struct credential *c,
499 						   const char *url)
500 {
501 	return credential_from_url_1(c, url, 1, 0);
502 }
503 
credential_from_url_gently(struct credential * c,const char * url,int quiet)504 int credential_from_url_gently(struct credential *c, const char *url, int quiet)
505 {
506 	return credential_from_url_1(c, url, 0, quiet);
507 }
508 
credential_from_url(struct credential * c,const char * url)509 void credential_from_url(struct credential *c, const char *url)
510 {
511 	if (credential_from_url_gently(c, url, 0) < 0)
512 		die(_("credential url cannot be parsed: %s"), url);
513 }
514