wincred: improve compatibility with windows versions

On WinXP, the windows credential helper doesn't work at all (due to missing Cred[Un]PackAuthenticationBuffer APIs). On Win7, the credential format used by wincred is incompatible with native Windows tools (such as the control panel applet or 'cmdkey.exe /generic'). These Windows tools only set the TargetName, UserName and CredentialBlob members of the CREDENTIAL structure (where CredentialBlob is the UTF-16-encoded password). Remove the unnecessary packing / unpacking of the password, along with the related API definitions, for compatibility with Windows XP. Don't use CREDENTIAL_ATTRIBUTEs to identify credentials for compatibility with Windows credential manager tools. Parse the protocol, username, host and path fields from the credential's target name instead. Credentials created with an old wincred version will have mangled or empty passwords after this change. Signed-off-by: Karsten Blees <blees@dcon.de> Signed-off-by: Erik Faye-Lund <kusmabite@gmail.com>

Karsten Blees committed Jan 10, 2013 at 11:52 UTC 8b2d219a3d6db49c8c3c0a5b620af33d6a40a974
1 file changed +70 -129
contrib/credential/wincred/git-credential-wincred.c
+70 -129
@@ -9,6 +9,8 @@
9
10 /* common helpers */
11
12 +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
13 +
14 static void die(const char *err, ...)
15 {
16 char msg[4096];
@@ -30,14 +32,6 @@ static void *xmalloc(size_t size)
32 return ret;
33 }
34
33 -static char *xstrdup(const char *str)
34 -{
35 - char *ret = strdup(str);
36 - if (!ret)
37 - die("Out of memory");
38 - return ret;
39 -}
40 -
35 /* MinGW doesn't have wincred.h, so we need to define stuff */
36
37 typedef struct _CREDENTIAL_ATTRIBUTEW {
@@ -67,20 +61,14 @@ typedef struct _CREDENTIALW {
61 #define CRED_MAX_ATTRIBUTES 64
62
63 typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);
70 -typedef BOOL (WINAPI *CredUnPackAuthenticationBufferWT)(DWORD, PVOID, DWORD,
71 - LPWSTR, DWORD *, LPWSTR, DWORD *, LPWSTR, DWORD *);
64 typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,
65 PCREDENTIALW **);
74 -typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,
75 - PBYTE, DWORD *);
66 typedef VOID (WINAPI *CredFreeT)(PVOID);
67 typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);
68
79 -static HMODULE advapi, credui;
69 +static HMODULE advapi;
70 static CredWriteWT CredWriteW;
81 -static CredUnPackAuthenticationBufferWT CredUnPackAuthenticationBufferW;
71 static CredEnumerateWT CredEnumerateW;
83 -static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;
72 static CredFreeT CredFree;
73 static CredDeleteWT CredDeleteW;
74
@@ -88,74 +76,84 @@ static void load_cred_funcs(void)
76 {
77 /* load DLLs */
78 advapi = LoadLibrary("advapi32.dll");
91 - credui = LoadLibrary("credui.dll");
92 - if (!advapi || !credui)
93 - die("failed to load DLLs");
79 + if (!advapi)
80 + die("failed to load advapi32.dll");
81
82 /* get function pointers */
83 CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW");
97 - CredUnPackAuthenticationBufferW = (CredUnPackAuthenticationBufferWT)
98 - GetProcAddress(credui, "CredUnPackAuthenticationBufferW");
84 CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,
85 "CredEnumerateW");
101 - CredPackAuthenticationBufferW = (CredPackAuthenticationBufferWT)
102 - GetProcAddress(credui, "CredPackAuthenticationBufferW");
86 CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree");
87 CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW");
105 - if (!CredWriteW || !CredUnPackAuthenticationBufferW ||
106 - !CredEnumerateW || !CredPackAuthenticationBufferW || !CredFree ||
107 - !CredDeleteW)
88 + if (!CredWriteW || !CredEnumerateW || !CredFree || !CredDeleteW)
89 die("failed to load functions");
90 }
91
111 -static char target_buf[1024];
112 -static char *protocol, *host, *path, *username;
113 -static WCHAR *wusername, *password, *target;
92 +static WCHAR *wusername, *password, *protocol, *host, *path, target[1024];
93
115 -static void write_item(const char *what, WCHAR *wbuf)
94 +static void write_item(const char *what, LPCWSTR wbuf, int wlen)
95 {
96 char *buf;
118 - int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, NULL, 0, NULL,
97 + int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, NULL, 0, NULL,
98 FALSE);
99 buf = xmalloc(len);
100
122 - if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, FALSE))
101 + if (!WideCharToMultiByte(CP_UTF8, 0, wbuf, wlen, buf, len, NULL, FALSE))
102 die("WideCharToMultiByte failed!");
103
104 printf("%s=", what);
126 - fwrite(buf, 1, len - 1, stdout);
105 + fwrite(buf, 1, len, stdout);
106 putchar('\n');
107 free(buf);
108 }
109
131 -static int match_attr(const CREDENTIALW *cred, const WCHAR *keyword,
132 - const char *want)
110 +/*
111 + * Match an (optional) expected string and a delimiter in the target string,
112 + * consuming the matched text by updating the target pointer.
113 + */
114 +static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)
115 {
134 - int i;
135 - if (!want)
136 - return 1;
137 -
138 - for (i = 0; i < cred->AttributeCount; ++i)
139 - if (!wcscmp(cred->Attributes[i].Keyword, keyword))
140 - return !strcmp((const char *)cred->Attributes[i].Value,
141 - want);
142 -
143 - return 0; /* not found */
116 + LPCWSTR delim_pos, start = *ptarget;
117 + int len;
118 +
119 + /* find start of delimiter (or end-of-string if delim is empty) */
120 + if (*delim)
121 + delim_pos = wcsstr(start, delim);
122 + else
123 + delim_pos = start + wcslen(start);
124 +
125 + /*
126 + * match text up to delimiter, or end of string (e.g. the '/' after
127 + * host is optional if not followed by a path)
128 + */
129 + if (delim_pos)
130 + len = delim_pos - start;
131 + else
132 + len = wcslen(start);
133 +
134 + /* update ptarget if we either found a delimiter or need a match */
135 + if (delim_pos || want)
136 + *ptarget = delim_pos ? delim_pos + wcslen(delim) : start + len;
137 +
138 + return !want || (!wcsncmp(want, start, len) && !want[len]);
139 }
140
141 static int match_cred(const CREDENTIALW *cred)
142 {
148 - return (!wusername || !wcscmp(wusername, cred->UserName)) &&
149 - match_attr(cred, L"git_protocol", protocol) &&
150 - match_attr(cred, L"git_host", host) &&
151 - match_attr(cred, L"git_path", path);
143 + LPCWSTR target = cred->TargetName;
144 + if (wusername && wcscmp(wusername, cred->UserName))
145 + return 0;
146 +
147 + return match_part(&target, L"git", L":") &&
148 + match_part(&target, protocol, L"://") &&
149 + match_part(&target, wusername, L"@") &&
150 + match_part(&target, host, L"/") &&
151 + match_part(&target, path, L"");
152 }
153
154 static void get_credential(void)
155 {
156 - WCHAR *user_buf, *pass_buf;
157 - DWORD user_buf_size = 0, pass_buf_size = 0;
158 - CREDENTIALW **creds, *cred = NULL;
156 + CREDENTIALW **creds;
157 DWORD num_creds;
158 int i;
159
@@ -165,90 +163,36 @@ static void get_credential(void)
163 /* search for the first credential that matches username */
164 for (i = 0; i < num_creds; ++i)
165 if (match_cred(creds[i])) {
168 - cred = creds[i];
166 + write_item("username", creds[i]->UserName,
167 + wcslen(creds[i]->UserName));
168 + write_item("password",
169 + (LPCWSTR)creds[i]->CredentialBlob,
170 + creds[i]->CredentialBlobSize / sizeof(WCHAR));
171 break;
172 }
171 - if (!cred)
172 - return;
173 -
174 - CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
175 - cred->CredentialBlobSize, NULL, &user_buf_size, NULL, NULL,
176 - NULL, &pass_buf_size);
177 -
178 - user_buf = xmalloc(user_buf_size * sizeof(WCHAR));
179 - pass_buf = xmalloc(pass_buf_size * sizeof(WCHAR));
180 -
181 - if (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,
182 - cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,
183 - pass_buf, &pass_buf_size))
184 - die("CredUnPackAuthenticationBuffer failed");
173
174 CredFree(creds);
187 -
188 - /* zero-terminate (sizes include zero-termination) */
189 - user_buf[user_buf_size - 1] = L'\0';
190 - pass_buf[pass_buf_size - 1] = L'\0';
191 -
192 - write_item("username", user_buf);
193 - write_item("password", pass_buf);
194 -
195 - free(user_buf);
196 - free(pass_buf);
197 -}
198 -
199 -static void write_attr(CREDENTIAL_ATTRIBUTEW *attr, const WCHAR *keyword,
200 - const char *value)
201 -{
202 - attr->Keyword = (LPWSTR)keyword;
203 - attr->Flags = 0;
204 - attr->ValueSize = strlen(value) + 1; /* store zero-termination */
205 - attr->Value = (LPBYTE)value;
175 }
176
177 static void store_credential(void)
178 {
179 CREDENTIALW cred;
211 - BYTE *auth_buf;
212 - DWORD auth_buf_size = 0;
213 - CREDENTIAL_ATTRIBUTEW attrs[CRED_MAX_ATTRIBUTES];
180
181 if (!wusername || !password)
182 return;
183
218 - /* query buffer size */
219 - CredPackAuthenticationBufferW(0, wusername, password,
220 - NULL, &auth_buf_size);
221 -
222 - auth_buf = xmalloc(auth_buf_size);
223 -
224 - if (!CredPackAuthenticationBufferW(0, wusername, password,
225 - auth_buf, &auth_buf_size))
226 - die("CredPackAuthenticationBuffer failed");
227 -
184 cred.Flags = 0;
185 cred.Type = CRED_TYPE_GENERIC;
186 cred.TargetName = target;
187 cred.Comment = L"saved by git-credential-wincred";
232 - cred.CredentialBlobSize = auth_buf_size;
233 - cred.CredentialBlob = auth_buf;
188 + cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
189 + cred.CredentialBlob = (LPVOID)password;
190 cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
235 - cred.AttributeCount = 1;
236 - cred.Attributes = attrs;
191 + cred.AttributeCount = 0;
192 + cred.Attributes = NULL;
193 cred.TargetAlias = NULL;
194 cred.UserName = wusername;
195
240 - write_attr(attrs, L"git_protocol", protocol);
241 -
242 - if (host) {
243 - write_attr(attrs + cred.AttributeCount, L"git_host", host);
244 - cred.AttributeCount++;
245 - }
246 -
247 - if (path) {
248 - write_attr(attrs + cred.AttributeCount, L"git_path", path);
249 - cred.AttributeCount++;
250 - }
251 -
196 if (!CredWriteW(&cred, 0))
197 die("CredWrite failed");
198 }
@@ -298,13 +242,12 @@ static void read_credential(void)
242 *v++ = '\0';
243
244 if (!strcmp(buf, "protocol"))
301 - protocol = xstrdup(v);
245 + protocol = utf8_to_utf16_dup(v);
246 else if (!strcmp(buf, "host"))
303 - host = xstrdup(v);
247 + host = utf8_to_utf16_dup(v);
248 else if (!strcmp(buf, "path"))
305 - path = xstrdup(v);
249 + path = utf8_to_utf16_dup(v);
250 else if (!strcmp(buf, "username")) {
307 - username = xstrdup(v);
251 wusername = utf8_to_utf16_dup(v);
252 } else if (!strcmp(buf, "password"))
253 password = utf8_to_utf16_dup(v);
@@ -333,22 +276,20 @@ int main(int argc, char *argv[])
276 return 0;
277
278 /* prepare 'target', the unique key for the credential */
336 - strncat(target_buf, "git:", sizeof(target_buf));
337 - strncat(target_buf, protocol, sizeof(target_buf));
338 - strncat(target_buf, "://", sizeof(target_buf));
339 - if (username) {
340 - strncat(target_buf, username, sizeof(target_buf));
341 - strncat(target_buf, "@", sizeof(target_buf));
279 + wcscpy(target, L"git:");
280 + wcsncat(target, protocol, ARRAY_SIZE(target));
281 + wcsncat(target, L"://", ARRAY_SIZE(target));
282 + if (wusername) {
283 + wcsncat(target, wusername, ARRAY_SIZE(target));
284 + wcsncat(target, L"@", ARRAY_SIZE(target));
285 }
286 if (host)
344 - strncat(target_buf, host, sizeof(target_buf));
287 + wcsncat(target, host, ARRAY_SIZE(target));
288 if (path) {
346 - strncat(target_buf, "/", sizeof(target_buf));
347 - strncat(target_buf, path, sizeof(target_buf));
289 + wcsncat(target, L"/", ARRAY_SIZE(target));
290 + wcsncat(target, path, ARRAY_SIZE(target));
291 }
292
350 - target = utf8_to_utf16_dup(target_buf);
351 -
293 if (!strcmp(argv[1], "get"))
294 get_credential();
295 else if (!strcmp(argv[1], "store"))