Seamless support for NTLM/Kerberos auth on Windows
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
diff --git a/include/git2/transport.h b/include/git2/transport.h
index f2b0c63..1f4d03e 100644
--- a/include/git2/transport.h
+++ b/include/git2/transport.h
@@ -185,7 +185,8 @@ GIT_EXTERN(int) git_cred_default_new(git_cred **out);
* remote url, or NULL if not included.
* - allowed_types: A bitmask stating which cred types are OK to return.
* - payload: The payload provided when specifying this callback.
- * - returns 0 for success or non-zero to indicate an error
+ * - returns 0 for success, < 0 to indicate an error, > 0 to indicate
+ * no credential was acquired
*/
typedef int (*git_cred_acquire_cb)(
git_cred **cred,
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index e47e19c..7ad2a16 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -21,6 +21,10 @@
#include <strsafe.h>
+/* For IInternetSecurityManager zone check */
+#include <objbase.h>
+#include <urlmon.h>
+
/* For UuidCreate */
#pragma comment(lib, "rpcrt4")
@@ -141,12 +145,12 @@ on_error:
static int apply_default_credentials(HINTERNET request)
{
- /* If we are explicitly asked to deliver default credentials, turn set
- * the security level to low which will guarantee they are delivered.
- * The default is "medium" which applies to the intranet and sounds
- * like it would correspond to Internet Explorer security zones, but
- * in fact does not.
- */
+ /* Either the caller explicitly requested that default credentials be passed,
+ * or our fallback credential callback was invoked and checked that the target
+ * URI was in the appropriate Internet Explorer security zone. By setting this
+ * flag, we guarantee that the credentials are delivered by WinHTTP. The default
+ * is "medium" which applies to the intranet and sounds like it would correspond
+ * to Internet Explorer security zones, but in fact does not. */
DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
if (!WinHttpSetOption(request, WINHTTP_OPTION_AUTOLOGON_POLICY, &data, sizeof(DWORD)))
@@ -155,6 +159,71 @@ static int apply_default_credentials(HINTERNET request)
return 0;
}
+static int fallback_cred_acquire_cb(
+ git_cred **cred,
+ const char *url,
+ const char *username_from_url,
+ unsigned int allowed_types,
+ void *payload)
+{
+ int error = 1;
+
+ /* If the target URI supports integrated Windows authentication
+ * as an authentication mechanism */
+ if (GIT_CREDTYPE_DEFAULT & allowed_types) {
+ LPWSTR wide_url;
+ DWORD wide_len;
+
+ /* Convert URL to wide characters */
+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, -1, NULL, 0);
+
+ if (!wide_len) {
+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
+ return -1;
+ }
+
+ wide_url = git__malloc(wide_len * sizeof(WCHAR));
+ GITERR_CHECK_ALLOC(wide_url);
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, url, -1, wide_url, wide_len)) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ git__free(wide_url);
+ return -1;
+ }
+
+ if (SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) {
+ IInternetSecurityManager* pISM;
+
+ /* And if the target URI is in the My Computer, Intranet, or Trusted zones */
+ if (SUCCEEDED(CoCreateInstance(&CLSID_InternetSecurityManager, NULL,
+ CLSCTX_ALL, &IID_IInternetSecurityManager, (void **)&pISM))) {
+ DWORD dwZone;
+
+ if (SUCCEEDED(pISM->lpVtbl->MapUrlToZone(pISM, wide_url, &dwZone, 0)) &&
+ (URLZONE_LOCAL_MACHINE == dwZone ||
+ URLZONE_INTRANET == dwZone ||
+ URLZONE_TRUSTED == dwZone)) {
+ git_cred *existing = *cred;
+
+ if (existing)
+ existing->free(existing);
+
+ /* Then use default Windows credentials to authenticate this request */
+ error = git_cred_default_new(cred);
+ }
+
+ pISM->lpVtbl->Release(pISM);
+ }
+
+ CoUninitialize();
+ }
+
+ git__free(wide_url);
+ }
+
+ return error;
+}
+
static int winhttp_stream_connect(winhttp_stream *s)
{
winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
@@ -657,8 +726,7 @@ replay:
}
/* Handle authentication failures */
- if (HTTP_STATUS_DENIED == status_code &&
- get_verb == s->verb && t->owner->cred_acquire_cb) {
+ if (HTTP_STATUS_DENIED == status_code && get_verb == s->verb) {
int allowed_types;
if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0)
@@ -666,21 +734,36 @@ replay:
if (allowed_types &&
(!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+ int cred_error = 1;
- int error = t->owner->cred_acquire_cb(
- &t->cred, t->owner->url, t->connection_data.user,
- allowed_types, t->owner->cred_acquire_payload);
- if (error < 0)
- return error;
+ /* Start with the user-supplied credential callback, if present */
+ if (t->owner->cred_acquire_cb) {
+ cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url,
+ t->connection_data.user, allowed_types, t->owner->cred_acquire_payload);
- assert(t->cred);
+ if (cred_error < 0)
+ return cred_error;
+ }
- WinHttpCloseHandle(s->request);
- s->request = NULL;
- s->sent_request = 0;
+ /* Invoke the fallback credentials acquisition callback if necessary */
+ if (cred_error > 0) {
+ cred_error = fallback_cred_acquire_cb(&t->cred, t->owner->url,
+ t->connection_data.user, allowed_types, NULL);
- /* Successfully acquired a credential */
- goto replay;
+ if (cred_error < 0)
+ return cred_error;
+ }
+
+ if (!cred_error) {
+ assert(t->cred);
+
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ s->sent_request = 0;
+
+ /* Successfully acquired a credential */
+ goto replay;
+ }
}
}