Files
git/credential.c
Jeff King 00b1daa2c1 allow the user to configure credential helpers
The functionality for helpers is already there; we just need
to give the users a way to turn it on.

The new functionality is enabled whenever a caller of the
credentials API passes a NULL method list. This will enable
it for all current callers (i.e., the http code).

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-03 15:25:12 -07:00

252 lines
5.1 KiB
C

#include "cache.h"
#include "credential.h"
#include "quote.h"
#include "string-list.h"
#include "run-command.h"
static struct string_list default_methods;
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 (!methods)
methods = &default_methods;
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->nr == 1)
strbuf_addf(&err, "; tried '%s'", methods->items[0].string);
else {
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 = &default_methods;
if (!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)
methods = &default_methods;
if (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;
}
int git_default_credential_config(const char *var, const char *value)
{
if (!strcmp(var, "credential.helper")) {
if (!value)
return config_error_nonbool(var);
string_list_append(&default_methods, xstrdup(value));
return 0;
}
return 0;
}