]> git.proxmox.com Git - libgit2.git/blame - src/transports/auth_negotiate.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / transports / auth_negotiate.c
CommitLineData
23135afa
ET
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
eae0bfdc
PP
8#include "auth_negotiate.h"
9
22a2d3d5 10#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK)
23135afa
ET
11
12#include "git2.h"
23135afa 13#include "auth.h"
22a2d3d5 14#include "git2/sys/credential.h"
23135afa 15
22a2d3d5
UG
16#ifdef GIT_GSSFRAMEWORK
17#import <GSS/GSS.h>
18#elif defined(GIT_GSSAPI)
23135afa
ET
19#include <gssapi.h>
20#include <krb5.h>
22a2d3d5 21#endif
23135afa
ET
22
23static gss_OID_desc negotiate_oid_spnego =
24 { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
25static gss_OID_desc negotiate_oid_krb5 =
26 { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
27
28static gss_OID negotiate_oids[] =
29 { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL };
30
31typedef struct {
32 git_http_auth_context parent;
33 unsigned configured : 1,
34 complete : 1;
e579e0f7 35 git_str target;
23135afa
ET
36 char *challenge;
37 gss_ctx_id_t gss_context;
38 gss_OID oid;
39} http_auth_negotiate_context;
40
41static void negotiate_err_set(
42 OM_uint32 status_major,
43 OM_uint32 status_minor,
44 const char *message)
45{
46 gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER;
47 OM_uint32 status_display, context = 0;
48
49 if (gss_display_status(&status_display, status_major, GSS_C_GSS_CODE,
50 GSS_C_NO_OID, &context, &buffer) == GSS_S_COMPLETE) {
ac3d33df 51 git_error_set(GIT_ERROR_NET, "%s: %.*s (%d.%d)",
23135afa
ET
52 message, (int)buffer.length, (const char *)buffer.value,
53 status_major, status_minor);
54 gss_release_buffer(&status_minor, &buffer);
55 } else {
ac3d33df 56 git_error_set(GIT_ERROR_NET, "%s: unknown negotiate error (%d.%d)",
23135afa
ET
57 message, status_major, status_minor);
58 }
59}
60
61static int negotiate_set_challenge(
62 git_http_auth_context *c,
63 const char *challenge)
64{
65 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
66
c25aa7cd
PP
67 GIT_ASSERT_ARG(ctx);
68 GIT_ASSERT_ARG(challenge);
69 GIT_ASSERT(ctx->configured);
23135afa
ET
70
71 git__free(ctx->challenge);
72
73 ctx->challenge = git__strdup(challenge);
ac3d33df 74 GIT_ERROR_CHECK_ALLOC(ctx->challenge);
23135afa
ET
75
76 return 0;
77}
78
22a2d3d5
UG
79static void negotiate_context_dispose(http_auth_negotiate_context *ctx)
80{
81 OM_uint32 status_minor;
82
83 if (ctx->gss_context != GSS_C_NO_CONTEXT) {
84 gss_delete_sec_context(
85 &status_minor, &ctx->gss_context, GSS_C_NO_BUFFER);
86 ctx->gss_context = GSS_C_NO_CONTEXT;
87 }
88
e579e0f7 89 git_str_dispose(&ctx->target);
22a2d3d5
UG
90
91 git__free(ctx->challenge);
92 ctx->challenge = NULL;
93}
94
23135afa 95static int negotiate_next_token(
e579e0f7 96 git_str *buf,
23135afa 97 git_http_auth_context *c,
22a2d3d5 98 git_credential *cred)
23135afa
ET
99{
100 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
101 OM_uint32 status_major, status_minor;
102 gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER,
103 input_token = GSS_C_EMPTY_BUFFER,
104 output_token = GSS_C_EMPTY_BUFFER;
105 gss_buffer_t input_token_ptr = GSS_C_NO_BUFFER;
e579e0f7 106 git_str input_buf = GIT_STR_INIT;
23135afa
ET
107 gss_name_t server = NULL;
108 gss_OID mech;
109 size_t challenge_len;
110 int error = 0;
111
c25aa7cd
PP
112 GIT_ASSERT_ARG(buf);
113 GIT_ASSERT_ARG(ctx);
114 GIT_ASSERT_ARG(cred);
115
116 GIT_ASSERT(ctx->configured);
117 GIT_ASSERT(cred->credtype == GIT_CREDENTIAL_DEFAULT);
23135afa
ET
118
119 if (ctx->complete)
120 return 0;
121
122 target_buffer.value = (void *)ctx->target.ptr;
123 target_buffer.length = ctx->target.size;
124
125 status_major = gss_import_name(&status_minor, &target_buffer,
126 GSS_C_NT_HOSTBASED_SERVICE, &server);
127
128 if (GSS_ERROR(status_major)) {
129 negotiate_err_set(status_major, status_minor,
22a2d3d5 130 "could not parse principal");
23135afa
ET
131 error = -1;
132 goto done;
133 }
134
135 challenge_len = ctx->challenge ? strlen(ctx->challenge) : 0;
136
22a2d3d5
UG
137 if (challenge_len < 9 || memcmp(ctx->challenge, "Negotiate", 9) != 0) {
138 git_error_set(GIT_ERROR_NET, "server did not request negotiate");
23135afa
ET
139 error = -1;
140 goto done;
22a2d3d5
UG
141 }
142
143 if (challenge_len > 9) {
e579e0f7 144 if (git_str_decode_base64(&input_buf,
23135afa 145 ctx->challenge + 10, challenge_len - 10) < 0) {
ac3d33df 146 git_error_set(GIT_ERROR_NET, "invalid negotiate challenge from server");
23135afa
ET
147 error = -1;
148 goto done;
149 }
150
151 input_token.value = input_buf.ptr;
152 input_token.length = input_buf.size;
153 input_token_ptr = &input_token;
154 } else if (ctx->gss_context != GSS_C_NO_CONTEXT) {
22a2d3d5 155 negotiate_context_dispose(ctx);
23135afa
ET
156 }
157
158 mech = &negotiate_oid_spnego;
159
22a2d3d5 160 status_major = gss_init_sec_context(
23135afa
ET
161 &status_minor,
162 GSS_C_NO_CREDENTIAL,
163 &ctx->gss_context,
164 server,
165 mech,
166 GSS_C_DELEG_FLAG | GSS_C_MUTUAL_FLAG,
167 GSS_C_INDEFINITE,
168 GSS_C_NO_CHANNEL_BINDINGS,
169 input_token_ptr,
170 NULL,
171 &output_token,
172 NULL,
22a2d3d5
UG
173 NULL);
174
175 if (GSS_ERROR(status_major)) {
176 negotiate_err_set(status_major, status_minor, "negotiate failure");
23135afa
ET
177 error = -1;
178 goto done;
179 }
180
181 /* This message merely told us auth was complete; we do not respond. */
182 if (status_major == GSS_S_COMPLETE) {
22a2d3d5 183 negotiate_context_dispose(ctx);
23135afa
ET
184 ctx->complete = 1;
185 goto done;
186 }
187
22a2d3d5
UG
188 if (output_token.length == 0) {
189 git_error_set(GIT_ERROR_NET, "GSSAPI did not return token");
190 error = -1;
191 goto done;
192 }
193
e579e0f7
MB
194 git_str_puts(buf, "Negotiate ");
195 git_str_encode_base64(buf, output_token.value, output_token.length);
23135afa 196
e579e0f7 197 if (git_str_oom(buf))
23135afa
ET
198 error = -1;
199
200done:
201 gss_release_name(&status_minor, &server);
202 gss_release_buffer(&status_minor, (gss_buffer_t) &output_token);
e579e0f7 203 git_str_dispose(&input_buf);
23135afa
ET
204 return error;
205}
206
22a2d3d5 207static int negotiate_is_complete(git_http_auth_context *c)
23135afa
ET
208{
209 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
23135afa 210
c25aa7cd 211 GIT_ASSERT_ARG(ctx);
23135afa 212
22a2d3d5
UG
213 return (ctx->complete == 1);
214}
23135afa 215
22a2d3d5
UG
216static void negotiate_context_free(git_http_auth_context *c)
217{
218 http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c;
219
220 negotiate_context_dispose(ctx);
23135afa
ET
221
222 ctx->configured = 0;
223 ctx->complete = 0;
224 ctx->oid = NULL;
225
226 git__free(ctx);
227}
228
229static int negotiate_init_context(
230 http_auth_negotiate_context *ctx,
22a2d3d5 231 const git_net_url *url)
23135afa
ET
232{
233 OM_uint32 status_major, status_minor;
234 gss_OID item, *oid;
235 gss_OID_set mechanism_list;
236 size_t i;
237
238 /* Query supported mechanisms looking for SPNEGO) */
22a2d3d5
UG
239 status_major = gss_indicate_mechs(&status_minor, &mechanism_list);
240
241 if (GSS_ERROR(status_major)) {
23135afa
ET
242 negotiate_err_set(status_major, status_minor,
243 "could not query mechanisms");
244 return -1;
245 }
246
247 if (mechanism_list) {
248 for (oid = negotiate_oids; *oid; oid++) {
249 for (i = 0; i < mechanism_list->count; i++) {
250 item = &mechanism_list->elements[i];
251
252 if (item->length == (*oid)->length &&
253 memcmp(item->elements, (*oid)->elements, item->length) == 0) {
254 ctx->oid = *oid;
255 break;
256 }
257
258 }
259
260 if (ctx->oid)
261 break;
262 }
263 }
264
265 gss_release_oid_set(&status_minor, &mechanism_list);
266
267 if (!ctx->oid) {
ac3d33df 268 git_error_set(GIT_ERROR_NET, "negotiate authentication is not supported");
c25aa7cd 269 return GIT_EAUTH;
23135afa
ET
270 }
271
e579e0f7
MB
272 git_str_puts(&ctx->target, "HTTP@");
273 git_str_puts(&ctx->target, url->host);
23135afa 274
e579e0f7 275 if (git_str_oom(&ctx->target))
23135afa
ET
276 return -1;
277
278 ctx->gss_context = GSS_C_NO_CONTEXT;
279 ctx->configured = 1;
280
281 return 0;
282}
283
284int git_http_auth_negotiate(
285 git_http_auth_context **out,
22a2d3d5 286 const git_net_url *url)
23135afa
ET
287{
288 http_auth_negotiate_context *ctx;
289
290 *out = NULL;
291
292 ctx = git__calloc(1, sizeof(http_auth_negotiate_context));
ac3d33df 293 GIT_ERROR_CHECK_ALLOC(ctx);
23135afa 294
22a2d3d5 295 if (negotiate_init_context(ctx, url) < 0) {
23135afa
ET
296 git__free(ctx);
297 return -1;
298 }
299
22a2d3d5
UG
300 ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE;
301 ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT;
302 ctx->parent.connection_affinity = 1;
23135afa
ET
303 ctx->parent.set_challenge = negotiate_set_challenge;
304 ctx->parent.next_token = negotiate_next_token;
22a2d3d5 305 ctx->parent.is_complete = negotiate_is_complete;
23135afa
ET
306 ctx->parent.free = negotiate_context_free;
307
308 *out = (git_http_auth_context *)ctx;
309
310 return 0;
311}
312
313#endif /* GIT_GSSAPI */
314