contrib / credential / osxkeychain / git-credential-osxkeychain.con commit Merge branch 'jc/same-encoding' (fbe8aa7)
   1#include <stdio.h>
   2#include <string.h>
   3#include <stdlib.h>
   4#include <Security/Security.h>
   5
   6static SecProtocolType protocol;
   7static char *host;
   8static char *path;
   9static char *username;
  10static char *password;
  11static UInt16 port;
  12
  13static void die(const char *err, ...)
  14{
  15        char msg[4096];
  16        va_list params;
  17        va_start(params, err);
  18        vsnprintf(msg, sizeof(msg), err, params);
  19        fprintf(stderr, "%s\n", msg);
  20        va_end(params);
  21        exit(1);
  22}
  23
  24static void *xstrdup(const char *s1)
  25{
  26        void *ret = strdup(s1);
  27        if (!ret)
  28                die("Out of memory");
  29        return ret;
  30}
  31
  32#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
  33#define KEYCHAIN_ARGS \
  34        NULL, /* default keychain */ \
  35        KEYCHAIN_ITEM(host), \
  36        0, NULL, /* account domain */ \
  37        KEYCHAIN_ITEM(username), \
  38        KEYCHAIN_ITEM(path), \
  39        port, \
  40        protocol, \
  41        kSecAuthenticationTypeDefault
  42
  43static void write_item(const char *what, const char *buf, int len)
  44{
  45        printf("%s=", what);
  46        fwrite(buf, 1, len, stdout);
  47        putchar('\n');
  48}
  49
  50static void find_username_in_item(SecKeychainItemRef item)
  51{
  52        SecKeychainAttributeList list;
  53        SecKeychainAttribute attr;
  54
  55        list.count = 1;
  56        list.attr = &attr;
  57        attr.tag = kSecAccountItemAttr;
  58
  59        if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
  60                return;
  61
  62        write_item("username", attr.data, attr.length);
  63        SecKeychainItemFreeContent(&list, NULL);
  64}
  65
  66static void find_internet_password(void)
  67{
  68        void *buf;
  69        UInt32 len;
  70        SecKeychainItemRef item;
  71
  72        if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
  73                return;
  74
  75        write_item("password", buf, len);
  76        if (!username)
  77                find_username_in_item(item);
  78
  79        SecKeychainItemFreeContent(NULL, buf);
  80}
  81
  82static void delete_internet_password(void)
  83{
  84        SecKeychainItemRef item;
  85
  86        /*
  87         * Require at least a protocol and host for removal, which is what git
  88         * will give us; if you want to do something more fancy, use the
  89         * Keychain manager.
  90         */
  91        if (!protocol || !host)
  92                return;
  93
  94        if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
  95                return;
  96
  97        SecKeychainItemDelete(item);
  98}
  99
 100static void add_internet_password(void)
 101{
 102        /* Only store complete credentials */
 103        if (!protocol || !host || !username || !password)
 104                return;
 105
 106        if (SecKeychainAddInternetPassword(
 107              KEYCHAIN_ARGS,
 108              KEYCHAIN_ITEM(password),
 109              NULL))
 110                return;
 111}
 112
 113static void read_credential(void)
 114{
 115        char buf[1024];
 116
 117        while (fgets(buf, sizeof(buf), stdin)) {
 118                char *v;
 119
 120                if (!strcmp(buf, "\n"))
 121                        break;
 122                buf[strlen(buf)-1] = '\0';
 123
 124                v = strchr(buf, '=');
 125                if (!v)
 126                        die("bad input: %s", buf);
 127                *v++ = '\0';
 128
 129                if (!strcmp(buf, "protocol")) {
 130                        if (!strcmp(v, "https"))
 131                                protocol = kSecProtocolTypeHTTPS;
 132                        else if (!strcmp(v, "http"))
 133                                protocol = kSecProtocolTypeHTTP;
 134                        else /* we don't yet handle other protocols */
 135                                exit(0);
 136                }
 137                else if (!strcmp(buf, "host")) {
 138                        char *colon = strchr(v, ':');
 139                        if (colon) {
 140                                *colon++ = '\0';
 141                                port = atoi(colon);
 142                        }
 143                        host = xstrdup(v);
 144                }
 145                else if (!strcmp(buf, "path"))
 146                        path = xstrdup(v);
 147                else if (!strcmp(buf, "username"))
 148                        username = xstrdup(v);
 149                else if (!strcmp(buf, "password"))
 150                        password = xstrdup(v);
 151        }
 152}
 153
 154int main(int argc, const char **argv)
 155{
 156        const char *usage =
 157                "Usage: git credential-osxkeychain <get|store|erase>";
 158
 159        if (!argv[1])
 160                die(usage);
 161
 162        read_credential();
 163
 164        if (!strcmp(argv[1], "get"))
 165                find_internet_password();
 166        else if (!strcmp(argv[1], "store"))
 167                add_internet_password();
 168        else if (!strcmp(argv[1], "erase"))
 169                delete_internet_password();
 170        /* otherwise, ignore unknown action */
 171
 172        return 0;
 173}