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