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