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