credential.con commit repository: enable initialization of submodules (96dc883)
   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
   9void credential_init(struct credential *c)
  10{
  11        memset(c, 0, sizeof(*c));
  12        c->helpers.strdup_strings = 1;
  13}
  14
  15void credential_clear(struct credential *c)
  16{
  17        free(c->protocol);
  18        free(c->host);
  19        free(c->path);
  20        free(c->username);
  21        free(c->password);
  22        string_list_clear(&c->helpers, 0);
  23
  24        credential_init(c);
  25}
  26
  27int credential_match(const struct credential *want,
  28                     const struct credential *have)
  29{
  30#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
  31        return CHECK(protocol) &&
  32               CHECK(host) &&
  33               CHECK(path) &&
  34               CHECK(username);
  35#undef CHECK
  36}
  37
  38static int credential_config_callback(const char *var, const char *value,
  39                                      void *data)
  40{
  41        struct credential *c = data;
  42        const char *key, *dot;
  43
  44        if (!skip_prefix(var, "credential.", &key))
  45                return 0;
  46
  47        if (!value)
  48                return config_error_nonbool(var);
  49
  50        dot = strrchr(key, '.');
  51        if (dot) {
  52                struct credential want = CREDENTIAL_INIT;
  53                char *url = xmemdupz(key, dot - key);
  54                int matched;
  55
  56                credential_from_url(&want, url);
  57                matched = credential_match(&want, c);
  58
  59                credential_clear(&want);
  60                free(url);
  61
  62                if (!matched)
  63                        return 0;
  64                key = dot + 1;
  65        }
  66
  67        if (!strcmp(key, "helper")) {
  68                if (*value)
  69                        string_list_append(&c->helpers, value);
  70                else
  71                        string_list_clear(&c->helpers, 0);
  72        } else if (!strcmp(key, "username")) {
  73                if (!c->username)
  74                        c->username = xstrdup(value);
  75        }
  76        else if (!strcmp(key, "usehttppath"))
  77                c->use_http_path = git_config_bool(var, value);
  78
  79        return 0;
  80}
  81
  82static int proto_is_http(const char *s)
  83{
  84        if (!s)
  85                return 0;
  86        return !strcmp(s, "https") || !strcmp(s, "http");
  87}
  88
  89static void credential_apply_config(struct credential *c)
  90{
  91        if (c->configured)
  92                return;
  93        git_config(credential_config_callback, c);
  94        c->configured = 1;
  95
  96        if (!c->use_http_path && proto_is_http(c->protocol)) {
  97                free(c->path);
  98                c->path = NULL;
  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        credential_write(c, fp);
 232        fclose(fp);
 233
 234        if (want_output) {
 235                int r;
 236                fp = xfdopen(helper.out, "r");
 237                r = credential_read(c, fp);
 238                fclose(fp);
 239                if (r < 0) {
 240                        finish_command(&helper);
 241                        return -1;
 242                }
 243        }
 244
 245        if (finish_command(&helper))
 246                return -1;
 247        return 0;
 248}
 249
 250static int credential_do(struct credential *c, const char *helper,
 251                         const char *operation)
 252{
 253        struct strbuf cmd = STRBUF_INIT;
 254        int r;
 255
 256        if (helper[0] == '!')
 257                strbuf_addstr(&cmd, helper + 1);
 258        else if (is_absolute_path(helper))
 259                strbuf_addstr(&cmd, helper);
 260        else
 261                strbuf_addf(&cmd, "git credential-%s", helper);
 262
 263        strbuf_addf(&cmd, " %s", operation);
 264        r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
 265
 266        strbuf_release(&cmd);
 267        return r;
 268}
 269
 270void credential_fill(struct credential *c)
 271{
 272        int i;
 273
 274        if (c->username && c->password)
 275                return;
 276
 277        credential_apply_config(c);
 278
 279        for (i = 0; i < c->helpers.nr; i++) {
 280                credential_do(c, c->helpers.items[i].string, "get");
 281                if (c->username && c->password)
 282                        return;
 283                if (c->quit)
 284                        die("credential helper '%s' told us to quit",
 285                            c->helpers.items[i].string);
 286        }
 287
 288        credential_getpass(c);
 289        if (!c->username && !c->password)
 290                die("unable to get password from user");
 291}
 292
 293void credential_approve(struct credential *c)
 294{
 295        int i;
 296
 297        if (c->approved)
 298                return;
 299        if (!c->username || !c->password)
 300                return;
 301
 302        credential_apply_config(c);
 303
 304        for (i = 0; i < c->helpers.nr; i++)
 305                credential_do(c, c->helpers.items[i].string, "store");
 306        c->approved = 1;
 307}
 308
 309void credential_reject(struct credential *c)
 310{
 311        int i;
 312
 313        credential_apply_config(c);
 314
 315        for (i = 0; i < c->helpers.nr; i++)
 316                credential_do(c, c->helpers.items[i].string, "erase");
 317
 318        free(c->username);
 319        c->username = NULL;
 320        free(c->password);
 321        c->password = NULL;
 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}