Imported Upstream version 3.1.0
[debian/amanda] / common-src / krb5-security.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1999 University of Maryland
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26
27 /*
28  * $Id: krb5-security.c,v 1.22 2006/06/16 10:55:05 martinea Exp $
29  *
30  * krb5-security.c - kerberos V5 security module
31  *
32  * XXX still need to check for initial keyword on connect so we can skip
33  * over shell garbage and other stuff that krb5 might want to spew out.
34  */
35
36 #include "amanda.h"
37 #include "util.h"
38 #include "event.h"
39 #include "packet.h"
40 #include "security.h"
41 #include "security-util.h"
42 #include "stream.h"
43 #include "sockaddr-util.h"
44
45 #ifdef KRB5_HEIMDAL_INCLUDES
46 #include "com_err.h"
47 #endif
48
49 #define BROKEN_MEMORY_CCACHE
50
51 #ifdef BROKEN_MEMORY_CCACHE
52 /*
53  * If you don't have atexit() or on_exit(), you could just consider
54  * making atexit() empty and clean up your ticket files some other
55  * way
56  */
57 #ifndef HAVE_ATEXIT
58 #ifdef HAVE_ON_EXIT
59 #define atexit(func)    on_exit(func, 0)
60 #else
61 #define atexit(func)    (you must to resolve lack of atexit)
62 #endif  /* HAVE_ON_EXIT */
63 #endif  /* ! HAVE_ATEXIT */
64 #endif
65 #ifndef KRB5_HEIMDAL_INCLUDES
66 #include <gssapi/gssapi_generic.h>
67 #else
68 #include <gssapi/gssapi.h>
69 #endif
70 #include <krb5.h>
71
72 #ifndef KRB5_ENV_CCNAME
73 #define KRB5_ENV_CCNAME "KRB5CCNAME"
74 #endif
75
76 /*
77  * consider undefining when kdestroy() is fixed.  The current version does
78  * not work under krb5-1.2.4 in rh7.3, perhaps others.
79  */
80 #define KDESTROY_VIA_UNLINK     1
81
82 /*
83  * Where the keytab lives, if defined.  Otherwise it expects something in the
84  * config file.
85  */
86 /* #define AMANDA_KEYTAB        "/.amanda-v5-keytab" */
87
88 /*
89  * The name of the principal we authenticate with, if defined.  Otherwise
90  * it expects something in the config file.
91  */
92 /* #define      AMANDA_PRINCIPAL        "service/amanda" */
93
94 /*
95  * The lifetime of our tickets in seconds.  This may or may not need to be
96  * configurable.
97  */
98 #define AMANDA_TKT_LIFETIME     (12*60*60)
99
100
101 /*
102  * The name of the service in /etc/services.  This probably shouldn't be
103  * configurable.
104  */
105 #define AMANDA_KRB5_SERVICE_NAME        "k5amanda"
106
107 /*
108  * The default port to use if above entry in /etc/services doesn't exist
109  */
110 #define AMANDA_KRB5_DEFAULT_PORT        10082
111
112 /*
113  * The timeout in seconds for each step of the GSS negotiation phase
114  */
115 #define GSS_TIMEOUT                     30
116
117 /*
118  * This is the tcp stream buffer size
119  */
120 #define KRB5_STREAM_BUFSIZE     (32768 * 2)
121
122 /*
123  * This is the max number of outgoing connections we can have at once.
124  * planner/amcheck/etc will open a bunch of connections as it tries
125  * to contact everything.  We need to limit this to avoid blowing
126  * the max number of open file descriptors a process can have.
127  */
128 #define AMANDA_KRB5_MAXCONN     40
129
130
131 /*
132  * Number of seconds krb5 has to start up
133  */
134 #define CONNECT_TIMEOUT 20
135
136 /*
137  * Cache the local hostname
138  */
139 static char myhostname[MAX_HOSTNAME_LENGTH+1];
140
141
142 /*
143  * Interface functions
144  */
145 static void krb5_accept(const struct security_driver *,
146     char *(*)(char *, void *),
147     int, int,
148     void (*)(security_handle_t *, pkt_t *),
149     void *);
150 static void krb5_connect(const char *,
151     char *(*)(char *, void *), 
152     void (*)(void *, security_handle_t *, security_status_t), void *, void *);
153
154 static void krb5_init(void);
155 #ifdef BROKEN_MEMORY_CCACHE
156 static void cleanup(void);
157 #endif
158 static const char *get_tgt(char *keytab_name, char *principal_name);
159 static int         gss_server(struct tcp_conn *);
160 static int         gss_client(struct sec_handle *);
161 static const char *gss_error(OM_uint32, OM_uint32);
162 static char       *krb5_checkuser(char *host, char *name, char *realm);
163
164 static int k5_encrypt(void *cookie, void *buf, ssize_t buflen,
165                       void **encbuf, ssize_t *encbuflen);
166 static int k5_decrypt(void *cookie, void *buf, ssize_t buflen,
167                       void **encbuf, ssize_t *encbuflen);
168
169 /*
170  * This is our interface to the outside world.
171  */
172 const security_driver_t krb5_security_driver = {
173     "KRB5",
174     krb5_connect,
175     krb5_accept,
176     sec_close,
177     stream_sendpkt,
178     stream_recvpkt,
179     stream_recvpkt_cancel,
180     tcpma_stream_server,
181     tcpma_stream_accept,
182     tcpma_stream_client,
183     tcpma_stream_close,
184     sec_stream_auth,
185     sec_stream_id,
186     tcpm_stream_write,
187     tcpm_stream_read,
188     tcpm_stream_read_sync,
189     tcpm_stream_read_cancel,
190     tcpm_close_connection,
191     k5_encrypt,
192     k5_decrypt,
193 };
194
195 static int newhandle = 1;
196
197 /*
198  * Local functions
199  */
200 static int runkrb5(struct sec_handle *);
201
202 char *keytab_name;
203 char *principal_name;
204
205 /*
206  * krb5 version of a security handle allocator.  Logically sets
207  * up a network "connection".
208  */
209 static void
210 krb5_connect(
211     const char *hostname,
212     char *      (*conf_fn)(char *, void *),
213     void        (*fn)(void *, security_handle_t *, security_status_t),
214     void *      arg,
215     void *      datap)
216 {
217     struct sec_handle *rh;
218     int result;
219     char *canonname;
220
221     assert(fn != NULL);
222     assert(hostname != NULL);
223
224     auth_debug(1, "krb5: krb5_connect: %s\n", hostname);
225
226     krb5_init();
227
228     rh = alloc(sizeof(*rh));
229     security_handleinit(&rh->sech, &krb5_security_driver);
230     rh->hostname = NULL;
231     rh->rs = NULL;
232     rh->ev_timeout = NULL;
233     rh->rc = NULL;
234
235     result = resolve_hostname(hostname, 0, NULL, &canonname);
236     if(result != 0) {
237         dbprintf(_("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result));
238         security_seterror(&rh->sech, _("resolve_hostname(%s): %s\n"), hostname,
239                           gai_strerror(result));
240         (*fn)(arg, &rh->sech, S_ERROR);
241         return;
242     }
243     if (canonname == NULL) {
244         dbprintf(_("resolve_hostname(%s) did not return a canonical name\n"), hostname);
245         security_seterror(&rh->sech,
246                 _("resolve_hostname(%s) did not return a canonical name\n"), hostname);
247         (*fn)(arg, &rh->sech, S_ERROR);
248        return;
249     }
250
251     rh->hostname = canonname;        /* will be replaced */
252     canonname = NULL; /* steal reference */
253     rh->rs = tcpma_stream_client(rh, newhandle++);
254     rh->rc->conf_fn = conf_fn;
255     rh->rc->datap = datap;
256     rh->rc->recv_security_ok = NULL;
257     rh->rc->prefix_packet = NULL;
258
259     if (rh->rs == NULL)
260         goto error;
261
262     amfree(rh->hostname);
263     rh->hostname = stralloc(rh->rs->rc->hostname);
264
265 #ifdef AMANDA_KEYTAB
266     keytab_name = AMANDA_KEYTAB;
267 #else
268     if(conf_fn) {
269         keytab_name = conf_fn("krb5keytab", datap);
270     }
271 #endif
272 #ifdef AMANDA_PRINCIPAL
273     principal_name = AMANDA_PRINCIPAL;
274 #else
275     if(conf_fn) {
276         principal_name = conf_fn("krb5principal", datap);
277     }
278 #endif
279
280     /*
281      * We need to open a new connection.
282      *
283      * XXX need to eventually limit number of outgoing connections here.
284      */
285     if(rh->rc->read == -1) {
286         if (runkrb5(rh) < 0)
287             goto error;
288         rh->rc->refcnt++;
289     }
290
291     /*
292      * The socket will be opened async so hosts that are down won't
293      * block everything.  We need to register a write event
294      * so we will know when the socket comes alive.
295      *
296      * Overload rh->rs->ev_read to provide a write event handle.
297      * We also register a timeout.
298      */
299     rh->fn.connect = fn;
300     rh->arg = arg;
301     rh->rs->ev_read = event_register((event_id_t)(rh->rs->rc->write),
302         EV_WRITEFD, sec_connect_callback, rh);
303     rh->ev_timeout = event_register(CONNECT_TIMEOUT, EV_TIME,
304         sec_connect_timeout, rh);
305
306     amfree(canonname);
307     return;
308
309 error:
310     amfree(canonname);
311     (*fn)(arg, &rh->sech, S_ERROR);
312 }
313
314 /*
315
316  * Setup to handle new incoming connections
317  */
318 static void
319 krb5_accept(
320     const struct security_driver *driver,
321     char       *(*conf_fn)(char *, void *),
322     int         in,
323     int         out,
324     void        (*fn)(security_handle_t *, pkt_t *),
325     void       *datap)
326 {
327     sockaddr_union sin;
328     socklen_t_equiv len;
329     struct tcp_conn *rc;
330     char hostname[NI_MAXHOST];
331     int result;
332     char *errmsg = NULL;
333
334     krb5_init();
335
336     len = sizeof(sin);
337     if (getpeername(in, (struct sockaddr *)&sin, &len) < 0) {
338         dbprintf(_("getpeername returned: %s\n"),
339                   strerror(errno));
340         return;
341
342     }
343     if ((result = getnameinfo((struct sockaddr *)&sin, len,
344                               hostname, NI_MAXHOST, NULL, 0, 0) != 0)) {
345         dbprintf(_("getnameinfo failed: %s\n"),
346                   gai_strerror(result));
347         return;
348     }
349     if (check_name_give_sockaddr(hostname,
350                                  (struct sockaddr *)&sin, &errmsg) < 0) {
351         dbprintf(_("check_name_give_sockaddr(%s): %s\n"),
352                   hostname, errmsg);
353         amfree(errmsg);
354         return;
355     }
356
357
358     rc = sec_tcp_conn_get(hostname, 0);
359     rc->conf_fn = conf_fn;
360     rc->datap = datap;
361     rc->recv_security_ok = NULL;
362     rc->prefix_packet = NULL;
363     copy_sockaddr(&rc->peer, &sin);
364     rc->read = in;
365     rc->write = out;
366     rc->driver = driver;
367     if (gss_server(rc) < 0)
368         error("gss_server failed: %s\n", rc->errmsg);
369     rc->accept_fn = fn;
370     sec_tcp_conn_read(rc);
371 }
372
373 /*
374  * Forks a krb5 to the host listed in rc->hostname
375  * Returns negative on error, with an errmsg in rc->errmsg.
376  */
377 static int
378 runkrb5(
379     struct sec_handle * rh)
380 {
381     struct servent *    sp;
382     int                 server_socket;
383     in_port_t           my_port, port;
384     struct tcp_conn *   rc = rh->rc;
385     const char *err;
386
387     if ((sp = getservbyname(AMANDA_KRB5_SERVICE_NAME, "tcp")) == NULL)
388         port = htons(AMANDA_KRB5_DEFAULT_PORT);
389     else
390         port = sp->s_port;
391
392     if ((err = get_tgt(keytab_name, principal_name)) != NULL) {
393         security_seterror(&rh->sech, "%s: could not get TGT: %s",
394             rc->hostname, err);
395         return -1;
396     }
397
398     set_root_privs(1);
399     server_socket = stream_client(rc->hostname,
400                                      (in_port_t)(ntohs(port)),
401                                      STREAM_BUFSIZE,
402                                      STREAM_BUFSIZE,
403                                      &my_port,
404                                      0);
405     set_root_privs(0);
406
407     if(server_socket < 0) {
408         security_seterror(&rh->sech,
409             "%s", strerror(errno));
410         
411         return -1;
412     }
413
414     rc->read = rc->write = server_socket;
415
416     if (gss_client(rh) < 0) {
417         return -1;
418     }
419
420
421     return 0;
422 }
423
424
425
426 /*
427
428  * Negotiate a krb5 gss context from the client end.
429  */
430 static int
431 gss_client(
432     struct sec_handle *rh)
433 {
434     struct sec_stream *rs = rh->rs;
435     struct tcp_conn *rc = rs->rc;
436     gss_buffer_desc send_tok, recv_tok, AA;
437     gss_OID doid;
438     OM_uint32 maj_stat, min_stat;
439     unsigned int ret_flags;
440     int rval = -1;
441     int rvalue;
442     gss_name_t gss_name;
443     char *errmsg = NULL;
444
445     auth_debug(1, "gss_client\n");
446
447     send_tok.value = vstralloc("host/", rs->rc->hostname, NULL);
448     send_tok.length = strlen(send_tok.value) + 1;
449     maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NULL_OID,
450         &gss_name);
451     if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
452         security_seterror(&rh->sech, _("can't import name %s: %s"),
453             (char *)send_tok.value, gss_error(maj_stat, min_stat));
454         amfree(send_tok.value);
455         return (-1);
456     }
457     amfree(send_tok.value);
458     rc->gss_context = GSS_C_NO_CONTEXT;
459     maj_stat = gss_display_name(&min_stat, gss_name, &AA, &doid);
460     dbprintf(_("gss_name %s\n"), (char *)AA.value);
461
462     /*
463      * Perform the context-establishement loop.
464      *
465      * Every generated token is stored in send_tok which is then
466      * transmitted to the server; every received token is stored in
467      * recv_tok (empty on the first pass) to be processed by
468      * the next call to gss_init_sec_context.
469      * 
470      * GSS-API guarantees that send_tok's length will be non-zero
471      * if and only if the server is expecting another token from us,
472      * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if
473      * and only if the server has another token to send us.
474      */
475
476     recv_tok.value = NULL;
477     for (recv_tok.length = 0;;) {
478         min_stat = 0;
479         maj_stat = gss_init_sec_context(&min_stat,
480             GSS_C_NO_CREDENTIAL,
481             &rc->gss_context,
482             gss_name,
483             GSS_C_NULL_OID,
484             (OM_uint32)GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG,
485             0, NULL,    /* no channel bindings */
486             (recv_tok.length == 0 ? GSS_C_NO_BUFFER : &recv_tok),
487             NULL,       /* ignore mech type */
488             &send_tok,
489             &ret_flags,
490             NULL);      /* ignore time_rec */
491
492         if (recv_tok.length != 0) {
493             amfree(recv_tok.value);
494             recv_tok.length = 0;
495         }
496         if (maj_stat != (OM_uint32)GSS_S_COMPLETE && maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) {
497             security_seterror(&rh->sech,
498                 _("error getting gss context: %s %s"),
499                 gss_error(maj_stat, min_stat), (char *)send_tok.value);
500             goto done;
501         }
502
503         /*
504          * Send back the response
505          */
506         if (send_tok.length != 0 && tcpm_send_token(rc, rc->write, rs->handle, &errmsg, send_tok.value, send_tok.length) < 0) {
507             security_seterror(&rh->sech, "%s", rc->errmsg);
508             gss_release_buffer(&min_stat, &send_tok);
509             goto done;
510         }
511         gss_release_buffer(&min_stat, &send_tok);
512
513         /*
514          * If we need to continue, then register for more packets
515          */
516         if (maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED)
517             break;
518
519         rvalue = tcpm_recv_token(rc, rc->read, &rc->handle, &rc->errmsg,
520                                  (void *)&recv_tok.value,
521                                  (ssize_t *)&recv_tok.length, 60);
522         if (rvalue <= 0) {
523             if (rvalue < 0)
524                 security_seterror(&rh->sech,
525                     _("recv error in gss loop: %s"), rc->errmsg);
526             else
527                 security_seterror(&rh->sech, _("EOF in gss loop"));
528             goto done;
529         }
530     }
531
532     rval = 0;
533     rc->auth = 1;
534 done:
535     gss_release_name(&min_stat, &gss_name);
536     return (rval);
537 }
538
539 /*
540  * Negotiate a krb5 gss context from the server end.
541  */
542 static int
543 gss_server(
544     struct tcp_conn *rc)
545 {
546     OM_uint32 maj_stat, min_stat, ret_flags;
547     gss_buffer_desc send_tok, recv_tok, AA;
548     gss_OID doid;
549     gss_name_t gss_name;
550     gss_cred_id_t gss_creds;
551     char *p, *realm, *msg;
552     int rval = -1;
553     int rvalue;
554     char errbuf[256];
555     char *errmsg = NULL;
556
557     auth_debug(1, "gss_server\n");
558
559     assert(rc != NULL);
560
561     /*
562      * We need to be root while in gss_acquire_cred() to read the host key
563      * out of the default keytab.  We also need to be root in
564      * gss_accept_context() thanks to the replay cache code.
565      */
566     if (!set_root_privs(0)) {
567         g_snprintf(errbuf, SIZEOF(errbuf),
568             _("can't take root privileges to read krb5 host key: %s"), strerror(errno));
569         goto out;
570     }
571
572     rc->gss_context = GSS_C_NO_CONTEXT;
573     send_tok.value = vstralloc("host/", myhostname, NULL);
574     send_tok.length = strlen(send_tok.value) + 1;
575     for (p = send_tok.value; *p != '\0'; p++) {
576         if (isupper((int)*p))
577             *p = tolower(*p);
578     }
579     maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NULL_OID,
580         &gss_name);
581     if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
582         set_root_privs(0);
583         g_snprintf(errbuf, SIZEOF(errbuf),
584             _("can't import name %s: %s"), (char *)send_tok.value,
585             gss_error(maj_stat, min_stat));
586         amfree(send_tok.value);
587         goto out;
588     }
589     amfree(send_tok.value);
590
591     maj_stat = gss_display_name(&min_stat, gss_name, &AA, &doid);
592     dbprintf(_("gss_name %s\n"), (char *)AA.value);
593     maj_stat = gss_acquire_cred(&min_stat, gss_name, 0,
594         GSS_C_NULL_OID_SET, GSS_C_ACCEPT, &gss_creds, NULL, NULL);
595     if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
596         g_snprintf(errbuf, SIZEOF(errbuf),
597             _("can't acquire creds for host key host/%s: %s"), myhostname,
598             gss_error(maj_stat, min_stat));
599         gss_release_name(&min_stat, &gss_name);
600         set_root_privs(0);
601         goto out;
602     }
603     gss_release_name(&min_stat, &gss_name);
604
605     for (recv_tok.length = 0;;) {
606         recv_tok.value = NULL;
607         rvalue = tcpm_recv_token(rc, rc->read, &rc->handle, &rc->errmsg,
608                                  /* (void *) is to avoid type-punning warning */
609                                  (char **)(void *)&recv_tok.value,
610                                  (ssize_t *)&recv_tok.length, 60);
611         if (rvalue <= 0) {
612             if (rvalue < 0) {
613                 g_snprintf(errbuf, SIZEOF(errbuf),
614                     _("recv error in gss loop: %s"), rc->errmsg);
615                 amfree(rc->errmsg);
616             } else
617                 g_snprintf(errbuf, SIZEOF(errbuf), _("EOF in gss loop"));
618             goto out;
619         }
620
621         maj_stat = gss_accept_sec_context(&min_stat, &rc->gss_context,
622             gss_creds, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS,
623             &gss_name, &doid, &send_tok, &ret_flags, NULL, NULL);
624
625         if (maj_stat != (OM_uint32)GSS_S_COMPLETE &&
626             maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) {
627             g_snprintf(errbuf, SIZEOF(errbuf),
628                 _("error accepting context: %s"), gss_error(maj_stat, min_stat));
629             amfree(recv_tok.value);
630             goto out;
631         }
632         amfree(recv_tok.value);
633
634         if (send_tok.length != 0 && tcpm_send_token(rc, rc->write, 0, &errmsg, send_tok.value, send_tok.length) < 0) {
635             strncpy(errbuf, rc->errmsg, SIZEOF(errbuf) - 1);
636             errbuf[SIZEOF(errbuf) - 1] = '\0';
637             amfree(rc->errmsg);
638             gss_release_buffer(&min_stat, &send_tok);
639             goto out;
640         }
641         gss_release_buffer(&min_stat, &send_tok);
642
643
644         /*
645          * If we need to get more from the client, then register for
646          * more packets.
647          */
648         if (maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED)
649             break;
650     }
651
652     maj_stat = gss_display_name(&min_stat, gss_name, &send_tok, &doid);
653     if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
654         g_snprintf(errbuf, SIZEOF(errbuf),
655             _("can't display gss name: %s"), gss_error(maj_stat, min_stat));
656         gss_release_name(&min_stat, &gss_name);
657         goto out;
658     }
659     gss_release_name(&min_stat, &gss_name);
660
661     /* get rid of the realm */
662     if ((p = strchr(send_tok.value, '@')) == NULL) {
663         g_snprintf(errbuf, SIZEOF(errbuf),
664             _("malformed gss name: %s"), (char *)send_tok.value);
665         amfree(send_tok.value);
666         goto out;
667     }
668     *p = '\0';
669     realm = ++p;
670
671     /* 
672      * If the principal doesn't match, complain
673      */
674     if ((msg = krb5_checkuser(rc->hostname, send_tok.value, realm)) != NULL) {
675         g_snprintf(errbuf, SIZEOF(errbuf),
676             _("access not allowed from %s: %s"), (char *)send_tok.value, msg);
677         amfree(send_tok.value);
678         goto out;
679     }
680     amfree(send_tok.value);
681
682     rval = 0;
683 out:
684     set_root_privs(0);
685     if (rval != 0) {
686         rc->errmsg = stralloc(errbuf);
687     } else {
688         rc->auth = 1;
689     }
690     auth_debug(1, _("gss_server returning %d\n"), rval);
691     return (rval);
692 }
693
694 /*
695  * Setup some things about krb5.  This should only be called once.
696  */
697 static void
698 krb5_init(void)
699 {
700     static int beenhere = 0;
701     char *p;
702     char *myfqhostname=NULL;
703
704     if (beenhere)
705         return;
706     beenhere = 1;
707
708 #ifndef BROKEN_MEMORY_CCACHE
709     putenv(stralloc("KRB5_ENV_CCNAME=MEMORY:amanda_ccache"));
710 #else
711     /*
712      * MEMORY ccaches seem buggy and cause a lot of internal heap
713      * corruption.  malloc has been known to core dump.  This behavior
714      * has been witnessed in Cygnus' kerbnet 1.2, MIT's krb V 1.0.5 and
715      * MIT's krb V -current as of 3/17/1999.
716      *
717      * We just use a lame ccache scheme with a uid suffix.
718      */
719     atexit(cleanup);
720     {
721         char *ccache;
722         ccache = malloc(128);
723         g_snprintf(ccache, SIZEOF(ccache),
724                  "KRB5_ENV_CCNAME=FILE:/tmp/amanda_ccache.%ld.%ld",
725                  (long)geteuid(), (long)getpid());
726         putenv(ccache);
727     }
728 #endif
729
730     gethostname(myhostname, SIZEOF(myhostname) - 1);
731     myhostname[SIZEOF(myhostname) - 1] = '\0';
732
733     /*
734      * In case it isn't fully qualified, do a DNS lookup.  Ignore
735      * any errors (this is best-effort).
736      */
737     if (resolve_hostname(myhostname, SOCK_STREAM, NULL, &myfqhostname) == 0
738         && myfqhostname != NULL) {
739         strncpy(myhostname, myfqhostname, SIZEOF(myhostname)-1);
740         myhostname[SIZEOF(myhostname)-1] = '\0';
741         amfree(myfqhostname);
742     }
743
744     /*
745      * Lowercase the results.  We assume all host/ principals will be
746      * lowercased.
747      */
748     for (p = myhostname; *p != '\0'; p++) {
749         if (isupper((int)*p))
750             *p = tolower(*p);
751     }
752 }
753
754 #ifdef BROKEN_MEMORY_CCACHE
755 static void
756 cleanup(void)
757 {
758 #ifdef KDESTROY_VIA_UNLINK
759     char ccache[64];
760     g_snprintf(ccache, SIZEOF(ccache), "/tmp/amanda_ccache.%ld.%ld",
761         (long)geteuid(), (long)getpid());
762     unlink(ccache);
763 #else
764     kdestroy();
765 #endif
766 }
767 #endif
768
769 /*
770  * Get a ticket granting ticket and stuff it in the cache
771  */
772 static const char *
773 get_tgt(
774     char *      keytab_name,
775     char *      principal_name)
776 {
777     krb5_context context;
778     krb5_error_code ret;
779     krb5_principal client = NULL, server = NULL;
780     krb5_creds creds;
781     krb5_keytab keytab;
782     krb5_ccache ccache;
783     krb5_timestamp now;
784 #ifdef KRB5_HEIMDAL_INCLUDES
785     krb5_data tgtname = { KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME };
786 #else
787     krb5_data tgtname = { 0, KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME };
788 #endif
789     static char *error = NULL;
790
791     if (error != NULL) {
792         amfree(error);
793         error = NULL;
794     }
795     if ((ret = krb5_init_context(&context)) != 0) {
796         error = vstrallocf(_("error initializing krb5 context: %s"),
797             error_message(ret));
798         return (error);
799     }
800
801     /*krb5_init_ets(context);*/
802
803     if(!keytab_name) {
804         error = vstrallocf(_("error  -- no krb5 keytab defined"));
805         return(error);
806     }
807
808     if(!principal_name) {
809         error = vstrallocf(_("error  -- no krb5 principal defined"));
810         return(error);
811     }
812
813     /*
814      * Resolve keytab file into a keytab object
815      */
816     if ((ret = krb5_kt_resolve(context, keytab_name, &keytab)) != 0) {
817         error = vstrallocf(_("error resolving keytab %s: %s"), keytab_name, 
818             error_message(ret));
819         return (error);
820     }
821
822     /*
823      * Resolve the amanda service held in the keytab into a principal
824      * object
825      */
826     ret = krb5_parse_name(context, principal_name, &client);
827     if (ret != 0) {
828         error = vstrallocf(_("error parsing %s: %s"), principal_name,
829             error_message(ret));
830         return (error);
831     }
832
833 #ifdef KRB5_HEIMDAL_INCLUDES
834     ret = krb5_build_principal_ext(context, &server,
835         krb5_realm_length(*krb5_princ_realm(context, client)),
836         krb5_realm_data(*krb5_princ_realm(context, client)),
837         tgtname.length, tgtname.data,
838         krb5_realm_length(*krb5_princ_realm(context, client)),
839         krb5_realm_data(*krb5_princ_realm(context, client)),
840         0);
841 #else
842     ret = krb5_build_principal_ext(context, &server,
843         krb5_princ_realm(context, client)->length,
844         krb5_princ_realm(context, client)->data,
845         tgtname.length, tgtname.data,
846         krb5_princ_realm(context, client)->length,
847         krb5_princ_realm(context, client)->data,
848         0);
849 #endif
850     if (ret != 0) {
851         error = vstrallocf(_("error while building server name: %s"),
852             error_message(ret));
853         return (error);
854     }
855
856     ret = krb5_timeofday(context, &now);
857     if (ret != 0) {
858         error = vstrallocf(_("error getting time of day: %s"), error_message(ret));
859         return (error);
860     }
861
862     memset(&creds, 0, SIZEOF(creds));
863     creds.times.starttime = 0;
864     creds.times.endtime = now + AMANDA_TKT_LIFETIME;
865
866     creds.client = client;
867     creds.server = server;
868
869     /*
870      * Get a ticket for the service, using the keytab
871      */
872     ret = krb5_get_in_tkt_with_keytab(context, 0, NULL, NULL, NULL,
873         keytab, 0, &creds, 0);
874
875     if (ret != 0) {
876         error = vstrallocf(_("error getting ticket for %s: %s"),
877             principal_name, error_message(ret));
878         goto cleanup2;
879     }
880
881     if ((ret = krb5_cc_default(context, &ccache)) != 0) {
882         error = vstrallocf(_("error initializing ccache: %s"), error_message(ret));
883         goto cleanup;
884     }
885     if ((ret = krb5_cc_initialize(context, ccache, client)) != 0) {
886         error = vstrallocf(_("error initializing ccache: %s"), error_message(ret));
887         goto cleanup;
888     }
889     if ((ret = krb5_cc_store_cred(context, ccache, &creds)) != 0) {
890         error = vstrallocf(_("error storing creds in ccache: %s"), 
891             error_message(ret));
892         /* FALLTHROUGH */
893     }
894     krb5_cc_close(context, ccache);
895 cleanup:
896     krb5_free_cred_contents(context, &creds);
897 cleanup2:
898 #if 0
899     krb5_free_principal(context, client);
900     krb5_free_principal(context, server);
901 #endif
902     krb5_free_context(context);
903     return (error);
904 }
905
906 #ifndef KDESTROY_VIA_UNLINK
907 /*
908  * get rid of tickets
909  */
910 static void
911 kdestroy(void)
912 {
913     krb5_context context;
914     krb5_ccache ccache;
915
916     if ((krb5_init_context(&context)) != 0) {
917         return;
918     }
919     if ((krb5_cc_default(context, &ccache)) != 0) {
920         goto cleanup;
921     }
922
923     krb5_cc_destroy(context, ccache);
924     krb5_cc_close(context, ccache);
925
926 cleanup:
927      krb5_free_context(context);
928      return;
929 }
930 #endif
931
932 /*
933  * Formats an error from the gss api
934  */
935 static const char *
936 gss_error(
937     OM_uint32   major,
938     OM_uint32   minor)
939 {
940     static gss_buffer_desc msg;
941     OM_uint32 min_stat, msg_ctx;
942
943     if (msg.length > 0)
944         gss_release_buffer(&min_stat, &msg);
945
946     msg_ctx = 0;
947     if (major == GSS_S_FAILURE)
948         gss_display_status(&min_stat, minor, GSS_C_MECH_CODE, GSS_C_NULL_OID,
949             &msg_ctx, &msg);
950     else
951         gss_display_status(&min_stat, major, GSS_C_GSS_CODE, GSS_C_NULL_OID,
952             &msg_ctx, &msg);
953     return ((const char *)msg.value);
954 }
955
956 static int
957 k5_encrypt(
958     void *cookie,
959     void *buf,
960     ssize_t buflen,
961     void **encbuf,
962     ssize_t *encbuflen)
963 {
964     struct tcp_conn *rc = cookie;
965     gss_buffer_desc dectok;
966     gss_buffer_desc enctok;
967     OM_uint32 maj_stat, min_stat;
968     int conf_state;
969
970     if (rc->conf_fn && rc->conf_fn("kencrypt", rc->datap)) {
971         auth_debug(1, _("krb5: k5_encrypt: enter %p\n"), rc);
972
973         dectok.length = buflen;
974         dectok.value  = buf;    
975
976         if (rc->auth == 1) {
977             assert(rc->gss_context != GSS_C_NO_CONTEXT);
978             maj_stat = gss_seal(&min_stat, rc->gss_context, 1,
979                                 GSS_C_QOP_DEFAULT, &dectok, &conf_state,
980                                 &enctok);
981             if (maj_stat != (OM_uint32)GSS_S_COMPLETE || conf_state == 0) {
982                 auth_debug(1, _("krb5 encrypt error to %s: %s\n"),
983                            rc->hostname, gss_error(maj_stat, min_stat));
984                 return (-1);
985             }
986             auth_debug(1, _("krb5: k5_encrypt: give %zu bytes\n"),
987                        enctok.length);
988             *encbuf = enctok.value;
989             *encbuflen = enctok.length;
990         } else {
991             *encbuf = buf;
992             *encbuflen = buflen;
993         }
994         auth_debug(1, _("krb5: k5_encrypt: exit\n"));
995     }
996     return (0);
997 }
998
999
1000 static int
1001 k5_decrypt(
1002     void *cookie,
1003     void *buf,
1004     ssize_t buflen,
1005     void **decbuf,
1006     ssize_t *decbuflen)
1007 {
1008     struct tcp_conn *rc = cookie;
1009     gss_buffer_desc enctok;
1010     gss_buffer_desc dectok;
1011     OM_uint32 maj_stat, min_stat;
1012     int conf_state, qop_state;
1013
1014     if (rc->conf_fn && rc->conf_fn("kencrypt", rc->datap)) {
1015         auth_debug(1, _("krb5: k5_decrypt: enter\n"));
1016         if (rc->auth == 1) {
1017             enctok.length = buflen;
1018             enctok.value  = buf;    
1019
1020             auth_debug(1, _("krb5: k5_decrypt: decrypting %zu bytes\n"), enctok.length);
1021
1022             assert(rc->gss_context != GSS_C_NO_CONTEXT);
1023             maj_stat = gss_unseal(&min_stat, rc->gss_context, &enctok, &dectok,
1024                               &conf_state, &qop_state);
1025             if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
1026                 auth_debug(1, _("krb5 decrypt error from %s: %s\n"),
1027                            rc->hostname, gss_error(maj_stat, min_stat));
1028                 return (-1);
1029             }
1030             auth_debug(1, _("krb5: k5_decrypt: give %zu bytes\n"),
1031                        dectok.length);
1032             *decbuf = dectok.value;
1033             *decbuflen = dectok.length;
1034         } else {
1035             *decbuf = buf;
1036             *decbuflen = buflen;
1037         }
1038         auth_debug(1, _("krb5: k5_decrypt: exit\n"));
1039     } else {
1040         *decbuf = buf;
1041         *decbuflen = buflen;
1042     }
1043     return (0);
1044 }
1045
1046 /*
1047  * check ~/.k5amandahosts to see if this principal is allowed in.  If it's
1048  * hardcoded, then we don't check the realm
1049  */
1050 static char *
1051 krb5_checkuser( char *  host,
1052     char *      name,
1053     char *      realm)
1054 {
1055 #ifdef AMANDA_PRINCIPAL
1056     if(strcmp(name, AMANDA_PRINCIPAL) == 0) {
1057         return(NULL);
1058     } else {
1059         return(vstrallocf(_("does not match compiled in default")));
1060     }
1061 #else
1062     struct passwd *pwd;
1063     char *ptmp;
1064     char *result = _("generic error");  /* default is to not permit */
1065     FILE *fp = NULL;
1066     struct stat sbuf;
1067     uid_t localuid;
1068     char *line = NULL;
1069     char *filehost = NULL, *fileuser = NULL, *filerealm = NULL;
1070
1071     assert( host != NULL);
1072     assert( name != NULL);
1073
1074     if((pwd = getpwnam(CLIENT_LOGIN)) == NULL) {
1075         result = vstrallocf(_("can not find user %s"), CLIENT_LOGIN);
1076     }
1077     localuid = pwd->pw_uid;
1078
1079 #ifdef USE_AMANDAHOSTS
1080     ptmp = stralloc2(pwd->pw_dir, "/.k5amandahosts");
1081 #else
1082     ptmp = stralloc2(pwd->pw_dir, "/.k5login");
1083 #endif
1084
1085     if(!ptmp) {
1086         result = vstrallocf(_("could not find home directory for %s"), CLIENT_LOGIN);
1087         goto common_exit;
1088    }
1089
1090    /*
1091     * check to see if the ptmp file does nto exist.
1092     */
1093    if(access(ptmp, R_OK) == -1 && errno == ENOENT) {
1094         /*
1095          * in this case we check to see if the principal matches
1096          * the destination user mimicing the .k5login functionality.
1097          */
1098          if(strcmp(name, CLIENT_LOGIN) != 0) {
1099                 result = vstrallocf(_("%s does not match %s"),
1100                         name, CLIENT_LOGIN);
1101                 return result;
1102         }
1103         result = NULL;
1104         goto common_exit;
1105     }
1106
1107     auth_debug(1, _("opening ptmp: %s\n"), (ptmp)?ptmp: "NULL!");
1108     if((fp = fopen(ptmp, "r")) == NULL) {
1109         result = vstrallocf(_("can not open %s"), ptmp);
1110         return result;
1111     }
1112     auth_debug(1, _("opened ptmp\n"));
1113
1114     if (fstat(fileno(fp), &sbuf) != 0) {
1115         result = vstrallocf(_("cannot fstat %s: %s"), ptmp, strerror(errno));
1116         goto common_exit;
1117     }
1118
1119     if (sbuf.st_uid != localuid) {
1120         result = vstrallocf(_("%s is owned by %ld, should be %ld"),
1121                 ptmp, (long)sbuf.st_uid, (long)localuid);
1122         goto common_exit;
1123     }
1124     if ((sbuf.st_mode & 077) != 0) {
1125         result = vstrallocf(
1126             _("%s: incorrect permissions; file must be accessible only by its owner"), ptmp);
1127         goto common_exit;
1128     }
1129
1130     while ((line = agets(fp)) != NULL) {
1131         if (line[0] == '\0') {
1132             amfree(line);
1133             continue;
1134         }
1135
1136         /* if there's more than one column, then it's the host */
1137         if( (filehost = strtok(line, " \t")) == NULL) {
1138             amfree(line);
1139             continue;
1140         }
1141
1142         /*
1143          * if there's only one entry, then it's a username and we have
1144          * no hostname.  (so the principal is allowed from anywhere.
1145          */
1146         if((fileuser = strtok(NULL, " \t")) == NULL) {
1147             fileuser = filehost;
1148             filehost = NULL;
1149         }
1150
1151         if(filehost && strcmp(filehost, host) != 0) {
1152             amfree(line);
1153             continue;
1154         } else {
1155                 auth_debug(1, _("found a host match\n"));
1156         }
1157
1158         if( (filerealm = strchr(fileuser, '@')) != NULL) {
1159             *filerealm++ = '\0';
1160         }
1161
1162         /*
1163          * we have a match.  We're going to be a little bit insecure
1164          * and indicate that the principal is correct but the realm is
1165          * not if that's the case.  Technically we should say nothing
1166          * and let the user figure it out, but it's helpful for debugging.
1167          * You likely only get this far if you've turned on cross-realm auth
1168          * anyway...
1169          */
1170         auth_debug(1, _("comparing %s %s\n"), fileuser, name);
1171         if(strcmp(fileuser, name) == 0) {
1172                 auth_debug(1, _("found a match!\n"));
1173                 if(realm && filerealm && (strcmp(realm, filerealm)!=0)) {
1174                         amfree(line);
1175                         continue;
1176                 }
1177                 result = NULL;
1178                 amfree(line);
1179                 goto common_exit;
1180         }
1181         amfree(line);
1182     }
1183     result = vstrallocf(_("no match in %s"), ptmp);
1184
1185 common_exit:
1186     afclose(fp);
1187     return(result);
1188 #endif /* AMANDA_PRINCIPAL */
1189 }