credential-store.con commit t9801: check git-p4's branch detection with client spec enabled (591707a)
   1#include "cache.h"
   2#include "lockfile.h"
   3#include "credential.h"
   4#include "string-list.h"
   5#include "parse-options.h"
   6
   7static struct lock_file credential_lock;
   8
   9static void parse_credential_file(const char *fn,
  10                                  struct credential *c,
  11                                  void (*match_cb)(struct credential *),
  12                                  void (*other_cb)(struct strbuf *))
  13{
  14        FILE *fh;
  15        struct strbuf line = STRBUF_INIT;
  16        struct credential entry = CREDENTIAL_INIT;
  17
  18        fh = fopen(fn, "r");
  19        if (!fh) {
  20                if (errno != ENOENT)
  21                        die_errno("unable to open %s", fn);
  22                return;
  23        }
  24
  25        while (strbuf_getline(&line, fh, '\n') != EOF) {
  26                credential_from_url(&entry, line.buf);
  27                if (entry.username && entry.password &&
  28                    credential_match(c, &entry)) {
  29                        if (match_cb) {
  30                                match_cb(&entry);
  31                                break;
  32                        }
  33                }
  34                else if (other_cb)
  35                        other_cb(&line);
  36        }
  37
  38        credential_clear(&entry);
  39        strbuf_release(&line);
  40        fclose(fh);
  41}
  42
  43static void print_entry(struct credential *c)
  44{
  45        printf("username=%s\n", c->username);
  46        printf("password=%s\n", c->password);
  47}
  48
  49static void print_line(struct strbuf *buf)
  50{
  51        strbuf_addch(buf, '\n');
  52        write_or_die(credential_lock.fd, buf->buf, buf->len);
  53}
  54
  55static void rewrite_credential_file(const char *fn, struct credential *c,
  56                                    struct strbuf *extra)
  57{
  58        if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0)
  59                die_errno("unable to get credential storage lock");
  60        if (extra)
  61                print_line(extra);
  62        parse_credential_file(fn, c, NULL, print_line);
  63        if (commit_lock_file(&credential_lock) < 0)
  64                die_errno("unable to commit credential store");
  65}
  66
  67static void store_credential(const char *fn, struct credential *c)
  68{
  69        struct strbuf buf = STRBUF_INIT;
  70
  71        /*
  72         * Sanity check that what we are storing is actually sensible.
  73         * In particular, we can't make a URL without a protocol field.
  74         * Without either a host or pathname (depending on the scheme),
  75         * we have no primary key. And without a username and password,
  76         * we are not actually storing a credential.
  77         */
  78        if (!c->protocol || !(c->host || c->path) ||
  79            !c->username || !c->password)
  80                return;
  81
  82        strbuf_addf(&buf, "%s://", c->protocol);
  83        strbuf_addstr_urlencode(&buf, c->username, 1);
  84        strbuf_addch(&buf, ':');
  85        strbuf_addstr_urlencode(&buf, c->password, 1);
  86        strbuf_addch(&buf, '@');
  87        if (c->host)
  88                strbuf_addstr_urlencode(&buf, c->host, 1);
  89        if (c->path) {
  90                strbuf_addch(&buf, '/');
  91                strbuf_addstr_urlencode(&buf, c->path, 0);
  92        }
  93
  94        rewrite_credential_file(fn, c, &buf);
  95        strbuf_release(&buf);
  96}
  97
  98static void remove_credential(const char *fn, struct credential *c)
  99{
 100        /*
 101         * Sanity check that we actually have something to match
 102         * against. The input we get is a restrictive pattern,
 103         * so technically a blank credential means "erase everything".
 104         * But it is too easy to accidentally send this, since it is equivalent
 105         * to empty input. So explicitly disallow it, and require that the
 106         * pattern have some actual content to match.
 107         */
 108        if (c->protocol || c->host || c->path || c->username)
 109                rewrite_credential_file(fn, c, NULL);
 110}
 111
 112static int lookup_credential(const char *fn, struct credential *c)
 113{
 114        parse_credential_file(fn, c, print_entry, NULL);
 115        return c->username && c->password;
 116}
 117
 118int main(int argc, char **argv)
 119{
 120        const char * const usage[] = {
 121                "git credential-store [options] <action>",
 122                NULL
 123        };
 124        const char *op;
 125        struct credential c = CREDENTIAL_INIT;
 126        char *file = NULL;
 127        struct option options[] = {
 128                OPT_STRING(0, "file", &file, "path",
 129                           "fetch and store credentials in <path>"),
 130                OPT_END()
 131        };
 132
 133        umask(077);
 134
 135        argc = parse_options(argc, (const char **)argv, NULL, options, usage, 0);
 136        if (argc != 1)
 137                usage_with_options(usage, options);
 138        op = argv[0];
 139
 140        if (!file)
 141                file = expand_user_path("~/.git-credentials");
 142        if (!file)
 143                die("unable to set up default path; use --file");
 144
 145        if (credential_read(&c, stdin) < 0)
 146                die("unable to read credential");
 147
 148        if (!strcmp(op, "get"))
 149                lookup_credential(file, &c);
 150        else if (!strcmp(op, "erase"))
 151                remove_credential(file, &c);
 152        else if (!strcmp(op, "store"))
 153                store_credential(file, &c);
 154        else
 155                ; /* Ignore unknown operation. */
 156
 157        return 0;
 158}