Git fork
at reftables-rust 710 lines 19 kB view raw
1#define DISABLE_SIGN_COMPARE_WARNINGS 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 "environment.h" 16#include "trace2.h" 17#include "repository.h" 18 19void credential_init(struct credential *c) 20{ 21 struct credential blank = CREDENTIAL_INIT; 22 memcpy(c, &blank, sizeof(*c)); 23} 24 25void credential_clear(struct credential *c) 26{ 27 credential_clear_secrets(c); 28 free(c->protocol); 29 free(c->host); 30 free(c->path); 31 free(c->username); 32 free(c->oauth_refresh_token); 33 free(c->authtype); 34 string_list_clear(&c->helpers, 0); 35 strvec_clear(&c->wwwauth_headers); 36 strvec_clear(&c->state_headers); 37 strvec_clear(&c->state_headers_to_send); 38 39 credential_init(c); 40} 41 42void credential_next_state(struct credential *c) 43{ 44 strvec_clear(&c->state_headers_to_send); 45 SWAP(c->state_headers, c->state_headers_to_send); 46} 47 48void credential_clear_secrets(struct credential *c) 49{ 50 FREE_AND_NULL(c->password); 51 FREE_AND_NULL(c->credential); 52} 53 54static void credential_set_capability(struct credential_capability *capa, 55 enum credential_op_type op_type) 56{ 57 switch (op_type) { 58 case CREDENTIAL_OP_INITIAL: 59 capa->request_initial = 1; 60 break; 61 case CREDENTIAL_OP_HELPER: 62 capa->request_helper = 1; 63 break; 64 case CREDENTIAL_OP_RESPONSE: 65 capa->response = 1; 66 break; 67 } 68} 69 70 71void credential_set_all_capabilities(struct credential *c, 72 enum credential_op_type op_type) 73{ 74 credential_set_capability(&c->capa_authtype, op_type); 75 credential_set_capability(&c->capa_state, op_type); 76} 77 78static void announce_one(struct credential_capability *cc, const char *name, FILE *fp) { 79 if (cc->request_initial) 80 fprintf(fp, "capability %s\n", name); 81} 82 83void credential_announce_capabilities(struct credential *c, FILE *fp) { 84 fprintf(fp, "version 0\n"); 85 announce_one(&c->capa_authtype, "authtype", fp); 86 announce_one(&c->capa_state, "state", fp); 87} 88 89int credential_match(const struct credential *want, 90 const struct credential *have, int match_password) 91{ 92#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x))) 93 return CHECK(protocol) && 94 CHECK(host) && 95 CHECK(path) && 96 CHECK(username) && 97 (!match_password || CHECK(password)) && 98 (!match_password || CHECK(credential)); 99#undef CHECK 100} 101 102 103static int credential_from_potentially_partial_url(struct credential *c, 104 const char *url); 105 106static int credential_config_callback(const char *var, const char *value, 107 const struct config_context *ctx UNUSED, 108 void *data) 109{ 110 struct credential *c = data; 111 const char *key; 112 113 if (!skip_prefix(var, "credential.", &key)) 114 return 0; 115 116 if (!value) 117 return config_error_nonbool(var); 118 119 if (!strcmp(key, "helper")) { 120 if (*value) 121 string_list_append(&c->helpers, value); 122 else 123 string_list_clear(&c->helpers, 0); 124 } else if (!strcmp(key, "username")) { 125 if (!c->username_from_proto) { 126 free(c->username); 127 c->username = xstrdup(value); 128 } 129 } 130 else if (!strcmp(key, "usehttppath")) 131 c->use_http_path = git_config_bool(var, value); 132 else if (!strcmp(key, "sanitizeprompt")) 133 c->sanitize_prompt = git_config_bool(var, value); 134 else if (!strcmp(key, "protectprotocol")) 135 c->protect_protocol = git_config_bool(var, value); 136 137 return 0; 138} 139 140static int proto_is_http(const char *s) 141{ 142 if (!s) 143 return 0; 144 return !strcmp(s, "https") || !strcmp(s, "http"); 145} 146 147static void credential_describe(struct credential *c, struct strbuf *out); 148static void credential_format(struct credential *c, struct strbuf *out); 149 150static int select_all(const struct urlmatch_item *a UNUSED, 151 const struct urlmatch_item *b UNUSED) 152{ 153 return 0; 154} 155 156static int match_partial_url(const char *url, void *cb) 157{ 158 struct credential *c = cb; 159 struct credential want = CREDENTIAL_INIT; 160 int matches = 0; 161 162 if (credential_from_potentially_partial_url(&want, url) < 0) 163 warning(_("skipping credential lookup for key: credential.%s"), 164 url); 165 else 166 matches = credential_match(&want, c, 0); 167 credential_clear(&want); 168 169 return matches; 170} 171 172static void credential_apply_config(struct repository *r, struct credential *c) 173{ 174 char *normalized_url; 175 struct urlmatch_config config = URLMATCH_CONFIG_INIT; 176 struct strbuf url = STRBUF_INIT; 177 178 if (!c->host) 179 die(_("refusing to work with credential missing host field")); 180 if (!c->protocol) 181 die(_("refusing to work with credential missing protocol field")); 182 183 if (c->configured) 184 return; 185 186 config.section = "credential"; 187 config.key = NULL; 188 config.collect_fn = credential_config_callback; 189 config.cascade_fn = NULL; 190 config.select_fn = select_all; 191 config.fallback_match_fn = match_partial_url; 192 config.cb = c; 193 194 credential_format(c, &url); 195 normalized_url = url_normalize(url.buf, &config.url); 196 197 repo_config(r, urlmatch_config_entry, &config); 198 string_list_clear(&config.vars, 1); 199 free(normalized_url); 200 urlmatch_config_release(&config); 201 strbuf_release(&url); 202 203 c->configured = 1; 204 205 if (!c->use_http_path && proto_is_http(c->protocol)) { 206 FREE_AND_NULL(c->path); 207 } 208} 209 210static void credential_describe(struct credential *c, struct strbuf *out) 211{ 212 if (!c->protocol) 213 return; 214 strbuf_addf(out, "%s://", c->protocol); 215 if (c->username && *c->username) 216 strbuf_addf(out, "%s@", c->username); 217 if (c->host) 218 strbuf_addstr(out, c->host); 219 if (c->path) 220 strbuf_addf(out, "/%s", c->path); 221} 222 223static void credential_format(struct credential *c, struct strbuf *out) 224{ 225 if (!c->protocol) 226 return; 227 strbuf_addf(out, "%s://", c->protocol); 228 if (c->username && *c->username) { 229 strbuf_add_percentencode(out, c->username, STRBUF_ENCODE_SLASH); 230 strbuf_addch(out, '@'); 231 } 232 if (c->host) 233 strbuf_add_percentencode(out, c->host, 234 STRBUF_ENCODE_HOST_AND_PORT); 235 if (c->path) { 236 strbuf_addch(out, '/'); 237 strbuf_add_percentencode(out, c->path, 0); 238 } 239} 240 241static char *credential_ask_one(const char *what, struct credential *c, 242 int flags) 243{ 244 struct strbuf desc = STRBUF_INIT; 245 struct strbuf prompt = STRBUF_INIT; 246 char *r; 247 248 if (c->sanitize_prompt) 249 credential_format(c, &desc); 250 else 251 credential_describe(c, &desc); 252 if (desc.len) 253 strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf); 254 else 255 strbuf_addf(&prompt, "%s: ", what); 256 257 r = git_prompt(prompt.buf, flags); 258 259 strbuf_release(&desc); 260 strbuf_release(&prompt); 261 return xstrdup(r); 262} 263 264static int credential_getpass(struct repository *r, struct credential *c) 265{ 266 int interactive; 267 char *value; 268 if (!repo_config_get_maybe_bool(r, "credential.interactive", &interactive) && 269 !interactive) { 270 trace2_data_intmax("credential", r, 271 "interactive/skipped", 1); 272 return -1; 273 } 274 if (!repo_config_get_string(r, "credential.interactive", &value)) { 275 int same = !strcmp(value, "never"); 276 free(value); 277 if (same) { 278 trace2_data_intmax("credential", r, 279 "interactive/skipped", 1); 280 return -1; 281 } 282 } 283 284 trace2_region_enter("credential", "interactive", r); 285 if (!c->username) 286 c->username = credential_ask_one("Username", c, 287 PROMPT_ASKPASS|PROMPT_ECHO); 288 if (!c->password) 289 c->password = credential_ask_one("Password", c, 290 PROMPT_ASKPASS); 291 trace2_region_leave("credential", "interactive", r); 292 293 return 0; 294} 295 296int credential_has_capability(const struct credential_capability *capa, 297 enum credential_op_type op_type) 298{ 299 /* 300 * We're checking here if each previous step indicated that we had the 301 * capability. If it did, then we want to pass it along; conversely, if 302 * it did not, we don't want to report that to our caller. 303 */ 304 switch (op_type) { 305 case CREDENTIAL_OP_HELPER: 306 return capa->request_initial; 307 case CREDENTIAL_OP_RESPONSE: 308 return capa->request_initial && capa->request_helper; 309 default: 310 return 0; 311 } 312} 313 314int credential_read(struct credential *c, FILE *fp, 315 enum credential_op_type op_type) 316{ 317 struct strbuf line = STRBUF_INIT; 318 319 while (strbuf_getline(&line, fp) != EOF) { 320 char *key = line.buf; 321 char *value = strchr(key, '='); 322 323 if (!line.len) 324 break; 325 326 if (!value) { 327 warning("invalid credential line: %s", key); 328 strbuf_release(&line); 329 return -1; 330 } 331 *value++ = '\0'; 332 333 if (!strcmp(key, "username")) { 334 free(c->username); 335 c->username = xstrdup(value); 336 c->username_from_proto = 1; 337 } else if (!strcmp(key, "password")) { 338 free(c->password); 339 c->password = xstrdup(value); 340 } else if (!strcmp(key, "credential")) { 341 free(c->credential); 342 c->credential = xstrdup(value); 343 } else if (!strcmp(key, "protocol")) { 344 free(c->protocol); 345 c->protocol = xstrdup(value); 346 } else if (!strcmp(key, "host")) { 347 free(c->host); 348 c->host = xstrdup(value); 349 } else if (!strcmp(key, "path")) { 350 free(c->path); 351 c->path = xstrdup(value); 352 } else if (!strcmp(key, "ephemeral")) { 353 c->ephemeral = !!git_config_bool("ephemeral", value); 354 } else if (!strcmp(key, "wwwauth[]")) { 355 strvec_push(&c->wwwauth_headers, value); 356 } else if (!strcmp(key, "state[]")) { 357 strvec_push(&c->state_headers, value); 358 } else if (!strcmp(key, "capability[]")) { 359 if (!strcmp(value, "authtype")) 360 credential_set_capability(&c->capa_authtype, op_type); 361 else if (!strcmp(value, "state")) 362 credential_set_capability(&c->capa_state, op_type); 363 } else if (!strcmp(key, "continue")) { 364 c->multistage = !!git_config_bool("continue", value); 365 } else if (!strcmp(key, "password_expiry_utc")) { 366 errno = 0; 367 c->password_expiry_utc = parse_timestamp(value, NULL, 10); 368 if (c->password_expiry_utc == 0 || errno == ERANGE) 369 c->password_expiry_utc = TIME_MAX; 370 } else if (!strcmp(key, "oauth_refresh_token")) { 371 free(c->oauth_refresh_token); 372 c->oauth_refresh_token = xstrdup(value); 373 } else if (!strcmp(key, "authtype")) { 374 free(c->authtype); 375 c->authtype = xstrdup(value); 376 } else if (!strcmp(key, "url")) { 377 credential_from_url(c, value); 378 } else if (!strcmp(key, "quit")) { 379 c->quit = !!git_config_bool("quit", value); 380 } 381 /* 382 * Ignore other lines; we don't know what they mean, but 383 * this future-proofs us when later versions of git do 384 * learn new lines, and the helpers are updated to match. 385 */ 386 } 387 388 strbuf_release(&line); 389 return 0; 390} 391 392static void credential_write_item(const struct credential *c, 393 FILE *fp, const char *key, const char *value, 394 int required) 395{ 396 if (!value && required) 397 BUG("credential value for %s is missing", key); 398 if (!value) 399 return; 400 if (strchr(value, '\n')) 401 die("credential value for %s contains newline", key); 402 if (c->protect_protocol && strchr(value, '\r')) 403 die("credential value for %s contains carriage return\n" 404 "If this is intended, set `credential.protectProtocol=false`", 405 key); 406 fprintf(fp, "%s=%s\n", key, value); 407} 408 409void credential_write(const struct credential *c, FILE *fp, 410 enum credential_op_type op_type) 411{ 412 if (credential_has_capability(&c->capa_authtype, op_type)) 413 credential_write_item(c, fp, "capability[]", "authtype", 0); 414 if (credential_has_capability(&c->capa_state, op_type)) 415 credential_write_item(c, fp, "capability[]", "state", 0); 416 417 if (credential_has_capability(&c->capa_authtype, op_type)) { 418 credential_write_item(c, fp, "authtype", c->authtype, 0); 419 credential_write_item(c, fp, "credential", c->credential, 0); 420 if (c->ephemeral) 421 credential_write_item(c, fp, "ephemeral", "1", 0); 422 } 423 credential_write_item(c, fp, "protocol", c->protocol, 1); 424 credential_write_item(c, fp, "host", c->host, 1); 425 credential_write_item(c, fp, "path", c->path, 0); 426 credential_write_item(c, fp, "username", c->username, 0); 427 credential_write_item(c, fp, "password", c->password, 0); 428 credential_write_item(c, fp, "oauth_refresh_token", c->oauth_refresh_token, 0); 429 if (c->password_expiry_utc != TIME_MAX) { 430 char *s = xstrfmt("%"PRItime, c->password_expiry_utc); 431 credential_write_item(c, fp, "password_expiry_utc", s, 0); 432 free(s); 433 } 434 for (size_t i = 0; i < c->wwwauth_headers.nr; i++) 435 credential_write_item(c, fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); 436 if (credential_has_capability(&c->capa_state, op_type)) { 437 if (c->multistage) 438 credential_write_item(c, fp, "continue", "1", 0); 439 for (size_t i = 0; i < c->state_headers_to_send.nr; i++) 440 credential_write_item(c, fp, "state[]", c->state_headers_to_send.v[i], 0); 441 } 442} 443 444static int run_credential_helper(struct credential *c, 445 const char *cmd, 446 int want_output) 447{ 448 struct child_process helper = CHILD_PROCESS_INIT; 449 FILE *fp; 450 451 strvec_push(&helper.args, cmd); 452 helper.use_shell = 1; 453 helper.in = -1; 454 if (want_output) 455 helper.out = -1; 456 else 457 helper.no_stdout = 1; 458 459 if (start_command(&helper) < 0) 460 return -1; 461 462 fp = xfdopen(helper.in, "w"); 463 sigchain_push(SIGPIPE, SIG_IGN); 464 credential_write(c, fp, want_output ? CREDENTIAL_OP_HELPER : CREDENTIAL_OP_RESPONSE); 465 fclose(fp); 466 sigchain_pop(SIGPIPE); 467 468 if (want_output) { 469 int r; 470 fp = xfdopen(helper.out, "r"); 471 r = credential_read(c, fp, CREDENTIAL_OP_HELPER); 472 fclose(fp); 473 if (r < 0) { 474 finish_command(&helper); 475 return -1; 476 } 477 } 478 479 if (finish_command(&helper)) 480 return -1; 481 return 0; 482} 483 484static int credential_do(struct credential *c, const char *helper, 485 const char *operation) 486{ 487 struct strbuf cmd = STRBUF_INIT; 488 int r; 489 490 if (helper[0] == '!') 491 strbuf_addstr(&cmd, helper + 1); 492 else if (is_absolute_path(helper)) 493 strbuf_addstr(&cmd, helper); 494 else 495 strbuf_addf(&cmd, "git credential-%s", helper); 496 497 strbuf_addf(&cmd, " %s", operation); 498 r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get")); 499 500 strbuf_release(&cmd); 501 return r; 502} 503 504void credential_fill(struct repository *r, 505 struct credential *c, int all_capabilities) 506{ 507 int i; 508 509 if ((c->username && c->password) || c->credential) 510 return; 511 512 credential_next_state(c); 513 c->multistage = 0; 514 515 credential_apply_config(r, c); 516 if (all_capabilities) 517 credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL); 518 519 for (i = 0; i < c->helpers.nr; i++) { 520 credential_do(c, c->helpers.items[i].string, "get"); 521 522 if (c->password_expiry_utc < time(NULL)) { 523 /* 524 * Don't use credential_clear() here: callers such as 525 * cmd_credential() expect to still be able to call 526 * credential_write() on a struct credential whose 527 * secrets have expired. 528 */ 529 credential_clear_secrets(c); 530 /* Reset expiry to maintain consistency */ 531 c->password_expiry_utc = TIME_MAX; 532 } 533 if ((c->username && c->password) || c->credential) { 534 strvec_clear(&c->wwwauth_headers); 535 return; 536 } 537 if (c->quit) 538 die("credential helper '%s' told us to quit", 539 c->helpers.items[i].string); 540 } 541 542 if (credential_getpass(r, c) || 543 (!c->username && !c->password && !c->credential)) 544 die("unable to get password from user"); 545} 546 547void credential_approve(struct repository *r, struct credential *c) 548{ 549 int i; 550 551 if (c->approved) 552 return; 553 if (((!c->username || !c->password) && !c->credential) || c->password_expiry_utc < time(NULL)) 554 return; 555 556 credential_next_state(c); 557 558 credential_apply_config(r, c); 559 560 for (i = 0; i < c->helpers.nr; i++) 561 credential_do(c, c->helpers.items[i].string, "store"); 562 c->approved = 1; 563} 564 565void credential_reject(struct repository *r, struct credential *c) 566{ 567 int i; 568 569 credential_next_state(c); 570 571 credential_apply_config(r, c); 572 573 for (i = 0; i < c->helpers.nr; i++) 574 credential_do(c, c->helpers.items[i].string, "erase"); 575 576 credential_clear_secrets(c); 577 FREE_AND_NULL(c->username); 578 FREE_AND_NULL(c->oauth_refresh_token); 579 c->password_expiry_utc = TIME_MAX; 580 c->approved = 0; 581} 582 583static int check_url_component(const char *url, int quiet, 584 const char *name, const char *value) 585{ 586 if (!value) 587 return 0; 588 if (!strchr(value, '\n')) 589 return 0; 590 591 if (!quiet) 592 warning(_("url contains a newline in its %s component: %s"), 593 name, url); 594 return -1; 595} 596 597/* 598 * Potentially-partial URLs can, but do not have to, contain 599 * 600 * - a protocol (or scheme) of the form "<protocol>://" 601 * 602 * - a host name (the part after the protocol and before the first slash after 603 * that, if any) 604 * 605 * - a user name and potentially a password (as "<user>[:<password>]@" part of 606 * the host name) 607 * 608 * - a path (the part after the host name, if any, starting with the slash) 609 * 610 * Missing parts will be left unset in `struct credential`. Thus, `https://` 611 * will have only the `protocol` set, `example.com` only the host name, and 612 * `/git` only the path. 613 * 614 * Note that an empty host name in an otherwise fully-qualified URL (e.g. 615 * `cert:///path/to/cert.pem`) will be treated as unset if we expect the URL to 616 * be potentially partial, and only then (otherwise, the empty string is used). 617 * 618 * The credential_from_url() function does not allow partial URLs. 619 */ 620static int credential_from_url_1(struct credential *c, const char *url, 621 int allow_partial_url, int quiet) 622{ 623 const char *at, *colon, *cp, *slash, *host, *proto_end; 624 625 credential_clear(c); 626 627 /* 628 * Match one of: 629 * (1) proto://<host>/... 630 * (2) proto://<user>@<host>/... 631 * (3) proto://<user>:<pass>@<host>/... 632 */ 633 proto_end = strstr(url, "://"); 634 if (!allow_partial_url && (!proto_end || proto_end == url)) { 635 if (!quiet) 636 warning(_("url has no scheme: %s"), url); 637 return -1; 638 } 639 cp = proto_end ? proto_end + 3 : url; 640 at = strchr(cp, '@'); 641 colon = strchr(cp, ':'); 642 643 /* 644 * A query or fragment marker before the slash ends the host portion. 645 * We'll just continue to call this "slash" for simplicity. Notably our 646 * "trim leading slashes" part won't skip over this part of the path, 647 * but that's what we'd want. 648 */ 649 slash = cp + strcspn(cp, "/?#"); 650 651 if (!at || slash <= at) { 652 /* Case (1) */ 653 host = cp; 654 } 655 else if (!colon || at <= colon) { 656 /* Case (2) */ 657 c->username = url_decode_mem(cp, at - cp); 658 if (c->username && *c->username) 659 c->username_from_proto = 1; 660 host = at + 1; 661 } else { 662 /* Case (3) */ 663 c->username = url_decode_mem(cp, colon - cp); 664 if (c->username && *c->username) 665 c->username_from_proto = 1; 666 c->password = url_decode_mem(colon + 1, at - (colon + 1)); 667 host = at + 1; 668 } 669 670 if (proto_end && proto_end - url > 0) 671 c->protocol = xmemdupz(url, proto_end - url); 672 if (!allow_partial_url || slash - host > 0) 673 c->host = url_decode_mem(host, slash - host); 674 /* Trim leading and trailing slashes from path */ 675 while (*slash == '/') 676 slash++; 677 if (*slash) { 678 char *p; 679 c->path = url_decode(slash); 680 p = c->path + strlen(c->path) - 1; 681 while (p > c->path && *p == '/') 682 *p-- = '\0'; 683 } 684 685 if (check_url_component(url, quiet, "username", c->username) < 0 || 686 check_url_component(url, quiet, "password", c->password) < 0 || 687 check_url_component(url, quiet, "protocol", c->protocol) < 0 || 688 check_url_component(url, quiet, "host", c->host) < 0 || 689 check_url_component(url, quiet, "path", c->path) < 0) 690 return -1; 691 692 return 0; 693} 694 695static int credential_from_potentially_partial_url(struct credential *c, 696 const char *url) 697{ 698 return credential_from_url_1(c, url, 1, 0); 699} 700 701int credential_from_url_gently(struct credential *c, const char *url, int quiet) 702{ 703 return credential_from_url_1(c, url, 0, quiet); 704} 705 706void credential_from_url(struct credential *c, const char *url) 707{ 708 if (credential_from_url_gently(c, url, 0) < 0) 709 die(_("credential url cannot be parsed: %s"), url); 710}