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
krb5_connect(const char * hostname,char * (* conf_fn)(char *,void *),void (* fn)(void *,security_handle_t *,security_status_t),void * arg,void * datap)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
krb5_accept(const struct security_driver * driver,char * (* conf_fn)(char *,void *),int in,int out,void (* fn)(security_handle_t *,pkt_t *),void * datap)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 struct passwd *pw;
338
339 krb5_init();
340
341 len = sizeof(sin);
342 if (getpeername(in, (struct sockaddr *)&sin, &len) < 0) {
343 dbprintf(_("getpeername returned: %s\n"),
344 strerror(errno));
345 return;
346
347 }
348 if ((result = getnameinfo((struct sockaddr *)&sin, len,
349 hostname, NI_MAXHOST, NULL, 0, 0) != 0)) {
350 dbprintf(_("getnameinfo failed: %s\n"),
351 gai_strerror(result));
352 return;
353 }
354 if (check_name_give_sockaddr(hostname,
355 (struct sockaddr *)&sin, &errmsg) < 0) {
356 dbprintf(_("check_name_give_sockaddr(%s): %s\n"),
357 hostname, errmsg);
358 amfree(errmsg);
359 return;
360 }
361
362
363 rc = sec_tcp_conn_get(hostname, 0);
364 rc->conf_fn = conf_fn;
365 rc->datap = datap;
366 rc->recv_security_ok = NULL;
367 rc->prefix_packet = NULL;
368 copy_sockaddr(&rc->peer, &sin);
369 rc->read = in;
370 rc->write = out;
371 rc->driver = driver;
372 if (gss_server(rc) < 0)
373 error("gss_server failed: %s\n", rc->errmsg);
374 rc->accept_fn = fn;
375 sec_tcp_conn_read(rc);
376
377 /* totally drop privileges at this point
378 *(making the userid equal to the dumpuser)
379 */
380 pw = getpwnam(CLIENT_LOGIN);
381 setreuid(pw->pw_uid, pw->pw_uid);
382 }
383
384 /*
385 * Forks a krb5 to the host listed in rc->hostname
386 * Returns negative on error, with an errmsg in rc->errmsg.
387 */
388 static int
runkrb5(struct sec_handle * rh)389 runkrb5(
390 struct sec_handle * rh)
391 {
392 struct servent * sp;
393 int server_socket;
394 in_port_t my_port, port;
395 struct tcp_conn * rc = rh->rc;
396 const char *err;
397
398 if ((sp = getservbyname(AMANDA_KRB5_SERVICE_NAME, "tcp")) == NULL)
399 port = htons(AMANDA_KRB5_DEFAULT_PORT);
400 else
401 port = sp->s_port;
402
403 if ((err = get_tgt(keytab_name, principal_name)) != NULL) {
404 security_seterror(&rh->sech, "%s: could not get TGT: %s",
405 rc->hostname, err);
406 return -1;
407 }
408
409 set_root_privs(1);
410 server_socket = stream_client(rc->hostname,
411 (in_port_t)(ntohs(port)),
412 STREAM_BUFSIZE,
413 STREAM_BUFSIZE,
414 &my_port,
415 0);
416 set_root_privs(0);
417
418 if(server_socket < 0) {
419 security_seterror(&rh->sech,
420 "%s", strerror(errno));
421
422 return -1;
423 }
424
425 rc->read = rc->write = server_socket;
426
427 if (gss_client(rh) < 0) {
428 return -1;
429 }
430
431
432 return 0;
433 }
434
435
436
437 /*
438
439 * Negotiate a krb5 gss context from the client end.
440 */
441 static int
gss_client(struct sec_handle * rh)442 gss_client(
443 struct sec_handle *rh)
444 {
445 struct sec_stream *rs = rh->rs;
446 struct tcp_conn *rc = rs->rc;
447 gss_buffer_desc send_tok, recv_tok, AA;
448 gss_OID doid;
449 OM_uint32 maj_stat, min_stat;
450 unsigned int ret_flags;
451 int rval = -1;
452 int rvalue;
453 gss_name_t gss_name;
454 char *errmsg = NULL;
455
456 auth_debug(1, "gss_client\n");
457
458 send_tok.value = vstralloc("host/", rs->rc->hostname, NULL);
459 send_tok.length = strlen(send_tok.value) + 1;
460 maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NULL_OID,
461 &gss_name);
462 if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
463 security_seterror(&rh->sech, _("can't import name %s: %s"),
464 (char *)send_tok.value, gss_error(maj_stat, min_stat));
465 amfree(send_tok.value);
466 return (-1);
467 }
468 amfree(send_tok.value);
469 rc->gss_context = GSS_C_NO_CONTEXT;
470 maj_stat = gss_display_name(&min_stat, gss_name, &AA, &doid);
471 dbprintf(_("gss_name %s\n"), (char *)AA.value);
472
473 /*
474 * Perform the context-establishement loop.
475 *
476 * Every generated token is stored in send_tok which is then
477 * transmitted to the server; every received token is stored in
478 * recv_tok (empty on the first pass) to be processed by
479 * the next call to gss_init_sec_context.
480 *
481 * GSS-API guarantees that send_tok's length will be non-zero
482 * if and only if the server is expecting another token from us,
483 * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if
484 * and only if the server has another token to send us.
485 */
486
487 recv_tok.value = NULL;
488 for (recv_tok.length = 0;;) {
489 min_stat = 0;
490 maj_stat = gss_init_sec_context(&min_stat,
491 GSS_C_NO_CREDENTIAL,
492 &rc->gss_context,
493 gss_name,
494 GSS_C_NULL_OID,
495 (OM_uint32)GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG,
496 0, NULL, /* no channel bindings */
497 (recv_tok.length == 0 ? GSS_C_NO_BUFFER : &recv_tok),
498 NULL, /* ignore mech type */
499 &send_tok,
500 &ret_flags,
501 NULL); /* ignore time_rec */
502
503 if (recv_tok.length != 0) {
504 amfree(recv_tok.value);
505 recv_tok.length = 0;
506 }
507 if (maj_stat != (OM_uint32)GSS_S_COMPLETE && maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) {
508 security_seterror(&rh->sech,
509 _("error getting gss context: %s %s"),
510 gss_error(maj_stat, min_stat), (char *)send_tok.value);
511 goto done;
512 }
513
514 /*
515 * Send back the response
516 */
517 if (send_tok.length != 0 && tcpm_send_token(rc, rc->write, rs->handle, &errmsg, send_tok.value, send_tok.length) < 0) {
518 security_seterror(&rh->sech, "%s", rc->errmsg);
519 gss_release_buffer(&min_stat, &send_tok);
520 goto done;
521 }
522 gss_release_buffer(&min_stat, &send_tok);
523
524 /*
525 * If we need to continue, then register for more packets
526 */
527 if (maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED)
528 break;
529
530 rvalue = krb5_tcpm_recv_token(rc, rc->read, &rc->handle,
531 &rc->errmsg,
532 (void *)&recv_tok.value,
533 (ssize_t *)&recv_tok.length, 60);
534 if (rvalue <= 0) {
535 if (rvalue < 0)
536 security_seterror(&rh->sech,
537 _("recv error in gss loop: %s"), rc->errmsg);
538 else
539 security_seterror(&rh->sech, _("EOF in gss loop"));
540 goto done;
541 }
542 }
543
544 rval = 0;
545 rc->auth = 1;
546 done:
547 gss_release_name(&min_stat, &gss_name);
548 return (rval);
549 }
550
551 /*
552 * Negotiate a krb5 gss context from the server end.
553 */
554 static int
gss_server(struct tcp_conn * rc)555 gss_server(
556 struct tcp_conn *rc)
557 {
558 OM_uint32 maj_stat, min_stat, ret_flags;
559 gss_buffer_desc send_tok, recv_tok, AA;
560 gss_OID doid;
561 gss_name_t gss_name;
562 gss_cred_id_t gss_creds;
563 char *p, *realm, *msg;
564 int rval = -1;
565 int rvalue;
566 char errbuf[256];
567 char *errmsg = NULL;
568
569 auth_debug(1, "gss_server\n");
570
571 assert(rc != NULL);
572
573 /*
574 * We need to be root while in gss_acquire_cred() to read the host key
575 * out of the default keytab. We also need to be root in
576 * gss_accept_context() thanks to the replay cache code.
577 */
578 if (!set_root_privs(0)) {
579 g_snprintf(errbuf, SIZEOF(errbuf),
580 _("can't take root privileges to read krb5 host key: %s"), strerror(errno));
581 goto out;
582 }
583
584 rc->gss_context = GSS_C_NO_CONTEXT;
585 send_tok.value = vstralloc("host/", myhostname, NULL);
586 send_tok.length = strlen(send_tok.value) + 1;
587 for (p = send_tok.value; *p != '\0'; p++) {
588 if (isupper((int)*p))
589 *p = tolower(*p);
590 }
591 maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NULL_OID,
592 &gss_name);
593 if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
594 set_root_privs(0);
595 g_snprintf(errbuf, SIZEOF(errbuf),
596 _("can't import name %s: %s"), (char *)send_tok.value,
597 gss_error(maj_stat, min_stat));
598 amfree(send_tok.value);
599 goto out;
600 }
601 amfree(send_tok.value);
602
603 maj_stat = gss_display_name(&min_stat, gss_name, &AA, &doid);
604 dbprintf(_("gss_name %s\n"), (char *)AA.value);
605 maj_stat = gss_acquire_cred(&min_stat, gss_name, 0,
606 GSS_C_NULL_OID_SET, GSS_C_ACCEPT, &gss_creds, NULL, NULL);
607 if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
608 g_snprintf(errbuf, SIZEOF(errbuf),
609 _("can't acquire creds for host key host/%s: %s"), myhostname,
610 gss_error(maj_stat, min_stat));
611 gss_release_name(&min_stat, &gss_name);
612 set_root_privs(0);
613 goto out;
614 }
615 gss_release_name(&min_stat, &gss_name);
616
617 for (recv_tok.length = 0;;) {
618 recv_tok.value = NULL;
619 rvalue = krb5_tcpm_recv_token(rc, rc->read, &rc->handle,
620 &rc->errmsg,
621 /* (void *) is to avoid type-punning warning */
622 (char **)(void *)&recv_tok.value,
623 (ssize_t *)&recv_tok.length, 60);
624 if (rvalue <= 0) {
625 if (rvalue < 0) {
626 g_snprintf(errbuf, SIZEOF(errbuf),
627 _("recv error in gss loop: %s"), rc->errmsg);
628 amfree(rc->errmsg);
629 } else
630 g_snprintf(errbuf, SIZEOF(errbuf), _("EOF in gss loop"));
631 goto out;
632 }
633
634 maj_stat = gss_accept_sec_context(&min_stat, &rc->gss_context,
635 gss_creds, &recv_tok, GSS_C_NO_CHANNEL_BINDINGS,
636 &gss_name, &doid, &send_tok, &ret_flags, NULL, NULL);
637
638 if (maj_stat != (OM_uint32)GSS_S_COMPLETE &&
639 maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) {
640 g_snprintf(errbuf, SIZEOF(errbuf),
641 _("error accepting context: %s"), gss_error(maj_stat, min_stat));
642 amfree(recv_tok.value);
643 goto out;
644 }
645 amfree(recv_tok.value);
646
647 if (send_tok.length != 0 && tcpm_send_token(rc, rc->write, 0, &errmsg, send_tok.value, send_tok.length) < 0) {
648 strncpy(errbuf, rc->errmsg, SIZEOF(errbuf) - 1);
649 errbuf[SIZEOF(errbuf) - 1] = '\0';
650 amfree(rc->errmsg);
651 gss_release_buffer(&min_stat, &send_tok);
652 goto out;
653 }
654 gss_release_buffer(&min_stat, &send_tok);
655
656
657 /*
658 * If we need to get more from the client, then register for
659 * more packets.
660 */
661 if (maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED)
662 break;
663 }
664
665 maj_stat = gss_display_name(&min_stat, gss_name, &send_tok, &doid);
666 if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
667 g_snprintf(errbuf, SIZEOF(errbuf),
668 _("can't display gss name: %s"), gss_error(maj_stat, min_stat));
669 gss_release_name(&min_stat, &gss_name);
670 goto out;
671 }
672 gss_release_name(&min_stat, &gss_name);
673
674 /* get rid of the realm */
675 if ((p = strchr(send_tok.value, '@')) == NULL) {
676 g_snprintf(errbuf, SIZEOF(errbuf),
677 _("malformed gss name: %s"), (char *)send_tok.value);
678 amfree(send_tok.value);
679 goto out;
680 }
681 *p = '\0';
682 realm = ++p;
683
684 /*
685 * If the principal doesn't match, complain
686 */
687 if ((msg = krb5_checkuser(rc->hostname, send_tok.value, realm)) != NULL) {
688 g_snprintf(errbuf, SIZEOF(errbuf),
689 _("access not allowed from %s: %s"), (char *)send_tok.value, msg);
690 amfree(send_tok.value);
691 goto out;
692 }
693 amfree(send_tok.value);
694
695 rval = 0;
696 out:
697 set_root_privs(0);
698 if (rval != 0) {
699 rc->errmsg = stralloc(errbuf);
700 } else {
701 rc->auth = 1;
702 }
703 auth_debug(1, _("gss_server returning %d\n"), rval);
704 return (rval);
705 }
706
707 /*
708 * Setup some things about krb5. This should only be called once.
709 */
710 static void
krb5_init(void)711 krb5_init(void)
712 {
713 static int beenhere = 0;
714 char *p;
715 char *myfqhostname=NULL;
716
717 if (beenhere)
718 return;
719 beenhere = 1;
720
721 #ifndef BROKEN_MEMORY_CCACHE
722 putenv(stralloc(KRB5_ENV_CCNAME"=MEMORY:amanda_ccache"));
723 #else
724 /*
725 * MEMORY ccaches seem buggy and cause a lot of internal heap
726 * corruption. malloc has been known to core dump. This behavior
727 * has been witnessed in Cygnus' kerbnet 1.2, MIT's krb V 1.0.5 and
728 * MIT's krb V -current as of 3/17/1999.
729 *
730 * We just use a lame ccache scheme with a uid suffix.
731 */
732 atexit(cleanup);
733 {
734 char *ccache;
735 ccache = malloc(128);
736 g_snprintf(ccache, SIZEOF(ccache),
737 KRB5_ENV_CCNAME"=FILE:/tmp/amanda_ccache.%ld.%ld",
738 (long)geteuid(), (long)getpid());
739 putenv(ccache);
740 }
741 #endif
742
743 gethostname(myhostname, SIZEOF(myhostname) - 1);
744 myhostname[SIZEOF(myhostname) - 1] = '\0';
745
746 /*
747 * In case it isn't fully qualified, do a DNS lookup. Ignore
748 * any errors (this is best-effort).
749 */
750 if (resolve_hostname(myhostname, SOCK_STREAM, NULL, &myfqhostname) == 0
751 && myfqhostname != NULL) {
752 strncpy(myhostname, myfqhostname, SIZEOF(myhostname)-1);
753 myhostname[SIZEOF(myhostname)-1] = '\0';
754 amfree(myfqhostname);
755 }
756
757 /*
758 * Lowercase the results. We assume all host/ principals will be
759 * lowercased.
760 */
761 for (p = myhostname; *p != '\0'; p++) {
762 if (isupper((int)*p))
763 *p = tolower(*p);
764 }
765 }
766
767 #ifdef BROKEN_MEMORY_CCACHE
768 static void
cleanup(void)769 cleanup(void)
770 {
771 #ifdef KDESTROY_VIA_UNLINK
772 char ccache[64];
773 g_snprintf(ccache, SIZEOF(ccache), "/tmp/amanda_ccache.%ld.%ld",
774 (long)geteuid(), (long)getpid());
775 unlink(ccache);
776 #else
777 kdestroy();
778 #endif
779 }
780 #endif
781
782 /*
783 * Get a ticket granting ticket and stuff it in the cache
784 */
785 static const char *
get_tgt(char * keytab_name,char * principal_name)786 get_tgt(
787 char * keytab_name,
788 char * principal_name)
789 {
790 krb5_context context;
791 krb5_error_code ret;
792 krb5_principal client = NULL, server = NULL;
793 krb5_creds creds;
794 krb5_keytab keytab;
795 krb5_ccache ccache;
796 krb5_timestamp now;
797 #ifdef KRB5_HEIMDAL_INCLUDES
798 krb5_data tgtname = { KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME };
799 #else
800 krb5_data tgtname = { 0, KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME };
801 #endif
802 static char *error = NULL;
803
804 if (error != NULL) {
805 amfree(error);
806 error = NULL;
807 }
808 if ((ret = krb5_init_context(&context)) != 0) {
809 error = vstrallocf(_("error initializing krb5 context: %s"),
810 error_message(ret));
811 return (error);
812 }
813
814 /*krb5_init_ets(context);*/
815
816 if(!keytab_name) {
817 error = vstrallocf(_("error -- no krb5 keytab defined"));
818 return(error);
819 }
820
821 if(!principal_name) {
822 error = vstrallocf(_("error -- no krb5 principal defined"));
823 return(error);
824 }
825
826 /*
827 * Resolve keytab file into a keytab object
828 */
829 if ((ret = krb5_kt_resolve(context, keytab_name, &keytab)) != 0) {
830 error = vstrallocf(_("error resolving keytab %s: %s"), keytab_name,
831 error_message(ret));
832 return (error);
833 }
834
835 /*
836 * Resolve the amanda service held in the keytab into a principal
837 * object
838 */
839 ret = krb5_parse_name(context, principal_name, &client);
840 if (ret != 0) {
841 error = vstrallocf(_("error parsing %s: %s"), principal_name,
842 error_message(ret));
843 return (error);
844 }
845
846 #ifdef KRB5_HEIMDAL_INCLUDES
847 ret = krb5_build_principal_ext(context, &server,
848 krb5_realm_length(*krb5_princ_realm(context, client)),
849 krb5_realm_data(*krb5_princ_realm(context, client)),
850 tgtname.length, tgtname.data,
851 krb5_realm_length(*krb5_princ_realm(context, client)),
852 krb5_realm_data(*krb5_princ_realm(context, client)),
853 0);
854 #else
855 ret = krb5_build_principal_ext(context, &server,
856 krb5_princ_realm(context, client)->length,
857 krb5_princ_realm(context, client)->data,
858 tgtname.length, tgtname.data,
859 krb5_princ_realm(context, client)->length,
860 krb5_princ_realm(context, client)->data,
861 0);
862 #endif
863 if (ret != 0) {
864 error = vstrallocf(_("error while building server name: %s"),
865 error_message(ret));
866 return (error);
867 }
868
869 ret = krb5_timeofday(context, &now);
870 if (ret != 0) {
871 error = vstrallocf(_("error getting time of day: %s"), error_message(ret));
872 return (error);
873 }
874
875 memset(&creds, 0, SIZEOF(creds));
876 creds.times.starttime = 0;
877 creds.times.endtime = now + AMANDA_TKT_LIFETIME;
878
879 creds.client = client;
880 creds.server = server;
881
882 /*
883 * Get a ticket for the service, using the keytab
884 */
885 ret = krb5_get_in_tkt_with_keytab(context, 0, NULL, NULL, NULL,
886 keytab, 0, &creds, 0);
887
888 if (ret != 0) {
889 error = vstrallocf(_("error getting ticket for %s: %s"),
890 principal_name, error_message(ret));
891 goto cleanup2;
892 }
893
894 if ((ret = krb5_cc_default(context, &ccache)) != 0) {
895 error = vstrallocf(_("error initializing ccache: %s"), error_message(ret));
896 goto cleanup;
897 }
898 if ((ret = krb5_cc_initialize(context, ccache, client)) != 0) {
899 error = vstrallocf(_("error initializing ccache: %s"), error_message(ret));
900 goto cleanup;
901 }
902 if ((ret = krb5_cc_store_cred(context, ccache, &creds)) != 0) {
903 error = vstrallocf(_("error storing creds in ccache: %s"),
904 error_message(ret));
905 /* FALLTHROUGH */
906 }
907 krb5_cc_close(context, ccache);
908 cleanup:
909 krb5_free_cred_contents(context, &creds);
910 cleanup2:
911 #if 0
912 krb5_free_principal(context, client);
913 krb5_free_principal(context, server);
914 #endif
915 krb5_free_context(context);
916 return (error);
917 }
918
919 #ifndef KDESTROY_VIA_UNLINK
920 /*
921 * get rid of tickets
922 */
923 static void
kdestroy(void)924 kdestroy(void)
925 {
926 krb5_context context;
927 krb5_ccache ccache;
928
929 if ((krb5_init_context(&context)) != 0) {
930 return;
931 }
932 if ((krb5_cc_default(context, &ccache)) != 0) {
933 goto cleanup;
934 }
935
936 krb5_cc_destroy(context, ccache);
937 krb5_cc_close(context, ccache);
938
939 cleanup:
940 krb5_free_context(context);
941 return;
942 }
943 #endif
944
945 /*
946 * Formats an error from the gss api
947 */
948 static const char *
gss_error(OM_uint32 major,OM_uint32 minor)949 gss_error(
950 OM_uint32 major,
951 OM_uint32 minor)
952 {
953 static gss_buffer_desc msg;
954 OM_uint32 min_stat, msg_ctx;
955
956 if (msg.length > 0)
957 gss_release_buffer(&min_stat, &msg);
958
959 msg_ctx = 0;
960 if (major == GSS_S_FAILURE)
961 gss_display_status(&min_stat, minor, GSS_C_MECH_CODE, GSS_C_NULL_OID,
962 &msg_ctx, &msg);
963 else
964 gss_display_status(&min_stat, major, GSS_C_GSS_CODE, GSS_C_NULL_OID,
965 &msg_ctx, &msg);
966 return ((const char *)msg.value);
967 }
968
969 static int
k5_encrypt(void * cookie,void * buf,ssize_t buflen,void ** encbuf,ssize_t * encbuflen)970 k5_encrypt(
971 void *cookie,
972 void *buf,
973 ssize_t buflen,
974 void **encbuf,
975 ssize_t *encbuflen)
976 {
977 struct tcp_conn *rc = cookie;
978 gss_buffer_desc dectok;
979 gss_buffer_desc enctok;
980 OM_uint32 maj_stat, min_stat;
981 int conf_state;
982
983 if (rc->conf_fn && rc->conf_fn("kencrypt", rc->datap)) {
984 auth_debug(1, _("krb5: k5_encrypt: enter %p\n"), rc);
985
986 dectok.length = buflen;
987 dectok.value = buf;
988
989 if (rc->auth == 1) {
990 assert(rc->gss_context != GSS_C_NO_CONTEXT);
991 maj_stat = gss_seal(&min_stat, rc->gss_context, 1,
992 GSS_C_QOP_DEFAULT, &dectok, &conf_state,
993 &enctok);
994 if (maj_stat != (OM_uint32)GSS_S_COMPLETE || conf_state == 0) {
995 auth_debug(1, _("krb5 encrypt error to %s: %s\n"),
996 rc->hostname, gss_error(maj_stat, min_stat));
997 return (-1);
998 }
999 auth_debug(1, _("krb5: k5_encrypt: give %zu bytes\n"),
1000 enctok.length);
1001 *encbuf = enctok.value;
1002 *encbuflen = enctok.length;
1003 } else {
1004 *encbuf = buf;
1005 *encbuflen = buflen;
1006 }
1007 auth_debug(1, _("krb5: k5_encrypt: exit\n"));
1008 }
1009 return (0);
1010 }
1011
1012
1013 static int
k5_decrypt(void * cookie,void * buf,ssize_t buflen,void ** decbuf,ssize_t * decbuflen)1014 k5_decrypt(
1015 void *cookie,
1016 void *buf,
1017 ssize_t buflen,
1018 void **decbuf,
1019 ssize_t *decbuflen)
1020 {
1021 struct tcp_conn *rc = cookie;
1022 gss_buffer_desc enctok;
1023 gss_buffer_desc dectok;
1024 OM_uint32 maj_stat, min_stat;
1025 int conf_state, qop_state;
1026
1027 if (rc->conf_fn && rc->conf_fn("kencrypt", rc->datap)) {
1028 auth_debug(1, _("krb5: k5_decrypt: enter\n"));
1029 if (rc->auth == 1) {
1030 enctok.length = buflen;
1031 enctok.value = buf;
1032
1033 auth_debug(1, _("krb5: k5_decrypt: decrypting %zu bytes\n"), enctok.length);
1034
1035 assert(rc->gss_context != GSS_C_NO_CONTEXT);
1036 maj_stat = gss_unseal(&min_stat, rc->gss_context, &enctok, &dectok,
1037 &conf_state, &qop_state);
1038 if (maj_stat != (OM_uint32)GSS_S_COMPLETE) {
1039 auth_debug(1, _("krb5 decrypt error from %s: %s\n"),
1040 rc->hostname, gss_error(maj_stat, min_stat));
1041 return (-1);
1042 }
1043 auth_debug(1, _("krb5: k5_decrypt: give %zu bytes\n"),
1044 dectok.length);
1045 *decbuf = dectok.value;
1046 *decbuflen = dectok.length;
1047 } else {
1048 *decbuf = buf;
1049 *decbuflen = buflen;
1050 }
1051 auth_debug(1, _("krb5: k5_decrypt: exit\n"));
1052 } else {
1053 *decbuf = buf;
1054 *decbuflen = buflen;
1055 }
1056 return (0);
1057 }
1058
1059 /*
1060 * check ~/.k5amandahosts to see if this principal is allowed in. If it's
1061 * hardcoded, then we don't check the realm
1062 */
1063 static char *
krb5_checkuser(char * host,char * name,char * realm)1064 krb5_checkuser( char * host,
1065 char * name,
1066 char * realm)
1067 {
1068 #ifdef AMANDA_PRINCIPAL
1069 if(strcmp(name, AMANDA_PRINCIPAL) == 0) {
1070 return(NULL);
1071 } else {
1072 return(vstrallocf(_("does not match compiled in default")));
1073 }
1074 #else
1075 struct passwd *pwd;
1076 char *ptmp;
1077 char *result = _("generic error"); /* default is to not permit */
1078 FILE *fp = NULL;
1079 struct stat sbuf;
1080 uid_t localuid;
1081 char *line = NULL;
1082 char *filehost = NULL, *fileuser = NULL, *filerealm = NULL;
1083
1084 assert( host != NULL);
1085 assert( name != NULL);
1086
1087 if((pwd = getpwnam(CLIENT_LOGIN)) == NULL) {
1088 result = vstrallocf(_("can not find user %s"), CLIENT_LOGIN);
1089 }
1090 localuid = pwd->pw_uid;
1091
1092 #ifdef USE_AMANDAHOSTS
1093 ptmp = stralloc2(pwd->pw_dir, "/.k5amandahosts");
1094 #else
1095 ptmp = stralloc2(pwd->pw_dir, "/.k5login");
1096 #endif
1097
1098 if(!ptmp) {
1099 result = vstrallocf(_("could not find home directory for %s"), CLIENT_LOGIN);
1100 goto common_exit;
1101 }
1102
1103 /*
1104 * check to see if the ptmp file does nto exist.
1105 */
1106 if(access(ptmp, R_OK) == -1 && errno == ENOENT) {
1107 /*
1108 * in this case we check to see if the principal matches
1109 * the destination user mimicing the .k5login functionality.
1110 */
1111 if(strcmp(name, CLIENT_LOGIN) != 0) {
1112 result = vstrallocf(_("%s does not match %s"),
1113 name, CLIENT_LOGIN);
1114 return result;
1115 }
1116 result = NULL;
1117 goto common_exit;
1118 }
1119
1120 auth_debug(1, _("opening ptmp: %s\n"), (ptmp)?ptmp: "NULL!");
1121 if((fp = fopen(ptmp, "r")) == NULL) {
1122 result = vstrallocf(_("can not open %s"), ptmp);
1123 return result;
1124 }
1125 auth_debug(1, _("opened ptmp\n"));
1126
1127 if (fstat(fileno(fp), &sbuf) != 0) {
1128 result = vstrallocf(_("cannot fstat %s: %s"), ptmp, strerror(errno));
1129 goto common_exit;
1130 }
1131
1132 if (sbuf.st_uid != localuid) {
1133 result = vstrallocf(_("%s is owned by %ld, should be %ld"),
1134 ptmp, (long)sbuf.st_uid, (long)localuid);
1135 goto common_exit;
1136 }
1137 if ((sbuf.st_mode & 077) != 0) {
1138 result = vstrallocf(
1139 _("%s: incorrect permissions; file must be accessible only by its owner"), ptmp);
1140 goto common_exit;
1141 }
1142
1143 while ((line = agets(fp)) != NULL) {
1144 if (line[0] == '\0') {
1145 amfree(line);
1146 continue;
1147 }
1148
1149 /* if there's more than one column, then it's the host */
1150 if( (filehost = strtok(line, " \t")) == NULL) {
1151 amfree(line);
1152 continue;
1153 }
1154
1155 /*
1156 * if there's only one entry, then it's a username and we have
1157 * no hostname. (so the principal is allowed from anywhere.
1158 */
1159 if((fileuser = strtok(NULL, " \t")) == NULL) {
1160 fileuser = filehost;
1161 filehost = NULL;
1162 }
1163
1164 if(filehost && strcmp(filehost, host) != 0) {
1165 amfree(line);
1166 continue;
1167 } else {
1168 auth_debug(1, _("found a host match\n"));
1169 }
1170
1171 if( (filerealm = strchr(fileuser, '@')) != NULL) {
1172 *filerealm++ = '\0';
1173 }
1174
1175 /*
1176 * we have a match. We're going to be a little bit insecure
1177 * and indicate that the principal is correct but the realm is
1178 * not if that's the case. Technically we should say nothing
1179 * and let the user figure it out, but it's helpful for debugging.
1180 * You likely only get this far if you've turned on cross-realm auth
1181 * anyway...
1182 */
1183 auth_debug(1, _("comparing %s %s\n"), fileuser, name);
1184 if(strcmp(fileuser, name) == 0) {
1185 auth_debug(1, _("found a match!\n"));
1186 if(realm && filerealm && (strcmp(realm, filerealm)!=0)) {
1187 amfree(line);
1188 continue;
1189 }
1190 result = NULL;
1191 amfree(line);
1192 goto common_exit;
1193 }
1194 amfree(line);
1195 }
1196 result = vstrallocf(_("no match in %s"), ptmp);
1197
1198 common_exit:
1199 afclose(fp);
1200 return(result);
1201 #endif /* AMANDA_PRINCIPAL */
1202 }
1203
1204 /*
1205 * return -1 on error
1206 * return 0 on EOF: *handle = H_EOF && *size = 0 if socket closed
1207 * return 0 on EOF: *handle = handle && *size = 0 if stream closed
1208 * return size : *handle = handle && *size = size for data read
1209 */
1210
1211 static ssize_t
krb5_tcpm_recv_token(struct tcp_conn * rc,int fd,int * handle,char ** errmsg,char ** buf,ssize_t * size,int timeout)1212 krb5_tcpm_recv_token(
1213 struct tcp_conn *rc,
1214 int fd,
1215 int * handle,
1216 char ** errmsg,
1217 char ** buf,
1218 ssize_t * size,
1219 int timeout)
1220 {
1221 unsigned int netint[2];
1222
1223 assert(SIZEOF(netint) == 8);
1224
1225 switch (net_read(fd, &netint, SIZEOF(netint), timeout)) {
1226 case -1:
1227 if (errmsg)
1228 *errmsg = newvstrallocf(*errmsg, _("recv error: %s"), strerror(errno));
1229 auth_debug(1, _("krb5_tcpm_recv_token: A return(-1)\n"));
1230 return (-1);
1231 case 0:
1232 *size = 0;
1233 *handle = H_EOF;
1234 *errmsg = newvstrallocf(*errmsg, _("SOCKET_EOF"));
1235 auth_debug(1, _("krb5_tcpm_recv_token: A return(0)\n"));
1236 return (0);
1237 default:
1238 break;
1239 }
1240
1241 *size = (ssize_t)ntohl(netint[0]);
1242 *handle = (int)ntohl(netint[1]);
1243 /* amanda protocol packet can be above NETWORK_BLOCK_BYTES */
1244 if (*size > 128*NETWORK_BLOCK_BYTES || *size < 0) {
1245 if (isprint((int)(*size ) & 0xFF) &&
1246 isprint((int)(*size >> 8 ) & 0xFF) &&
1247 isprint((int)(*size >> 16) & 0xFF) &&
1248 isprint((int)(*size >> 24) & 0xFF) &&
1249 isprint((*handle ) & 0xFF) &&
1250 isprint((*handle >> 8 ) & 0xFF) &&
1251 isprint((*handle >> 16) & 0xFF) &&
1252 isprint((*handle >> 24) & 0xFF)) {
1253 char s[101];
1254 int i;
1255 s[0] = ((int)(*size) >> 24) & 0xFF;
1256 s[1] = ((int)(*size) >> 16) & 0xFF;
1257 s[2] = ((int)(*size) >> 8) & 0xFF;
1258 s[3] = ((int)(*size) ) & 0xFF;
1259 s[4] = (*handle >> 24) & 0xFF;
1260 s[5] = (*handle >> 16) & 0xFF;
1261 s[6] = (*handle >> 8 ) & 0xFF;
1262 s[7] = (*handle ) & 0xFF;
1263 i = 8; s[i] = ' ';
1264 while(i<100 && isprint((int)s[i]) && s[i] != '\n') {
1265 switch(net_read(fd, &s[i], 1, 0)) {
1266 case -1: s[i] = '\0'; break;
1267 case 0: s[i] = '\0'; break;
1268 default:
1269 dbprintf(_("read: %c\n"), s[i]); i++; s[i]=' ';
1270 break;
1271 }
1272 }
1273 s[i] = '\0';
1274 *errmsg = newvstrallocf(*errmsg, _("krb5_tcpm_recv_token: invalid size: %s"), s);
1275 dbprintf(_("krb5_tcpm_recv_token: invalid size %s\n"), s);
1276 } else {
1277 *errmsg = newvstrallocf(*errmsg, _("krb5_tcpm_recv_token: invalid size"));
1278 dbprintf(_("krb5_tcpm_recv_token: invalid size %zd\n"), *size);
1279 }
1280 *size = -1;
1281 return -1;
1282 }
1283 amfree(*buf);
1284 *buf = alloc((size_t)*size);
1285
1286 if(*size == 0) {
1287 auth_debug(1, _("krb5_tcpm_recv_token: read EOF from %d\n"), *handle);
1288 *errmsg = newvstrallocf(*errmsg, _("EOF"));
1289 return 0;
1290 }
1291 switch (net_read(fd, *buf, (size_t)*size, timeout)) {
1292 case -1:
1293 if (errmsg)
1294 *errmsg = newvstrallocf(*errmsg, _("recv error: %s"), strerror(errno));
1295 auth_debug(1, _("krb5_tcpm_recv_token: B return(-1)\n"));
1296 return (-1);
1297 case 0:
1298 *size = 0;
1299 *errmsg = newvstrallocf(*errmsg, _("SOCKET_EOF"));
1300 auth_debug(1, _("krb5_tcpm_recv_token: B return(0)\n"));
1301 return (0);
1302 default:
1303 break;
1304 }
1305
1306 auth_debug(1, _("krb5_tcpm_recv_token: read %zd bytes from %d\n"), *size, *handle);
1307
1308 if (*size > 0 && rc->driver->data_decrypt != NULL) {
1309 void *decbuf;
1310 ssize_t decsize;
1311 rc->driver->data_decrypt(rc, *buf, *size, &decbuf, &decsize);
1312 if (*buf != (char *)decbuf) {
1313 amfree(*buf);
1314 *buf = (char *)decbuf;
1315 }
1316 *size = decsize;
1317 }
1318
1319 return((*size));
1320 }
1321
1322