]>
Commit | Line | Data |
---|---|---|
b5dd96b7 | 1 | #include "builtin.h" |
df7f915f | 2 | #include "config.h" |
f394e093 | 3 | #include "gettext.h" |
697cc8ef | 4 | #include "lockfile.h" |
71e1b4b6 | 5 | #include "credential.h" |
d1cbe1e6 | 6 | #include "path.h" |
71e1b4b6 JK |
7 | #include "string-list.h" |
8 | #include "parse-options.h" | |
d48be35c | 9 | #include "write-or-die.h" |
71e1b4b6 JK |
10 | |
11 | static struct lock_file credential_lock; | |
12 | ||
cb2c2796 | 13 | static int parse_credential_file(const char *fn, |
71e1b4b6 JK |
14 | struct credential *c, |
15 | void (*match_cb)(struct credential *), | |
aeb21ce2 H |
16 | void (*other_cb)(struct strbuf *), |
17 | int match_password) | |
71e1b4b6 JK |
18 | { |
19 | FILE *fh; | |
20 | struct strbuf line = STRBUF_INIT; | |
21 | struct credential entry = CREDENTIAL_INIT; | |
cb2c2796 | 22 | int found_credential = 0; |
71e1b4b6 JK |
23 | |
24 | fh = fopen(fn, "r"); | |
25 | if (!fh) { | |
cb2c2796 | 26 | if (errno != ENOENT && errno != EACCES) |
71e1b4b6 | 27 | die_errno("unable to open %s", fn); |
cb2c2796 | 28 | return found_credential; |
71e1b4b6 JK |
29 | } |
30 | ||
8f309aeb | 31 | while (strbuf_getline_lf(&line, fh) != EOF) { |
c03859a6 CMAB |
32 | if (!credential_from_url_gently(&entry, line.buf, 1) && |
33 | entry.username && entry.password && | |
aeb21ce2 | 34 | credential_match(c, &entry, match_password)) { |
cb2c2796 | 35 | found_credential = 1; |
71e1b4b6 JK |
36 | if (match_cb) { |
37 | match_cb(&entry); | |
38 | break; | |
39 | } | |
40 | } | |
41 | else if (other_cb) | |
42 | other_cb(&line); | |
43 | } | |
44 | ||
45 | credential_clear(&entry); | |
46 | strbuf_release(&line); | |
47 | fclose(fh); | |
cb2c2796 | 48 | return found_credential; |
71e1b4b6 JK |
49 | } |
50 | ||
51 | static void print_entry(struct credential *c) | |
52 | { | |
53 | printf("username=%s\n", c->username); | |
54 | printf("password=%s\n", c->password); | |
55 | } | |
56 | ||
57 | static void print_line(struct strbuf *buf) | |
58 | { | |
59 | strbuf_addch(buf, '\n'); | |
c99a4c2d | 60 | write_or_die(get_lock_file_fd(&credential_lock), buf->buf, buf->len); |
71e1b4b6 JK |
61 | } |
62 | ||
63 | static void rewrite_credential_file(const char *fn, struct credential *c, | |
aeb21ce2 | 64 | struct strbuf *extra, int match_password) |
71e1b4b6 | 65 | { |
df7f915f SA |
66 | int timeout_ms = 1000; |
67 | ||
68 | git_config_get_int("credentialstore.locktimeoutms", &timeout_ms); | |
69 | if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms) < 0) | |
70 | die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms); | |
71e1b4b6 JK |
71 | if (extra) |
72 | print_line(extra); | |
aeb21ce2 | 73 | parse_credential_file(fn, c, NULL, print_line, match_password); |
71e1b4b6 | 74 | if (commit_lock_file(&credential_lock) < 0) |
87d01c85 | 75 | die_errno("unable to write credential store"); |
71e1b4b6 JK |
76 | } |
77 | ||
f8985436 CW |
78 | static int is_rfc3986_unreserved(char ch) |
79 | { | |
80 | return isalnum(ch) || | |
81 | ch == '-' || ch == '_' || ch == '.' || ch == '~'; | |
82 | } | |
83 | ||
84 | static int is_rfc3986_reserved_or_unreserved(char ch) | |
85 | { | |
86 | if (is_rfc3986_unreserved(ch)) | |
87 | return 1; | |
88 | switch (ch) { | |
89 | case '!': case '*': case '\'': case '(': case ')': case ';': | |
90 | case ':': case '@': case '&': case '=': case '+': case '$': | |
91 | case ',': case '/': case '?': case '#': case '[': case ']': | |
92 | return 1; | |
93 | } | |
94 | return 0; | |
95 | } | |
96 | ||
cb2c2796 | 97 | static void store_credential_file(const char *fn, struct credential *c) |
71e1b4b6 JK |
98 | { |
99 | struct strbuf buf = STRBUF_INIT; | |
100 | ||
71e1b4b6 | 101 | strbuf_addf(&buf, "%s://", c->protocol); |
c2694952 | 102 | strbuf_addstr_urlencode(&buf, c->username, is_rfc3986_unreserved); |
71e1b4b6 | 103 | strbuf_addch(&buf, ':'); |
c2694952 | 104 | strbuf_addstr_urlencode(&buf, c->password, is_rfc3986_unreserved); |
71e1b4b6 JK |
105 | strbuf_addch(&buf, '@'); |
106 | if (c->host) | |
c2694952 | 107 | strbuf_addstr_urlencode(&buf, c->host, is_rfc3986_unreserved); |
71e1b4b6 JK |
108 | if (c->path) { |
109 | strbuf_addch(&buf, '/'); | |
c2694952 MD |
110 | strbuf_addstr_urlencode(&buf, c->path, |
111 | is_rfc3986_reserved_or_unreserved); | |
71e1b4b6 JK |
112 | } |
113 | ||
aeb21ce2 | 114 | rewrite_credential_file(fn, c, &buf, 0); |
71e1b4b6 JK |
115 | strbuf_release(&buf); |
116 | } | |
117 | ||
cb2c2796 | 118 | static void store_credential(const struct string_list *fns, struct credential *c) |
71e1b4b6 | 119 | { |
cb2c2796 PT |
120 | struct string_list_item *fn; |
121 | ||
122 | /* | |
123 | * Sanity check that what we are storing is actually sensible. | |
124 | * In particular, we can't make a URL without a protocol field. | |
125 | * Without either a host or pathname (depending on the scheme), | |
126 | * we have no primary key. And without a username and password, | |
127 | * we are not actually storing a credential. | |
128 | */ | |
129 | if (!c->protocol || !(c->host || c->path) || !c->username || !c->password) | |
130 | return; | |
131 | ||
132 | for_each_string_list_item(fn, fns) | |
133 | if (!access(fn->string, F_OK)) { | |
134 | store_credential_file(fn->string, c); | |
135 | return; | |
136 | } | |
137 | /* | |
138 | * Write credential to the filename specified by fns->items[0], thus | |
139 | * creating it | |
140 | */ | |
141 | if (fns->nr) | |
142 | store_credential_file(fns->items[0].string, c); | |
143 | } | |
144 | ||
145 | static void remove_credential(const struct string_list *fns, struct credential *c) | |
71e1b4b6 | 146 | { |
cb2c2796 PT |
147 | struct string_list_item *fn; |
148 | ||
71e1b4b6 JK |
149 | /* |
150 | * Sanity check that we actually have something to match | |
151 | * against. The input we get is a restrictive pattern, | |
152 | * so technically a blank credential means "erase everything". | |
153 | * But it is too easy to accidentally send this, since it is equivalent | |
154 | * to empty input. So explicitly disallow it, and require that the | |
155 | * pattern have some actual content to match. | |
156 | */ | |
cb2c2796 PT |
157 | if (!c->protocol && !c->host && !c->path && !c->username) |
158 | return; | |
159 | for_each_string_list_item(fn, fns) | |
160 | if (!access(fn->string, F_OK)) | |
aeb21ce2 | 161 | rewrite_credential_file(fn->string, c, NULL, 1); |
71e1b4b6 JK |
162 | } |
163 | ||
cb2c2796 | 164 | static void lookup_credential(const struct string_list *fns, struct credential *c) |
71e1b4b6 | 165 | { |
cb2c2796 PT |
166 | struct string_list_item *fn; |
167 | ||
168 | for_each_string_list_item(fn, fns) | |
aeb21ce2 | 169 | if (parse_credential_file(fn->string, c, print_entry, NULL, 0)) |
cb2c2796 | 170 | return; /* Found credential */ |
71e1b4b6 JK |
171 | } |
172 | ||
b5dd96b7 | 173 | int cmd_credential_store(int argc, const char **argv, const char *prefix) |
71e1b4b6 JK |
174 | { |
175 | const char * const usage[] = { | |
9c9b4f2f | 176 | "git credential-store [<options>] <action>", |
71e1b4b6 JK |
177 | NULL |
178 | }; | |
179 | const char *op; | |
180 | struct credential c = CREDENTIAL_INIT; | |
cb2c2796 | 181 | struct string_list fns = STRING_LIST_INIT_DUP; |
71e1b4b6 JK |
182 | char *file = NULL; |
183 | struct option options[] = { | |
184 | OPT_STRING(0, "file", &file, "path", | |
185 | "fetch and store credentials in <path>"), | |
186 | OPT_END() | |
187 | }; | |
188 | ||
189 | umask(077); | |
190 | ||
b5dd96b7 | 191 | argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); |
71e1b4b6 JK |
192 | if (argc != 1) |
193 | usage_with_options(usage, options); | |
194 | op = argv[0]; | |
195 | ||
44b22898 | 196 | if (file) { |
cb2c2796 | 197 | string_list_append(&fns, file); |
44b22898 | 198 | } else { |
a03b097d | 199 | if ((file = interpolate_path("~/.git-credentials", 0))) |
44b22898 | 200 | string_list_append_nodup(&fns, file); |
64ab71db | 201 | file = xdg_config_home("credentials"); |
44b22898 PT |
202 | if (file) |
203 | string_list_append_nodup(&fns, file); | |
204 | } | |
205 | if (!fns.nr) | |
71e1b4b6 JK |
206 | die("unable to set up default path; use --file"); |
207 | ||
ca9ccbf6 | 208 | if (credential_read(&c, stdin, CREDENTIAL_OP_HELPER) < 0) |
71e1b4b6 JK |
209 | die("unable to read credential"); |
210 | ||
211 | if (!strcmp(op, "get")) | |
cb2c2796 | 212 | lookup_credential(&fns, &c); |
71e1b4b6 | 213 | else if (!strcmp(op, "erase")) |
cb2c2796 | 214 | remove_credential(&fns, &c); |
71e1b4b6 | 215 | else if (!strcmp(op, "store")) |
cb2c2796 | 216 | store_credential(&fns, &c); |
71e1b4b6 JK |
217 | else |
218 | ; /* Ignore unknown operation. */ | |
219 | ||
cb2c2796 | 220 | string_list_clear(&fns, 0); |
71e1b4b6 JK |
221 | return 0; |
222 | } |