34961d30da
With this installed in your $PATH, you can store git-over-http passwords in your keychain by doing: git config credential.helper osxkeychain The code is based in large part on the work of Jay Soffian, who wrote the helper originally for the initial, unpublished version of the credential helper protocol. This version will pass t0303 if you do: GIT_TEST_CREDENTIAL_HELPER=osxkeychain \ GIT_TEST_CREDENTIAL_HELPER_SETUP="export HOME=$HOME" \ ./t0303-credential-external.sh The "HOME" setup is unfortunately necessary. The test scripts set HOME to the trash directory, but this causes the keychain API to complain. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
174 lines
3.4 KiB
C
174 lines
3.4 KiB
C
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <Security/Security.h>
|
|
|
|
static SecProtocolType protocol;
|
|
static char *host;
|
|
static char *path;
|
|
static char *username;
|
|
static char *password;
|
|
static UInt16 port;
|
|
|
|
static void die(const char *err, ...)
|
|
{
|
|
char msg[4096];
|
|
va_list params;
|
|
va_start(params, err);
|
|
vsnprintf(msg, sizeof(msg), err, params);
|
|
fprintf(stderr, "%s\n", msg);
|
|
va_end(params);
|
|
exit(1);
|
|
}
|
|
|
|
static void *xstrdup(const char *s1)
|
|
{
|
|
void *ret = strdup(s1);
|
|
if (!ret)
|
|
die("Out of memory");
|
|
return ret;
|
|
}
|
|
|
|
#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
|
|
#define KEYCHAIN_ARGS \
|
|
NULL, /* default keychain */ \
|
|
KEYCHAIN_ITEM(host), \
|
|
0, NULL, /* account domain */ \
|
|
KEYCHAIN_ITEM(username), \
|
|
KEYCHAIN_ITEM(path), \
|
|
port, \
|
|
protocol, \
|
|
kSecAuthenticationTypeDefault
|
|
|
|
static void write_item(const char *what, const char *buf, int len)
|
|
{
|
|
printf("%s=", what);
|
|
fwrite(buf, 1, len, stdout);
|
|
putchar('\n');
|
|
}
|
|
|
|
static void find_username_in_item(SecKeychainItemRef item)
|
|
{
|
|
SecKeychainAttributeList list;
|
|
SecKeychainAttribute attr;
|
|
|
|
list.count = 1;
|
|
list.attr = &attr;
|
|
attr.tag = kSecAccountItemAttr;
|
|
|
|
if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
|
|
return;
|
|
|
|
write_item("username", attr.data, attr.length);
|
|
SecKeychainItemFreeContent(&list, NULL);
|
|
}
|
|
|
|
static void find_internet_password(void)
|
|
{
|
|
void *buf;
|
|
UInt32 len;
|
|
SecKeychainItemRef item;
|
|
|
|
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
|
|
return;
|
|
|
|
write_item("password", buf, len);
|
|
if (!username)
|
|
find_username_in_item(item);
|
|
|
|
SecKeychainItemFreeContent(NULL, buf);
|
|
}
|
|
|
|
static void delete_internet_password(void)
|
|
{
|
|
SecKeychainItemRef item;
|
|
|
|
/*
|
|
* Require at least a protocol and host for removal, which is what git
|
|
* will give us; if you want to do something more fancy, use the
|
|
* Keychain manager.
|
|
*/
|
|
if (!protocol || !host)
|
|
return;
|
|
|
|
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
|
|
return;
|
|
|
|
SecKeychainItemDelete(item);
|
|
}
|
|
|
|
static void add_internet_password(void)
|
|
{
|
|
/* Only store complete credentials */
|
|
if (!protocol || !host || !username || !password)
|
|
return;
|
|
|
|
if (SecKeychainAddInternetPassword(
|
|
KEYCHAIN_ARGS,
|
|
KEYCHAIN_ITEM(password),
|
|
NULL))
|
|
return;
|
|
}
|
|
|
|
static void read_credential(void)
|
|
{
|
|
char buf[1024];
|
|
|
|
while (fgets(buf, sizeof(buf), stdin)) {
|
|
char *v;
|
|
|
|
if (!strcmp(buf, "\n"))
|
|
break;
|
|
buf[strlen(buf)-1] = '\0';
|
|
|
|
v = strchr(buf, '=');
|
|
if (!v)
|
|
die("bad input: %s", buf);
|
|
*v++ = '\0';
|
|
|
|
if (!strcmp(buf, "protocol")) {
|
|
if (!strcmp(v, "https"))
|
|
protocol = kSecProtocolTypeHTTPS;
|
|
else if (!strcmp(v, "http"))
|
|
protocol = kSecProtocolTypeHTTP;
|
|
else /* we don't yet handle other protocols */
|
|
exit(0);
|
|
}
|
|
else if (!strcmp(buf, "host")) {
|
|
char *colon = strchr(v, ':');
|
|
if (colon) {
|
|
*colon++ = '\0';
|
|
port = atoi(colon);
|
|
}
|
|
host = xstrdup(v);
|
|
}
|
|
else if (!strcmp(buf, "path"))
|
|
path = xstrdup(v);
|
|
else if (!strcmp(buf, "username"))
|
|
username = xstrdup(v);
|
|
else if (!strcmp(buf, "password"))
|
|
password = xstrdup(v);
|
|
}
|
|
}
|
|
|
|
int main(int argc, const char **argv)
|
|
{
|
|
const char *usage =
|
|
"Usage: git credential-osxkeychain <get|store|erase>";
|
|
|
|
if (!argv[1])
|
|
die(usage);
|
|
|
|
read_credential();
|
|
|
|
if (!strcmp(argv[1], "get"))
|
|
find_internet_password();
|
|
else if (!strcmp(argv[1], "store"))
|
|
add_internet_password();
|
|
else if (!strcmp(argv[1], "erase"))
|
|
delete_internet_password();
|
|
/* otherwise, ignore unknown action */
|
|
|
|
return 0;
|
|
}
|