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