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