lib: Use asn1_current_ofs()
[metze/samba/wip.git] / source4 / auth / gensec / gensec_krb5.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    Kerberos backend for GENSEC
5    
6    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004
7    Copyright (C) Andrew Tridgell 2001
8    Copyright (C) Luke Howard 2002-2003
9    Copyright (C) Stefan Metzmacher 2004-2005
10
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 3 of the License, or
14    (at your option) any later version.
15    
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20
21    
22    You should have received a copy of the GNU General Public License
23    along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 */
25
26 #include "includes.h"
27 #include "system/kerberos.h"
28 #include "auth/kerberos/kerberos.h"
29 #include "auth/auth.h"
30 #include "lib/tsocket/tsocket.h"
31 #include "librpc/gen_ndr/dcerpc.h"
32 #include "auth/credentials/credentials.h"
33 #include "auth/credentials/credentials_krb5.h"
34 #include "auth/kerberos/kerberos_credentials.h"
35 #include "auth/gensec/gensec.h"
36 #include "auth/gensec/gensec_internal.h"
37 #include "auth/gensec/gensec_proto.h"
38 #include "auth/gensec/gensec_toplevel_proto.h"
39 #include "param/param.h"
40 #include "auth/auth_sam_reply.h"
41 #include "lib/util/util_net.h"
42 #include "../lib/util/asn1.h"
43 #include "auth/kerberos/pac_utils.h"
44 #include "gensec_krb5_util.h"
45
46 _PUBLIC_ NTSTATUS gensec_krb5_init(void);
47
48 enum GENSEC_KRB5_STATE {
49         GENSEC_KRB5_SERVER_START,
50         GENSEC_KRB5_CLIENT_START,
51         GENSEC_KRB5_CLIENT_MUTUAL_AUTH,
52         GENSEC_KRB5_DONE
53 };
54
55 struct gensec_krb5_state {
56         enum GENSEC_KRB5_STATE state_position;
57         struct smb_krb5_context *smb_krb5_context;
58         krb5_auth_context auth_context;
59         krb5_data enc_ticket;
60         krb5_keyblock *keyblock;
61         krb5_ticket *ticket;
62         bool gssapi;
63         krb5_flags ap_req_options;
64 };
65
66 static int gensec_krb5_destroy(struct gensec_krb5_state *gensec_krb5_state)
67 {
68         if (!gensec_krb5_state->smb_krb5_context) {
69                 /* We can't clean anything else up unless we started up this far */
70                 return 0;
71         }
72         if (gensec_krb5_state->enc_ticket.length) { 
73                 kerberos_free_data_contents(gensec_krb5_state->smb_krb5_context->krb5_context, 
74                                             &gensec_krb5_state->enc_ticket); 
75         }
76
77         if (gensec_krb5_state->ticket) {
78                 krb5_free_ticket(gensec_krb5_state->smb_krb5_context->krb5_context, 
79                                  gensec_krb5_state->ticket);
80         }
81
82         /* ccache freed in a child destructor */
83
84         krb5_free_keyblock(gensec_krb5_state->smb_krb5_context->krb5_context, 
85                            gensec_krb5_state->keyblock);
86                 
87         if (gensec_krb5_state->auth_context) {
88                 krb5_auth_con_free(gensec_krb5_state->smb_krb5_context->krb5_context, 
89                                    gensec_krb5_state->auth_context);
90         }
91
92         return 0;
93 }
94
95 static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security, bool gssapi)
96 {
97         krb5_error_code ret;
98         struct gensec_krb5_state *gensec_krb5_state;
99         struct cli_credentials *creds;
100         const struct tsocket_address *tlocal_addr, *tremote_addr;
101         krb5_address my_krb5_addr, peer_krb5_addr;
102         
103         creds = gensec_get_credentials(gensec_security);
104         if (!creds) {
105                 return NT_STATUS_INVALID_PARAMETER;
106         }
107
108         gensec_krb5_state = talloc(gensec_security, struct gensec_krb5_state);
109         if (!gensec_krb5_state) {
110                 return NT_STATUS_NO_MEMORY;
111         }
112
113         gensec_security->private_data = gensec_krb5_state;
114         gensec_krb5_state->smb_krb5_context = NULL;
115         gensec_krb5_state->auth_context = NULL;
116         gensec_krb5_state->ticket = NULL;
117         ZERO_STRUCT(gensec_krb5_state->enc_ticket);
118         gensec_krb5_state->keyblock = NULL;
119         gensec_krb5_state->gssapi = gssapi;
120
121         talloc_set_destructor(gensec_krb5_state, gensec_krb5_destroy); 
122
123         if (cli_credentials_get_krb5_context(creds, 
124                                              gensec_security->settings->lp_ctx, &gensec_krb5_state->smb_krb5_context)) {
125                 talloc_free(gensec_krb5_state);
126                 return NT_STATUS_INTERNAL_ERROR;
127         }
128
129         ret = krb5_auth_con_init(gensec_krb5_state->smb_krb5_context->krb5_context, &gensec_krb5_state->auth_context);
130         if (ret) {
131                 DEBUG(1,("gensec_krb5_start: krb5_auth_con_init failed (%s)\n", 
132                          smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
133                                                     ret, gensec_krb5_state)));
134                 talloc_free(gensec_krb5_state);
135                 return NT_STATUS_INTERNAL_ERROR;
136         }
137
138         ret = krb5_auth_con_setflags(gensec_krb5_state->smb_krb5_context->krb5_context, 
139                                      gensec_krb5_state->auth_context,
140                                      KRB5_AUTH_CONTEXT_DO_SEQUENCE);
141         if (ret) {
142                 DEBUG(1,("gensec_krb5_start: krb5_auth_con_setflags failed (%s)\n", 
143                          smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
144                                                     ret, gensec_krb5_state)));
145                 talloc_free(gensec_krb5_state);
146                 return NT_STATUS_INTERNAL_ERROR;
147         }
148
149         tlocal_addr = gensec_get_local_address(gensec_security);
150         if (tlocal_addr) {
151                 ssize_t socklen;
152                 struct sockaddr_storage ss;
153
154                 socklen = tsocket_address_bsd_sockaddr(tlocal_addr,
155                                 (struct sockaddr *) &ss,
156                                 sizeof(struct sockaddr_storage));
157                 if (socklen < 0) {
158                         talloc_free(gensec_krb5_state);
159                         return NT_STATUS_INTERNAL_ERROR;
160                 }
161                 ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context,
162                                 (const struct sockaddr *) &ss, &my_krb5_addr);
163                 if (ret) {
164                         DEBUG(1,("gensec_krb5_start: krb5_sockaddr2address (local) failed (%s)\n", 
165                                  smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
166                                                             ret, gensec_krb5_state)));
167                         talloc_free(gensec_krb5_state);
168                         return NT_STATUS_INTERNAL_ERROR;
169                 }
170         }
171
172         tremote_addr = gensec_get_remote_address(gensec_security);
173         if (tremote_addr) {
174                 ssize_t socklen;
175                 struct sockaddr_storage ss;
176
177                 socklen = tsocket_address_bsd_sockaddr(tremote_addr,
178                                 (struct sockaddr *) &ss,
179                                 sizeof(struct sockaddr_storage));
180                 if (socklen < 0) {
181                         talloc_free(gensec_krb5_state);
182                         return NT_STATUS_INTERNAL_ERROR;
183                 }
184                 ret = krb5_sockaddr2address(gensec_krb5_state->smb_krb5_context->krb5_context,
185                                 (const struct sockaddr *) &ss, &peer_krb5_addr);
186                 if (ret) {
187                         DEBUG(1,("gensec_krb5_start: krb5_sockaddr2address (local) failed (%s)\n", 
188                                  smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
189                                                             ret, gensec_krb5_state)));
190                         talloc_free(gensec_krb5_state);
191                         return NT_STATUS_INTERNAL_ERROR;
192                 }
193         }
194
195         ret = krb5_auth_con_setaddrs(gensec_krb5_state->smb_krb5_context->krb5_context, 
196                                      gensec_krb5_state->auth_context,
197                                      tlocal_addr ? &my_krb5_addr : NULL,
198                                      tremote_addr ? &peer_krb5_addr : NULL);
199         if (ret) {
200                 DEBUG(1,("gensec_krb5_start: krb5_auth_con_setaddrs failed (%s)\n", 
201                          smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
202                                                     ret, gensec_krb5_state)));
203                 talloc_free(gensec_krb5_state);
204                 return NT_STATUS_INTERNAL_ERROR;
205         }
206
207         return NT_STATUS_OK;
208 }
209
210 static NTSTATUS gensec_krb5_common_server_start(struct gensec_security *gensec_security, bool gssapi)
211 {
212         NTSTATUS nt_status;
213         struct gensec_krb5_state *gensec_krb5_state;
214
215         nt_status = gensec_krb5_start(gensec_security, gssapi);
216         if (!NT_STATUS_IS_OK(nt_status)) {
217                 return nt_status;
218         }
219         
220         gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
221         gensec_krb5_state->state_position = GENSEC_KRB5_SERVER_START;
222
223         return NT_STATUS_OK;
224 }
225
226 static NTSTATUS gensec_krb5_server_start(struct gensec_security *gensec_security)
227 {
228         return gensec_krb5_common_server_start(gensec_security, false);
229 }
230
231 static NTSTATUS gensec_fake_gssapi_krb5_server_start(struct gensec_security *gensec_security)
232 {
233         return gensec_krb5_common_server_start(gensec_security, true);
234 }
235
236 static NTSTATUS gensec_krb5_common_client_start(struct gensec_security *gensec_security, bool gssapi)
237 {
238         const char *hostname;
239         struct gensec_krb5_state *gensec_krb5_state;
240         NTSTATUS nt_status;
241         hostname = gensec_get_target_hostname(gensec_security);
242         if (!hostname) {
243                 DEBUG(3, ("No hostname for target computer passed in, cannot use kerberos for this connection\n"));
244                 return NT_STATUS_INVALID_PARAMETER;
245         }
246         if (is_ipaddress(hostname)) {
247                 DEBUG(2, ("Cannot do krb5 to an IP address"));
248                 return NT_STATUS_INVALID_PARAMETER;
249         }
250         if (strcmp(hostname, "localhost") == 0) {
251                 DEBUG(2, ("krb5 to 'localhost' does not make sense"));
252                 return NT_STATUS_INVALID_PARAMETER;
253         }
254                         
255         nt_status = gensec_krb5_start(gensec_security, gssapi);
256         if (!NT_STATUS_IS_OK(nt_status)) {
257                 return nt_status;
258         }
259
260         gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
261         gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START;
262         gensec_krb5_state->ap_req_options = AP_OPTS_USE_SUBKEY;
263
264         if (gensec_krb5_state->gssapi) {
265                 /* The Fake GSSAPI model emulates Samba3, which does not do mutual authentication */
266                 if (gensec_setting_bool(gensec_security->settings, "gensec_fake_gssapi_krb5", "mutual", false)) {
267                         gensec_krb5_state->ap_req_options |= AP_OPTS_MUTUAL_REQUIRED;
268                 }
269         } else {
270                 /* The wrapping for KPASSWD (a user of the raw KRB5 API) should be mutually authenticated */
271                 if (gensec_setting_bool(gensec_security->settings, "gensec_krb5", "mutual", true)) {
272                         gensec_krb5_state->ap_req_options |= AP_OPTS_MUTUAL_REQUIRED;
273                 }
274         }
275         return NT_STATUS_OK;
276 }
277
278 static NTSTATUS gensec_krb5_common_client_creds(struct gensec_security *gensec_security,
279                                                 struct tevent_context *ev,
280                                                 bool gssapi)
281 {
282         struct gensec_krb5_state *gensec_krb5_state;
283         krb5_error_code ret;
284         struct ccache_container *ccache_container;
285         const char *error_string;
286         const char *principal;
287         const char *hostname;
288         krb5_data in_data = { .length = 0 };
289         krb5_data *in_data_p = NULL;
290         struct tevent_context *previous_ev;
291
292         if (lpcfg_parm_bool(gensec_security->settings->lp_ctx,
293                             NULL, "gensec_krb5", "send_authenticator_checksum", true)) {
294                 in_data_p = &in_data;
295         }
296         
297         gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
298
299         principal = gensec_get_target_principal(gensec_security);
300         hostname = gensec_get_target_hostname(gensec_security);
301
302         ret = cli_credentials_get_ccache(gensec_get_credentials(gensec_security), 
303                                          ev,
304                                          gensec_security->settings->lp_ctx, &ccache_container, &error_string);
305         switch (ret) {
306         case 0:
307                 break;
308         case KRB5KDC_ERR_PREAUTH_FAILED:
309         case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
310                 return NT_STATUS_LOGON_FAILURE;
311         case KRB5_KDC_UNREACH:
312                 DEBUG(3, ("Cannot reach a KDC we require to contact %s: %s\n", principal, error_string));
313                 return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
314         case KRB5_CC_NOTFOUND:
315         case KRB5_CC_END:
316                 DEBUG(3, ("Error preparing credentials we require to contact %s : %s\n", principal, error_string));
317                 return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
318         default:
319                 DEBUG(1, ("gensec_krb5_start: Aquiring initiator credentials failed: %s\n", error_string));
320                 return NT_STATUS_UNSUCCESSFUL;
321         }
322         
323         /* Do this every time, in case we have weird recursive issues here */
324         ret = smb_krb5_context_set_event_ctx(gensec_krb5_state->smb_krb5_context, ev, &previous_ev);
325         if (ret != 0) {
326                 DEBUG(1, ("gensec_krb5_start: Setting event context failed\n"));
327                 return NT_STATUS_NO_MEMORY;
328         }
329         if (principal) {
330                 krb5_principal target_principal;
331                 ret = krb5_parse_name(gensec_krb5_state->smb_krb5_context->krb5_context, principal,
332                                       &target_principal);
333                 if (ret == 0) {
334                         ret = krb5_mk_req_exact(gensec_krb5_state->smb_krb5_context->krb5_context, 
335                                                 &gensec_krb5_state->auth_context,
336                                                 gensec_krb5_state->ap_req_options, 
337                                                 target_principal,
338                                                 in_data_p, ccache_container->ccache, 
339                                                 &gensec_krb5_state->enc_ticket);
340                         krb5_free_principal(gensec_krb5_state->smb_krb5_context->krb5_context, 
341                                             target_principal);
342                 }
343         } else {
344                 ret = krb5_mk_req(gensec_krb5_state->smb_krb5_context->krb5_context, 
345                                   &gensec_krb5_state->auth_context,
346                                   gensec_krb5_state->ap_req_options,
347                                   gensec_get_target_service(gensec_security),
348                                   hostname,
349                                   in_data_p, ccache_container->ccache, 
350                                   &gensec_krb5_state->enc_ticket);
351         }
352
353         smb_krb5_context_remove_event_ctx(gensec_krb5_state->smb_krb5_context, previous_ev, ev);
354
355         switch (ret) {
356         case 0:
357                 return NT_STATUS_OK;
358         case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN:
359                 DEBUG(3, ("Server [%s] is not registered with our KDC: %s\n", 
360                           hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
361                 return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
362         case KRB5_KDC_UNREACH:
363                 DEBUG(3, ("Cannot reach a KDC we require to contact host [%s]: %s\n",
364                           hostname, smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
365                 return NT_STATUS_INVALID_PARAMETER; /* Make SPNEGO ignore us, we can't go any further here */
366         case KRB5KDC_ERR_PREAUTH_FAILED:
367         case KRB5KRB_AP_ERR_TKT_EXPIRED:
368         case KRB5_CC_END:
369                 /* Too much clock skew - we will need to kinit to re-skew the clock */
370         case KRB5KRB_AP_ERR_SKEW:
371         case KRB5_KDCREP_SKEW:
372         {
373                 DEBUG(3, ("kerberos (mk_req) failed: %s\n", 
374                           smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
375                 /*fall through*/
376         }
377         
378         /* just don't print a message for these really ordinary messages */
379         case KRB5_FCC_NOFILE:
380         case KRB5_CC_NOTFOUND:
381         case ENOENT:
382                 
383                 return NT_STATUS_UNSUCCESSFUL;
384                 break;
385                 
386         default:
387                 DEBUG(0, ("kerberos: %s\n", 
388                           smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, gensec_krb5_state)));
389                 return NT_STATUS_UNSUCCESSFUL;
390         }
391 }
392
393 static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security)
394 {
395         return gensec_krb5_common_client_start(gensec_security, false);
396 }
397
398 static NTSTATUS gensec_fake_gssapi_krb5_client_start(struct gensec_security *gensec_security)
399 {
400         return gensec_krb5_common_client_start(gensec_security, true);
401 }
402
403
404 /*
405   generate a krb5 GSS-API wrapper packet given a ticket
406 */
407 static DATA_BLOB gensec_gssapi_gen_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *ticket, const uint8_t tok_id[2])
408 {
409         struct asn1_data *data;
410         DATA_BLOB ret = data_blob_null;
411
412         data = asn1_init(mem_ctx);
413         if (!data || !ticket->data) {
414                 return ret;
415         }
416
417         if (!asn1_push_tag(data, ASN1_APPLICATION(0))) goto err;
418         if (!asn1_write_OID(data, GENSEC_OID_KERBEROS5)) goto err;
419
420         if (!asn1_write(data, tok_id, 2)) goto err;
421         if (!asn1_write(data, ticket->data, ticket->length)) goto err;
422         if (!asn1_pop_tag(data)) goto err;
423
424
425         if (!asn1_extract_blob(data, mem_ctx, &ret)) {
426                 goto err;
427         }
428         asn1_free(data);
429
430         return ret;
431
432   err:
433
434         DEBUG(1, ("Failed to build krb5 wrapper at offset %d\n",
435                   (int)asn1_current_ofs(data)));
436         asn1_free(data);
437         return ret;
438 }
439
440 /*
441   parse a krb5 GSS-API wrapper packet giving a ticket
442 */
443 static bool gensec_gssapi_parse_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, DATA_BLOB *ticket, uint8_t tok_id[2])
444 {
445         bool ret = false;
446         struct asn1_data *data = asn1_init(mem_ctx);
447         int data_remaining;
448
449         if (!data) {
450                 return false;
451         }
452
453         if (!asn1_load(data, *blob)) goto err;
454         if (!asn1_start_tag(data, ASN1_APPLICATION(0))) goto err;
455         if (!asn1_check_OID(data, GENSEC_OID_KERBEROS5)) goto err;
456
457         data_remaining = asn1_tag_remaining(data);
458
459         if (data_remaining < 3) {
460                 asn1_set_error(data);
461         } else {
462                 if (!asn1_read(data, tok_id, 2)) goto err;
463                 data_remaining -= 2;
464                 *ticket = data_blob_talloc(mem_ctx, NULL, data_remaining);
465                 if (!asn1_read(data, ticket->data, ticket->length)) goto err;
466         }
467
468         if (!asn1_end_tag(data)) goto err;
469
470         ret = !asn1_has_error(data);
471
472   err:
473
474         asn1_free(data);
475
476         return ret;
477 }
478
479 /**
480  * Next state function for the Krb5 GENSEC mechanism
481  * 
482  * @param gensec_krb5_state KRB5 State
483  * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on
484  * @param in The request, as a DATA_BLOB
485  * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx
486  * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, 
487  *                or NT_STATUS_OK if the user is authenticated. 
488  */
489
490 static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security, 
491                                    TALLOC_CTX *out_mem_ctx, 
492                                    struct tevent_context *ev,
493                                    const DATA_BLOB in, DATA_BLOB *out) 
494 {
495         struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
496         krb5_error_code ret = 0;
497         NTSTATUS nt_status;
498
499         switch (gensec_krb5_state->state_position) {
500         case GENSEC_KRB5_CLIENT_START:
501         {
502                 DATA_BLOB unwrapped_out;
503                 
504                 nt_status = gensec_krb5_common_client_creds(gensec_security, ev, gensec_krb5_state->gssapi);
505                 if (!NT_STATUS_IS_OK(nt_status)) {
506                         return nt_status;
507                 }
508
509                 if (gensec_krb5_state->gssapi) {
510                         unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length);
511                         
512                         /* wrap that up in a nice GSS-API wrapping */
513                         *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ);
514                 } else {
515                         *out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->enc_ticket.data, gensec_krb5_state->enc_ticket.length);
516                 }
517                 if (gensec_krb5_state->ap_req_options & AP_OPTS_MUTUAL_REQUIRED) {
518                         gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH;
519                         nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
520                 } else {
521                         gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
522                         nt_status = NT_STATUS_OK;
523                 }
524                 return nt_status;
525         }
526                 
527         case GENSEC_KRB5_CLIENT_MUTUAL_AUTH:
528         {
529                 DATA_BLOB unwrapped_in;
530                 krb5_data inbuf;
531                 krb5_ap_rep_enc_part *repl = NULL;
532                 uint8_t tok_id[2];
533
534                 if (gensec_krb5_state->gssapi) {
535                         if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
536                                 DEBUG(1,("gensec_gssapi_parse_krb5_wrap(mutual authentication) failed to parse\n"));
537                                 dump_data_pw("Mutual authentication message:\n", in.data, in.length);
538                                 return NT_STATUS_INVALID_PARAMETER;
539                         }
540                 } else {
541                         unwrapped_in = in;
542                 }
543                 /* TODO: check the tok_id */
544
545                 inbuf.data = unwrapped_in.data;
546                 inbuf.length = unwrapped_in.length;
547                 ret = krb5_rd_rep(gensec_krb5_state->smb_krb5_context->krb5_context, 
548                                   gensec_krb5_state->auth_context,
549                                   &inbuf, &repl);
550                 if (ret) {
551                         DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n",
552                                  smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, ret, out_mem_ctx)));
553                         dump_data_pw("Mutual authentication message:\n", (uint8_t *)inbuf.data, inbuf.length);
554                         nt_status = NT_STATUS_ACCESS_DENIED;
555                 } else {
556                         *out = data_blob(NULL, 0);
557                         nt_status = NT_STATUS_OK;
558                         gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
559                 }
560                 if (repl) {
561                         krb5_free_ap_rep_enc_part(gensec_krb5_state->smb_krb5_context->krb5_context, repl);
562                 }
563                 return nt_status;
564         }
565
566         case GENSEC_KRB5_SERVER_START:
567         {
568                 DATA_BLOB unwrapped_in;
569                 DATA_BLOB unwrapped_out = data_blob(NULL, 0);
570                 krb5_data inbuf, outbuf;
571                 uint8_t tok_id[2];
572                 struct keytab_container *keytab;
573                 krb5_principal server_in_keytab;
574                 const char *error_string;
575                 enum credentials_obtained obtained;
576
577                 if (!in.data) {
578                         return NT_STATUS_INVALID_PARAMETER;
579                 }       
580
581                 /* Grab the keytab, however generated */
582                 ret = cli_credentials_get_keytab(gensec_get_credentials(gensec_security), 
583                                                  gensec_security->settings->lp_ctx, &keytab);
584                 if (ret) {
585                         return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
586                 }
587                 
588                 /* This ensures we lookup the correct entry in that
589                  * keytab.  A NULL principal is acceptable, and means
590                  * that the krb5 libs should search the keytab at
591                  * accept time for any matching key */
592                 ret = principal_from_credentials(out_mem_ctx, gensec_get_credentials(gensec_security), 
593                                                  gensec_krb5_state->smb_krb5_context, 
594                                                  &server_in_keytab, &obtained, &error_string);
595
596                 if (ret) {
597                         DEBUG(2,("Failed to make credentials from principal: %s\n", error_string));
598                         return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
599                 }
600
601                 if (keytab->password_based || obtained < CRED_SPECIFIED) {
602                         /* 
603                          * Use match-by-key in this case (matches
604                          * cli_credentials_get_server_gss_creds()
605                          * behaviour).  No need to free the memory,
606                          * this is handled with a talloc destructor.
607                          */
608                         server_in_keytab = NULL;
609                 }
610
611                 /* Parse the GSSAPI wrapping, if it's there... (win2k3 allows it to be omited) */
612                 if (gensec_krb5_state->gssapi
613                     && gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) {
614                         inbuf.data = unwrapped_in.data;
615                         inbuf.length = unwrapped_in.length;
616                 } else {
617                         inbuf.data = in.data;
618                         inbuf.length = in.length;
619                 }
620
621                 ret = smb_rd_req_return_stuff(gensec_krb5_state->smb_krb5_context->krb5_context,
622                                               &gensec_krb5_state->auth_context, 
623                                               &inbuf, keytab->keytab, server_in_keytab,  
624                                               &outbuf, 
625                                               &gensec_krb5_state->ticket, 
626                                               &gensec_krb5_state->keyblock);
627
628                 if (ret) {
629                         return NT_STATUS_LOGON_FAILURE;
630                 }
631                 unwrapped_out.data = (uint8_t *)outbuf.data;
632                 unwrapped_out.length = outbuf.length;
633                 gensec_krb5_state->state_position = GENSEC_KRB5_DONE;
634                 /* wrap that up in a nice GSS-API wrapping */
635                 if (gensec_krb5_state->gssapi) {
636                         *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REP);
637                 } else {
638                         *out = data_blob_talloc(out_mem_ctx, outbuf.data, outbuf.length);
639                 }
640                 krb5_data_free(&outbuf);
641                 return NT_STATUS_OK;
642         }
643
644         case GENSEC_KRB5_DONE:
645         default:
646                 /* Asking too many times... */
647                 return NT_STATUS_INVALID_PARAMETER;
648         }
649 }
650
651 static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security, 
652                                         TALLOC_CTX *mem_ctx,
653                                         DATA_BLOB *session_key) 
654 {
655         struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
656         krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
657         krb5_auth_context auth_context = gensec_krb5_state->auth_context;
658         krb5_keyblock *skey;
659         krb5_error_code err = -1;
660
661         if (gensec_krb5_state->state_position != GENSEC_KRB5_DONE) {
662                 return NT_STATUS_NO_USER_SESSION_KEY;
663         }
664
665         switch (gensec_security->gensec_role) {
666         case GENSEC_CLIENT:
667                 err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey);
668                 break;
669         case GENSEC_SERVER:
670                 err = krb5_auth_con_getremotesubkey(context, auth_context, &skey);
671                 break;
672         }
673         if (err == 0 && skey != NULL) {
674                 DEBUG(10, ("Got KRB5 session key of length %d\n",  
675                            (int)KRB5_KEY_LENGTH(skey)));
676                 *session_key = data_blob_talloc(mem_ctx,
677                                                KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey));
678                 dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length);
679
680                 krb5_free_keyblock(context, skey);
681                 return NT_STATUS_OK;
682         } else {
683                 DEBUG(10, ("KRB5 error getting session key %d\n", err));
684                 return NT_STATUS_NO_USER_SESSION_KEY;
685         }
686 }
687
688 static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security,
689                                          TALLOC_CTX *mem_ctx,
690                                          struct auth_session_info **_session_info) 
691 {
692         NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
693         struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
694         krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
695         struct auth_session_info *session_info = NULL;
696
697         krb5_principal client_principal;
698         char *principal_string;
699         
700         DATA_BLOB pac_blob, *pac_blob_ptr = NULL;
701         krb5_data pac_data;
702
703         krb5_error_code ret;
704
705         TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
706         if (!tmp_ctx) {
707                 return NT_STATUS_NO_MEMORY;
708         }
709         
710         ret = krb5_ticket_get_client(context, gensec_krb5_state->ticket, &client_principal);
711         if (ret) {
712                 DEBUG(5, ("krb5_ticket_get_client failed to get cleint principal: %s\n", 
713                           smb_get_krb5_error_message(context, 
714                                                      ret, tmp_ctx)));
715                 talloc_free(tmp_ctx);
716                 return NT_STATUS_NO_MEMORY;
717         }
718         
719         ret = krb5_unparse_name(gensec_krb5_state->smb_krb5_context->krb5_context, 
720                                 client_principal, &principal_string);
721         if (ret) {
722                 DEBUG(1, ("Unable to parse client principal: %s\n",
723                           smb_get_krb5_error_message(context, 
724                                                      ret, tmp_ctx)));
725                 krb5_free_principal(context, client_principal);
726                 talloc_free(tmp_ctx);
727                 return NT_STATUS_NO_MEMORY;
728         }
729
730         ret = krb5_ticket_get_authorization_data_type(context, gensec_krb5_state->ticket, 
731                                                       KRB5_AUTHDATA_WIN2K_PAC, 
732                                                       &pac_data);
733         
734         if (ret) {
735                 /* NO pac */
736                 DEBUG(5, ("krb5_ticket_get_authorization_data_type failed to find PAC: %s\n", 
737                           smb_get_krb5_error_message(context, 
738                                                      ret, tmp_ctx)));
739         } else {
740                 /* Found pac */
741                 pac_blob = data_blob_talloc(tmp_ctx, pac_data.data, pac_data.length);
742                 kerberos_free_data_contents(context, &pac_data);
743                 if (!pac_blob.data) {
744                         free(principal_string);
745                         krb5_free_principal(context, client_principal);
746                         talloc_free(tmp_ctx);
747                         return NT_STATUS_NO_MEMORY;
748                 }
749
750                 /* decode and verify the pac */
751                 nt_status = kerberos_decode_pac(gensec_krb5_state,
752                                                 pac_blob,
753                                                 gensec_krb5_state->smb_krb5_context->krb5_context,
754                                                 NULL, gensec_krb5_state->keyblock,
755                                                 client_principal,
756                                                 gensec_krb5_state->ticket->ticket.authtime, NULL);
757
758                 if (!NT_STATUS_IS_OK(nt_status)) {
759                         free(principal_string);
760                         krb5_free_principal(context, client_principal);
761                         talloc_free(tmp_ctx);
762                         return nt_status;
763                 }
764
765                 pac_blob_ptr = &pac_blob;
766         }
767
768         nt_status = gensec_generate_session_info_pac(tmp_ctx,
769                                                      gensec_security,
770                                                      gensec_krb5_state->smb_krb5_context,
771                                                      pac_blob_ptr, principal_string,
772                                                      gensec_get_remote_address(gensec_security),
773                                                      &session_info);
774
775         free(principal_string);
776         krb5_free_principal(context, client_principal);
777
778         if (!NT_STATUS_IS_OK(nt_status)) {
779                 talloc_free(tmp_ctx);
780                 return nt_status;
781         }
782
783         nt_status = gensec_krb5_session_key(gensec_security, session_info, &session_info->session_key);
784
785         if (!NT_STATUS_IS_OK(nt_status)) {
786                 talloc_free(tmp_ctx);
787                 return nt_status;
788         }
789
790         *_session_info = talloc_steal(mem_ctx, session_info);
791
792         talloc_free(tmp_ctx);
793         return NT_STATUS_OK;
794 }
795
796 static NTSTATUS gensec_krb5_wrap(struct gensec_security *gensec_security, 
797                                    TALLOC_CTX *mem_ctx, 
798                                    const DATA_BLOB *in, 
799                                    DATA_BLOB *out)
800 {
801         struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
802         krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
803         krb5_auth_context auth_context = gensec_krb5_state->auth_context;
804         krb5_error_code ret;
805         krb5_data input, output;
806         input.length = in->length;
807         input.data = in->data;
808         
809         if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
810                 ret = krb5_mk_priv(context, auth_context, &input, &output, NULL);
811                 if (ret) {
812                         DEBUG(1, ("krb5_mk_priv failed: %s\n", 
813                                   smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
814                                                              ret, mem_ctx)));
815                         return NT_STATUS_ACCESS_DENIED;
816                 }
817                 *out = data_blob_talloc(mem_ctx, output.data, output.length);
818                 
819                 krb5_data_free(&output);
820         } else {
821                 return NT_STATUS_ACCESS_DENIED;
822         }
823         return NT_STATUS_OK;
824 }
825
826 static NTSTATUS gensec_krb5_unwrap(struct gensec_security *gensec_security, 
827                                      TALLOC_CTX *mem_ctx, 
828                                      const DATA_BLOB *in, 
829                                      DATA_BLOB *out)
830 {
831         struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
832         krb5_context context = gensec_krb5_state->smb_krb5_context->krb5_context;
833         krb5_auth_context auth_context = gensec_krb5_state->auth_context;
834         krb5_error_code ret;
835         krb5_data input, output;
836         krb5_replay_data replay;
837         input.length = in->length;
838         input.data = in->data;
839         
840         if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) {
841                 ret = krb5_rd_priv(context, auth_context, &input, &output, &replay);
842                 if (ret) {
843                         DEBUG(1, ("krb5_rd_priv failed: %s\n", 
844                                   smb_get_krb5_error_message(gensec_krb5_state->smb_krb5_context->krb5_context, 
845                                                              ret, mem_ctx)));
846                         return NT_STATUS_ACCESS_DENIED;
847                 }
848                 *out = data_blob_talloc(mem_ctx, output.data, output.length);
849                 
850                 krb5_data_free(&output);
851         } else {
852                 return NT_STATUS_ACCESS_DENIED;
853         }
854         return NT_STATUS_OK;
855 }
856
857 static bool gensec_krb5_have_feature(struct gensec_security *gensec_security,
858                                      uint32_t feature)
859 {
860         struct gensec_krb5_state *gensec_krb5_state = (struct gensec_krb5_state *)gensec_security->private_data;
861         if (feature & GENSEC_FEATURE_SESSION_KEY) {
862                 return true;
863         } 
864         if (!gensec_krb5_state->gssapi && 
865             (feature & GENSEC_FEATURE_SEAL)) {
866                 return true;
867         } 
868         
869         return false;
870 }
871
872 static const char *gensec_krb5_oids[] = { 
873         GENSEC_OID_KERBEROS5,
874         GENSEC_OID_KERBEROS5_OLD,
875         NULL 
876 };
877
878 static const struct gensec_security_ops gensec_fake_gssapi_krb5_security_ops = {
879         .name           = "fake_gssapi_krb5",
880         .auth_type      = DCERPC_AUTH_TYPE_KRB5,
881         .oid            = gensec_krb5_oids,
882         .client_start   = gensec_fake_gssapi_krb5_client_start,
883         .server_start   = gensec_fake_gssapi_krb5_server_start,
884         .update         = gensec_krb5_update,
885         .magic          = gensec_magic_check_krb5_oid,
886         .session_key    = gensec_krb5_session_key,
887         .session_info   = gensec_krb5_session_info,
888         .have_feature   = gensec_krb5_have_feature,
889         .enabled        = false,
890         .kerberos       = true,
891         .priority       = GENSEC_KRB5
892 };
893
894 static const struct gensec_security_ops gensec_krb5_security_ops = {
895         .name           = "krb5",
896         .client_start   = gensec_krb5_client_start,
897         .server_start   = gensec_krb5_server_start,
898         .update         = gensec_krb5_update,
899         .session_key    = gensec_krb5_session_key,
900         .session_info   = gensec_krb5_session_info,
901         .have_feature   = gensec_krb5_have_feature,
902         .wrap           = gensec_krb5_wrap,
903         .unwrap         = gensec_krb5_unwrap,
904         .enabled        = true,
905         .kerberos       = true,
906         .priority       = GENSEC_KRB5
907 };
908
909 _PUBLIC_ NTSTATUS gensec_krb5_init(void)
910 {
911         NTSTATUS ret;
912
913         ret = gensec_register(&gensec_krb5_security_ops);
914         if (!NT_STATUS_IS_OK(ret)) {
915                 DEBUG(0,("Failed to register '%s' gensec backend!\n",
916                         gensec_krb5_security_ops.name));
917                 return ret;
918         }
919
920         ret = gensec_register(&gensec_fake_gssapi_krb5_security_ops);
921         if (!NT_STATUS_IS_OK(ret)) {
922                 DEBUG(0,("Failed to register '%s' gensec backend!\n",
923                         gensec_fake_gssapi_krb5_security_ops.name));
924                 return ret;
925         }
926
927         return ret;
928 }