1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 #include "git2.h"
9 #include "common.h"
10 #include "buffer.h"
11 #include "auth.h"
12 #include "auth_ntlm.h"
13 #include "git2/sys/credential.h"
14 
15 #ifdef GIT_NTLM
16 
17 #include "ntlmclient.h"
18 
19 typedef struct {
20 	git_http_auth_context parent;
21 	ntlm_client *ntlm;
22 	char *challenge;
23 	bool complete;
24 } http_auth_ntlm_context;
25 
ntlm_set_challenge(git_http_auth_context * c,const char * challenge)26 static int ntlm_set_challenge(
27 	git_http_auth_context *c,
28 	const char *challenge)
29 {
30 	http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
31 
32 	GIT_ASSERT_ARG(ctx);
33 	GIT_ASSERT_ARG(challenge);
34 
35 	git__free(ctx->challenge);
36 
37 	ctx->challenge = git__strdup(challenge);
38 	GIT_ERROR_CHECK_ALLOC(ctx->challenge);
39 
40 	return 0;
41 }
42 
ntlm_set_credentials(http_auth_ntlm_context * ctx,git_credential * _cred)43 static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred)
44 {
45 	git_credential_userpass_plaintext *cred;
46 	const char *sep, *username;
47 	char *domain = NULL, *domainuser = NULL;
48 	int error = 0;
49 
50 	GIT_ASSERT(_cred->credtype == GIT_CREDENTIAL_USERPASS_PLAINTEXT);
51 	cred = (git_credential_userpass_plaintext *)_cred;
52 
53 	if ((sep = strchr(cred->username, '\\')) != NULL) {
54 		domain = git__strndup(cred->username, (sep - cred->username));
55 		GIT_ERROR_CHECK_ALLOC(domain);
56 
57 		domainuser = git__strdup(sep + 1);
58 		GIT_ERROR_CHECK_ALLOC(domainuser);
59 
60 		username = domainuser;
61 	} else {
62 		username = cred->username;
63 	}
64 
65 	if (ntlm_client_set_credentials(ctx->ntlm,
66 	    username, domain, cred->password) < 0) {
67 		git_error_set(GIT_ERROR_NET, "could not set credentials: %s",
68 		    ntlm_client_errmsg(ctx->ntlm));
69 		error = -1;
70 		goto done;
71 	}
72 
73 done:
74 	git__free(domain);
75 	git__free(domainuser);
76 	return error;
77 }
78 
ntlm_next_token(git_buf * buf,git_http_auth_context * c,git_credential * cred)79 static int ntlm_next_token(
80 	git_buf *buf,
81 	git_http_auth_context *c,
82 	git_credential *cred)
83 {
84 	http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
85 	git_buf input_buf = GIT_BUF_INIT;
86 	const unsigned char *msg;
87 	size_t challenge_len, msg_len;
88 	int error = GIT_EAUTH;
89 
90 	GIT_ASSERT_ARG(buf);
91 	GIT_ASSERT_ARG(ctx);
92 
93 	GIT_ASSERT(ctx->ntlm);
94 
95 	challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
96 
97 	if (ctx->complete)
98 		ntlm_client_reset(ctx->ntlm);
99 
100 	/*
101 	 * Set us complete now since it's the default case; the one
102 	 * incomplete case (successfully created a client request)
103 	 * will explicitly set that it requires a second step.
104 	 */
105 	ctx->complete = true;
106 
107 	if (cred && ntlm_set_credentials(ctx, cred) != 0)
108 		goto done;
109 
110 	if (challenge_len < 4) {
111 		git_error_set(GIT_ERROR_NET, "no ntlm challenge sent from server");
112 		goto done;
113 	} else if (challenge_len == 4) {
114 		if (memcmp(ctx->challenge, "NTLM", 4) != 0) {
115 			git_error_set(GIT_ERROR_NET, "server did not request NTLM");
116 			goto done;
117 		}
118 
119 		if (ntlm_client_negotiate(&msg, &msg_len, ctx->ntlm) != 0) {
120 			git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s",
121 				ntlm_client_errmsg(ctx->ntlm));
122 			goto done;
123 		}
124 
125 		ctx->complete = false;
126 	} else {
127 		if (memcmp(ctx->challenge, "NTLM ", 5) != 0) {
128 			git_error_set(GIT_ERROR_NET, "challenge from server was not NTLM");
129 			goto done;
130 		}
131 
132 		if (git_buf_decode_base64(&input_buf,
133 		    ctx->challenge + 5, challenge_len - 5) < 0) {
134 			git_error_set(GIT_ERROR_NET, "invalid NTLM challenge from server");
135 			goto done;
136 		}
137 
138 		if (ntlm_client_set_challenge(ctx->ntlm,
139 		    (const unsigned char *)input_buf.ptr, input_buf.size) != 0) {
140 			git_error_set(GIT_ERROR_NET, "ntlm challenge failed: %s",
141 				ntlm_client_errmsg(ctx->ntlm));
142 			goto done;
143 		}
144 
145 		if (ntlm_client_response(&msg, &msg_len, ctx->ntlm) != 0) {
146 			git_error_set(GIT_ERROR_NET, "ntlm authentication failed: %s",
147 				ntlm_client_errmsg(ctx->ntlm));
148 			goto done;
149 		}
150 	}
151 
152 	git_buf_puts(buf, "NTLM ");
153 	git_buf_encode_base64(buf, (const char *)msg, msg_len);
154 
155 	if (git_buf_oom(buf))
156 		goto done;
157 
158 	error = 0;
159 
160 done:
161 	git_buf_dispose(&input_buf);
162 	return error;
163 }
164 
ntlm_is_complete(git_http_auth_context * c)165 static int ntlm_is_complete(git_http_auth_context *c)
166 {
167 	http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
168 
169 	GIT_ASSERT_ARG(ctx);
170 	return (ctx->complete == true);
171 }
172 
ntlm_context_free(git_http_auth_context * c)173 static void ntlm_context_free(git_http_auth_context *c)
174 {
175 	http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c;
176 
177 	ntlm_client_free(ctx->ntlm);
178 	git__free(ctx->challenge);
179 	git__free(ctx);
180 }
181 
ntlm_init_context(http_auth_ntlm_context * ctx,const git_net_url * url)182 static int ntlm_init_context(
183 	http_auth_ntlm_context *ctx,
184 	const git_net_url *url)
185 {
186 	GIT_UNUSED(url);
187 
188 	if ((ctx->ntlm = ntlm_client_init(NTLM_CLIENT_DEFAULTS)) == NULL) {
189 		git_error_set_oom();
190 		return -1;
191 	}
192 
193 	return 0;
194 }
195 
git_http_auth_ntlm(git_http_auth_context ** out,const git_net_url * url)196 int git_http_auth_ntlm(
197 	git_http_auth_context **out,
198 	const git_net_url *url)
199 {
200 	http_auth_ntlm_context *ctx;
201 
202 	GIT_UNUSED(url);
203 
204 	*out = NULL;
205 
206 	ctx = git__calloc(1, sizeof(http_auth_ntlm_context));
207 	GIT_ERROR_CHECK_ALLOC(ctx);
208 
209 	if (ntlm_init_context(ctx, url) < 0) {
210 		git__free(ctx);
211 		return -1;
212 	}
213 
214 	ctx->parent.type = GIT_HTTP_AUTH_NTLM;
215 	ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT;
216 	ctx->parent.connection_affinity = 1;
217 	ctx->parent.set_challenge = ntlm_set_challenge;
218 	ctx->parent.next_token = ntlm_next_token;
219 	ctx->parent.is_complete = ntlm_is_complete;
220 	ctx->parent.free = ntlm_context_free;
221 
222 	*out = (git_http_auth_context *)ctx;
223 
224 	return 0;
225 }
226 
227 #endif /* GIT_NTLM */
228