krb-init: define out heimdal specific stuff in mitkrb build
[idra/samba.git] / source4 / auth / kerberos / krb5_init_context.c
1 /*
2    Unix SMB/CIFS implementation.
3    Wrapper for krb5_init_context
4
5    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
6    Copyright (C) Andrew Tridgell 2005
7    Copyright (C) Stefan Metzmacher 2004
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "includes.h"
24 #include "system/kerberos.h"
25 #include "system/gssapi.h"
26 #include <tevent.h>
27 #include "auth/kerberos/kerberos.h"
28 #include "lib/socket/socket.h"
29 #include "lib/stream/packet.h"
30 #include "system/network.h"
31 #include "param/param.h"
32 #include "libcli/resolve/resolve.h"
33 #include "../lib/tsocket/tsocket.h"
34 #include "krb5_init_context.h"
35 /*
36   context structure for operations on cldap packets
37 */
38 struct smb_krb5_socket {
39         struct socket_context *sock;
40
41         /* the fd event */
42         struct tevent_fd *fde;
43
44         NTSTATUS status;
45         DATA_BLOB request, reply;
46
47         struct packet_context *packet;
48
49         size_t partial_read;
50 #ifdef SAMBA4_USES_HEIMDAL
51         krb5_krbhst_info *hi;
52 #endif
53 };
54
55 static krb5_error_code smb_krb5_context_destroy(struct smb_krb5_context *ctx)
56 {
57 #ifdef SAMBA4_USES_HEIMDAL
58         if (ctx->pvt_log_data) {
59                 /* Otherwise krb5_free_context will try and close what we
60                  * have already free()ed */
61                 krb5_set_warn_dest(ctx->krb5_context, NULL);
62                 krb5_closelog(ctx->krb5_context,
63                                 (krb5_log_facility *)ctx->pvt_log_data);
64         }
65 #else
66         krb5_set_trace_callback(ctx->krb5_context, NULL, NULL);
67 #endif
68         krb5_free_context(ctx->krb5_context);
69         return 0;
70 }
71
72 #ifdef SAMBA4_USES_HEIMDAL
73 /* We never close down the DEBUG system, and no need to unreference the use */
74 static void smb_krb5_debug_close(void *private_data) {
75         return;
76 }
77 #endif
78
79 #ifdef SAMBA4_USES_HEIMDAL
80 static void smb_krb5_debug_wrapper(const char *timestr, const char *msg, void *private_data)
81 {
82         DEBUG(3, ("Kerberos: %s\n", msg));
83 }
84 #else
85 static void smb_krb5_debug_wrapper(krb5_context context,
86                                    const struct krb5_trace_info *info,
87                                    void *cb_data)
88 {
89         DEBUG(3, ("Kerberos: %s\n", info->message));
90 }
91 #endif
92
93 #ifdef SAMBA4_USES_HEIMDAL
94 /*
95   handle recv events on a smb_krb5 socket
96 */
97 static void smb_krb5_socket_recv(struct smb_krb5_socket *smb_krb5)
98 {
99         TALLOC_CTX *tmp_ctx = talloc_new(smb_krb5);
100         DATA_BLOB blob;
101         size_t nread, dsize;
102
103         smb_krb5->status = socket_pending(smb_krb5->sock, &dsize);
104         if (!NT_STATUS_IS_OK(smb_krb5->status)) {
105                 talloc_free(tmp_ctx);
106                 return;
107         }
108
109         blob = data_blob_talloc(tmp_ctx, NULL, dsize);
110         if (blob.data == NULL && dsize != 0) {
111                 smb_krb5->status = NT_STATUS_NO_MEMORY;
112                 talloc_free(tmp_ctx);
113                 return;
114         }
115
116         smb_krb5->status = socket_recv(smb_krb5->sock, blob.data, blob.length, &nread);
117         if (!NT_STATUS_IS_OK(smb_krb5->status)) {
118                 talloc_free(tmp_ctx);
119                 return;
120         }
121         blob.length = nread;
122
123         if (nread == 0) {
124                 smb_krb5->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
125                 talloc_free(tmp_ctx);
126                 return;
127         }
128
129         DEBUG(4,("Received smb_krb5 packet of length %d\n",
130                  (int)blob.length));
131
132         talloc_steal(smb_krb5, blob.data);
133         smb_krb5->reply = blob;
134         talloc_free(tmp_ctx);
135 }
136
137 static NTSTATUS smb_krb5_full_packet(void *private_data, DATA_BLOB data)
138 {
139         struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket);
140         talloc_steal(smb_krb5, data.data);
141         smb_krb5->reply = data;
142         smb_krb5->reply.length -= 4;
143         smb_krb5->reply.data += 4;
144         return NT_STATUS_OK;
145 }
146
147 /*
148   handle request timeouts
149 */
150 static void smb_krb5_request_timeout(struct tevent_context *event_ctx,
151                                   struct tevent_timer *te, struct timeval t,
152                                   void *private_data)
153 {
154         struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket);
155         DEBUG(5,("Timed out smb_krb5 packet\n"));
156         smb_krb5->status = NT_STATUS_IO_TIMEOUT;
157 }
158
159 static void smb_krb5_error_handler(void *private_data, NTSTATUS status)
160 {
161         struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket);
162         smb_krb5->status = status;
163 }
164
165 /*
166   handle send events on a smb_krb5 socket
167 */
168 static void smb_krb5_socket_send(struct smb_krb5_socket *smb_krb5)
169 {
170         NTSTATUS status;
171
172         size_t len;
173
174         len = smb_krb5->request.length;
175         status = socket_send(smb_krb5->sock, &smb_krb5->request, &len);
176
177         if (!NT_STATUS_IS_OK(status)) return;
178
179         TEVENT_FD_READABLE(smb_krb5->fde);
180
181         TEVENT_FD_NOT_WRITEABLE(smb_krb5->fde);
182         return;
183 }
184
185
186 /*
187   handle fd events on a smb_krb5_socket
188 */
189 static void smb_krb5_socket_handler(struct tevent_context *ev, struct tevent_fd *fde,
190                                  uint16_t flags, void *private_data)
191 {
192         struct smb_krb5_socket *smb_krb5 = talloc_get_type(private_data, struct smb_krb5_socket);
193         switch (smb_krb5->hi->proto) {
194         case KRB5_KRBHST_UDP:
195                 if (flags & TEVENT_FD_READ) {
196                         smb_krb5_socket_recv(smb_krb5);
197                         return;
198                 }
199                 if (flags & TEVENT_FD_WRITE) {
200                         smb_krb5_socket_send(smb_krb5);
201                         return;
202                 }
203                 /* not reached */
204                 return;
205         case KRB5_KRBHST_TCP:
206                 if (flags & TEVENT_FD_READ) {
207                         packet_recv(smb_krb5->packet);
208                         return;
209                 }
210                 if (flags & TEVENT_FD_WRITE) {
211                         packet_queue_run(smb_krb5->packet);
212                         return;
213                 }
214                 /* not reached */
215                 return;
216         case KRB5_KRBHST_HTTP:
217                 /* can't happen */
218                 break;
219         }
220 }
221
222 krb5_error_code smb_krb5_send_and_recv_func(krb5_context context,
223                                             void *data,
224                                             krb5_krbhst_info *hi,
225                                             time_t timeout,
226                                             const krb5_data *send_buf,
227                                             krb5_data *recv_buf)
228 {
229         krb5_error_code ret;
230         NTSTATUS status;
231         const char *name;
232         struct addrinfo *ai, *a;
233         struct smb_krb5_socket *smb_krb5;
234
235         DATA_BLOB send_blob;
236
237         struct tevent_context *ev;
238         TALLOC_CTX *tmp_ctx = talloc_new(NULL);
239         if (!tmp_ctx) {
240                 return ENOMEM;
241         }
242
243         if (!data) {
244                 /* If no event context was available, then create one for this loop */
245                 ev = tevent_context_init(tmp_ctx);
246                 if (!ev) {
247                         talloc_free(tmp_ctx);
248                         return ENOMEM;
249                 }
250         } else {
251                 ev = talloc_get_type_abort(data, struct tevent_context);
252         }
253
254         send_blob = data_blob_const(send_buf->data, send_buf->length);
255
256         ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
257         if (ret) {
258                 talloc_free(tmp_ctx);
259                 return ret;
260         }
261
262         for (a = ai; a; a = a->ai_next) {
263                 struct socket_address *remote_addr;
264                 smb_krb5 = talloc(tmp_ctx, struct smb_krb5_socket);
265                 if (!smb_krb5) {
266                         talloc_free(tmp_ctx);
267                         return ENOMEM;
268                 }
269                 smb_krb5->hi = hi;
270
271                 switch (a->ai_family) {
272                 case PF_INET:
273                         name = "ipv4";
274                         break;
275 #ifdef HAVE_IPV6
276                 case PF_INET6:
277                         name = "ipv6";
278                         break;
279 #endif
280                 default:
281                         talloc_free(tmp_ctx);
282                         return EINVAL;
283                 }
284
285                 status = NT_STATUS_INVALID_PARAMETER;
286                 switch (hi->proto) {
287                 case KRB5_KRBHST_UDP:
288                         status = socket_create(name, SOCKET_TYPE_DGRAM, &smb_krb5->sock, 0);
289                         break;
290                 case KRB5_KRBHST_TCP:
291                         status = socket_create(name, SOCKET_TYPE_STREAM, &smb_krb5->sock, 0);
292                         break;
293                 case KRB5_KRBHST_HTTP:
294                         talloc_free(tmp_ctx);
295                         return EINVAL;
296                 }
297                 if (!NT_STATUS_IS_OK(status)) {
298                         talloc_free(smb_krb5);
299                         continue;
300                 }
301
302                 talloc_steal(smb_krb5, smb_krb5->sock);
303
304                 remote_addr = socket_address_from_sockaddr(smb_krb5, a->ai_addr, a->ai_addrlen);
305                 if (!remote_addr) {
306                         talloc_free(smb_krb5);
307                         continue;
308                 }
309
310                 status = socket_connect_ev(smb_krb5->sock, NULL, remote_addr, 0, ev);
311                 if (!NT_STATUS_IS_OK(status)) {
312                         talloc_free(smb_krb5);
313                         continue;
314                 }
315
316                 /* Setup the FDE, start listening for read events
317                  * from the start (otherwise we may miss a socket
318                  * drop) and mark as AUTOCLOSE along with the fde */
319
320                 /* Ths is equivilant to EVENT_FD_READABLE(smb_krb5->fde) */
321                 smb_krb5->fde = tevent_add_fd(ev, smb_krb5->sock,
322                                               socket_get_fd(smb_krb5->sock),
323                                               TEVENT_FD_READ,
324                                               smb_krb5_socket_handler, smb_krb5);
325                 /* its now the job of the event layer to close the socket */
326                 tevent_fd_set_close_fn(smb_krb5->fde, socket_tevent_fd_close_fn);
327                 socket_set_flags(smb_krb5->sock, SOCKET_FLAG_NOCLOSE);
328
329                 tevent_add_timer(ev, smb_krb5,
330                                  timeval_current_ofs(timeout, 0),
331                                  smb_krb5_request_timeout, smb_krb5);
332
333                 smb_krb5->status = NT_STATUS_OK;
334                 smb_krb5->reply = data_blob(NULL, 0);
335
336                 switch (hi->proto) {
337                 case KRB5_KRBHST_UDP:
338                         TEVENT_FD_WRITEABLE(smb_krb5->fde);
339                         smb_krb5->request = send_blob;
340                         break;
341                 case KRB5_KRBHST_TCP:
342
343                         smb_krb5->packet = packet_init(smb_krb5);
344                         if (smb_krb5->packet == NULL) {
345                                 talloc_free(smb_krb5);
346                                 return ENOMEM;
347                         }
348                         packet_set_private(smb_krb5->packet, smb_krb5);
349                         packet_set_socket(smb_krb5->packet, smb_krb5->sock);
350                         packet_set_callback(smb_krb5->packet, smb_krb5_full_packet);
351                         packet_set_full_request(smb_krb5->packet, packet_full_request_u32);
352                         packet_set_error_handler(smb_krb5->packet, smb_krb5_error_handler);
353                         packet_set_event_context(smb_krb5->packet, ev);
354                         packet_set_fde(smb_krb5->packet, smb_krb5->fde);
355
356                         smb_krb5->request = data_blob_talloc(smb_krb5, NULL, send_blob.length + 4);
357                         RSIVAL(smb_krb5->request.data, 0, send_blob.length);
358                         memcpy(smb_krb5->request.data+4, send_blob.data, send_blob.length);
359                         packet_send(smb_krb5->packet, smb_krb5->request);
360                         break;
361                 case KRB5_KRBHST_HTTP:
362                         talloc_free(tmp_ctx);
363                         return EINVAL;
364                 }
365                 while ((NT_STATUS_IS_OK(smb_krb5->status)) && !smb_krb5->reply.length) {
366                         if (tevent_loop_once(ev) != 0) {
367                                 talloc_free(tmp_ctx);
368                                 return EINVAL;
369                         }
370
371                         /* After each and every event loop, reset the
372                          * send_to_kdc pointers to what they were when
373                          * we entered this loop.  That way, if a
374                          * nested event has invalidated them, we put
375                          * it back before we return to the heimdal
376                          * code */
377                         ret = krb5_set_send_to_kdc_func(context,
378                                                         smb_krb5_send_and_recv_func,
379                                                         data);
380                         if (ret != 0) {
381                                 talloc_free(tmp_ctx);
382                                 return ret;
383                         }
384                 }
385                 if (NT_STATUS_EQUAL(smb_krb5->status, NT_STATUS_IO_TIMEOUT)) {
386                         talloc_free(smb_krb5);
387                         continue;
388                 }
389
390                 if (!NT_STATUS_IS_OK(smb_krb5->status)) {
391                         struct tsocket_address *addr = socket_address_to_tsocket_address(smb_krb5, remote_addr);
392                         const char *addr_string = NULL;
393                         if (addr) {
394                                 addr_string = tsocket_address_inet_addr_string(addr, smb_krb5);
395                         } else {
396                                 addr_string = NULL;
397                         }
398                         DEBUG(2,("Error reading smb_krb5 reply packet: %s from %s\n", nt_errstr(smb_krb5->status),
399                                  addr_string));
400                         talloc_free(smb_krb5);
401                         continue;
402                 }
403
404                 ret = krb5_data_copy(recv_buf, smb_krb5->reply.data, smb_krb5->reply.length);
405                 if (ret) {
406                         talloc_free(tmp_ctx);
407                         return ret;
408                 }
409                 talloc_free(smb_krb5);
410
411                 break;
412         }
413         talloc_free(tmp_ctx);
414         if (a) {
415                 return 0;
416         }
417         return KRB5_KDC_UNREACH;
418 }
419 #endif
420
421 krb5_error_code
422 smb_krb5_init_context_basic(TALLOC_CTX *tmp_ctx,
423                             struct loadparm_context *lp_ctx,
424                             krb5_context *_krb5_context)
425 {
426         krb5_error_code ret;
427 #ifdef SAMBA4_USES_HEIMDAL
428         char **config_files;
429         const char *config_file, *realm;
430 #endif
431         krb5_context krb5_ctx;
432
433         initialize_krb5_error_table();
434
435         ret = krb5_init_context(&krb5_ctx);
436         if (ret) {
437                 DEBUG(1,("krb5_init_context failed (%s)\n",
438                          error_message(ret)));
439                 return ret;
440         }
441
442         /* The MIT Kerberos build relies on using the system krb5.conf file.
443          * If you really want to use another file please set KRB5_CONFIG
444          * accordingly. */
445 #ifdef SAMBA4_USES_HEIMDAL
446         config_file = lpcfg_config_path(tmp_ctx, lp_ctx, "krb5.conf");
447         if (!config_file) {
448                 krb5_free_context(krb5_ctx);
449                 return ENOMEM;
450         }
451
452         /* Use our local krb5.conf file by default */
453         ret = krb5_prepend_config_files_default(config_file, &config_files);
454         if (ret) {
455                 DEBUG(1,("krb5_prepend_config_files_default failed (%s)\n",
456                          smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx)));
457                 krb5_free_context(krb5_ctx);
458                 return ret;
459         }
460
461         ret = krb5_set_config_files(krb5_ctx, config_files);
462         krb5_free_config_files(config_files);
463         if (ret) {
464                 DEBUG(1,("krb5_set_config_files failed (%s)\n",
465                          smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx)));
466                 krb5_free_context(krb5_ctx);
467                 return ret;
468         }
469
470         realm = lpcfg_realm(lp_ctx);
471         if (realm != NULL) {
472                 ret = krb5_set_default_realm(krb5_ctx, realm);
473                 if (ret) {
474                         DEBUG(1,("krb5_set_default_realm failed (%s)\n",
475                                  smb_get_krb5_error_message(krb5_ctx, ret, tmp_ctx)));
476                         krb5_free_context(krb5_ctx);
477                         return ret;
478                 }
479         }
480 #endif
481         *_krb5_context = krb5_ctx;
482         return 0;
483 }
484
485 krb5_error_code smb_krb5_init_context(void *parent_ctx,
486                                       struct tevent_context *ev,
487                                       struct loadparm_context *lp_ctx,
488                                       struct smb_krb5_context **smb_krb5_context)
489 {
490         krb5_error_code ret;
491         TALLOC_CTX *tmp_ctx;
492         krb5_context kctx;
493 #ifdef SAMBA4_USES_HEIMDAL
494         krb5_log_facility *logf;
495 #endif
496
497         initialize_krb5_error_table();
498
499         tmp_ctx = talloc_new(parent_ctx);
500         *smb_krb5_context = talloc_zero(tmp_ctx, struct smb_krb5_context);
501
502         if (!*smb_krb5_context || !tmp_ctx) {
503                 talloc_free(tmp_ctx);
504                 return ENOMEM;
505         }
506
507         ret = smb_krb5_init_context_basic(tmp_ctx, lp_ctx, &kctx);
508         if (ret) {
509                 DEBUG(1,("smb_krb5_context_init_basic failed (%s)\n",
510                          error_message(ret)));
511                 talloc_free(tmp_ctx);
512                 return ret;
513         }
514         (*smb_krb5_context)->krb5_context = kctx;
515
516         talloc_set_destructor(*smb_krb5_context, smb_krb5_context_destroy);
517
518 #ifdef SAMBA4_USES_HEIMDAL
519         /* TODO: Should we have a different name here? */
520         ret = krb5_initlog(kctx, "Samba", &logf);
521
522         if (ret) {
523                 DEBUG(1,("krb5_initlog failed (%s)\n",
524                          smb_get_krb5_error_message(kctx, ret, tmp_ctx)));
525                 talloc_free(tmp_ctx);
526                 return ret;
527         }
528         (*smb_krb5_context)->pvt_log_data = logf;
529
530         ret = krb5_addlog_func(kctx, logf, 0 /* min */, -1 /* max */,
531                                smb_krb5_debug_wrapper,
532                                 smb_krb5_debug_close, NULL);
533         if (ret) {
534                 DEBUG(1,("krb5_addlog_func failed (%s)\n",
535                          smb_get_krb5_error_message(kctx, ret, tmp_ctx)));
536                 talloc_free(tmp_ctx);
537                 return ret;
538         }
539         krb5_set_warn_dest(kctx, logf);
540
541         /* Set use of our socket lib */
542         if (ev) {
543                 struct tevent_context *previous_ev;
544                 ret = smb_krb5_context_set_event_ctx(*smb_krb5_context,
545                                                      ev, &previous_ev);
546                 if (ret) {
547                         talloc_free(tmp_ctx);
548                         return ret;
549                 }
550         }
551
552         /* Set options in kerberos */
553
554         krb5_set_dns_canonicalize_hostname(kctx,
555                         lpcfg_parm_bool(lp_ctx, NULL, "krb5",
556                                         "set_dns_canonicalize", false));
557 #else
558         ret = krb5_set_trace_callback(kctx, smb_krb5_debug_wrapper, NULL);
559         if (ret && ret != KRB5_TRACE_NOSUPP) {
560                 DEBUG(1, ("krb5_set_trace_callback failed (%s)\n",
561                           smb_get_krb5_error_message(kctx, ret, tmp_ctx)));
562                 talloc_free(tmp_ctx);
563                 return ret;
564         }
565 #endif
566         talloc_steal(parent_ctx, *smb_krb5_context);
567         talloc_free(tmp_ctx);
568
569         return 0;
570 }
571
572 #ifdef SAMBA4_USES_HEIMDAL
573 krb5_error_code smb_krb5_context_set_event_ctx(struct smb_krb5_context *smb_krb5_context,
574                                                struct tevent_context *ev,
575                                                struct tevent_context **previous_ev)
576 {
577         int ret;
578         if (!ev) {
579                 return EINVAL;
580         }
581
582         *previous_ev = smb_krb5_context->current_ev;
583
584         smb_krb5_context->current_ev = talloc_reference(smb_krb5_context, ev);
585         if (!smb_krb5_context->current_ev) {
586                 return ENOMEM;
587         }
588
589         /* Set use of our socket lib */
590         ret = krb5_set_send_to_kdc_func(smb_krb5_context->krb5_context,
591                                         smb_krb5_send_and_recv_func,
592                                         ev);
593         if (ret) {
594                 TALLOC_CTX *tmp_ctx = talloc_new(NULL);
595                 DEBUG(1,("krb5_set_send_recv_func failed (%s)\n",
596                          smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, tmp_ctx)));
597                 talloc_free(tmp_ctx);
598                 talloc_unlink(smb_krb5_context, smb_krb5_context->current_ev);
599                 smb_krb5_context->current_ev = NULL;
600                 return ret;
601         }
602         return 0;
603 }
604
605 krb5_error_code smb_krb5_context_remove_event_ctx(struct smb_krb5_context *smb_krb5_context,
606                                                   struct tevent_context *previous_ev,
607                                                   struct tevent_context *ev)
608 {
609         int ret;
610         talloc_unlink(smb_krb5_context, ev);
611         /* If there was a mismatch with things happening on a stack, then don't wipe things */
612         smb_krb5_context->current_ev = previous_ev;
613         /* Set use of our socket lib */
614         ret = krb5_set_send_to_kdc_func(smb_krb5_context->krb5_context,
615                                         smb_krb5_send_and_recv_func,
616                                         previous_ev);
617         if (ret) {
618                 TALLOC_CTX *tmp_ctx = talloc_new(NULL);
619                 DEBUG(1,("krb5_set_send_recv_func failed (%s)\n",
620                          smb_get_krb5_error_message(smb_krb5_context->krb5_context, ret, tmp_ctx)));
621                 talloc_free(tmp_ctx);
622                 return ret;
623         }
624         return 0;
625 }
626 #endif