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