Commit 3192e3c970f490164852dd76f928b753f821e40d

Edward Thomson 2019-03-07T16:57:11

http: provide an NTLM authentication provider

diff --git a/src/features.h.in b/src/features.h.in
index 63800f8..f2931cb 100644
--- a/src/features.h.in
+++ b/src/features.h.in
@@ -25,6 +25,7 @@
 #cmakedefine GIT_SSH 1
 #cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1
 
+#cmakedefine GIT_NTLM 1
 #cmakedefine GIT_GSSAPI 1
 #cmakedefine GIT_WINHTTP 1
 #cmakedefine GIT_NTLM 1
diff --git a/src/transports/auth.h b/src/transports/auth.h
index 9ead558..396e793 100644
--- a/src/transports/auth.h
+++ b/src/transports/auth.h
@@ -16,6 +16,7 @@
 typedef enum {
 	GIT_AUTHTYPE_BASIC = 1,
 	GIT_AUTHTYPE_NEGOTIATE = 2,
+	GIT_AUTHTYPE_NTLM = 4,
 } git_http_authtype_t;
 
 typedef struct git_http_auth_context git_http_auth_context;
diff --git a/src/transports/auth_ntlm.c b/src/transports/auth_ntlm.c
new file mode 100644
index 0000000..55320e9
--- /dev/null
+++ b/src/transports/auth_ntlm.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "git2.h"
+#include "common.h"
+#include "buffer.h"
+#include "auth.h"
+#include "auth_ntlm.h"
+
+#ifdef GIT_NTLM
+
+#include "ntlm.h"
+
+typedef struct {
+	git_http_auth_context parent;
+	ntlm_client *ntlm;
+	char *challenge;
+	bool complete;
+} http_auth_ntlm_context;
+
+static int ntlm_set_challenge(
+	git_http_auth_context *c,
+	const char *challenge)
+{
+	http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
+
+	assert(ctx && challenge);
+
+	git__free(ctx->challenge);
+
+	ctx->challenge = git__strdup(challenge);
+	GIT_ERROR_CHECK_ALLOC(ctx->challenge);
+
+	return 0;
+}
+
+static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_cred *_cred)
+{
+	git_cred_userpass_plaintext *cred;
+	const char *sep, *username;
+	char *domain = NULL, *domainuser = NULL;
+	int error = 0;
+
+	assert(_cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT);
+	cred = (git_cred_userpass_plaintext *)_cred;
+
+	if ((sep = strchr(cred->username, '\\')) != NULL) {
+		domain = strndup(cred->username, (sep - cred->username));
+		GIT_ERROR_CHECK_ALLOC(domain);
+
+		domainuser = strdup(sep + 1);
+		GIT_ERROR_CHECK_ALLOC(domainuser);
+
+		username = domainuser;
+	} else {
+		username = cred->username;
+	}
+
+	if (ntlm_client_set_credentials(ctx->ntlm,
+	    username, domain, cred->password) < 0) {
+		git_error_set(GIT_ERROR_NET, "could not set credentials: %s",
+		    ntlm_client_errmsg(ctx->ntlm));
+		error = -1;
+		goto done;
+	}
+
+done:
+	git__free(domain);
+	git__free(domainuser);
+	return error;
+}
+
+static int ntlm_next_token(
+	git_buf *buf,
+	git_http_auth_context *c,
+	const char *header_name,
+	git_cred *cred)
+{
+	http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
+	git_buf input_buf = GIT_BUF_INIT;
+	const unsigned char *msg;
+	size_t challenge_len, msg_len;
+	int error = -1;
+
+	assert(buf && ctx && ctx->ntlm);
+
+	challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
+
+	if (ctx->complete)
+		ntlm_client_reset(ctx->ntlm);
+
+	/*
+	 * Set us complete now since it's the default case; the one
+	 * incomplete case (successfully created a client request)
+	 * will explicitly set that it requires a second step.
+	 */
+	ctx->complete = true;
+
+	if (cred && ntlm_set_credentials(ctx, cred) != 0)
+		goto done;
+
+	if (challenge_len < 4) {
+		git_error_set(GIT_ERROR_NET, "no ntlm challenge sent from server");
+		goto done;
+	} else if (challenge_len == 4) {
+		if (memcmp(ctx->challenge, "NTLM", 4) != 0) {
+			git_error_set(GIT_ERROR_NET, "server did not request NTLM");
+			goto done;
+		}
+
+		if (ntlm_client_negotiate(&msg, &msg_len, ctx->ntlm) != 0) {
+			git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s",
+				ntlm_client_errmsg(ctx->ntlm));
+			goto done;
+		}
+
+		ctx->complete = false;
+	} else {
+		if (memcmp(ctx->challenge, "NTLM ", 5) != 0) {
+			git_error_set(GIT_ERROR_NET, "challenge from server was not NTLM");
+			goto done;
+		}
+
+		if (git_buf_decode_base64(&input_buf,
+		    ctx->challenge + 5, challenge_len - 5) < 0) {
+			git_error_set(GIT_ERROR_NET, "invalid NTLM challenge from server");
+			goto done;
+		}
+
+		if (ntlm_client_set_challenge(ctx->ntlm,
+		    (const unsigned char *)input_buf.ptr, input_buf.size) != 0) {
+			git_error_set(GIT_ERROR_NET, "ntlm challenge failed: %s",
+				ntlm_client_errmsg(ctx->ntlm));
+			goto done;
+		}
+
+		if (ntlm_client_response(&msg, &msg_len, ctx->ntlm) != 0) {
+			git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s",
+				ntlm_client_errmsg(ctx->ntlm));
+			goto done;
+		}
+	}
+
+	git_buf_printf(buf, "%s: NTLM ", header_name);
+	git_buf_encode_base64(buf, (const char *)msg, msg_len);
+	git_buf_puts(buf, "\r\n");
+
+	if (git_buf_oom(buf))
+		goto done;
+
+	error = 0;
+
+done:
+	git_buf_dispose(&input_buf);
+	return error;
+}
+
+static int ntlm_is_complete(git_http_auth_context *c)
+{
+	http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
+
+	assert(ctx);
+	return (ctx->complete == true);
+}
+
+static void ntlm_context_free(git_http_auth_context *c)
+{
+	http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
+
+	ntlm_client_free(ctx->ntlm);
+	git__free(ctx->challenge);
+	git__free(ctx);
+}
+
+static int ntlm_init_context(
+	http_auth_ntlm_context *ctx,
+	const git_net_url *url)
+{
+	GIT_UNUSED(url);
+
+	if ((ctx->ntlm = ntlm_client_init(NTLM_CLIENT_DEFAULTS)) == NULL) {
+		git_error_set_oom();
+		return -1;
+	}
+
+	return 0;
+}
+
+int git_http_auth_ntlm(
+	git_http_auth_context **out,
+	const git_net_url *url)
+{
+	http_auth_ntlm_context *ctx;
+
+	GIT_UNUSED(url);
+
+	*out = NULL;
+
+	ctx = git__calloc(1, sizeof(http_auth_ntlm_context));
+	GIT_ERROR_CHECK_ALLOC(ctx);
+
+	if (ntlm_init_context(ctx, url) < 0) {
+		git__free(ctx);
+		return -1;
+	}
+
+	ctx->parent.type = GIT_AUTHTYPE_NTLM;
+	ctx->parent.credtypes = GIT_CREDTYPE_USERPASS_PLAINTEXT;
+	ctx->parent.set_challenge = ntlm_set_challenge;
+	ctx->parent.next_token = ntlm_next_token;
+	ctx->parent.is_complete = ntlm_is_complete;
+	ctx->parent.free = ntlm_context_free;
+
+	*out = (git_http_auth_context *)ctx;
+
+	return 0;
+}
+
+#endif /* GIT_NTLM */
diff --git a/src/transports/auth_ntlm.h b/src/transports/auth_ntlm.h
new file mode 100644
index 0000000..5b42b2b
--- /dev/null
+++ b/src/transports/auth_ntlm.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) the libgit2 contributors. All rights reserved.
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#ifndef INCLUDE_transports_auth_ntlm_h__
+#define INCLUDE_transports_auth_ntlm_h__
+
+#include "git2.h"
+#include "auth.h"
+
+#ifdef GIT_NTLM
+
+#if defined(GIT_OPENSSL)
+# define CRYPT_OPENSSL
+#elif defined(GIT_MBEDTLS)
+# define CRYPT_MBEDTLS
+#elif defined(GIT_SECURE_TRANSPORT)
+# define CRYPT_COMMONCRYPTO
+#endif
+
+extern int git_http_auth_ntlm(
+	git_http_auth_context **out,
+	const git_net_url *url);
+
+#else
+
+#define git_http_auth_ntlm git_http_auth_dummy
+
+#endif /* GIT_NTLM */
+
+#endif
+
diff --git a/src/transports/http.c b/src/transports/http.c
index 6ac13ca..745da92 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -20,11 +20,13 @@
 #include "auth.h"
 #include "http.h"
 #include "auth_negotiate.h"
+#include "auth_ntlm.h"
 #include "streams/tls.h"
 #include "streams/socket.h"
 
 git_http_auth_scheme auth_schemes[] = {
 	{ GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
+	{ GIT_AUTHTYPE_NTLM, "NTLM", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_ntlm },
 	{ GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic },
 };