credential.con commit introduce credentials API (abca927)
   1#include "cache.h"
   2#include "credential.h"
   3#include "string-list.h"
   4#include "run-command.h"
   5
   6void credential_init(struct credential *c)
   7{
   8        memset(c, 0, sizeof(*c));
   9        c->helpers.strdup_strings = 1;
  10}
  11
  12void credential_clear(struct credential *c)
  13{
  14        free(c->protocol);
  15        free(c->host);
  16        free(c->path);
  17        free(c->username);
  18        free(c->password);
  19        string_list_clear(&c->helpers, 0);
  20
  21        credential_init(c);
  22}
  23
  24static void credential_describe(struct credential *c, struct strbuf *out)
  25{
  26        if (!c->protocol)
  27                return;
  28        strbuf_addf(out, "%s://", c->protocol);
  29        if (c->username && *c->username)
  30                strbuf_addf(out, "%s@", c->username);
  31        if (c->host)
  32                strbuf_addstr(out, c->host);
  33        if (c->path)
  34                strbuf_addf(out, "/%s", c->path);
  35}
  36
  37static char *credential_ask_one(const char *what, struct credential *c)
  38{
  39        struct strbuf desc = STRBUF_INIT;
  40        struct strbuf prompt = STRBUF_INIT;
  41        char *r;
  42
  43        credential_describe(c, &desc);
  44        if (desc.len)
  45                strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
  46        else
  47                strbuf_addf(&prompt, "%s: ", what);
  48
  49        /* FIXME: for usernames, we should do something less magical that
  50         * actually echoes the characters. However, we need to read from
  51         * /dev/tty and not stdio, which is not portable (but getpass will do
  52         * it for us). http.c uses the same workaround. */
  53        r = git_getpass(prompt.buf);
  54
  55        strbuf_release(&desc);
  56        strbuf_release(&prompt);
  57        return xstrdup(r);
  58}
  59
  60static void credential_getpass(struct credential *c)
  61{
  62        if (!c->username)
  63                c->username = credential_ask_one("Username", c);
  64        if (!c->password)
  65                c->password = credential_ask_one("Password", c);
  66}
  67
  68int credential_read(struct credential *c, FILE *fp)
  69{
  70        struct strbuf line = STRBUF_INIT;
  71
  72        while (strbuf_getline(&line, fp, '\n') != EOF) {
  73                char *key = line.buf;
  74                char *value = strchr(key, '=');
  75
  76                if (!line.len)
  77                        break;
  78
  79                if (!value) {
  80                        warning("invalid credential line: %s", key);
  81                        strbuf_release(&line);
  82                        return -1;
  83                }
  84                *value++ = '\0';
  85
  86                if (!strcmp(key, "username")) {
  87                        free(c->username);
  88                        c->username = xstrdup(value);
  89                } else if (!strcmp(key, "password")) {
  90                        free(c->password);
  91                        c->password = xstrdup(value);
  92                } else if (!strcmp(key, "protocol")) {
  93                        free(c->protocol);
  94                        c->protocol = xstrdup(value);
  95                } else if (!strcmp(key, "host")) {
  96                        free(c->host);
  97                        c->host = xstrdup(value);
  98                } else if (!strcmp(key, "path")) {
  99                        free(c->path);
 100                        c->path = xstrdup(value);
 101                }
 102                /*
 103                 * Ignore other lines; we don't know what they mean, but
 104                 * this future-proofs us when later versions of git do
 105                 * learn new lines, and the helpers are updated to match.
 106                 */
 107        }
 108
 109        strbuf_release(&line);
 110        return 0;
 111}
 112
 113static void credential_write_item(FILE *fp, const char *key, const char *value)
 114{
 115        if (!value)
 116                return;
 117        fprintf(fp, "%s=%s\n", key, value);
 118}
 119
 120static void credential_write(const struct credential *c, FILE *fp)
 121{
 122        credential_write_item(fp, "protocol", c->protocol);
 123        credential_write_item(fp, "host", c->host);
 124        credential_write_item(fp, "path", c->path);
 125        credential_write_item(fp, "username", c->username);
 126        credential_write_item(fp, "password", c->password);
 127}
 128
 129static int run_credential_helper(struct credential *c,
 130                                 const char *cmd,
 131                                 int want_output)
 132{
 133        struct child_process helper;
 134        const char *argv[] = { NULL, NULL };
 135        FILE *fp;
 136
 137        memset(&helper, 0, sizeof(helper));
 138        argv[0] = cmd;
 139        helper.argv = argv;
 140        helper.use_shell = 1;
 141        helper.in = -1;
 142        if (want_output)
 143                helper.out = -1;
 144        else
 145                helper.no_stdout = 1;
 146
 147        if (start_command(&helper) < 0)
 148                return -1;
 149
 150        fp = xfdopen(helper.in, "w");
 151        credential_write(c, fp);
 152        fclose(fp);
 153
 154        if (want_output) {
 155                int r;
 156                fp = xfdopen(helper.out, "r");
 157                r = credential_read(c, fp);
 158                fclose(fp);
 159                if (r < 0) {
 160                        finish_command(&helper);
 161                        return -1;
 162                }
 163        }
 164
 165        if (finish_command(&helper))
 166                return -1;
 167        return 0;
 168}
 169
 170static int credential_do(struct credential *c, const char *helper,
 171                         const char *operation)
 172{
 173        struct strbuf cmd = STRBUF_INIT;
 174        int r;
 175
 176        if (helper[0] == '!')
 177                strbuf_addstr(&cmd, helper + 1);
 178        else if (is_absolute_path(helper))
 179                strbuf_addstr(&cmd, helper);
 180        else
 181                strbuf_addf(&cmd, "git credential-%s", helper);
 182
 183        strbuf_addf(&cmd, " %s", operation);
 184        r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
 185
 186        strbuf_release(&cmd);
 187        return r;
 188}
 189
 190void credential_fill(struct credential *c)
 191{
 192        int i;
 193
 194        if (c->username && c->password)
 195                return;
 196
 197        for (i = 0; i < c->helpers.nr; i++) {
 198                credential_do(c, c->helpers.items[i].string, "get");
 199                if (c->username && c->password)
 200                        return;
 201        }
 202
 203        credential_getpass(c);
 204        if (!c->username && !c->password)
 205                die("unable to get password from user");
 206}
 207
 208void credential_approve(struct credential *c)
 209{
 210        int i;
 211
 212        if (c->approved)
 213                return;
 214        if (!c->username || !c->password)
 215                return;
 216
 217        for (i = 0; i < c->helpers.nr; i++)
 218                credential_do(c, c->helpers.items[i].string, "store");
 219        c->approved = 1;
 220}
 221
 222void credential_reject(struct credential *c)
 223{
 224        int i;
 225
 226        for (i = 0; i < c->helpers.nr; i++)
 227                credential_do(c, c->helpers.items[i].string, "erase");
 228
 229        free(c->username);
 230        c->username = NULL;
 231        free(c->password);
 232        c->password = NULL;
 233        c->approved = 0;
 234}