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