]> git.ipfire.org Git - thirdparty/git.git/blame - credential.c
credential: use the last matching username in the config
[thirdparty/git.git] / credential.c
CommitLineData
abca927d 1#include "cache.h"
b2141fc1 2#include "config.h"
abca927d
JK
3#include "credential.h"
4#include "string-list.h"
5#include "run-command.h"
d3e847c1 6#include "url.h"
d3c58b83 7#include "prompt.h"
a0d51e8d 8#include "sigchain.h"
abca927d
JK
9
10void credential_init(struct credential *c)
11{
12 memset(c, 0, sizeof(*c));
13 c->helpers.strdup_strings = 1;
14}
15
16void credential_clear(struct credential *c)
17{
18 free(c->protocol);
19 free(c->host);
20 free(c->path);
21 free(c->username);
22 free(c->password);
23 string_list_clear(&c->helpers, 0);
24
25 credential_init(c);
26}
27
11825072
JK
28int credential_match(const struct credential *want,
29 const struct credential *have)
30{
31#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
32 return CHECK(protocol) &&
33 CHECK(host) &&
34 CHECK(path) &&
35 CHECK(username);
36#undef CHECK
37}
38
39static int credential_config_callback(const char *var, const char *value,
40 void *data)
41{
42 struct credential *c = data;
43 const char *key, *dot;
44
cf4fff57 45 if (!skip_prefix(var, "credential.", &key))
11825072
JK
46 return 0;
47
48 if (!value)
49 return config_error_nonbool(var);
50
51 dot = strrchr(key, '.');
52 if (dot) {
53 struct credential want = CREDENTIAL_INIT;
54 char *url = xmemdupz(key, dot - key);
55 int matched;
56
57 credential_from_url(&want, url);
58 matched = credential_match(&want, c);
59
60 credential_clear(&want);
61 free(url);
62
63 if (!matched)
64 return 0;
65 key = dot + 1;
66 }
67
24321375
JK
68 if (!strcmp(key, "helper")) {
69 if (*value)
70 string_list_append(&c->helpers, value);
71 else
72 string_list_clear(&c->helpers, 0);
73 } else if (!strcmp(key, "username")) {
82eb2498 74 if (!c->username_from_proto) {
75 free(c->username);
d5742425 76 c->username = xstrdup(value);
82eb2498 77 }
d5742425 78 }
a78fbb4f
JK
79 else if (!strcmp(key, "usehttppath"))
80 c->use_http_path = git_config_bool(var, value);
11825072
JK
81
82 return 0;
83}
84
a78fbb4f
JK
85static int proto_is_http(const char *s)
86{
87 if (!s)
88 return 0;
89 return !strcmp(s, "https") || !strcmp(s, "http");
90}
91
11825072
JK
92static void credential_apply_config(struct credential *c)
93{
94 if (c->configured)
95 return;
96 git_config(credential_config_callback, c);
97 c->configured = 1;
a78fbb4f
JK
98
99 if (!c->use_http_path && proto_is_http(c->protocol)) {
6a83d902 100 FREE_AND_NULL(c->path);
a78fbb4f 101 }
11825072
JK
102}
103
abca927d
JK
104static void credential_describe(struct credential *c, struct strbuf *out)
105{
106 if (!c->protocol)
107 return;
108 strbuf_addf(out, "%s://", c->protocol);
109 if (c->username && *c->username)
110 strbuf_addf(out, "%s@", c->username);
111 if (c->host)
112 strbuf_addstr(out, c->host);
113 if (c->path)
114 strbuf_addf(out, "/%s", c->path);
115}
116
ce77aa48
JK
117static char *credential_ask_one(const char *what, struct credential *c,
118 int flags)
abca927d
JK
119{
120 struct strbuf desc = STRBUF_INIT;
121 struct strbuf prompt = STRBUF_INIT;
122 char *r;
123
124 credential_describe(c, &desc);
125 if (desc.len)
126 strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
127 else
128 strbuf_addf(&prompt, "%s: ", what);
129
ce77aa48 130 r = git_prompt(prompt.buf, flags);
abca927d
JK
131
132 strbuf_release(&desc);
133 strbuf_release(&prompt);
134 return xstrdup(r);
135}
136
137static void credential_getpass(struct credential *c)
138{
139 if (!c->username)
ce77aa48
JK
140 c->username = credential_ask_one("Username", c,
141 PROMPT_ASKPASS|PROMPT_ECHO);
abca927d 142 if (!c->password)
ce77aa48
JK
143 c->password = credential_ask_one("Password", c,
144 PROMPT_ASKPASS);
abca927d
JK
145}
146
147int credential_read(struct credential *c, FILE *fp)
148{
149 struct strbuf line = STRBUF_INIT;
150
8f309aeb 151 while (strbuf_getline_lf(&line, fp) != EOF) {
abca927d
JK
152 char *key = line.buf;
153 char *value = strchr(key, '=');
154
155 if (!line.len)
156 break;
157
158 if (!value) {
159 warning("invalid credential line: %s", key);
160 strbuf_release(&line);
161 return -1;
162 }
163 *value++ = '\0';
164
165 if (!strcmp(key, "username")) {
166 free(c->username);
167 c->username = xstrdup(value);
82eb2498 168 c->username_from_proto = 1;
abca927d
JK
169 } else if (!strcmp(key, "password")) {
170 free(c->password);
171 c->password = xstrdup(value);
172 } else if (!strcmp(key, "protocol")) {
173 free(c->protocol);
174 c->protocol = xstrdup(value);
175 } else if (!strcmp(key, "host")) {
176 free(c->host);
177 c->host = xstrdup(value);
178 } else if (!strcmp(key, "path")) {
179 free(c->path);
180 c->path = xstrdup(value);
9c183a70
JK
181 } else if (!strcmp(key, "url")) {
182 credential_from_url(c, value);
59b38652
JK
183 } else if (!strcmp(key, "quit")) {
184 c->quit = !!git_config_bool("quit", value);
abca927d
JK
185 }
186 /*
187 * Ignore other lines; we don't know what they mean, but
188 * this future-proofs us when later versions of git do
189 * learn new lines, and the helpers are updated to match.
190 */
191 }
192
193 strbuf_release(&line);
194 return 0;
195}
196
197static void credential_write_item(FILE *fp, const char *key, const char *value)
198{
199 if (!value)
200 return;
201 fprintf(fp, "%s=%s\n", key, value);
202}
203
2d6dc182 204void credential_write(const struct credential *c, FILE *fp)
abca927d
JK
205{
206 credential_write_item(fp, "protocol", c->protocol);
207 credential_write_item(fp, "host", c->host);
208 credential_write_item(fp, "path", c->path);
209 credential_write_item(fp, "username", c->username);
210 credential_write_item(fp, "password", c->password);
211}
212
213static int run_credential_helper(struct credential *c,
214 const char *cmd,
215 int want_output)
216{
d3180279 217 struct child_process helper = CHILD_PROCESS_INIT;
abca927d
JK
218 const char *argv[] = { NULL, NULL };
219 FILE *fp;
220
abca927d
JK
221 argv[0] = cmd;
222 helper.argv = argv;
223 helper.use_shell = 1;
224 helper.in = -1;
225 if (want_output)
226 helper.out = -1;
227 else
228 helper.no_stdout = 1;
229
230 if (start_command(&helper) < 0)
231 return -1;
232
233 fp = xfdopen(helper.in, "w");
a0d51e8d 234 sigchain_push(SIGPIPE, SIG_IGN);
abca927d
JK
235 credential_write(c, fp);
236 fclose(fp);
a0d51e8d 237 sigchain_pop(SIGPIPE);
abca927d
JK
238
239 if (want_output) {
240 int r;
241 fp = xfdopen(helper.out, "r");
242 r = credential_read(c, fp);
243 fclose(fp);
244 if (r < 0) {
245 finish_command(&helper);
246 return -1;
247 }
248 }
249
250 if (finish_command(&helper))
251 return -1;
252 return 0;
253}
254
255static int credential_do(struct credential *c, const char *helper,
256 const char *operation)
257{
258 struct strbuf cmd = STRBUF_INIT;
259 int r;
260
261 if (helper[0] == '!')
262 strbuf_addstr(&cmd, helper + 1);
263 else if (is_absolute_path(helper))
264 strbuf_addstr(&cmd, helper);
265 else
266 strbuf_addf(&cmd, "git credential-%s", helper);
267
268 strbuf_addf(&cmd, " %s", operation);
269 r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
270
271 strbuf_release(&cmd);
272 return r;
273}
274
275void credential_fill(struct credential *c)
276{
277 int i;
278
279 if (c->username && c->password)
280 return;
281
11825072
JK
282 credential_apply_config(c);
283
abca927d
JK
284 for (i = 0; i < c->helpers.nr; i++) {
285 credential_do(c, c->helpers.items[i].string, "get");
286 if (c->username && c->password)
287 return;
59b38652
JK
288 if (c->quit)
289 die("credential helper '%s' told us to quit",
290 c->helpers.items[i].string);
abca927d
JK
291 }
292
293 credential_getpass(c);
294 if (!c->username && !c->password)
295 die("unable to get password from user");
296}
297
298void credential_approve(struct credential *c)
299{
300 int i;
301
302 if (c->approved)
303 return;
304 if (!c->username || !c->password)
305 return;
306
11825072
JK
307 credential_apply_config(c);
308
abca927d
JK
309 for (i = 0; i < c->helpers.nr; i++)
310 credential_do(c, c->helpers.items[i].string, "store");
311 c->approved = 1;
312}
313
314void credential_reject(struct credential *c)
315{
316 int i;
317
11825072
JK
318 credential_apply_config(c);
319
abca927d
JK
320 for (i = 0; i < c->helpers.nr; i++)
321 credential_do(c, c->helpers.items[i].string, "erase");
322
88ce3ef6
ÆAB
323 FREE_AND_NULL(c->username);
324 FREE_AND_NULL(c->password);
abca927d
JK
325 c->approved = 0;
326}
d3e847c1
JK
327
328void credential_from_url(struct credential *c, const char *url)
329{
330 const char *at, *colon, *cp, *slash, *host, *proto_end;
331
332 credential_clear(c);
333
334 /*
335 * Match one of:
336 * (1) proto://<host>/...
337 * (2) proto://<user>@<host>/...
338 * (3) proto://<user>:<pass>@<host>/...
339 */
340 proto_end = strstr(url, "://");
341 if (!proto_end)
342 return;
343 cp = proto_end + 3;
344 at = strchr(cp, '@');
345 colon = strchr(cp, ':');
346 slash = strchrnul(cp, '/');
347
348 if (!at || slash <= at) {
349 /* Case (1) */
350 host = cp;
351 }
352 else if (!colon || at <= colon) {
353 /* Case (2) */
354 c->username = url_decode_mem(cp, at - cp);
82eb2498 355 if (c->username && *c->username)
356 c->username_from_proto = 1;
d3e847c1
JK
357 host = at + 1;
358 } else {
359 /* Case (3) */
360 c->username = url_decode_mem(cp, colon - cp);
82eb2498 361 if (c->username && *c->username)
362 c->username_from_proto = 1;
d3e847c1
JK
363 c->password = url_decode_mem(colon + 1, at - (colon + 1));
364 host = at + 1;
365 }
366
367 if (proto_end - url > 0)
368 c->protocol = xmemdupz(url, proto_end - url);
369 if (slash - host > 0)
370 c->host = url_decode_mem(host, slash - host);
371 /* Trim leading and trailing slashes from path */
372 while (*slash == '/')
373 slash++;
374 if (*slash) {
375 char *p;
376 c->path = url_decode(slash);
377 p = c->path + strlen(c->path) - 1;
378 while (p > c->path && *p == '/')
379 *p-- = '\0';
380 }
381}