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