327 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			327 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * A git credential helper that interface with Windows' Credential Manager
 | 
						|
 *
 | 
						|
 */
 | 
						|
#include <windows.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <io.h>
 | 
						|
#include <fcntl.h>
 | 
						|
 | 
						|
/* common helpers */
 | 
						|
 | 
						|
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 | 
						|
 | 
						|
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 *xmalloc(size_t size)
 | 
						|
{
 | 
						|
	void *ret = malloc(size);
 | 
						|
	if (!ret && !size)
 | 
						|
		ret = malloc(1);
 | 
						|
	if (!ret)
 | 
						|
		 die("Out of memory");
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* MinGW doesn't have wincred.h, so we need to define stuff */
 | 
						|
 | 
						|
typedef struct _CREDENTIAL_ATTRIBUTEW {
 | 
						|
	LPWSTR Keyword;
 | 
						|
	DWORD  Flags;
 | 
						|
	DWORD  ValueSize;
 | 
						|
	LPBYTE Value;
 | 
						|
} CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW;
 | 
						|
 | 
						|
typedef struct _CREDENTIALW {
 | 
						|
	DWORD                  Flags;
 | 
						|
	DWORD                  Type;
 | 
						|
	LPWSTR                 TargetName;
 | 
						|
	LPWSTR                 Comment;
 | 
						|
	FILETIME               LastWritten;
 | 
						|
	DWORD                  CredentialBlobSize;
 | 
						|
	LPBYTE                 CredentialBlob;
 | 
						|
	DWORD                  Persist;
 | 
						|
	DWORD                  AttributeCount;
 | 
						|
	PCREDENTIAL_ATTRIBUTEW Attributes;
 | 
						|
	LPWSTR                 TargetAlias;
 | 
						|
	LPWSTR                 UserName;
 | 
						|
} CREDENTIALW, *PCREDENTIALW;
 | 
						|
 | 
						|
#define CRED_TYPE_GENERIC 1
 | 
						|
#define CRED_PERSIST_LOCAL_MACHINE 2
 | 
						|
#define CRED_MAX_ATTRIBUTES 64
 | 
						|
 | 
						|
typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
 | 
						|
typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
 | 
						|
    PCREDENTIALW **);
 | 
						|
typedef VOID (WINAPI *CredFreeT)(PVOID);
 | 
						|
typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
 | 
						|
 | 
						|
static HMODULE advapi;
 | 
						|
static CredWriteWT CredWriteW;
 | 
						|
static CredEnumerateWT CredEnumerateW;
 | 
						|
static CredFreeT CredFree;
 | 
						|
static CredDeleteWT CredDeleteW;
 | 
						|
 | 
						|
static void load_cred_funcs(void)
 | 
						|
{
 | 
						|
	/* load DLLs */
 | 
						|
	advapi = LoadLibraryExA("advapi32.dll", NULL,
 | 
						|
				LOAD_LIBRARY_SEARCH_SYSTEM32);
 | 
						|
	if (!advapi)
 | 
						|
		die("failed to load advapi32.dll");
 | 
						|
 | 
						|
	/* get function pointers */
 | 
						|
	CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
 | 
						|
	CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
 | 
						|
	    "CredEnumerateW");
 | 
						|
	CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
 | 
						|
	CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
 | 
						|
	if (!CredWriteW || !CredEnumerateW || !CredFree || !CredDeleteW)
 | 
						|
		die("failed to load functions");
 | 
						|
}
 | 
						|
 | 
						|
static WCHAR *wusername, *password, *protocol, *host, *path, target[1024];
 | 
						|
 | 
						|
static void write_item(const char *what, LPCWSTR wbuf, int wlen)
 | 
						|
{
 | 
						|
	char *buf;
 | 
						|
 | 
						|
	if (!wbuf || !wlen) {
 | 
						|
		printf("%s=\n", what);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL,
 | 
						|
	    FALSE);
 | 
						|
	buf = xmalloc(len);
 | 
						|
 | 
						|
	if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, len, NULL, FALSE))
 | 
						|
		die("WideCharToMultiByte failed!");
 | 
						|
 | 
						|
	printf("%s=", what);
 | 
						|
	fwrite(buf, 1, len, stdout);
 | 
						|
	putchar('\n');
 | 
						|
	free(buf);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Match an (optional) expected string and a delimiter in the target string,
 | 
						|
 * consuming the matched text by updating the target pointer.
 | 
						|
 */
 | 
						|
 | 
						|
static LPCWSTR wcsstr_last(LPCWSTR str, LPCWSTR find)
 | 
						|
{
 | 
						|
	LPCWSTR res = NULL, pos;
 | 
						|
	for (pos = wcsstr(str, find); pos; pos = wcsstr(pos + 1, find))
 | 
						|
		res = pos;
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
static int match_part_with_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim, int last)
 | 
						|
{
 | 
						|
	LPCWSTR delim_pos, start = *ptarget;
 | 
						|
	int len;
 | 
						|
 | 
						|
	/* find start of delimiter (or end-of-string if delim is empty) */
 | 
						|
	if (*delim)
 | 
						|
		delim_pos = last ? wcsstr_last(start, delim) : wcsstr(start, delim);
 | 
						|
	else
 | 
						|
		delim_pos = start + wcslen(start);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * match text up to delimiter, or end of string (e.g. the '/' after
 | 
						|
	 * host is optional if not followed by a path)
 | 
						|
	 */
 | 
						|
	if (delim_pos)
 | 
						|
		len = delim_pos - start;
 | 
						|
	else
 | 
						|
		len = wcslen(start);
 | 
						|
 | 
						|
	/* update ptarget if we either found a delimiter or need a match */
 | 
						|
	if (delim_pos || want)
 | 
						|
		*ptarget = delim_pos ? delim_pos + wcslen(delim) : start + len;
 | 
						|
 | 
						|
	return !want || (!wcsncmp(want, start, len) && !want[len]);
 | 
						|
}
 | 
						|
 | 
						|
static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
 | 
						|
{
 | 
						|
	return match_part_with_last(ptarget, want, delim, 0);
 | 
						|
}
 | 
						|
 | 
						|
static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
 | 
						|
{
 | 
						|
	return match_part_with_last(ptarget, want, delim, 1);
 | 
						|
}
 | 
						|
 | 
						|
static int match_cred(const CREDENTIALW *cred)
 | 
						|
{
 | 
						|
	LPCWSTR target = cred->TargetName;
 | 
						|
	if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L""))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	return match_part(&target, L"git", L":") &&
 | 
						|
		match_part(&target, protocol, L"://") &&
 | 
						|
		match_part_last(&target, wusername, L"@") &&
 | 
						|
		match_part(&target, host, L"/") &&
 | 
						|
		match_part(&target, path, L"");
 | 
						|
}
 | 
						|
 | 
						|
static void get_credential(void)
 | 
						|
{
 | 
						|
	CREDENTIALW **creds;
 | 
						|
	DWORD num_creds;
 | 
						|
	int i;
 | 
						|
 | 
						|
	if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
 | 
						|
		return;
 | 
						|
 | 
						|
	/* search for the first credential that matches username */
 | 
						|
	for (i = 0; i < num_creds; ++i)
 | 
						|
		if (match_cred(creds[i])) {
 | 
						|
			write_item("username", creds[i]->UserName,
 | 
						|
				creds[i]->UserName ? wcslen(creds[i]->UserName) : 0);
 | 
						|
			write_item("password",
 | 
						|
				(LPCWSTR)creds[i]->CredentialBlob,
 | 
						|
				creds[i]->CredentialBlobSize / sizeof(WCHAR));
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
	CredFree(creds);
 | 
						|
}
 | 
						|
 | 
						|
static void store_credential(void)
 | 
						|
{
 | 
						|
	CREDENTIALW cred;
 | 
						|
 | 
						|
	if (!wusername || !password)
 | 
						|
		return;
 | 
						|
 | 
						|
	cred.Flags = 0;
 | 
						|
	cred.Type = CRED_TYPE_GENERIC;
 | 
						|
	cred.TargetName = target;
 | 
						|
	cred.Comment = L"saved by git-credential-wincred";
 | 
						|
	cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
 | 
						|
	cred.CredentialBlob = (LPVOID)password;
 | 
						|
	cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
 | 
						|
	cred.AttributeCount = 0;
 | 
						|
	cred.Attributes = NULL;
 | 
						|
	cred.TargetAlias = NULL;
 | 
						|
	cred.UserName = wusername;
 | 
						|
 | 
						|
	if (!CredWriteW(&cred, 0))
 | 
						|
		die("CredWrite failed");
 | 
						|
}
 | 
						|
 | 
						|
static void erase_credential(void)
 | 
						|
{
 | 
						|
	CREDENTIALW **creds;
 | 
						|
	DWORD num_creds;
 | 
						|
	int i;
 | 
						|
 | 
						|
	if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds))
 | 
						|
		return;
 | 
						|
 | 
						|
	for (i = 0; i < num_creds; ++i) {
 | 
						|
		if (match_cred(creds[i]))
 | 
						|
			CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0);
 | 
						|
	}
 | 
						|
 | 
						|
	CredFree(creds);
 | 
						|
}
 | 
						|
 | 
						|
static WCHAR *utf8_to_utf16_dup(const char *str)
 | 
						|
{
 | 
						|
	int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
 | 
						|
	WCHAR *wstr = xmalloc(sizeof(WCHAR) * wlen);
 | 
						|
	MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, wlen);
 | 
						|
	return wstr;
 | 
						|
}
 | 
						|
 | 
						|
static void read_credential(void)
 | 
						|
{
 | 
						|
	char buf[1024];
 | 
						|
 | 
						|
	while (fgets(buf, sizeof(buf), stdin)) {
 | 
						|
		char *v;
 | 
						|
		int len = strlen(buf);
 | 
						|
		/* strip trailing CR / LF */
 | 
						|
		while (len && strchr("\r\n", buf[len - 1]))
 | 
						|
			buf[--len] = 0;
 | 
						|
 | 
						|
		if (!*buf)
 | 
						|
			break;
 | 
						|
 | 
						|
		v = strchr(buf, '=');
 | 
						|
		if (!v)
 | 
						|
			die("bad input: %s", buf);
 | 
						|
		*v++ = '\0';
 | 
						|
 | 
						|
		if (!strcmp(buf, "protocol"))
 | 
						|
			protocol = utf8_to_utf16_dup(v);
 | 
						|
		else if (!strcmp(buf, "host"))
 | 
						|
			host = utf8_to_utf16_dup(v);
 | 
						|
		else if (!strcmp(buf, "path"))
 | 
						|
			path = utf8_to_utf16_dup(v);
 | 
						|
		else if (!strcmp(buf, "username")) {
 | 
						|
			wusername = utf8_to_utf16_dup(v);
 | 
						|
		} else if (!strcmp(buf, "password"))
 | 
						|
			password = utf8_to_utf16_dup(v);
 | 
						|
		else
 | 
						|
			die("unrecognized input");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int main(int argc, char *argv[])
 | 
						|
{
 | 
						|
	const char *usage =
 | 
						|
	    "usage: git credential-wincred <get|store|erase>\n";
 | 
						|
 | 
						|
	if (!argv[1])
 | 
						|
		die(usage);
 | 
						|
 | 
						|
	/* git use binary pipes to avoid CRLF-issues */
 | 
						|
	_setmode(_fileno(stdin), _O_BINARY);
 | 
						|
	_setmode(_fileno(stdout), _O_BINARY);
 | 
						|
 | 
						|
	read_credential();
 | 
						|
 | 
						|
	load_cred_funcs();
 | 
						|
 | 
						|
	if (!protocol || !(host || path))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	/* prepare 'target', the unique key for the credential */
 | 
						|
	wcscpy(target, L"git:");
 | 
						|
	wcsncat(target, protocol, ARRAY_SIZE(target));
 | 
						|
	wcsncat(target, L"://", ARRAY_SIZE(target));
 | 
						|
	if (wusername) {
 | 
						|
		wcsncat(target, wusername, ARRAY_SIZE(target));
 | 
						|
		wcsncat(target, L"@", ARRAY_SIZE(target));
 | 
						|
	}
 | 
						|
	if (host)
 | 
						|
		wcsncat(target, host, ARRAY_SIZE(target));
 | 
						|
	if (path) {
 | 
						|
		wcsncat(target, L"/", ARRAY_SIZE(target));
 | 
						|
		wcsncat(target, path, ARRAY_SIZE(target));
 | 
						|
	}
 | 
						|
 | 
						|
	if (!strcmp(argv[1], "get"))
 | 
						|
		get_credential();
 | 
						|
	else if (!strcmp(argv[1], "store"))
 | 
						|
		store_credential();
 | 
						|
	else if (!strcmp(argv[1], "erase"))
 | 
						|
		erase_credential();
 | 
						|
	/* otherwise, ignore unknown action */
 | 
						|
	return 0;
 | 
						|
}
 |