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