mirror of
https://github.com/git/git.git
synced 2026-03-14 18:59:04 +01:00
When an http request receives a 401, we ask the user for both a username and password. While it's generally not a good idea for us to store the password in plaintext, having to input the username each time is annoying, and can be easily solved with a config variable. This patch teaches the credential subsystem to look up items in the git config file before prompting. Items are indexed by the "unique" token passed to the credential system. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
229 lines
4.7 KiB
C
229 lines
4.7 KiB
C
#include "cache.h"
|
|
#include "credential.h"
|
|
#include "quote.h"
|
|
#include "string-list.h"
|
|
#include "run-command.h"
|
|
|
|
static int credential_config_callback(const char *var, const char *value,
|
|
void *data)
|
|
{
|
|
struct credential *c = data;
|
|
|
|
if (!value)
|
|
return 0;
|
|
|
|
var = skip_prefix(var, "credential.");
|
|
if (!var)
|
|
return 0;
|
|
|
|
var = skip_prefix(var, c->unique);
|
|
if (!var)
|
|
return 0;
|
|
|
|
if (*var != '.')
|
|
return 0;
|
|
var++;
|
|
|
|
if (!strcmp(var, "username")) {
|
|
if (!c->username)
|
|
c->username = xstrdup(value);
|
|
}
|
|
else if (!strcmp(var, "password")) {
|
|
free(c->password);
|
|
c->password = xstrdup(value);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void credential_from_config(struct credential *c)
|
|
{
|
|
if (c->unique)
|
|
git_config(credential_config_callback, c);
|
|
}
|
|
|
|
static char *credential_ask_one(const char *what, const char *desc)
|
|
{
|
|
struct strbuf prompt = STRBUF_INIT;
|
|
char *r;
|
|
|
|
if (desc)
|
|
strbuf_addf(&prompt, "%s for '%s': ", what, desc);
|
|
else
|
|
strbuf_addf(&prompt, "%s: ", what);
|
|
|
|
/* FIXME: for usernames, we should do something less magical that
|
|
* actually echoes the characters. However, we need to read from
|
|
* /dev/tty and not stdio, which is not portable (but getpass will do
|
|
* it for us). http.c uses the same workaround. */
|
|
r = git_getpass(prompt.buf);
|
|
|
|
strbuf_release(&prompt);
|
|
return xstrdup(r);
|
|
}
|
|
|
|
int credential_getpass(struct credential *c)
|
|
{
|
|
credential_from_config(c);
|
|
|
|
if (!c->username)
|
|
c->username = credential_ask_one("Username", c->description);
|
|
if (!c->password)
|
|
c->password = credential_ask_one("Password", c->description);
|
|
return 0;
|
|
}
|
|
|
|
static int read_credential_response(struct credential *c, FILE *fp)
|
|
{
|
|
struct strbuf response = STRBUF_INIT;
|
|
|
|
while (strbuf_getline(&response, fp, '\n') != EOF) {
|
|
char *key = response.buf;
|
|
char *value = strchr(key, '=');
|
|
|
|
if (!value) {
|
|
warning("bad output from credential helper: %s", key);
|
|
strbuf_release(&response);
|
|
return -1;
|
|
}
|
|
*value++ = '\0';
|
|
|
|
if (!strcmp(key, "username")) {
|
|
free(c->username);
|
|
c->username = xstrdup(value);
|
|
}
|
|
else if (!strcmp(key, "password")) {
|
|
free(c->password);
|
|
c->password = xstrdup(value);
|
|
}
|
|
/* ignore other responses; we don't know what they mean */
|
|
}
|
|
|
|
strbuf_release(&response);
|
|
return 0;
|
|
}
|
|
|
|
static int run_credential_helper(struct credential *c, const char *cmd)
|
|
{
|
|
struct child_process helper;
|
|
const char *argv[] = { NULL, NULL };
|
|
FILE *fp;
|
|
int r;
|
|
|
|
memset(&helper, 0, sizeof(helper));
|
|
argv[0] = cmd;
|
|
helper.argv = argv;
|
|
helper.use_shell = 1;
|
|
helper.no_stdin = 1;
|
|
helper.out = -1;
|
|
|
|
if (start_command(&helper))
|
|
return -1;
|
|
fp = xfdopen(helper.out, "r");
|
|
|
|
r = read_credential_response(c, fp);
|
|
|
|
fclose(fp);
|
|
if (finish_command(&helper))
|
|
r = -1;
|
|
|
|
return r;
|
|
}
|
|
|
|
static void add_item(struct strbuf *out, const char *key, const char *value)
|
|
{
|
|
if (!value)
|
|
return;
|
|
strbuf_addf(out, " --%s=", key);
|
|
sq_quote_buf(out, value);
|
|
}
|
|
|
|
static int first_word_is_alnum(const char *s)
|
|
{
|
|
for (; *s && *s != ' '; s++)
|
|
if (!isalnum(*s))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int credential_do(struct credential *c, const char *method,
|
|
const char *extra)
|
|
{
|
|
struct strbuf cmd = STRBUF_INIT;
|
|
int r;
|
|
|
|
if (first_word_is_alnum(method))
|
|
strbuf_addf(&cmd, "git credential-%s", method);
|
|
else
|
|
strbuf_addstr(&cmd, method);
|
|
|
|
if (extra)
|
|
strbuf_addf(&cmd, " %s", extra);
|
|
|
|
add_item(&cmd, "description", c->description);
|
|
add_item(&cmd, "unique", c->unique);
|
|
add_item(&cmd, "username", c->username);
|
|
|
|
r = run_credential_helper(c, cmd.buf);
|
|
|
|
strbuf_release(&cmd);
|
|
return r;
|
|
}
|
|
|
|
void credential_fill(struct credential *c, const struct string_list *methods)
|
|
{
|
|
struct strbuf err = STRBUF_INIT;
|
|
|
|
if (!credential_fill_gently(c, methods))
|
|
return;
|
|
|
|
strbuf_addstr(&err, "unable to get credentials");
|
|
if (c->description)
|
|
strbuf_addf(&err, "for '%s'", c->description);
|
|
if (methods && methods->nr == 1)
|
|
strbuf_addf(&err, "; tried '%s'", methods->items[0].string);
|
|
else if (methods) {
|
|
int i;
|
|
strbuf_addstr(&err, "; tried:");
|
|
for (i = 0; i < methods->nr; i++)
|
|
strbuf_addf(&err, "\n %s", methods->items[i].string);
|
|
}
|
|
die("%s", err.buf);
|
|
}
|
|
|
|
int credential_fill_gently(struct credential *c,
|
|
const struct string_list *methods)
|
|
{
|
|
int i;
|
|
|
|
if (c->username && c->password)
|
|
return 0;
|
|
|
|
if (!methods || !methods->nr)
|
|
return credential_getpass(c);
|
|
|
|
for (i = 0; i < methods->nr; i++) {
|
|
if (!credential_do(c, methods->items[i].string, NULL) &&
|
|
c->username && c->password)
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void credential_reject(struct credential *c, const struct string_list *methods)
|
|
{
|
|
int i;
|
|
|
|
if (methods && c->username) {
|
|
for (i = 0; i < methods->nr; i++) {
|
|
/* ignore errors, there's nothing we can do */
|
|
credential_do(c, methods->items[i].string, "--reject");
|
|
}
|
|
}
|
|
|
|
free(c->username);
|
|
c->username = NULL;
|
|
free(c->password);
|
|
c->password = NULL;
|
|
}
|