git

Форк
0
/
credential.c 
672 строки · 17.5 Кб
1
#define USE_THE_REPOSITORY_VARIABLE
2

3
#include "git-compat-util.h"
4
#include "abspath.h"
5
#include "config.h"
6
#include "credential.h"
7
#include "gettext.h"
8
#include "string-list.h"
9
#include "run-command.h"
10
#include "url.h"
11
#include "prompt.h"
12
#include "sigchain.h"
13
#include "strbuf.h"
14
#include "urlmatch.h"
15
#include "git-compat-util.h"
16

17
void credential_init(struct credential *c)
18
{
19
	struct credential blank = CREDENTIAL_INIT;
20
	memcpy(c, &blank, sizeof(*c));
21
}
22

23
void credential_clear(struct credential *c)
24
{
25
	credential_clear_secrets(c);
26
	free(c->protocol);
27
	free(c->host);
28
	free(c->path);
29
	free(c->username);
30
	free(c->oauth_refresh_token);
31
	free(c->authtype);
32
	string_list_clear(&c->helpers, 0);
33
	strvec_clear(&c->wwwauth_headers);
34
	strvec_clear(&c->state_headers);
35
	strvec_clear(&c->state_headers_to_send);
36

37
	credential_init(c);
38
}
39

40
void credential_next_state(struct credential *c)
41
{
42
	strvec_clear(&c->state_headers_to_send);
43
	SWAP(c->state_headers, c->state_headers_to_send);
44
}
45

46
void credential_clear_secrets(struct credential *c)
47
{
48
	FREE_AND_NULL(c->password);
49
	FREE_AND_NULL(c->credential);
50
}
51

52
static void credential_set_capability(struct credential_capability *capa,
53
				      enum credential_op_type op_type)
54
{
55
	switch (op_type) {
56
	case CREDENTIAL_OP_INITIAL:
57
		capa->request_initial = 1;
58
		break;
59
	case CREDENTIAL_OP_HELPER:
60
		capa->request_helper = 1;
61
		break;
62
	case CREDENTIAL_OP_RESPONSE:
63
		capa->response = 1;
64
		break;
65
	}
66
}
67

68

69
void credential_set_all_capabilities(struct credential *c,
70
				     enum credential_op_type op_type)
71
{
72
	credential_set_capability(&c->capa_authtype, op_type);
73
	credential_set_capability(&c->capa_state, op_type);
74
}
75

76
static void announce_one(struct credential_capability *cc, const char *name, FILE *fp) {
77
	if (cc->request_initial)
78
		fprintf(fp, "capability %s\n", name);
79
}
80

81
void credential_announce_capabilities(struct credential *c, FILE *fp) {
82
	fprintf(fp, "version 0\n");
83
	announce_one(&c->capa_authtype, "authtype", fp);
84
	announce_one(&c->capa_state, "state", fp);
85
}
86

87
int credential_match(const struct credential *want,
88
		     const struct credential *have, int match_password)
89
{
90
#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
91
	return CHECK(protocol) &&
92
	       CHECK(host) &&
93
	       CHECK(path) &&
94
	       CHECK(username) &&
95
	       (!match_password || CHECK(password)) &&
96
	       (!match_password || CHECK(credential));
97
#undef CHECK
98
}
99

100

101
static int credential_from_potentially_partial_url(struct credential *c,
102
						   const char *url);
103

104
static int credential_config_callback(const char *var, const char *value,
105
				      const struct config_context *ctx UNUSED,
106
				      void *data)
107
{
108
	struct credential *c = data;
109
	const char *key;
110

111
	if (!skip_prefix(var, "credential.", &key))
112
		return 0;
113

114
	if (!value)
115
		return config_error_nonbool(var);
116

117
	if (!strcmp(key, "helper")) {
118
		if (*value)
119
			string_list_append(&c->helpers, value);
120
		else
121
			string_list_clear(&c->helpers, 0);
122
	} else if (!strcmp(key, "username")) {
123
		if (!c->username_from_proto) {
124
			free(c->username);
125
			c->username = xstrdup(value);
126
		}
127
	}
128
	else if (!strcmp(key, "usehttppath"))
129
		c->use_http_path = git_config_bool(var, value);
130

131
	return 0;
132
}
133

134
static int proto_is_http(const char *s)
135
{
136
	if (!s)
137
		return 0;
138
	return !strcmp(s, "https") || !strcmp(s, "http");
139
}
140

141
static void credential_describe(struct credential *c, struct strbuf *out);
142
static void credential_format(struct credential *c, struct strbuf *out);
143

144
static int select_all(const struct urlmatch_item *a UNUSED,
145
		      const struct urlmatch_item *b UNUSED)
146
{
147
	return 0;
148
}
149

150
static int match_partial_url(const char *url, void *cb)
151
{
152
	struct credential *c = cb;
153
	struct credential want = CREDENTIAL_INIT;
154
	int matches = 0;
155

156
	if (credential_from_potentially_partial_url(&want, url) < 0)
157
		warning(_("skipping credential lookup for key: credential.%s"),
158
			url);
159
	else
160
		matches = credential_match(&want, c, 0);
161
	credential_clear(&want);
162

163
	return matches;
164
}
165

166
static void credential_apply_config(struct credential *c)
167
{
168
	char *normalized_url;
169
	struct urlmatch_config config = URLMATCH_CONFIG_INIT;
170
	struct strbuf url = STRBUF_INIT;
171

172
	if (!c->host)
173
		die(_("refusing to work with credential missing host field"));
174
	if (!c->protocol)
175
		die(_("refusing to work with credential missing protocol field"));
176

177
	if (c->configured)
178
		return;
179

180
	config.section = "credential";
181
	config.key = NULL;
182
	config.collect_fn = credential_config_callback;
183
	config.cascade_fn = NULL;
184
	config.select_fn = select_all;
185
	config.fallback_match_fn = match_partial_url;
186
	config.cb = c;
187

188
	credential_format(c, &url);
189
	normalized_url = url_normalize(url.buf, &config.url);
190

191
	git_config(urlmatch_config_entry, &config);
192
	string_list_clear(&config.vars, 1);
193
	free(normalized_url);
194
	urlmatch_config_release(&config);
195
	strbuf_release(&url);
196

197
	c->configured = 1;
198

199
	if (!c->use_http_path && proto_is_http(c->protocol)) {
200
		FREE_AND_NULL(c->path);
201
	}
202
}
203

204
static void credential_describe(struct credential *c, struct strbuf *out)
205
{
206
	if (!c->protocol)
207
		return;
208
	strbuf_addf(out, "%s://", c->protocol);
209
	if (c->username && *c->username)
210
		strbuf_addf(out, "%s@", c->username);
211
	if (c->host)
212
		strbuf_addstr(out, c->host);
213
	if (c->path)
214
		strbuf_addf(out, "/%s", c->path);
215
}
216

217
static void credential_format(struct credential *c, struct strbuf *out)
218
{
219
	if (!c->protocol)
220
		return;
221
	strbuf_addf(out, "%s://", c->protocol);
222
	if (c->username && *c->username) {
223
		strbuf_add_percentencode(out, c->username, STRBUF_ENCODE_SLASH);
224
		strbuf_addch(out, '@');
225
	}
226
	if (c->host)
227
		strbuf_addstr(out, c->host);
228
	if (c->path) {
229
		strbuf_addch(out, '/');
230
		strbuf_add_percentencode(out, c->path, 0);
231
	}
232
}
233

234
static char *credential_ask_one(const char *what, struct credential *c,
235
				int flags)
236
{
237
	struct strbuf desc = STRBUF_INIT;
238
	struct strbuf prompt = STRBUF_INIT;
239
	char *r;
240

241
	credential_describe(c, &desc);
242
	if (desc.len)
243
		strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
244
	else
245
		strbuf_addf(&prompt, "%s: ", what);
246

247
	r = git_prompt(prompt.buf, flags);
248

249
	strbuf_release(&desc);
250
	strbuf_release(&prompt);
251
	return xstrdup(r);
252
}
253

254
static void credential_getpass(struct credential *c)
255
{
256
	if (!c->username)
257
		c->username = credential_ask_one("Username", c,
258
						 PROMPT_ASKPASS|PROMPT_ECHO);
259
	if (!c->password)
260
		c->password = credential_ask_one("Password", c,
261
						 PROMPT_ASKPASS);
262
}
263

264
int credential_has_capability(const struct credential_capability *capa,
265
			      enum credential_op_type op_type)
266
{
267
	/*
268
	 * We're checking here if each previous step indicated that we had the
269
	 * capability.  If it did, then we want to pass it along; conversely, if
270
	 * it did not, we don't want to report that to our caller.
271
	 */
272
	switch (op_type) {
273
	case CREDENTIAL_OP_HELPER:
274
		return capa->request_initial;
275
	case CREDENTIAL_OP_RESPONSE:
276
		return capa->request_initial && capa->request_helper;
277
	default:
278
		return 0;
279
	}
280
}
281

282
int credential_read(struct credential *c, FILE *fp,
283
		    enum credential_op_type op_type)
284
{
285
	struct strbuf line = STRBUF_INIT;
286

287
	while (strbuf_getline(&line, fp) != EOF) {
288
		char *key = line.buf;
289
		char *value = strchr(key, '=');
290

291
		if (!line.len)
292
			break;
293

294
		if (!value) {
295
			warning("invalid credential line: %s", key);
296
			strbuf_release(&line);
297
			return -1;
298
		}
299
		*value++ = '\0';
300

301
		if (!strcmp(key, "username")) {
302
			free(c->username);
303
			c->username = xstrdup(value);
304
			c->username_from_proto = 1;
305
		} else if (!strcmp(key, "password")) {
306
			free(c->password);
307
			c->password = xstrdup(value);
308
		} else if (!strcmp(key, "credential")) {
309
			free(c->credential);
310
			c->credential = xstrdup(value);
311
		} else if (!strcmp(key, "protocol")) {
312
			free(c->protocol);
313
			c->protocol = xstrdup(value);
314
		} else if (!strcmp(key, "host")) {
315
			free(c->host);
316
			c->host = xstrdup(value);
317
		} else if (!strcmp(key, "path")) {
318
			free(c->path);
319
			c->path = xstrdup(value);
320
		} else if (!strcmp(key, "ephemeral")) {
321
			c->ephemeral = !!git_config_bool("ephemeral", value);
322
		} else if (!strcmp(key, "wwwauth[]")) {
323
			strvec_push(&c->wwwauth_headers, value);
324
		} else if (!strcmp(key, "state[]")) {
325
			strvec_push(&c->state_headers, value);
326
		} else if (!strcmp(key, "capability[]")) {
327
			if (!strcmp(value, "authtype"))
328
				credential_set_capability(&c->capa_authtype, op_type);
329
			else if (!strcmp(value, "state"))
330
				credential_set_capability(&c->capa_state, op_type);
331
		} else if (!strcmp(key, "continue")) {
332
			c->multistage = !!git_config_bool("continue", value);
333
		} else if (!strcmp(key, "password_expiry_utc")) {
334
			errno = 0;
335
			c->password_expiry_utc = parse_timestamp(value, NULL, 10);
336
			if (c->password_expiry_utc == 0 || errno == ERANGE)
337
				c->password_expiry_utc = TIME_MAX;
338
		} else if (!strcmp(key, "oauth_refresh_token")) {
339
			free(c->oauth_refresh_token);
340
			c->oauth_refresh_token = xstrdup(value);
341
		} else if (!strcmp(key, "authtype")) {
342
			free(c->authtype);
343
			c->authtype = xstrdup(value);
344
		} else if (!strcmp(key, "url")) {
345
			credential_from_url(c, value);
346
		} else if (!strcmp(key, "quit")) {
347
			c->quit = !!git_config_bool("quit", value);
348
		}
349
		/*
350
		 * Ignore other lines; we don't know what they mean, but
351
		 * this future-proofs us when later versions of git do
352
		 * learn new lines, and the helpers are updated to match.
353
		 */
354
	}
355

356
	strbuf_release(&line);
357
	return 0;
358
}
359

360
static void credential_write_item(FILE *fp, const char *key, const char *value,
361
				  int required)
362
{
363
	if (!value && required)
364
		BUG("credential value for %s is missing", key);
365
	if (!value)
366
		return;
367
	if (strchr(value, '\n'))
368
		die("credential value for %s contains newline", key);
369
	fprintf(fp, "%s=%s\n", key, value);
370
}
371

372
void credential_write(const struct credential *c, FILE *fp,
373
		      enum credential_op_type op_type)
374
{
375
	if (credential_has_capability(&c->capa_authtype, op_type))
376
		credential_write_item(fp, "capability[]", "authtype", 0);
377
	if (credential_has_capability(&c->capa_state, op_type))
378
		credential_write_item(fp, "capability[]", "state", 0);
379

380
	if (credential_has_capability(&c->capa_authtype, op_type)) {
381
		credential_write_item(fp, "authtype", c->authtype, 0);
382
		credential_write_item(fp, "credential", c->credential, 0);
383
		if (c->ephemeral)
384
			credential_write_item(fp, "ephemeral", "1", 0);
385
	}
386
	credential_write_item(fp, "protocol", c->protocol, 1);
387
	credential_write_item(fp, "host", c->host, 1);
388
	credential_write_item(fp, "path", c->path, 0);
389
	credential_write_item(fp, "username", c->username, 0);
390
	credential_write_item(fp, "password", c->password, 0);
391
	credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0);
392
	if (c->password_expiry_utc != TIME_MAX) {
393
		char *s = xstrfmt("%"PRItime, c->password_expiry_utc);
394
		credential_write_item(fp, "password_expiry_utc", s, 0);
395
		free(s);
396
	}
397
	for (size_t i = 0; i < c->wwwauth_headers.nr; i++)
398
		credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
399
	if (credential_has_capability(&c->capa_state, op_type)) {
400
		if (c->multistage)
401
			credential_write_item(fp, "continue", "1", 0);
402
		for (size_t i = 0; i < c->state_headers_to_send.nr; i++)
403
			credential_write_item(fp, "state[]", c->state_headers_to_send.v[i], 0);
404
	}
405
}
406

407
static int run_credential_helper(struct credential *c,
408
				 const char *cmd,
409
				 int want_output)
410
{
411
	struct child_process helper = CHILD_PROCESS_INIT;
412
	FILE *fp;
413

414
	strvec_push(&helper.args, cmd);
415
	helper.use_shell = 1;
416
	helper.in = -1;
417
	if (want_output)
418
		helper.out = -1;
419
	else
420
		helper.no_stdout = 1;
421

422
	if (start_command(&helper) < 0)
423
		return -1;
424

425
	fp = xfdopen(helper.in, "w");
426
	sigchain_push(SIGPIPE, SIG_IGN);
427
	credential_write(c, fp, want_output ? CREDENTIAL_OP_HELPER : CREDENTIAL_OP_RESPONSE);
428
	fclose(fp);
429
	sigchain_pop(SIGPIPE);
430

431
	if (want_output) {
432
		int r;
433
		fp = xfdopen(helper.out, "r");
434
		r = credential_read(c, fp, CREDENTIAL_OP_HELPER);
435
		fclose(fp);
436
		if (r < 0) {
437
			finish_command(&helper);
438
			return -1;
439
		}
440
	}
441

442
	if (finish_command(&helper))
443
		return -1;
444
	return 0;
445
}
446

447
static int credential_do(struct credential *c, const char *helper,
448
			 const char *operation)
449
{
450
	struct strbuf cmd = STRBUF_INIT;
451
	int r;
452

453
	if (helper[0] == '!')
454
		strbuf_addstr(&cmd, helper + 1);
455
	else if (is_absolute_path(helper))
456
		strbuf_addstr(&cmd, helper);
457
	else
458
		strbuf_addf(&cmd, "git credential-%s", helper);
459

460
	strbuf_addf(&cmd, " %s", operation);
461
	r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
462

463
	strbuf_release(&cmd);
464
	return r;
465
}
466

467
void credential_fill(struct credential *c, int all_capabilities)
468
{
469
	int i;
470

471
	if ((c->username && c->password) || c->credential)
472
		return;
473

474
	credential_next_state(c);
475
	c->multistage = 0;
476

477
	credential_apply_config(c);
478
	if (all_capabilities)
479
		credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL);
480

481
	for (i = 0; i < c->helpers.nr; i++) {
482
		credential_do(c, c->helpers.items[i].string, "get");
483

484
		if (c->password_expiry_utc < time(NULL)) {
485
			/*
486
			 * Don't use credential_clear() here: callers such as
487
			 * cmd_credential() expect to still be able to call
488
			 * credential_write() on a struct credential whose
489
			 * secrets have expired.
490
			 */
491
			credential_clear_secrets(c);
492
			/* Reset expiry to maintain consistency */
493
			c->password_expiry_utc = TIME_MAX;
494
		}
495
		if ((c->username && c->password) || c->credential) {
496
			strvec_clear(&c->wwwauth_headers);
497
			return;
498
		}
499
		if (c->quit)
500
			die("credential helper '%s' told us to quit",
501
			    c->helpers.items[i].string);
502
	}
503

504
	credential_getpass(c);
505
	if (!c->username && !c->password && !c->credential)
506
		die("unable to get password from user");
507
}
508

509
void credential_approve(struct credential *c)
510
{
511
	int i;
512

513
	if (c->approved)
514
		return;
515
	if (((!c->username || !c->password) && !c->credential) || c->password_expiry_utc < time(NULL))
516
		return;
517

518
	credential_next_state(c);
519

520
	credential_apply_config(c);
521

522
	for (i = 0; i < c->helpers.nr; i++)
523
		credential_do(c, c->helpers.items[i].string, "store");
524
	c->approved = 1;
525
}
526

527
void credential_reject(struct credential *c)
528
{
529
	int i;
530

531
	credential_next_state(c);
532

533
	credential_apply_config(c);
534

535
	for (i = 0; i < c->helpers.nr; i++)
536
		credential_do(c, c->helpers.items[i].string, "erase");
537

538
	credential_clear_secrets(c);
539
	FREE_AND_NULL(c->username);
540
	FREE_AND_NULL(c->oauth_refresh_token);
541
	c->password_expiry_utc = TIME_MAX;
542
	c->approved = 0;
543
}
544

545
static int check_url_component(const char *url, int quiet,
546
			       const char *name, const char *value)
547
{
548
	if (!value)
549
		return 0;
550
	if (!strchr(value, '\n'))
551
		return 0;
552

553
	if (!quiet)
554
		warning(_("url contains a newline in its %s component: %s"),
555
			name, url);
556
	return -1;
557
}
558

559
/*
560
 * Potentially-partial URLs can, but do not have to, contain
561
 *
562
 * - a protocol (or scheme) of the form "<protocol>://"
563
 *
564
 * - a host name (the part after the protocol and before the first slash after
565
 *   that, if any)
566
 *
567
 * - a user name and potentially a password (as "<user>[:<password>]@" part of
568
 *   the host name)
569
 *
570
 * - a path (the part after the host name, if any, starting with the slash)
571
 *
572
 * Missing parts will be left unset in `struct credential`. Thus, `https://`
573
 * will have only the `protocol` set, `example.com` only the host name, and
574
 * `/git` only the path.
575
 *
576
 * Note that an empty host name in an otherwise fully-qualified URL (e.g.
577
 * `cert:///path/to/cert.pem`) will be treated as unset if we expect the URL to
578
 * be potentially partial, and only then (otherwise, the empty string is used).
579
 *
580
 * The credential_from_url() function does not allow partial URLs.
581
 */
582
static int credential_from_url_1(struct credential *c, const char *url,
583
				 int allow_partial_url, int quiet)
584
{
585
	const char *at, *colon, *cp, *slash, *host, *proto_end;
586

587
	credential_clear(c);
588

589
	/*
590
	 * Match one of:
591
	 *   (1) proto://<host>/...
592
	 *   (2) proto://<user>@<host>/...
593
	 *   (3) proto://<user>:<pass>@<host>/...
594
	 */
595
	proto_end = strstr(url, "://");
596
	if (!allow_partial_url && (!proto_end || proto_end == url)) {
597
		if (!quiet)
598
			warning(_("url has no scheme: %s"), url);
599
		return -1;
600
	}
601
	cp = proto_end ? proto_end + 3 : url;
602
	at = strchr(cp, '@');
603
	colon = strchr(cp, ':');
604

605
	/*
606
	 * A query or fragment marker before the slash ends the host portion.
607
	 * We'll just continue to call this "slash" for simplicity. Notably our
608
	 * "trim leading slashes" part won't skip over this part of the path,
609
	 * but that's what we'd want.
610
	 */
611
	slash = cp + strcspn(cp, "/?#");
612

613
	if (!at || slash <= at) {
614
		/* Case (1) */
615
		host = cp;
616
	}
617
	else if (!colon || at <= colon) {
618
		/* Case (2) */
619
		c->username = url_decode_mem(cp, at - cp);
620
		if (c->username && *c->username)
621
			c->username_from_proto = 1;
622
		host = at + 1;
623
	} else {
624
		/* Case (3) */
625
		c->username = url_decode_mem(cp, colon - cp);
626
		if (c->username && *c->username)
627
			c->username_from_proto = 1;
628
		c->password = url_decode_mem(colon + 1, at - (colon + 1));
629
		host = at + 1;
630
	}
631

632
	if (proto_end && proto_end - url > 0)
633
		c->protocol = xmemdupz(url, proto_end - url);
634
	if (!allow_partial_url || slash - host > 0)
635
		c->host = url_decode_mem(host, slash - host);
636
	/* Trim leading and trailing slashes from path */
637
	while (*slash == '/')
638
		slash++;
639
	if (*slash) {
640
		char *p;
641
		c->path = url_decode(slash);
642
		p = c->path + strlen(c->path) - 1;
643
		while (p > c->path && *p == '/')
644
			*p-- = '\0';
645
	}
646

647
	if (check_url_component(url, quiet, "username", c->username) < 0 ||
648
	    check_url_component(url, quiet, "password", c->password) < 0 ||
649
	    check_url_component(url, quiet, "protocol", c->protocol) < 0 ||
650
	    check_url_component(url, quiet, "host", c->host) < 0 ||
651
	    check_url_component(url, quiet, "path", c->path) < 0)
652
		return -1;
653

654
	return 0;
655
}
656

657
static int credential_from_potentially_partial_url(struct credential *c,
658
						   const char *url)
659
{
660
	return credential_from_url_1(c, url, 1, 0);
661
}
662

663
int credential_from_url_gently(struct credential *c, const char *url, int quiet)
664
{
665
	return credential_from_url_1(c, url, 0, quiet);
666
}
667

668
void credential_from_url(struct credential *c, const char *url)
669
{
670
	if (credential_from_url_gently(c, url, 0) < 0)
671
		die(_("credential url cannot be parsed: %s"), url);
672
}
673

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.