diff options
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/features.h.in | 2 | ||||
-rw-r--r-- | src/transports/auth.c | 2 | ||||
-rw-r--r-- | src/transports/auth.h | 1 | ||||
-rw-r--r-- | src/transports/auth_ntlm.c | 216 | ||||
-rw-r--r-- | src/transports/auth_ntlm.h | 35 | ||||
-rw-r--r-- | src/transports/http.c | 31 | ||||
-rw-r--r-- | tests/online/clone.c | 15 |
8 files changed, 299 insertions, 7 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b2c124e21..c05458297 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -363,12 +363,12 @@ ADD_FEATURE_INFO(SSH GIT_SSH "SSH transport support") # Optional external dependency: ntlmclient IF (USE_NTLMCLIENT) - SET(GIT_NTLMCLIENT 1) + SET(GIT_NTLM 1) ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/ntlmclient" "${libgit2_BINARY_DIR}/deps/ntlmclient") LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/ntlmclient") LIST(APPEND LIBGIT2_OBJECTS "$<TARGET_OBJECTS:ntlmclient>") ENDIF() -ADD_FEATURE_INFO(ntlmclient GIT_NTLMCLIENT "NTLM authentication support for Unix") +ADD_FEATURE_INFO(ntlmclient GIT_NTLM "NTLM authentication support for Unix") # Optional external dependency: libgssapi IF (USE_GSSAPI) diff --git a/src/features.h.in b/src/features.h.in index f414c5843..fab6c9e16 100644 --- a/src/features.h.in +++ b/src/features.h.in @@ -35,4 +35,6 @@ #cmakedefine GIT_SHA1_OPENSSL 1 #cmakedefine GIT_SHA1_MBEDTLS 1 +#cmakedefine GIT_NTLM 1 + #endif diff --git a/src/transports/auth.c b/src/transports/auth.c index c8e6adb12..cf9603d4c 100644 --- a/src/transports/auth.c +++ b/src/transports/auth.c @@ -28,6 +28,8 @@ static int basic_next_token( git_buf_printf(&raw, "%s:%s", cred->username, cred->password); + printf("authenticating: %s\n", raw.ptr); + if (git_buf_oom(&raw) || git_buf_puts(out, "Authorization: Basic ") < 0 || git_buf_encode_base64(out, git_buf_cstr(&raw), raw.size) < 0 || diff --git a/src/transports/auth.h b/src/transports/auth.h index 38c1b0f88..8020e8f00 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 000000000..7065a1fc6 --- /dev/null +++ b/src/transports/auth_ntlm.c @@ -0,0 +1,216 @@ +/* + * 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; +} 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); + GITERR_CHECK_ALLOC(ctx->challenge); + + printf("ok, challenge is: %s\n", 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; + const char *domain = NULL, *username; + char *parsed_domain = NULL, *parsed_username = NULL; + int error = 0; + + assert(_cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT); + cred = (git_cred_userpass_plaintext *)_cred; + + username = cred->username; + + if ((sep = strchr(username, '\\')) != NULL) { + parsed_domain = strndup(username, (sep - username)); + GITERR_CHECK_ALLOC(parsed_domain); + + parsed_username = strdup(sep + 1); + GITERR_CHECK_ALLOC(parsed_username); + + domain = parsed_domain; + username = parsed_username; + } + + if (ntlm_client_set_credentials(ctx->ntlm, + username, domain, cred->password) < 0) { + giterr_set(GITERR_NET, "could not set credentials: %s", + ntlm_client_errmsg(ctx->ntlm)); + error = -1; + goto done; + } + + printf("woo, my ntlm creds are %s\\%s / %s\n", domain ? domain : "(none)", username, cred->password); + +done: + git__free(parsed_domain); + git__free(parsed_username); + return error; +} + +static int ntlm_next_token( + git_buf *buf, + git_http_auth_context *c, + 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; + + printf("next token...\n"); + + assert(buf && ctx && ctx->ntlm); + + challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0; + + if (cred && (error = ntlm_set_credentials(ctx, cred)) < 0) + goto done; + + if (challenge_len < 4) { + giterr_set(GITERR_NET, "no ntlm challenge sent from server"); + goto done; + } else if (challenge_len == 4) { + if (memcmp(ctx->challenge, "NTLM", 4) != 0) { + giterr_set(GITERR_NET, "server did not request NTLM"); + goto done; + } + + if (ntlm_client_negotiate(&msg, &msg_len, ctx->ntlm) != 0) { + giterr_set(GITERR_NET, "ntlm authentication failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + } else { + if (memcmp(ctx->challenge, "NTLM ", 5) != 0) { + giterr_set(GITERR_NET, "challenge from server was not NTLM"); + goto done; + } + + if (git_buf_decode_base64(&input_buf, + ctx->challenge + 5, challenge_len - 5) < 0) { + giterr_set(GITERR_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) { + giterr_set(GITERR_NET, "ntlm challenge failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + + if (ntlm_client_response(&msg, &msg_len, ctx->ntlm) != 0) { + giterr_set(GITERR_NET, "ntlm authentication failed: %s", + ntlm_client_errmsg(ctx->ntlm)); + goto done; + } + } + + git_buf_puts(buf, "Authorization: NTLM "); + git_buf_encode_base64(buf, (const char *)msg, msg_len); + git_buf_puts(buf, "\r\n"); + + printf("RESPONSE: %s\n", buf->ptr); + + if (git_buf_oom(buf)) + goto done; + + error = 0; + +done: + return error; +} + +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 gitno_connection_data *connection_data) +{ + if ((ctx->ntlm = ntlm_client_init(NTLM_CLIENT_DEFAULTS)) == NULL) { + giterr_set_oom(); + return -1; + } + + if (ntlm_client_set_target(ctx->ntlm, connection_data->host) < 0) { + giterr_set(GITERR_NET, "failed to initialize NTLM: %s", + ntlm_client_errmsg(ctx->ntlm)); + return -1; + } + + return 0; +} + +int git_http_auth_ntlm( + git_http_auth_context **out, + const gitno_connection_data *connection_data) +{ + http_auth_ntlm_context *ctx; + + GIT_UNUSED(connection_data); + + *out = NULL; + + ctx = git__calloc(1, sizeof(http_auth_ntlm_context)); + GITERR_CHECK_ALLOC(ctx); + + if (ntlm_init_context(ctx, connection_data) < 0) { + git__free(ctx); + return -1; + } + + printf("--------------------------------------------------------\n"); + printf("starting ntlm?\n"); + + 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.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 000000000..b5ef1e384 --- /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 gitno_connection_data *connection_data); + +#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 c0e0d31e3..6fb3e7ad1 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -19,12 +19,14 @@ #include "auth.h" #include "http.h" #include "auth_negotiate.h" +#include "auth_ntlm.h" #include "streams/tls.h" #include "streams/socket.h" #include "streams/curl.h" git_http_auth_scheme auth_schemes[] = { { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, 0, git_http_auth_negotiate }, + { GIT_AUTHTYPE_NTLM, "NTLM", GIT_CREDTYPE_USERPASS_PLAINTEXT, 0, git_http_auth_ntlm }, { GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, 1, git_http_auth_basic }, }; @@ -154,9 +156,15 @@ static int auth_context_match( } } + if(!scheme) printf("no scheme for that...\n"); + if (!scheme) return 0; + printf("i have a scheme: %p %s\n", scheme, scheme->name); + printf("init_context: %p (dummy is %p, ntlm is %p)\n", scheme->init_context, git_http_auth_dummy, git_http_auth_ntlm); + + /* See if authentication has already started for this scheme */ git_vector_foreach(&t->auth_contexts, i, c) { if (c->type == scheme->type) { @@ -166,14 +174,19 @@ static int auth_context_match( } if (!context) { + printf("starting context...\n"); if (scheme->init_context(&context, &t->connection_data) < 0) return -1; + + printf("context is: %p\n", context); if (0) ; + else if (!context) return 0; else if (git_vector_insert(&t->auth_contexts, context) < 0) return -1; } + printf("done: %p\n", context); *out = context; return 0; @@ -289,6 +302,8 @@ static int parse_authenticate_response( size_t i; git_vector_foreach(www_authenticate, i, challenge) { + printf("challenge: %s\n", challenge); + if (auth_context_match(&context, t, challenge_match, challenge) < 0) return -1; else if (!context) @@ -298,6 +313,8 @@ static int parse_authenticate_response( context->set_challenge(context, challenge) < 0) return -1; + printf("context type: %x / cred types: %x\n", context->type, context->credtypes); + /* * Record both the supported mechs on the server and the * corresponding credential types so that we know what mech @@ -307,6 +324,8 @@ static int parse_authenticate_response( *allowed_types |= context->credtypes; } + printf("server types: %x\n", t->auth_server_types); + return 0; } @@ -381,7 +400,9 @@ static int on_headers_complete(http_parser *parser) http_subtransport *t = ctx->t; http_stream *s = ctx->s; git_buf buf = GIT_BUF_INIT; - int error = 0, no_callback = 0, allowed_auth_types = 0; + int error = 0, no_callback = 0, allowed_credtypes = 0; + + printf("------> %d ------->\n", parser->status_code); /* Both parse_header_name and parse_header_value are populated * and ready for consumption. */ @@ -394,7 +415,7 @@ static int on_headers_complete(http_parser *parser) * complete.) */ if (parse_authenticate_response(&t->www_authenticate, t, - &allowed_auth_types) < 0) + &allowed_credtypes) < 0) return t->parse_error = PARSE_ERROR_GENERIC; /* Check for an authentication failure. */ @@ -402,7 +423,7 @@ static int on_headers_complete(http_parser *parser) if (!t->owner->cred_acquire_cb) { no_callback = 1; } else { - if (allowed_auth_types) { + if (allowed_credtypes) { if (t->cred) { t->cred->free(t->cred); t->cred = NULL; @@ -411,7 +432,7 @@ static int on_headers_complete(http_parser *parser) error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->connection_data.user, - allowed_auth_types, + allowed_credtypes, t->owner->cred_acquire_payload); if (error == GIT_PASSTHROUGH) { @@ -422,7 +443,7 @@ static int on_headers_complete(http_parser *parser) } else { assert(t->cred); - if (!(t->cred->credtype & allowed_auth_types)) { + if (!(t->cred->credtype & allowed_credtypes)) { giterr_set(GITERR_NET, "credentials callback returned an invalid cred type"); return t->parse_error = PARSE_ERROR_GENERIC; } diff --git a/tests/online/clone.c b/tests/online/clone.c index de66238e3..d705ac26e 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -372,6 +372,21 @@ void test_online_clone__bitbucket_style(void) cl_fixture_cleanup("./foo"); } +void test_online_clone__ntlm(void) +{ + git_cred_userpass_payload user_pass = { + "zapp\\ethomson", "3,mFBVcrWi7C&r+a" + }; + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = "https://localhost:8888/"; + g_options.fetch_opts.callbacks.credentials = git_cred_userpass; + g_options.fetch_opts.callbacks.payload = &user_pass; + + cl_git_pass(git_clone(&g_repo, "https://zapp.edwardthomson.com/DefaultCollection/DemoProject/_git/TestRepository", "/tmp/test", &g_options)); + git_repository_free(g_repo); g_repo = NULL; +} + void test_online_clone__bitbucket_uses_creds_in_url(void) { git_cred_userpass_payload user_pass = { |