📄 krb4-security.c
字号:
/* * Amanda, The Advanced Maryland Automatic Network Disk Archiver * Copyright (c) 1993,1999 University of Maryland * All Rights Reserved. * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of U.M. not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. U.M. makes no representations about the * suitability of this software for any purpose. It is provided "as is" * without express or implied warranty. * * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M. * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Authors: the Amanda Development Team. Its members are listed in a * file named AUTHORS, in the root directory of this distribution. *//* * $Id: krb4-security.c,v 1.18 2006/07/13 03:22:20 paddy_s Exp $ * * krb4-security.c - helper functions for kerberos v4 security. */#include "config.h"#include <des.h>#include <krb.h>#include "amanda.h"#include "dgram.h"#include "event.h"#include "packet.h"#include "queue.h"#include "security.h"#include "security-util.h"#include "protocol.h"#include "stream.h"#include "version.h"/* * If you don't have atexit() or on_exit(), you could just consider * making atexit() empty and clean up your ticket files some other * way */#ifndef HAVE_ATEXIT#ifdef HAVE_ON_EXIT#define atexit(func) on_exit(func, 0)#else#define atexit(func) (you must to resolve lack of atexit)#endif /* HAVE_ON_EXIT */#endif /* ! HAVE_ATEXIT */int krb_set_lifetime(int);int kuserok(AUTH_DAT *, char *);/* * This is the private handle data */struct krb4_handle { security_handle_t sech; /* MUST be first */ struct sockaddr_in6 peer; /* host on other side */ char hostname[MAX_HOSTNAME_LENGTH+1]; /* human form of above */ char proto_handle[32]; /* protocol handle for this req */ int sequence; /* last sequence number we received */ char inst[INST_SZ]; /* krb4 instance form of above */ char realm[REALM_SZ]; /* krb4 realm of this host */ unsigned long cksum; /* cksum of the req packet we sent */ des_cblock session_key; /* session key */ /* * The rest is used for the async recvpkt/recvpkt_cancel * interface. */ void (*fn)(void *, pkt_t *, security_status_t); /* func to call when packet recvd */ void *arg; /* argument to pass function */ event_handle_t *ev_timeout; /* timeout handle for recv */ TAILQ_ENTRY(krb4_handle) tq; /* queue handle */};/* * This is the internal security_stream data for krb4. */struct krb4_stream { security_stream_t secstr; /* MUST be first */ struct krb4_handle *krb4_handle; /* pointer into above */ int fd; /* io file descriptor */ in_port_t port; /* local port this is bound to */ int socket; /* fd for server-side accepts */ event_handle_t *ev_read; /* read event handle */ char databuf[MAX_TAPE_BLOCK_BYTES]; /* read buffer */ int len; /* */ void (*fn)(void *, void *, ssize_t);/* read event fn */ void *arg; /* arg for previous */};/* * This is the tcp stream buffer size */#ifndef STREAM_BUFSIZE#define STREAM_BUFSIZE (MAX_TAPE_BLOCK_BYTES * 2)#endif/* * Interface functions */static void krb4_connect(const char *, char *(*)(char *, void *), void (*)(void *, security_handle_t *, security_status_t), void *, void *);static void krb4_accept(const struct security_driver *, int, int, void (*)(security_handle_t *, pkt_t *));static void krb4_close(void *);static int krb4_sendpkt(void *, pkt_t *);static void krb4_recvpkt(void *, void (*)(void *, pkt_t *, security_status_t), void *, int);static void krb4_recvpkt_cancel(void *);static void * krb4_stream_server(void *);static int krb4_stream_accept(void *);static void * krb4_stream_client(void *, int);static void krb4_stream_close(void *);static int krb4_stream_auth(void *);static int krb4_stream_id(void *);static int krb4_stream_write(void *, const void *, size_t);static void krb4_stream_read(void *, void (*)(void *, void *, int), void *);static int krb4_stream_read_sync(void *, void **);static void krb4_stream_read_cancel(void *);/* * This is our interface to the outside world. */const security_driver_t krb4_security_driver = { "KRB4", krb4_connect, krb4_accept, krb4_close, krb4_sendpkt, krb4_recvpkt, krb4_recvpkt_cancel, krb4_stream_server, krb4_stream_accept, krb4_stream_client, krb4_stream_close, krb4_stream_auth, krb4_stream_id, krb4_stream_write, krb4_stream_read, krb4_stream_read_sync, krb4_stream_read_cancel, sec_close_connection_none, NULL, NULL};/* * Cache the local hostname */static char hostname[MAX_HOSTNAME_LENGTH+1];/* * This is the dgram_t that we use to send and recv protocol packets * over the net. There is only one per process, so it lives globally * here. */static dgram_t netfd;/* * This is a queue of outstanding async requests */static struct { TAILQ_HEAD(, krb4_handle) tailq; int qlength;} handleq = { TAILQ_HEAD_INITIALIZER(handleq.tailq), 0};/* * Macros to add or remove krb4_handles from the above queue */#define handleq_add(kh) do { \ assert(handleq.qlength == 0 ? TAILQ_FIRST(&handleq.tailq) == NULL : 1); \ TAILQ_INSERT_TAIL(&handleq.tailq, kh, tq); \ handleq.qlength++; \} while (0)#define handleq_remove(kh) do { \ assert(handleq.qlength > 0); \ assert(TAILQ_FIRST(&handleq.tailq) != NULL); \ TAILQ_REMOVE(&handleq.tailq, kh, tq); \ handleq.qlength--; \ assert((handleq.qlength == 0) ^ (TAILQ_FIRST(&handleq.tailq) != NULL)); \} while (0)#define handleq_first() TAILQ_FIRST(&handleq.tailq)#define handleq_next(kh) TAILQ_NEXT(kh, tq)/* * This is the event manager's handle for our netfd */static event_handle_t *ev_netfd;/* * This is a function that should be called if a new security_handle_t is * created. If NULL, no new handles are created. * It is passed the new handle and the received pkt */static void (*accept_fn)(security_handle_t *, pkt_t *);/* * This is a structure used in encoding the cksum in a mutual-auth * transaction. The checksum is placed in here first before encryption * because encryption requires at least 8 bytes of data, and an unsigned * long on most machines (32 bit ones) is 4 bytes. */union mutual { char pad[8]; unsigned long cksum;};/* * Private functions */static unsigned long krb4_cksum(const char *);static void krb4_getinst(const char *, char *, size_t);static void host2key(const char *, const char *, des_cblock *);static void init(void);static void inithandle(struct krb4_handle *, struct hostent *, int, const char *);static void get_tgt(void);static void killtickets(void);static void recvpkt_callback(void *);static void recvpkt_timeout(void *);static int recv_security_ok(struct krb4_handle *, pkt_t *);static void stream_read_callback(void *);static void stream_read_sync_callback(void *);static int knet_write(int, const void *, size_t);static int knet_read(int, void *, size_t, int);static int add_ticket(struct krb4_handle *, const pkt_t *, dgram_t *);static void add_mutual_auth(struct krb4_handle *, dgram_t *);static int check_ticket(struct krb4_handle *, const pkt_t *, const char *, unsigned long);static int check_mutual_auth(struct krb4_handle *, const char *);static const char *kpkthdr2str(const struct krb4_handle *, const pkt_t *);static int str2kpkthdr(const char *, pkt_t *, char *, size_t, int *);static const char *bin2astr(const unsigned char *, int);static void astr2bin(const unsigned char *, unsigned char *, int *);static void encrypt_data(void *, size_t, des_cblock *);static void decrypt_data(void *, size_t, des_cblock *);#define HOSTNAME_INSTANCE inststatic char *ticketfilename = NULL;static voidkilltickets(void){ if (ticketfilename != NULL) unlink(ticketfilename); amfree(ticketfilename);}/* * Setup some things about krb4. This should only be called once. */static voidinit(void){ char tktfile[256]; in_port_t port; static int beenhere = 0; if (beenhere) return; beenhere = 1; gethostname(hostname, SIZEOF(hostname) - 1); hostname[SIZEOF(hostname) - 1] = '\0'; if (atexit(killtickets) < 0) error(_("could not setup krb4 exit handler: %s"), strerror(errno)); /* * [XXX] It could be argued that if KRBTKFILE is set outside of amanda, * that it's value should be used instead of us setting one up. * This file also needs to be removed so that no extra tickets are * hanging around. */ g_snprintf(tktfile, SIZEOF(tktfile), "/tmp/tkt%ld-%ld.amanda", (long)getuid(), (long)getpid()); ticketfilename = stralloc(tktfile); unlink(ticketfilename); krb_set_tkt_string(ticketfilename);#if defined(HAVE_PUTENV) { char *tkt_env = stralloc2("KRBTKFILE=", ticketfilename); putenv(tkt_env); }#else setenv("KRBTKFILE", ticketfile, 1);#endif dgram_zero(&netfd); dgram_bind(&netfd, &port);}/* * Get a ticket granting ticket and stuff it in the cache */static voidget_tgt(void){ char realm[REALM_SZ]; int rc; strncpy(realm, krb_realmofhost(hostname), SIZEOF(realm) - 1); realm[SIZEOF(realm) - 1] = '\0'; rc = krb_get_svc_in_tkt(SERVER_HOST_PRINCIPAL, SERVER_HOST_INSTANCE, realm, "krbtgt", realm, TICKET_LIFETIME, SERVER_HOST_KEY_FILE); if (rc != 0) { error(_("could not get krbtgt for %s.%s@%s from %s: %s"), SERVER_HOST_PRINCIPAL, SERVER_HOST_INSTANCE, realm, SERVER_HOST_KEY_FILE, krb_err_txt[rc]); } krb_set_lifetime(TICKET_LIFETIME);}/* * krb4 version of a security handle allocator. Logically sets * up a network "connection". */static voidkrb4_connect( const char *hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap){ struct krb4_handle *kh; char handle[32]; struct servent *se; struct hostent *he; in_port_t port; (void)conf_fn; /* Quiet unused parameter warning */ (void)datap; /* Quiet unused parameter warning */ assert(hostname != NULL); /* * Make sure we're initted */ init(); kh = alloc(SIZEOF(*kh)); security_handleinit(&kh->sech, &krb4_security_driver); if ((he = gethostbyname(hostname)) == NULL) { security_seterror(&kh->sech, _("%s: could not resolve hostname"), hostname); (*fn)(arg, &kh->sech, S_ERROR); return; } if ((se = getservbyname(KAMANDA_SERVICE_NAME, "udp")) == NULL) port = (int)KAMANDA_SERVICE_DEFAULT; else port = ntohs(se->s_port); g_snprintf(handle, SIZEOF(handle), "%ld", (long)time(NULL)); inithandle(kh, he, (int)port, handle); (*fn)(arg, &kh->sech, S_OK);}/* * Setup to handle new incoming connections */static voidkrb4_accept( const struct security_driver *driver, int in, int out, void (*fn)(security_handle_t *, pkt_t *)){ (void)driver; /* Quiet unused parameter warning */ (void)out; /* Quiet unused parameter warning */ /* * Make sure we're initted */ init(); /* * We assume that in and out both point to the same socket */ dgram_socket(&netfd, in); /* * Assign the function and return. When they call recvpkt later, * the recvpkt callback will call this function when it discovers * new incoming connections */ accept_fn = fn; if (ev_netfd == NULL) ev_netfd = event_register((event_id_t)netfd.socket, EV_READFD, recvpkt_callback, NULL);}/* * Given a hostname and a port, setup a krb4_handle */static voidinithandle( struct krb4_handle *kh, struct hostent * he, int port, const char * handle){ /* * Get the instance and realm for this host * (krb_realmofhost always returns something) */ krb4_getinst(he->h_name, kh->inst, SIZEOF(kh->inst)); strncpy(kh->realm, krb_realmofhost(he->h_name), SIZEOF(kh->realm) - 1); kh->realm[SIZEOF(kh->realm) - 1] = '\0'; /* * Save a copy of the hostname */ strncpy(kh->hostname, he->h_name, SIZEOF(kh->hostname) - 1); kh->hostname[SIZEOF(kh->hostname) - 1] = '\0'; /* * We have no checksum or session key at this point */ kh->cksum = 0; memset(kh->session_key, 0, SIZEOF(kh->session_key)); /* * Setup our peer info. We don't do anything with the sequence yet, * so just leave it at 0. */ kh->peer.sin6_family = (sa_family_t)AF_INET6; kh->peer.sin6_port = (in_port_t)port; kh->peer.sin6_addr = *(struct in6_addr *)he->h_addr; strncpy(kh->proto_handle, handle, SIZEOF(kh->proto_handle) - 1); kh->proto_handle[SIZEOF(kh->proto_handle) - 1] = '\0'; kh->sequence = 0; kh->fn = NULL; kh->arg = NULL; kh->ev_timeout = NULL;}/* * frees a handle allocated by the above */static voidkrb4_close( void * inst){ krb4_recvpkt_cancel(inst); amfree(inst);}/* * Transmit a packet. Add security information first. */static ssize_tkrb4_sendpkt( void * cookie, pkt_t * pkt){ struct krb4_handle *kh = cookie; assert(kh != NULL); assert(pkt != NULL); /* * Initialize this datagram */ dgram_zero(&netfd); /* * Add the header to the packet */ dgram_cat(&netfd, kpkthdr2str(kh, pkt)); /* * Add the security info. This depends on which kind of packet we're * sending. */ switch (pkt->type) { case P_REQ: /* * Requests get sent with a ticket embedded in the header. The * checksum is generated from the contents of the body. */ if (add_ticket(kh, pkt, &netfd) < 0) return (-1); break; case P_REP: /* * Replies get sent with a mutual authenticator added. The * mutual authenticator is the encrypted checksum from the * ticket + 1 */ add_mutual_auth(kh, &netfd); break; case P_ACK: case P_NAK: default: /* * The other types have no security stuff added for krb4. * Shamefull. */ break; } /* * Add the body, and send it */ dgram_cat(&netfd, pkt->body); if (dgram_send_addr(&kh->peer, &netfd) != 0) { security_seterror(&kh->sech, _("send %s to %s failed: %s"), pkt_type2str(pkt->type), kh->hostname, strerror(errno)); return (-1); } return (0);}/* * Set up to receive a packet asyncronously, and call back when * it has been read. */static voidkrb4_recvpkt( void * cookie, void (*fn)(void *, pkt_t *, security_status_t), void * arg, int timeout){ struct krb4_handle *kh = cookie; assert(netfd.socket >= 0); assert(kh != NULL); /* * We register one event handler for our network fd which takes * care of all of our async requests. When all async requests * have either been satisfied or cancelled, we unregister our * network event handler. */ if (ev_netfd == NULL) { assert(handleq.qlength == 0); ev_netfd = event_register((event_id_t)netfd.socket, EV_READFD, recvpkt_callback, NULL); } /* * Multiple recvpkt calls override previous ones * If kh->fn is NULL then it is not in the queue. */ if (kh->fn == NULL) handleq_add(kh); if (kh->ev_timeout != NULL) event_release(kh->ev_timeout); if (timeout < 0) kh->ev_timeout = NULL; else kh->ev_timeout = event_register((event_id_t)timeout, EV_TIME, recvpkt_timeout, kh); kh->fn = fn; kh->arg = arg;}/* * Remove a async receive request from the queue * If it is the last one to be removed, then remove the event handler
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -