#include "cache.h" #include "lockfile.h" #include "credential.h" #include "string-list.h" #include "parse-options.h" static struct lock_file credential_lock; static int parse_credential_file(const char *fn, struct credential *c, void (*match_cb)(struct credential *), void (*other_cb)(struct strbuf *)) { FILE *fh; struct strbuf line = STRBUF_INIT; struct credential entry = CREDENTIAL_INIT; int found_credential = 0; fh = fopen(fn, "r"); if (!fh) { if (errno != ENOENT && errno != EACCES) die_errno("unable to open %s", fn); return found_credential; } while (strbuf_getline_lf(&line, fh) != EOF) { credential_from_url(&entry, line.buf); if (entry.username && entry.password && credential_match(c, &entry)) { found_credential = 1; if (match_cb) { match_cb(&entry); break; } } else if (other_cb) other_cb(&line); } credential_clear(&entry); strbuf_release(&line); fclose(fh); return found_credential; } static void print_entry(struct credential *c) { printf("username=%s\n", c->username); printf("password=%s\n", c->password); } static void print_line(struct strbuf *buf) { strbuf_addch(buf, '\n'); write_or_die(get_lock_file_fd(&credential_lock), buf->buf, buf->len); } static void rewrite_credential_file(const char *fn, struct credential *c, struct strbuf *extra) { if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0) die_errno("unable to get credential storage lock"); if (extra) print_line(extra); parse_credential_file(fn, c, NULL, print_line); if (commit_lock_file(&credential_lock) < 0) die_errno("unable to write credential store"); } static void store_credential_file(const char *fn, struct credential *c) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%s://", c->protocol); strbuf_addstr_urlencode(&buf, c->username, 1); strbuf_addch(&buf, ':'); strbuf_addstr_urlencode(&buf, c->password, 1); strbuf_addch(&buf, '@'); if (c->host) strbuf_addstr_urlencode(&buf, c->host, 1); if (c->path) { strbuf_addch(&buf, '/'); strbuf_addstr_urlencode(&buf, c->path, 0); } rewrite_credential_file(fn, c, &buf); strbuf_release(&buf); } static void store_credential(const struct string_list *fns, struct credential *c) { struct string_list_item *fn; /* * Sanity check that what we are storing is actually sensible. * In particular, we can't make a URL without a protocol field. * Without either a host or pathname (depending on the scheme), * we have no primary key. And without a username and password, * we are not actually storing a credential. */ if (!c->protocol || !(c->host || c->path) || !c->username || !c->password) return; for_each_string_list_item(fn, fns) if (!access(fn->string, F_OK)) { store_credential_file(fn->string, c); return; } /* * Write credential to the filename specified by fns->items[0], thus * creating it */ if (fns->nr) store_credential_file(fns->items[0].string, c); } static void remove_credential(const struct string_list *fns, struct credential *c) { struct string_list_item *fn; /* * Sanity check that we actually have something to match * against. The input we get is a restrictive pattern, * so technically a blank credential means "erase everything". * But it is too easy to accidentally send this, since it is equivalent * to empty input. So explicitly disallow it, and require that the * pattern have some actual content to match. */ if (!c->protocol && !c->host && !c->path && !c->username) return; for_each_string_list_item(fn, fns) if (!access(fn->string, F_OK)) rewrite_credential_file(fn->string, c, NULL); } static void lookup_credential(const struct string_list *fns, struct credential *c) { struct string_list_item *fn; for_each_string_list_item(fn, fns) if (parse_credential_file(fn->string, c, print_entry, NULL)) return; /* Found credential */ } int cmd_main(int argc, const char **argv) { const char * const usage[] = { "git credential-store [<options>] <action>", NULL }; const char *op; struct credential c = CREDENTIAL_INIT; struct string_list fns = STRING_LIST_INIT_DUP; char *file = NULL; struct option options[] = { OPT_STRING(0, "file", &file, "path", "fetch and store credentials in <path>"), OPT_END() }; umask(077); argc = parse_options(argc, (const char **)argv, NULL, options, usage, 0); if (argc != 1) usage_with_options(usage, options); op = argv[0]; if (file) { string_list_append(&fns, file); } else { if ((file = expand_user_path("~/.git-credentials", 0))) string_list_append_nodup(&fns, file); file = xdg_config_home("credentials"); if (file) string_list_append_nodup(&fns, file); } if (!fns.nr) die("unable to set up default path; use --file"); if (credential_read(&c, stdin) < 0) die("unable to read credential"); if (!strcmp(op, "get")) lookup_credential(&fns, &c); else if (!strcmp(op, "erase")) remove_credential(&fns, &c); else if (!strcmp(op, "store")) store_credential(&fns, &c); else ; /* Ignore unknown operation. */ string_list_clear(&fns, 0); return 0; }