freebsd-src/sys/netkey/key.c
Garrett Wollman bd22f58e11 This is the `netkey' kernel key-management service (the PF_KEY analogue
to PF_ROUTE) from NRL's IPv6 distribution, heavily modified by me for
better source layout, formatting, and textual conventions.  I am told
that this code is no longer under active development, but it's a useful
hack for those interested in doing work on network security, key management,
etc.  This code has only been tested twice, so it should be considered
highly experimental.

Obtained from: ftp.ripe.net
1996-06-14 17:22:18 +00:00

2271 lines
65 KiB
C

/*----------------------------------------------------------------------
key.c : Key Management Engine for BSD
Copyright 1995 by Bao Phan, Randall Atkinson, & Dan McDonald,
All Rights Reserved. All Rights have been assigned to the US
Naval Research Laboratory (NRL). The NRL Copyright Notice and
License governs distribution and use of this software.
Patents are pending on this technology. NRL grants a license
to use this technology at no cost under the terms below with
the additional requirement that software, hardware, and
documentation relating to use of this technology must include
the note that:
This product includes technology developed at and
licensed from the Information Technology Division,
US Naval Research Laboratory.
----------------------------------------------------------------------*/
/*----------------------------------------------------------------------
# @(#)COPYRIGHT 1.1a (NRL) 17 August 1995
COPYRIGHT NOTICE
All of the documentation and software included in this software
distribution from the US Naval Research Laboratory (NRL) are
copyrighted by their respective developers.
This software and documentation were developed at NRL by various
people. Those developers have each copyrighted the portions that they
developed at NRL and have assigned All Rights for those portions to
NRL. Outside the USA, NRL also has copyright on the software
developed at NRL. The affected files all contain specific copyright
notices and those notices must be retained in any derived work.
NRL LICENSE
NRL grants permission for redistribution and use in source and binary
forms, with or without modification, of the software and documentation
created at NRL provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
This product includes software developed at the Information
Technology Division, US Naval Research Laboratory.
4. Neither the name of the NRL nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THE SOFTWARE PROVIDED BY NRL IS PROVIDED BY NRL AND CONTRIBUTORS ``AS
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NRL OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation
are those of the authors and should not be interpreted as representing
official policies, either expressed or implied, of the US Naval
Research Laboratory (NRL).
----------------------------------------------------------------------*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/domain.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/time.h>
#include <net/raw_cb.h>
#include <net/if.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/if_ether.h>
#ifdef INET6
#include <netinet6/in6.h>
#include <netinet6/in6_var.h>
#endif /* INET6 */
#include <netkey/key.h>
#include <netkey/key_debug.h>
#define SOCKADDR struct sockaddr
#define KMALLOC(p, t, n) (p = (t) malloc((unsigned long)(n), M_SECA, M_DONTWAIT))
#define KFREE(p) free((caddr_t)p, M_SECA);
#define CRITICAL_DCL int critical_s;
#define CRITICAL_START critical_s = splnet()
#define CRITICAL_END splx(critical_s)
#define TIME_SECONDS time.tv_sec
#define CURRENT_PID curproc->p_pid
#define DEFARGS(arglist, args) arglist args;
#define AND ;
#ifdef INET6
#define MAXHASHKEYLEN (2 * sizeof(int) + 2 * sizeof(struct sockaddr_in6))
#else
#define MAXHASHKEYLEN (2 * sizeof(int) + 2 * sizeof(struct sockaddr_in))
#endif
/*
* Not clear whether these values should be
* tweakable at kernel config time.
*/
#define KEYTBLSIZE 61
#define KEYALLOCTBLSIZE 61
#define SO2SPITBLSIZE 61
/*
* These values should be tweakable...
* perhaps by using sysctl
*/
#define MAXLARVALTIME 240; /* Lifetime of a larval key table entry */
#define MAXKEYACQUIRE 1; /* Max number of key acquire messages sent */
/* per destination address */
#define MAXACQUIRETIME 15; /* Lifetime of acquire message */
/*
* Key engine tables and global variables
*/
struct key_tblnode keytable[KEYTBLSIZE];
struct key_allocnode keyalloctbl[KEYALLOCTBLSIZE];
struct key_so2spinode so2spitbl[SO2SPITBLSIZE];
struct keyso_cb keyso_cb;
struct key_tblnode nullkeynode;
struct key_registry *keyregtable;
struct key_acquirelist *key_acquirelist;
u_long maxlarvallifetime = MAXLARVALTIME;
int maxkeyacquire = MAXKEYACQUIRE;
u_long maxacquiretime = MAXACQUIRETIME;
extern SOCKADDR key_addr;
#define ROUNDUP(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
#define ADVANCE(x, n) \
{ x += ROUNDUP(n); }
static int my_addr __P((SOCKADDR *));
static int key_sendup __P((struct socket *, struct key_msghdr *));
/*----------------------------------------------------------------------
* key_secassoc2msghdr():
* Copy info from a security association into a key message buffer.
* Assume message buffer is sufficiently large to hold all security
* association information including src, dst, from, key and iv.
----------------------------------------------------------------------*/
int
key_secassoc2msghdr(struct key_secassoc *secassoc,
struct key_msghdr *km,
struct key_msgdata *keyinfo)
{
char *cp;
DPRINTF(IDL_FINISHED, ("Entering key_secassoc2msghdr\n"));
if ((km == 0) || (keyinfo == 0) || (secassoc == 0))
return(-1);
km->type = secassoc->type;
km->state = secassoc->state;
km->label = secassoc->label;
km->spi = secassoc->spi;
km->keylen = secassoc->keylen;
km->ivlen = secassoc->ivlen;
km->algorithm = secassoc->algorithm;
km->lifetype = secassoc->lifetype;
km->lifetime1 = secassoc->lifetime1;
km->lifetime2 = secassoc->lifetime2;
/*
* Stuff src/dst/from/key/iv in buffer after
* the message header.
*/
cp = (char *)(km + 1);
DPRINTF(IDL_FINISHED, ("sa2msghdr: 1\n"));
keyinfo->src = (SOCKADDR *)cp;
if (secassoc->src->sa_len) {
bcopy(secassoc->src, cp, secassoc->src->sa_len);
ADVANCE(cp, secassoc->src->sa_len);
} else {
bzero(cp, MAX_SOCKADDR_SZ);
ADVANCE(cp, MAX_SOCKADDR_SZ);
}
DPRINTF(IDL_FINISHED, ("sa2msghdr: 2\n"));
keyinfo->dst = (SOCKADDR *)cp;
if (secassoc->dst->sa_len) {
bcopy(secassoc->dst, cp, secassoc->dst->sa_len);
ADVANCE(cp, secassoc->dst->sa_len);
} else {
bzero(cp, MAX_SOCKADDR_SZ);
ADVANCE(cp, MAX_SOCKADDR_SZ);
}
DPRINTF(IDL_FINISHED, ("sa2msghdr: 3\n"));
keyinfo->from = (SOCKADDR *)cp;
if (secassoc->from->sa_len) {
bcopy(secassoc->from, cp, secassoc->from->sa_len);
ADVANCE(cp, secassoc->from->sa_len);
} else {
bzero(cp, MAX_SOCKADDR_SZ);
ADVANCE(cp, MAX_SOCKADDR_SZ);
}
DPRINTF(IDL_FINISHED, ("sa2msghdr: 4\n"));
keyinfo->key = cp;
keyinfo->keylen = secassoc->keylen;
if (secassoc->keylen) {
bcopy((char *)(secassoc->key), cp, secassoc->keylen);
ADVANCE(cp, secassoc->keylen);
}
DPRINTF(IDL_FINISHED, ("sa2msghdr: 5\n"));
keyinfo->iv = cp;
keyinfo->ivlen = secassoc->ivlen;
if (secassoc->ivlen) {
bcopy((char *)(secassoc->iv), cp, secassoc->ivlen);
ADVANCE(cp, secassoc->ivlen);
}
DDO(IDL_FINISHED,printf("msgbuf(len=%d):\n",(char *)cp - (char *)km));
DDO(IDL_FINISHED,dump_buf((char *)km, (char *)cp - (char *)km));
DPRINTF(IDL_FINISHED, ("sa2msghdr: 6\n"));
return(0);
}
/*----------------------------------------------------------------------
* key_msghdr2secassoc():
* Copy info from a key message buffer into a key_secassoc
* structure
----------------------------------------------------------------------*/
int
key_msghdr2secassoc(struct key_secassoc *secassoc,
struct key_msghdr *km,
struct key_msgdata *keyinfo)
{
DPRINTF(IDL_FINISHED, ("Entering key_msghdr2secassoc\n"));
if ((km == 0) || (keyinfo == 0) || (secassoc == 0))
return(-1);
secassoc->len = sizeof(*secassoc);
secassoc->type = km->type;
secassoc->state = km->state;
secassoc->label = km->label;
secassoc->spi = km->spi;
secassoc->keylen = km->keylen;
secassoc->ivlen = km->ivlen;
secassoc->algorithm = km->algorithm;
secassoc->lifetype = km->lifetype;
secassoc->lifetime1 = km->lifetime1;
secassoc->lifetime2 = km->lifetime2;
if (keyinfo->src) {
KMALLOC(secassoc->src, SOCKADDR *, keyinfo->src->sa_len);
if (!secassoc->src) {
DPRINTF(IDL_ERROR,("msghdr2secassoc: can't allocate mem for src\n"));
return(-1);
}
bcopy((char *)keyinfo->src, (char *)secassoc->src,
keyinfo->src->sa_len);
} else
secassoc->src = NULL;
if (keyinfo->dst) {
KMALLOC(secassoc->dst, SOCKADDR *, keyinfo->dst->sa_len);
if (!secassoc->dst) {
DPRINTF(IDL_ERROR,("msghdr2secassoc: can't allocate mem for dst\n"));
return(-1);
}
bcopy((char *)keyinfo->dst, (char *)secassoc->dst,
keyinfo->dst->sa_len);
} else
secassoc->dst = NULL;
if (keyinfo->from) {
KMALLOC(secassoc->from, SOCKADDR *, keyinfo->from->sa_len);
if (!secassoc->from) {
DPRINTF(IDL_ERROR,("msghdr2secassoc: can't allocate mem for from\n"));
return(-1);
}
bcopy((char *)keyinfo->from, (char *)secassoc->from,
keyinfo->from->sa_len);
} else
secassoc->from = NULL;
/*
* Make copies of key and iv
*/
if (secassoc->ivlen) {
KMALLOC(secassoc->iv, caddr_t, secassoc->ivlen);
if (secassoc->iv == 0) {
DPRINTF(IDL_ERROR,("msghdr2secassoc: can't allocate mem for iv\n"));
return(-1);
}
bcopy((char *)keyinfo->iv, (char *)secassoc->iv, secassoc->ivlen);
} else
secassoc->iv = NULL;
if (secassoc->keylen) {
KMALLOC(secassoc->key, caddr_t, secassoc->keylen);
if (secassoc->key == 0) {
DPRINTF(IDL_ERROR,("msghdr2secassoc: can't allocate mem for key\n"));
if (secassoc->iv)
KFREE(secassoc->iv);
return(-1);
}
bcopy((char *)keyinfo->key, (char *)secassoc->key, secassoc->keylen);
} else
secassoc->key = NULL;
return(0);
}
/*----------------------------------------------------------------------
* addrpart_equal():
* Determine if the address portion of two sockaddrs are equal.
* Currently handles only AF_INET and AF_INET6 address families.
----------------------------------------------------------------------*/
static int
addrpart_equal(SOCKADDR *sa1, SOCKADDR *sa2)
{
if ((sa1->sa_family != sa2->sa_family) ||
(sa1->sa_len != sa2->sa_len))
return 0;
switch(sa1->sa_family) {
case AF_INET:
return (((struct sockaddr_in *)sa1)->sin_addr.s_addr ==
((struct sockaddr_in *)sa2)->sin_addr.s_addr);
#ifdef INET6
case AF_INET6:
return (IN6_ADDR_EQUAL(((struct sockaddr_in6 *)sa1)->sin6_addr,
((struct sockaddr_in6 *)sa2)->sin6_addr));
#endif /* INET6 */
}
return(0);
}
/*----------------------------------------------------------------------
* key_inittables():
* Allocate space and initialize key engine tables
----------------------------------------------------------------------*/
int
key_inittables(void)
{
int i;
KMALLOC(keyregtable, struct key_registry *, sizeof(struct key_registry));
if (!keyregtable)
return -1;
bzero((char *)keyregtable, sizeof(struct key_registry));
KMALLOC(key_acquirelist, struct key_acquirelist *,
sizeof(struct key_acquirelist));
if (!key_acquirelist)
return -1;
bzero((char *)key_acquirelist, sizeof(struct key_acquirelist));
for (i = 0; i < KEYTBLSIZE; i++)
bzero((char *)&keytable[i], sizeof(struct key_tblnode));
for (i = 0; i < KEYALLOCTBLSIZE; i++)
bzero((char *)&keyalloctbl[i], sizeof(struct key_allocnode));
for (i = 0; i < SO2SPITBLSIZE; i++)
bzero((char *)&so2spitbl[i], sizeof(struct key_so2spinode));
return 0;
}
static int
key_freetables(void)
{
KFREE(keyregtable);
keyregtable = NULL;
KFREE(key_acquirelist);
key_acquirelist = NULL;
return 0;
}
/*----------------------------------------------------------------------
* key_gethashval():
* Determine keytable hash value.
----------------------------------------------------------------------*/
static int
key_gethashval(char *buf, int len, int tblsize)
{
int i, j = 0;
/*
* Todo: Use word size xor and check for alignment
* and zero pad if necessary. Need to also pick
* a good hash function and table size.
*/
if (len <= 0) {
DPRINTF(IDL_ERROR,("key_gethashval got bogus len!\n"));
return(-1);
}
for(i = 0; i < len; i++) {
j ^= (u_int8_t)(*(buf + i));
}
return (j % tblsize);
}
/*----------------------------------------------------------------------
* key_createkey():
* Create hash key for hash function
* key is: type+src+dst if keytype = 1
* type+src+dst+spi if keytype = 0
* Uses only the address portion of the src and dst sockaddrs to
* form key. Currently handles only AF_INET and AF_INET6 sockaddrs
----------------------------------------------------------------------*/
static int
key_createkey(char *buf, u_int type, SOCKADDR *src, SOCKADDR *dst,
u_int32_t spi, u_int keytype)
{
char *cp, *p;
DPRINTF(IDL_FINISHED,("Entering key_createkey\n"));
if (!buf || !src || !dst)
return(-1);
cp = buf;
bcopy((char *)&type, cp, sizeof(type));
cp += sizeof(type);
#ifdef INET6
/*
* Assume only IPv4 and IPv6 addresses.
*/
#define ADDRPART(a) \
((a)->sa_family == AF_INET6) ? \
(char *)&(((struct sockaddr_in6 *)(a))->sin6_addr) : \
(char *)&(((struct sockaddr_in *)(a))->sin_addr)
#define ADDRSIZE(a) \
((a)->sa_family == AF_INET6) ? sizeof(struct in_addr6) : \
sizeof(struct in_addr)
#else /* INET6 */
#define ADDRPART(a) (char *)&(((struct sockaddr_in *)(a))->sin_addr)
#define ADDRSIZE(a) sizeof(struct in_addr)
#endif /* INET6 */
DPRINTF(IDL_FINISHED,("src addr:\n"));
DDO(IDL_FINISHED,dump_smart_sockaddr(src));
DPRINTF(IDL_FINISHED,("dst addr:\n"));
DDO(IDL_FINISHED,dump_smart_sockaddr(dst));
p = ADDRPART(src);
bcopy(p, cp, ADDRSIZE(src));
cp += ADDRSIZE(src);
p = ADDRPART(dst);
bcopy(p, cp, ADDRSIZE(dst));
cp += ADDRSIZE(dst);
#undef ADDRPART
#undef ADDRSIZE
if (keytype == 0) {
bcopy((char *)&spi, cp, sizeof(spi));
cp += sizeof(spi);
}
DPRINTF(IDL_FINISHED,("hash key:\n"));
DDO(IDL_FINISHED, dump_buf(buf, cp - buf));
return(cp - buf);
}
/*----------------------------------------------------------------------
* key_sosearch():
* Search the so2spi table for the security association allocated to
* the socket. Returns pointer to a struct key_so2spinode which can
* be used to locate the security association entry in the keytable.
----------------------------------------------------------------------*/
static struct key_so2spinode *
key_sosearch(u_int type, SOCKADDR *src, SOCKADDR *dst, struct socket *so)
{
struct key_so2spinode *np = 0;
if (!(src && dst)) {
DPRINTF(IDL_ERROR,("key_sosearch: got null src or dst pointer!\n"));
return(NULL);
}
for (np = so2spitbl[((u_int32_t)so) % SO2SPITBLSIZE].next; np; np = np->next) {
if ((so == np->socket) && (type == np->keynode->secassoc->type)
&& addrpart_equal(src, np->keynode->secassoc->src)
&& addrpart_equal(dst, np->keynode->secassoc->dst))
return(np);
}
return(NULL);
}
/*----------------------------------------------------------------------
* key_sodelete():
* Delete entries from the so2spi table.
* flag = 1 purge all entries
* flag = 0 delete entries with socket pointer matching socket
----------------------------------------------------------------------*/
static void
key_sodelete(struct socket *socket, int flag)
{
struct key_so2spinode *prevnp, *np;
CRITICAL_DCL
CRITICAL_START;
DPRINTF(IDL_EVENT,("Entering keysodelete w/so=0x%x flag=%d\n",
(unsigned int)socket,flag));
if (flag) {
int i;
for (i = 0; i < SO2SPITBLSIZE; i++)
for(np = so2spitbl[i].next; np; np = np->next) {
KFREE(np);
}
CRITICAL_END;
return;
}
prevnp = &so2spitbl[((u_int32_t)socket) % SO2SPITBLSIZE];
for(np = prevnp->next; np; np = np->next) {
if (np->socket == socket) {
struct socketlist *socklp, *prevsocklp;
(np->keynode->alloc_count)--;
/*
* If this socket maps to a unique secassoc,
* we go ahead and delete the secassoc, since it
* can no longer be allocated or used by any other
* socket.
*/
if (np->keynode->secassoc->state & K_UNIQUE) {
if (key_delete(np->keynode->secassoc) != 0)
panic("key_sodelete");
np = prevnp;
continue;
}
/*
* We traverse the socketlist and remove the entry
* for this socket
*/
DPRINTF(IDL_FINISHED,("keysodelete: deleting from socklist..."));
prevsocklp = np->keynode->solist;
for (socklp = prevsocklp->next; socklp; socklp = socklp->next) {
if (socklp->socket == socket) {
prevsocklp->next = socklp->next;
KFREE(socklp);
break;
}
prevsocklp = socklp;
}
DPRINTF(IDL_FINISHED,("done\n"));
prevnp->next = np->next;
KFREE(np);
np = prevnp;
}
prevnp = np;
}
CRITICAL_END;
}
/*----------------------------------------------------------------------
* key_deleteacquire():
* Delete an entry from the key_acquirelist
----------------------------------------------------------------------*/
static void
key_deleteacquire(u_int type, SOCKADDR *target)
{
struct key_acquirelist *ap, *prev;
prev = key_acquirelist;
for(ap = key_acquirelist->next; ap; ap = ap->next) {
if (addrpart_equal(target, (SOCKADDR *)&(ap->target)) &&
(type == ap->type)) {
DPRINTF(IDL_EVENT,("Deleting entry from acquire list!\n"));
prev->next = ap->next;
KFREE(ap);
ap = prev;
}
prev = ap;
}
}
/*----------------------------------------------------------------------
* key_search():
* Search the key table for an entry with same type, src addr, dest
* addr, and spi. Returns a pointer to struct key_tblnode if found
* else returns null.
----------------------------------------------------------------------*/
static struct key_tblnode *
key_search(u_int type, SOCKADDR *src, SOCKADDR *dst, u_int32_t spi,
int indx, struct key_tblnode **prevkeynode)
{
struct key_tblnode *keynode, *prevnode;
if (indx > KEYTBLSIZE || indx < 0)
return (NULL);
if (!(&keytable[indx]))
return (NULL);
#define sec_type keynode->secassoc->type
#define sec_spi keynode->secassoc->spi
#define sec_src keynode->secassoc->src
#define sec_dst keynode->secassoc->dst
prevnode = &keytable[indx];
for (keynode = keytable[indx].next; keynode; keynode = keynode->next) {
if ((type == sec_type) && (spi == sec_spi) &&
addrpart_equal(src, sec_src)
&& addrpart_equal(dst, sec_dst))
break;
prevnode = keynode;
}
*prevkeynode = prevnode;
return(keynode);
}
/*----------------------------------------------------------------------
* key_addnode():
* Insert a key_tblnode entry into the key table. Returns a pointer
* to the newly created key_tblnode.
----------------------------------------------------------------------*/
static struct key_tblnode *
key_addnode(int indx, struct key_secassoc *secassoc)
{
struct key_tblnode *keynode;
DPRINTF(IDL_FINISHED,("Entering key_addnode w/indx=%d secassoc=0x%x\n",
indx, (unsigned int)secassoc));
if (!(&keytable[indx]))
return(NULL);
if (!secassoc) {
panic("key_addnode: Someone passed in a null secassoc!\n");
}
KMALLOC(keynode, struct key_tblnode *, sizeof(struct key_tblnode));
if (keynode == 0)
return(NULL);
bzero((char *)keynode, sizeof(struct key_tblnode));
KMALLOC(keynode->solist, struct socketlist *, sizeof(struct socketlist));
if (keynode->solist == 0) {
KFREE(keynode);
return(NULL);
}
bzero((char *)(keynode->solist), sizeof(struct socketlist));
keynode->secassoc = secassoc;
keynode->solist->next = NULL;
keynode->next = keytable[indx].next;
keytable[indx].next = keynode;
return(keynode);
}
/*----------------------------------------------------------------------
* key_add():
* Add a new security association to the key table. Caller is
* responsible for allocating memory for the key_secassoc as
* well as the buffer space for the key, iv. Assumes the security
* association passed in is well-formed.
----------------------------------------------------------------------*/
int
key_add(struct key_secassoc *secassoc)
{
char buf[MAXHASHKEYLEN];
int len, indx;
int inbound = 0;
int outbound = 0;
struct key_tblnode *keynode, *prevkeynode;
struct key_allocnode *np = NULL;
CRITICAL_DCL
DPRINTF(IDL_FINISHED, ("Entering key_add w/secassoc=0x%x\n",
(unsigned int)secassoc));
if (!secassoc) {
panic("key_add: who the hell is passing me a null pointer");
}
/*
* Should we allow a null key to be inserted into the table ?
* or can we use null key to indicate some policy action...
*/
#if 0
/*
* For esp using des-cbc or tripple-des we call
* des_set_odd_parity.
*/
if (secassoc->key && (secassoc->type == KEY_TYPE_ESP) &&
((secassoc->algorithm == IPSEC_ALGTYPE_ESP_DES_CBC) ||
(secassoc->algorithm == IPSEC_ALGTYPE_ESP_3DES)))
des_set_odd_parity(secassoc->key);
#endif /* 0 */
/*
* Check if secassoc with same spi exists before adding
*/
bzero((char *)&buf, sizeof(buf));
len = key_createkey((char *)&buf, secassoc->type, secassoc->src,
secassoc->dst, secassoc->spi, 0);
indx = key_gethashval((char *)&buf, len, KEYTBLSIZE);
DPRINTF(IDL_FINISHED,("keyadd: keytbl hash position=%d\n", indx));
keynode = key_search(secassoc->type, secassoc->src, secassoc->dst,
secassoc->spi, indx, &prevkeynode);
if (keynode) {
DPRINTF(IDL_EVENT,("keyadd: secassoc already exists!\n"));
return(-2);
}
inbound = my_addr(secassoc->dst);
outbound = my_addr(secassoc->src);
DPRINTF(IDL_FINISHED,("inbound=%d outbound=%d\n", inbound, outbound));
/*
* We allocate mem for an allocation entry if needed.
* This is done here instead of in the allocaton code
* segment so that we can easily recover/cleanup from a
* memory allocation error.
*/
if (outbound || (!inbound && !outbound)) {
KMALLOC(np, struct key_allocnode *, sizeof(struct key_allocnode));
if (np == 0) {
DPRINTF(IDL_ERROR,("keyadd: can't allocate allocnode!\n"));
return(-1);
}
}
CRITICAL_START;
if ((keynode = key_addnode(indx, secassoc)) == NULL) {
DPRINTF(IDL_ERROR,("keyadd: key_addnode failed!\n"));
if (np)
KFREE(np);
CRITICAL_END;
return(-1);
}
DPRINTF(IDL_GROSS_EVENT,("Added new keynode:\n"));
DDO(IDL_FINISHED, dump_keytblnode(keynode));
DDO(IDL_FINISHED, dump_secassoc(keynode->secassoc));
/*
* We add an entry to the allocation table for
* this secassoc if the interfaces are up,
* the secassoc is outbound. In the case
* where the interfaces are not up, we go ahead
* , do it anyways. This wastes an allocation
* entry if the secassoc later turned out to be
* inbound when the interfaces are ifconfig up.
*/
if (outbound || (!inbound && !outbound)) {
len = key_createkey((char *)&buf, secassoc->type, secassoc->src,
secassoc->dst, 0, 1);
indx = key_gethashval((char *)&buf, len, KEYALLOCTBLSIZE);
DPRINTF(IDL_FINISHED,("keyadd: keyalloc hash position=%d\n", indx));
np->keynode = keynode;
np->next = keyalloctbl[indx].next;
keyalloctbl[indx].next = np;
}
if (inbound)
secassoc->state |= K_INBOUND;
if (outbound)
secassoc->state |= K_OUTBOUND;
key_deleteacquire(secassoc->type, secassoc->dst);
CRITICAL_END;
return 0;
}
/*----------------------------------------------------------------------
* key_get():
* Get a security association from the key table.
----------------------------------------------------------------------*/
int
key_get(u_int type, SOCKADDR *src, SOCKADDR *dst, u_int32_t spi,
struct key_secassoc **secassoc)
{
char buf[MAXHASHKEYLEN];
struct key_tblnode *keynode, *prevkeynode;
int len, indx;
bzero(&buf, sizeof(buf));
*secassoc = NULL;
len = key_createkey((char *)&buf, type, src, dst, spi, 0);
indx = key_gethashval((char *)&buf, len, KEYTBLSIZE);
DPRINTF(IDL_FINISHED,("keyget: indx=%d\n",indx));
keynode = key_search(type, src, dst, spi, indx, &prevkeynode);
if (keynode) {
DPRINTF(IDL_GROSS_EVENT,("keyget: found it! keynode=0x%x",
(unsigned int)keynode));
*secassoc = keynode->secassoc;
return(0);
} else
return(-1); /* Not found */
}
/*----------------------------------------------------------------------
* key_dump():
* Dump all valid entries in the keytable to a pf_key socket. Each
* security associaiton is sent one at a time in a pf_key message. A
* message with seqno = 0 signifies the end of the dump transaction.
----------------------------------------------------------------------*/
int
key_dump(struct socket *so)
{
int len, i;
int seq = 1;
struct key_msgdata keyinfo;
struct key_msghdr *km;
struct key_tblnode *keynode;
/*
* Routine to dump the key table to a routing socket
* Use for debugging only!
*/
KMALLOC(km, struct key_msghdr *, sizeof(struct key_msghdr) +
3 * MAX_SOCKADDR_SZ + MAX_KEY_SZ + MAX_IV_SZ);
if (!km)
return(ENOBUFS);
DPRINTF(IDL_FINISHED,("Entering key_dump()"));
/*
* We need to speed this up later. Fortunately, key_dump
* messages are not sent often.
*/
for (i = 0; i < KEYTBLSIZE; i++) {
for (keynode = keytable[i].next; keynode; keynode = keynode->next) {
/*
* We exclude dead/larval/zombie security associations for now
* but it may be useful to also send these up for debugging purposes
*/
if (keynode->secassoc->state & (K_DEAD | K_LARVAL | K_ZOMBIE))
continue;
len = (sizeof(struct key_msghdr) +
ROUNDUP(keynode->secassoc->src->sa_len) +
ROUNDUP(keynode->secassoc->dst->sa_len) +
ROUNDUP(keynode->secassoc->from->sa_len) +
ROUNDUP(keynode->secassoc->keylen) +
ROUNDUP(keynode->secassoc->ivlen));
if (key_secassoc2msghdr(keynode->secassoc, km, &keyinfo) != 0)
panic("key_dump");
km->key_msglen = len;
km->key_msgvers = KEY_VERSION;
km->key_msgtype = KEY_DUMP;
km->key_pid = CURRENT_PID;
km->key_seq = seq++;
km->key_errno = 0;
key_sendup(so, km);
}
}
bzero((char *)km, sizeof(struct key_msghdr));
km->key_msglen = sizeof(struct key_msghdr);
km->key_msgvers = KEY_VERSION;
km->key_msgtype = KEY_DUMP;
km->key_pid = CURRENT_PID;
km->key_seq = 0;
km->key_errno = 0;
key_sendup(so, km);
KFREE(km);
DPRINTF(IDL_FINISHED,("Leaving key_dump()\n"));
return(0);
}
/*----------------------------------------------------------------------
* key_delete():
* Delete a security association from the key table.
----------------------------------------------------------------------*/
int
key_delete(struct key_secassoc *secassoc)
{
char buf[MAXHASHKEYLEN];
int len, indx;
struct key_tblnode *keynode = 0;
struct key_tblnode *prevkeynode = 0;
struct socketlist *socklp, *deadsocklp;
struct key_so2spinode *np, *prevnp;
struct key_allocnode *ap, *prevap;
CRITICAL_DCL
DPRINTF(IDL_FINISHED,("Entering key_delete w/secassoc=0x%x\n",
(unsigned int)secassoc));
bzero((char *)&buf, sizeof(buf));
len = key_createkey((char *)&buf, secassoc->type, secassoc->src,
secassoc->dst, secassoc->spi, 0);
indx = key_gethashval((char *)&buf, len, KEYTBLSIZE);
DPRINTF(IDL_FINISHED,("keydelete: keytbl hash position=%d\n", indx));
keynode = key_search(secassoc->type, secassoc->src, secassoc->dst,
secassoc->spi, indx, &prevkeynode);
if (keynode) {
CRITICAL_START;
DPRINTF(IDL_GROSS_EVENT,("keydelete: found keynode to delete\n"));
keynode->secassoc->state |= K_DEAD;
if (keynode->ref_count > 0) {
DPRINTF(IDL_EVENT,("keydelete: secassoc still held, marking for deletion only!\n"));
CRITICAL_END;
return(0);
}
prevkeynode->next = keynode->next;
/*
* Walk the socketlist, delete the
* entries mapping sockets to this secassoc
* from the so2spi table.
*/
DPRINTF(IDL_FINISHED,("keydelete: deleting socklist..."));
for(socklp = keynode->solist->next; socklp; ) {
prevnp = &so2spitbl[((u_int32_t)(socklp->socket)) % SO2SPITBLSIZE];
for(np = prevnp->next; np; np = np->next) {
if ((np->socket == socklp->socket) && (np->keynode == keynode)) {
prevnp->next = np->next;
KFREE(np);
break;
}
prevnp = np;
}
deadsocklp = socklp;
socklp = socklp->next;
KFREE(deadsocklp);
}
DPRINTF(IDL_FINISHED,("done\n"));
/*
* If an allocation entry exist for this
* secassoc, delete it.
*/
bzero((char *)&buf, sizeof(buf));
len = key_createkey((char *)&buf, secassoc->type, secassoc->src,
secassoc->dst, 0, 1);
indx = key_gethashval((char *)&buf, len, KEYALLOCTBLSIZE);
DPRINTF(IDL_FINISHED,("keydelete: alloctbl hash position=%d\n", indx));
prevap = &keyalloctbl[indx];
for (ap = prevap->next; ap; ap = ap->next) {
if (ap->keynode == keynode) {
prevap->next = ap->next;
KFREE(ap);
break;
}
prevap = ap;
}
if (keynode->secassoc->iv)
KFREE(keynode->secassoc->iv);
if (keynode->secassoc->key)
KFREE(keynode->secassoc->key);
KFREE(keynode->secassoc);
if (keynode->solist)
KFREE(keynode->solist);
KFREE(keynode);
CRITICAL_END;
return(0);
}
return(-1);
}
/*----------------------------------------------------------------------
* key_flush():
* Delete all entries from the key table.
----------------------------------------------------------------------*/
void
key_flush(void)
{
struct key_tblnode *keynode;
int i;
/*
* This is slow, but simple.
*/
DPRINTF(IDL_FINISHED,("Flushing key table..."));
for (i = 0; i < KEYTBLSIZE; i++) {
while ((keynode = keytable[i].next))
if (key_delete(keynode->secassoc) != 0)
panic("key_flush");
}
DPRINTF(IDL_FINISHED,("done\n"));
}
/*----------------------------------------------------------------------
* key_getspi():
* Get a unique spi value for a key management daemon/program. The
* spi value, once assigned, cannot be assigned again (as long as the
* entry with that same spi value remains in the table).
----------------------------------------------------------------------*/
int
key_getspi(u_int type, SOCKADDR *src, SOCKADDR *dst, u_int32_t lowval,
u_int32_t highval, u_int32_t *spi)
{
struct key_secassoc *secassoc;
struct key_tblnode *keynode, *prevkeynode;
int count, done, len, indx;
int maxcount = 1000;
u_int32_t val;
char buf[MAXHASHKEYLEN];
CRITICAL_DCL
DPRINTF(IDL_EVENT,("Entering getspi w/type=%d,low=%u,high=%u\n",
type, lowval, highval));
if (!(src && dst))
return(EINVAL);
if ((lowval == 0) || (highval == 0))
return(EINVAL);
if (lowval > highval) {
u_int32_t temp;
temp = lowval;
lowval = highval;
highval = lowval;
}
done = count = 0;
do {
count++;
/*
* This may not be "random enough".
*/
val = lowval + (random() % (highval - lowval + 1));
if (lowval == highval)
count = maxcount;
DPRINTF(IDL_FINISHED,("%u ",val));
if (val) {
DPRINTF(IDL_FINISHED,("\n"));
bzero(&buf, sizeof(buf));
len = key_createkey((char *)&buf, type, src, dst, val, 0);
indx = key_gethashval((char *)&buf, len, KEYTBLSIZE);
if (!key_search(type, src, dst, val, indx, &prevkeynode)) {
CRITICAL_START;
KMALLOC(secassoc, struct key_secassoc *, sizeof(struct key_secassoc));
if (secassoc == 0) {
DPRINTF(IDL_ERROR,("key_getspi: can't allocate memory\n"));
CRITICAL_END;
return(ENOBUFS);
}
bzero((char *)secassoc, sizeof(*secassoc));
DPRINTF(IDL_FINISHED,("getspi: indx=%d\n",indx));
secassoc->len = sizeof(struct key_secassoc);
secassoc->type = type;
secassoc->spi = val;
secassoc->state |= K_LARVAL;
if (my_addr(secassoc->dst))
secassoc->state |= K_INBOUND;
if (my_addr(secassoc->src))
secassoc->state |= K_OUTBOUND;
bcopy((char *)src, (char *)secassoc->src, src->sa_len);
bcopy((char *)dst, (char *)secassoc->dst, dst->sa_len);
/* We fill this in with a plausable value now to insure
that other routines don't break. These will get
overwritten later with the correct values. */
#ifdef INET6
secassoc->from->sa_family = AF_INET6;
secassoc->from->sa_len = sizeof(struct sockaddr_in6);
#else /* INET6 */
secassoc->from->sa_family = AF_INET;
secassoc->from->sa_len = sizeof(struct sockaddr_in);
#endif /* INET6 */
/*
* We need to add code to age these larval key table
* entries so they don't linger forever waiting for
* a KEY_UPDATE message that may not come for various
* reasons. This is another task that key_reaper can
* do once we have it coded.
*/
secassoc->lifetime1 += TIME_SECONDS + maxlarvallifetime;
if (!(keynode = key_addnode(indx, secassoc))) {
DPRINTF(IDL_ERROR,("key_getspi: can't add node\n"));
CRITICAL_END;
return(ENOBUFS);
}
DPRINTF(IDL_FINISHED,("key_getspi: added node 0x%x\n",
(unsigned int)keynode));
done++;
CRITICAL_END;
}
}
} while ((count < maxcount) && !done);
DPRINTF(IDL_EVENT,("getspi returns w/spi=%u,count=%d\n",val,count));
if (done) {
*spi = val;
return(0);
} else {
*spi = 0;
return(EADDRNOTAVAIL);
}
}
/*----------------------------------------------------------------------
* key_update():
* Update a keytable entry that has an spi value assigned but is
* incomplete (e.g. no key/iv).
----------------------------------------------------------------------*/
int
key_update(struct key_secassoc *secassoc)
{
struct key_tblnode *keynode, *prevkeynode;
struct key_allocnode *np = 0;
u_int8_t newstate;
int len, indx, inbound, outbound;
char buf[MAXHASHKEYLEN];
CRITICAL_DCL
bzero(&buf, sizeof(buf));
len = key_createkey((char *)&buf, secassoc->type, secassoc->src,
secassoc->dst, secassoc->spi, 0);
indx = key_gethashval((char *)&buf, len, KEYTBLSIZE);
if(!(keynode = key_search(secassoc->type, secassoc->src, secassoc->dst,
secassoc->spi, indx, &prevkeynode))) {
return(ESRCH);
}
if (keynode->secassoc->state & K_DEAD)
return(ESRCH);
/* Should we also restrict updating of only LARVAL entries ? */
CRITICAL_START;
inbound = my_addr(secassoc->dst);
outbound = my_addr(secassoc->src);
newstate = keynode->secassoc->state;
newstate &= ~K_LARVAL;
if (inbound)
newstate |= K_INBOUND;
if (outbound)
newstate |= K_OUTBOUND;
if (outbound || (!inbound && !outbound)) {
KMALLOC(np, struct key_allocnode *, sizeof(struct key_allocnode));
if (np == 0) {
DPRINTF(IDL_ERROR,("keyupdate: can't allocate allocnode!\n"));
CRITICAL_END;
return(ENOBUFS);
}
}
/*
* Free the old key, iv if they're there.
*/
if (keynode->secassoc->key)
KFREE(keynode->secassoc->key);
if (keynode->secassoc->iv)
KFREE(keynode->secassoc->iv);
/*
* We now copy the secassoc over. We don't need to copy
* the key, iv into new buffers since the calling routine
* does that already.
*/
*(keynode->secassoc) = *secassoc;
keynode->secassoc->state = newstate;
/*
* Should we allow a null key to be inserted into the table ?
* or can we use null key to indicate some policy action...
*/
#if 0
if (keynode->secassoc->key &&
(keynode->secassoc->type == KEY_TYPE_ESP) &&
((keynode->secassoc->algorithm == IPSEC_ALGTYPE_ESP_DES_CBC) ||
(keynode->secassoc->algorithm == IPSEC_ALGTYPE_ESP_3DES)))
des_set_odd_parity(keynode->secassoc->key);
#endif /* 0 */
/*
* We now add an entry to the allocation table for this
* updated key table entry.
*/
if (outbound || (!inbound && !outbound)) {
len = key_createkey((char *)&buf, secassoc->type, secassoc->src,
secassoc->dst, 0, 1);
indx = key_gethashval((char *)&buf, len, KEYALLOCTBLSIZE);
DPRINTF(IDL_FINISHED,("keyupdate: keyalloc hash position=%d\n", indx));
np->keynode = keynode;
np->next = keyalloctbl[indx].next;
keyalloctbl[indx].next = np;
}
key_deleteacquire(secassoc->type, (SOCKADDR *)&(secassoc->dst));
CRITICAL_END;
return(0);
}
/*----------------------------------------------------------------------
* key_register():
* Register a socket as one capable of acquiring security associations
* for the kernel.
----------------------------------------------------------------------*/
int
key_register(struct socket *socket, u_int type)
{
struct key_registry *p, *new;
CRITICAL_DCL
CRITICAL_START;
DPRINTF(IDL_EVENT,("Entering key_register w/so=0x%x,type=%d\n",
(unsigned int)socket,type));
if (!(keyregtable && socket))
panic("key_register");
/*
* Make sure entry is not already in table
*/
for(p = keyregtable->next; p; p = p->next) {
if ((p->type == type) && (p->socket == socket)) {
CRITICAL_END;
return(EEXIST);
}
}
KMALLOC(new, struct key_registry *, sizeof(struct key_registry));
if (new == 0) {
CRITICAL_END;
return(ENOBUFS);
}
new->type = type;
new->socket = socket;
new->next = keyregtable->next;
keyregtable->next = new;
CRITICAL_END;
return(0);
}
/*----------------------------------------------------------------------
* key_unregister():
* Delete entries from the registry list.
* allflag = 1 : delete all entries with matching socket
* allflag = 0 : delete only the entry matching socket, type
----------------------------------------------------------------------*/
void
key_unregister(struct socket *socket, u_int type, int allflag)
{
struct key_registry *p, *prev;
CRITICAL_DCL
CRITICAL_START;
DPRINTF(IDL_EVENT,("Entering key_unregister w/so=0x%x,type=%d,flag=%d\n",
(unsigned int)socket, type, allflag));
if (!(keyregtable && socket))
panic("key_register");
prev = keyregtable;
for(p = keyregtable->next; p; p = p->next) {
if ((allflag && (p->socket == socket)) ||
((p->type == type) && (p->socket == socket))) {
prev->next = p->next;
KFREE(p);
p = prev;
}
prev = p;
}
CRITICAL_END;
}
/*----------------------------------------------------------------------
* key_acquire():
* Send a key_acquire message to all registered key mgnt daemons
* capable of acquire security association of type type.
*
* Return: 0 if succesfully called key mgnt. daemon(s)
* -1 if not successfull.
----------------------------------------------------------------------*/
int
key_acquire(u_int type, SOCKADDR *src, SOCKADDR *dst)
{
struct key_registry *p;
struct key_acquirelist *ap, *prevap;
int success = 0, created = 0;
u_int etype;
struct key_msghdr *km = NULL;
int len;
DPRINTF(IDL_EVENT,("Entering key_acquire()\n"));
if (!keyregtable || !src || !dst)
return (-1);
/*
* We first check the acquirelist to see if a key_acquire
* message has been sent for this destination.
*/
etype = type;
prevap = key_acquirelist;
for(ap = key_acquirelist->next; ap; ap = ap->next) {
if (addrpart_equal(dst, ap->target) &&
(etype == ap->type)) {
DPRINTF(IDL_EVENT,("acquire message previously sent!\n"));
if (ap->expiretime < TIME_SECONDS) {
DPRINTF(IDL_EVENT,("acquire message has expired!\n"));
ap->count = 0;
break;
}
if (ap->count < maxkeyacquire) {
DPRINTF(IDL_EVENT,("max acquire messages not yet exceeded!\n"));
break;
}
return(0);
} else if (ap->expiretime < TIME_SECONDS) {
/*
* Since we're already looking at the list, we may as
* well delete expired entries as we scan through the list.
* This should really be done by a function like key_reaper()
* but until we code key_reaper(), this is a quick, dirty
* hack.
*/
DPRINTF(IDL_EVENT,("found an expired entry...deleting it!\n"));
prevap->next = ap->next;
KFREE(ap);
ap = prevap;
}
prevap = ap;
}
/*
* Scan registry, send KEY_ACQUIRE message to
* appropriate key management daemons.
*/
for(p = keyregtable->next; p; p = p->next) {
if (p->type != type)
continue;
if (!created) {
len = sizeof(struct key_msghdr) + ROUNDUP(src->sa_len) +
ROUNDUP(dst->sa_len);
KMALLOC(km, struct key_msghdr *, len);
if (!km) {
DPRINTF(IDL_ERROR,("key_acquire: no memory\n"));
return(-1);
}
DPRINTF(IDL_FINISHED,("key_acquire/created: 1\n"));
bzero((char *)km, len);
km->key_msglen = len;
km->key_msgvers = KEY_VERSION;
km->key_msgtype = KEY_ACQUIRE;
km->type = type;
DPRINTF(IDL_FINISHED,("key_acquire/created: 2\n"));
/*
* This is inefficient, slow.
*/
/*
* We zero out sin_zero here for AF_INET addresses because
* ip_output() currently does not do it for performance reasons.
*/
if (src->sa_family == AF_INET)
bzero((char *)(((struct sockaddr_in *)src)->sin_zero),
sizeof(((struct sockaddr_in *)src)->sin_zero));
if (dst->sa_family == AF_INET)
bzero((char *)(((struct sockaddr_in *)dst)->sin_zero),
sizeof(((struct sockaddr_in *)dst)->sin_zero));
bcopy((char *)src, (char *)(km + 1), src->sa_len);
bcopy((char *)dst, (char *)((int)(km + 1) + ROUNDUP(src->sa_len)),
dst->sa_len);
DPRINTF(IDL_FINISHED,("key_acquire/created: 3\n"));
created++;
}
if (key_sendup(p->socket, km))
success++;
}
if (km)
KFREE(km);
/*
* Update the acquirelist
*/
if (success) {
if (!ap) {
DPRINTF(IDL_EVENT,("Adding new entry in acquirelist\n"));
KMALLOC(ap, struct key_acquirelist *, sizeof(struct key_acquirelist));
if (ap == 0)
return(success ? 0 : -1);
bzero((char *)ap, sizeof(struct key_acquirelist));
bcopy((char *)dst, (char *)ap->target, dst->sa_len);
ap->type = etype;
ap->next = key_acquirelist->next;
key_acquirelist->next = ap;
}
DPRINTF(IDL_GROSS_EVENT,("Updating acquire counter, expiration time\n"));
ap->count++;
ap->expiretime = TIME_SECONDS + maxacquiretime;
}
DPRINTF(IDL_EVENT,("key_acquire: done! success=%d\n",success));
return(success ? 0 : -1);
}
/*----------------------------------------------------------------------
* key_alloc():
* Allocate a security association to a socket. A socket requesting
* unique keying (per-socket keying) is assigned a security assocation
* exclusively for its use. Sockets not requiring unique keying are
* assigned the first security association which may or may not be
* used by another socket.
----------------------------------------------------------------------*/
static int
key_alloc(u_int type, SOCKADDR *src, SOCKADDR *dst, struct socket *socket,
u_int unique_key, struct key_tblnode **keynodep)
{
struct key_tblnode *keynode;
char buf[MAXHASHKEYLEN];
struct key_allocnode *np, *prevnp;
struct key_so2spinode *newnp;
int len;
int indx;
DPRINTF(IDL_FINISHED,("Entering key_alloc w/type=%u!\n",type));
if (!(src && dst)) {
DPRINTF(IDL_ERROR,("key_alloc: received null src or dst!\n"));
return(-1);
}
/*
* Search key allocation table
*/
bzero((char *)&buf, sizeof(buf));
len = key_createkey((char *)&buf, type, src, dst, 0, 1);
indx = key_gethashval((char *)&buf, len, KEYALLOCTBLSIZE);
#define np_type np->keynode->secassoc->type
#define np_state np->keynode->secassoc->state
#define np_src np->keynode->secassoc->src
#define np_dst np->keynode->secassoc->dst
prevnp = &keyalloctbl[indx];
for (np = keyalloctbl[indx].next; np; np = np->next) {
if ((type == np_type) && addrpart_equal(src, np_src) &&
addrpart_equal(dst, np_dst) &&
!(np_state & (K_LARVAL | K_DEAD | K_UNIQUE))) {
if (!(unique_key))
break;
if (!(np_state & K_USED))
break;
}
prevnp = np;
}
if (np) {
struct socketlist *newsp;
CRITICAL_DCL
CRITICAL_START;
DPRINTF(IDL_EVENT,("key_alloc: found node to allocate\n"));
keynode = np->keynode;
KMALLOC(newnp, struct key_so2spinode *, sizeof(struct key_so2spinode));
if (newnp == 0) {
DPRINTF(IDL_ERROR,("key_alloc: Can't alloc mem for so2spi node!\n"));
CRITICAL_END;
return(ENOBUFS);
}
KMALLOC(newsp, struct socketlist *, sizeof(struct socketlist));
if (newsp == 0) {
DPRINTF(IDL_ERROR,("key_alloc: Can't alloc mem for socketlist!\n"));
if (newnp)
KFREE(newnp);
CRITICAL_END;
return(ENOBUFS);
}
/*
* Add a hash entry into the so2spi table to
* map socket to allocated secassoc.
*/
DPRINTF(IDL_FINISHED,("key_alloc: adding entry to so2spi table..."));
newnp->keynode = keynode;
newnp->socket = socket;
newnp->next = so2spitbl[((u_int32_t)socket) % SO2SPITBLSIZE].next;
so2spitbl[((u_int32_t)socket) % SO2SPITBLSIZE].next = newnp;
DPRINTF(IDL_FINISHED,("done\n"));
if (unique_key) {
/*
* Need to remove the allocation entry
* since the secassoc is now unique,
* can't be allocated to any other socket
*/
DPRINTF(IDL_EVENT,("key_alloc: making keynode unique..."));
keynode->secassoc->state |= K_UNIQUE;
prevnp->next = np->next;
KFREE(np);
DPRINTF(IDL_EVENT,("done\n"));
}
keynode->secassoc->state |= K_USED;
keynode->secassoc->state |= K_OUTBOUND;
keynode->alloc_count++;
/*
* Add socket to list of socket using secassoc.
*/
DPRINTF(IDL_FINISHED,("key_alloc: adding so to solist..."));
newsp->socket = socket;
newsp->next = keynode->solist->next;
keynode->solist->next = newsp;
DPRINTF(IDL_FINISHED,("done\n"));
*keynodep = keynode;
CRITICAL_END;
return(0);
}
*keynodep = NULL;
return(0);
}
/*----------------------------------------------------------------------
* key_free():
* Decrement the refcount for a key table entry. If the entry is
* marked dead,, the refcount is zero, we go ahead, delete it.
----------------------------------------------------------------------*/
void
key_free(struct key_tblnode *keynode)
{
DPRINTF(IDL_GROSS_EVENT,("Entering key_free w/keynode=0x%x\n",
(unsigned int)keynode));
if (!keynode) {
DPRINTF(IDL_ERROR,("Warning: key_free got null pointer\n"));
return;
}
(keynode->ref_count)--;
if (keynode->ref_count < 0) {
DPRINTF(IDL_ERROR,("Warning: key_free decremented refcount to %d\n",keynode->ref_count));
}
if ((keynode->secassoc->state & K_DEAD) && (keynode->ref_count <= 0)) {
DPRINTF(IDL_GROSS_EVENT,("key_free: calling key_delete\n"));
key_delete(keynode->secassoc);
}
}
/*----------------------------------------------------------------------
* getassocbyspi():
* Get a security association for a given type, src, dst,, spi.
*
* Returns: 0 if sucessfull
* -1 if error/not found
*
* Caller must convert spi to host order. Function assumes spi is
* in host order!
----------------------------------------------------------------------*/
int
getassocbyspi(u_int type, SOCKADDR *src, SOCKADDR *dst, u_int32_t spi,
struct key_tblnode **keyentry)
{
char buf[MAXHASHKEYLEN];
int len, indx;
struct key_tblnode *keynode, *prevkeynode = 0;
DPRINTF(IDL_FINISHED,("Entering getassocbyspi w/type=%u spi=%u\n",type,spi));
*keyentry = NULL;
bzero(&buf, sizeof(buf));
len = key_createkey((char *)&buf, type, src, dst, spi, 0);
indx = key_gethashval((char *)&buf, len, KEYTBLSIZE);
DPRINTF(IDL_FINISHED,("getassocbyspi: indx=%d\n",indx));
DDO(IDL_FINISHED,dump_sockaddr(src);dump_sockaddr(dst));
keynode = key_search(type, src, dst, spi, indx, &prevkeynode);
DPRINTF(IDL_FINISHED,("getassocbyspi: keysearch ret=0x%x\n",
(unsigned int)keynode));
if (keynode && !(keynode->secassoc->state & (K_DEAD | K_LARVAL))) {
DPRINTF(IDL_GROSS_EVENT,("getassocbyspi: found secassoc!\n"));
(keynode->ref_count)++;
keynode->secassoc->state |= K_USED;
*keyentry = keynode;
} else {
DPRINTF(IDL_EVENT,("getassocbyspi: secassoc not found!\n"));
return (-1);
}
return(0);
}
/*----------------------------------------------------------------------
* getassocbysocket():
* Get a security association for a given type, src, dst,, socket.
* If not found, try to allocate one.
* Returns: 0 if successfull
* -1 if error condition/secassoc not found (*keyentry = NULL)
* 1 if secassoc temporarily unavailable (*keynetry = NULL)
* (e.g., key mgnt. daemon(s) called)
----------------------------------------------------------------------*/
int
getassocbysocket(u_int type, SOCKADDR *src, SOCKADDR *dst,
struct socket *socket, u_int unique_key,
struct key_tblnode **keyentry)
{
struct key_tblnode *keynode = 0;
struct key_so2spinode *np;
u_int realtype;
DPRINTF(IDL_FINISHED,("Entering getassocbysocket w/type=%u so=0x%x\n",
type,(unsigned int)socket));
/*
* We treat esp-transport mode, esp-tunnel mode
* as a single type in the keytable. This has a side
* effect that socket using both esp-transport,
* esp-tunnel will use the same security association
* for both modes. Is this a problem?
*/
realtype = type;
if ((np = key_sosearch(type, src, dst, socket))) {
if (np->keynode && np->keynode->secassoc &&
!(np->keynode->secassoc->state & (K_DEAD | K_LARVAL))) {
DPRINTF(IDL_FINISHED,("getassocbysocket: found secassoc!\n"));
(np->keynode->ref_count)++;
*keyentry = np->keynode;
return(0);
}
}
/*
* No secassoc has been allocated to socket,
* so allocate one, if available
*/
DPRINTF(IDL_GROSS_EVENT,("getassocbyso: can't find it, trying to allocate!\n"));
if (key_alloc(realtype, src, dst, socket, unique_key, &keynode) == 0) {
if (keynode) {
DPRINTF(IDL_GROSS_EVENT,("getassocbyso: key_alloc found secassoc!\n"));
keynode->ref_count++;
*keyentry = keynode;
return(0);
} else {
/*
* Kick key mgnt. daemon(s)
* (this should be done in ipsec_output_policy() instead or
* selectively called based on a flag value)
*/
DPRINTF(IDL_FINISHED,("getassocbyso: calling key mgnt daemons!\n"));
*keyentry = NULL;
if (key_acquire(realtype, src, dst) == 0)
return (1);
else
return(-1);
}
}
*keyentry = NULL;
return(-1);
}
/*----------------------------------------------------------------------
* key_xdata():
* Parse message buffer for src/dst/from/iv/key if parseflag = 0
* else parse for src/dst only.
----------------------------------------------------------------------*/
static int
key_xdata(struct key_msghdr *km, struct key_msgdata *kip, int parseflag)
{
char *cp, *cpmax;
if (!km || (km->key_msglen <= 0))
return (-1);
cp = (caddr_t)(km + 1);
cpmax = (caddr_t)km + km->key_msglen;
/*
* Assumes user process passes message with
* correct word alignment.
*/
/*
* Need to clean up this code later.
*/
/* Grab src addr */
kip->src = (SOCKADDR *)cp;
if (!kip->src->sa_len) {
DPRINTF(IDL_MAJOR_EVENT,("key_xdata couldn't parse src addr\n"));
return(-1);
}
ADVANCE(cp, kip->src->sa_len);
/* Grab dest addr */
kip->dst = (SOCKADDR *)cp;
if (!kip->dst->sa_len) {
DPRINTF(IDL_MAJOR_EVENT,("key_xdata couldn't parse dest addr\n"));
return(-1);
}
ADVANCE(cp, kip->dst->sa_len);
if (parseflag == 1) {
kip->from = 0;
kip->key = kip->iv = 0;
kip->keylen = kip->ivlen = 0;
return(0);
}
/* Grab from addr */
kip->from = (SOCKADDR *)cp;
if (!kip->from->sa_len) {
DPRINTF(IDL_MAJOR_EVENT,("key_xdata couldn't parse from addr\n"));
return(-1);
}
ADVANCE(cp, kip->from->sa_len);
/* Grab key */
if ((kip->keylen = km->keylen)) {
kip->key = cp;
ADVANCE(cp, km->keylen);
} else
kip->key = 0;
/* Grab iv */
if ((kip->ivlen = km->ivlen))
kip->iv = cp;
else
kip->iv = 0;
return (0);
}
int
key_parse(struct key_msghdr **kmp, struct socket *so, int *dstfamily)
{
int error = 0, keyerror = 0;
struct key_msgdata keyinfo;
struct key_secassoc *secassoc = NULL;
struct key_msghdr *km = *kmp;
DPRINTF(IDL_MAJOR_EVENT, ("Entering key_parse\n"));
#define senderr(e) \
{ error = (e); goto flush; }
if (km->key_msgvers != KEY_VERSION) {
DPRINTF(IDL_CRITICAL,("keyoutput: Unsupported key message version!\n"));
senderr(EPROTONOSUPPORT);
}
km->key_pid = CURRENT_PID;
DDO(IDL_MAJOR_EVENT, printf("keymsghdr:\n"); dump_keymsghdr(km));
/*
* Parse buffer for src addr, dest addr, from addr, key, iv
*/
bzero((char *)&keyinfo, sizeof(keyinfo));
switch (km->key_msgtype) {
case KEY_ADD:
DPRINTF(IDL_MAJOR_EVENT,("key_output got KEY_ADD msg\n"));
if (key_xdata(km, &keyinfo, 0) < 0)
goto parsefail;
/*
* Allocate the secassoc structure to insert
* into key table here.
*/
KMALLOC(secassoc, struct key_secassoc *, sizeof(struct key_secassoc));
if (secassoc == 0) {
DPRINTF(IDL_CRITICAL,("keyoutput: No more memory!\n"));
senderr(ENOBUFS);
}
if (key_msghdr2secassoc(secassoc, km, &keyinfo) < 0) {
DPRINTF(IDL_CRITICAL,("keyoutput: key_msghdr2secassoc failed!\n"));
KFREE(secassoc);
senderr(EINVAL);
}
DPRINTF(IDL_MAJOR_EVENT,("secassoc to add:\n"));
DDO(IDL_MAJOR_EVENT,dump_secassoc(secassoc));
if ((keyerror = key_add(secassoc)) != 0) {
DPRINTF(IDL_CRITICAL,("keyoutput: key_add failed\n"));
if (secassoc->key)
KFREE(secassoc->key);
if (secassoc->iv)
KFREE(secassoc->iv);
KFREE(secassoc);
if (keyerror == -2) {
senderr(EEXIST);
} else {
senderr(ENOBUFS);
}
}
break;
case KEY_DELETE:
DPRINTF(IDL_MAJOR_EVENT,("key_output got KEY_DELETE msg\n"));
if (key_xdata(km, &keyinfo, 1) < 0)
goto parsefail;
KMALLOC(secassoc, struct key_secassoc *, sizeof(struct key_secassoc));
if (secassoc == 0) {
senderr(ENOBUFS);
}
if (key_msghdr2secassoc(secassoc, km, &keyinfo) < 0) {
KFREE(secassoc);
senderr(EINVAL);
}
if (key_delete(secassoc) != 0) {
if (secassoc->iv)
KFREE(secassoc->iv);
if (secassoc->key)
KFREE(secassoc->key);
KFREE(secassoc);
senderr(ESRCH);
}
if (secassoc->iv)
KFREE(secassoc->iv);
if (secassoc->key)
KFREE(secassoc->key);
KFREE(secassoc);
break;
case KEY_UPDATE:
DPRINTF(IDL_EVENT,("key_output got KEY_UPDATE msg\n"));
if (key_xdata(km, &keyinfo, 0) < 0)
goto parsefail;
KMALLOC(secassoc, struct key_secassoc *, sizeof(struct key_secassoc));
if (secassoc == 0) {
senderr(ENOBUFS);
}
if (key_msghdr2secassoc(secassoc, km, &keyinfo) < 0) {
KFREE(secassoc);
senderr(EINVAL);
}
if ((keyerror = key_update(secassoc)) != 0) {
DPRINTF(IDL_CRITICAL,("Error updating key entry\n"));
if (secassoc->iv)
KFREE(secassoc->iv);
if (secassoc->key)
KFREE(secassoc->key);
KFREE(secassoc);
senderr(keyerror);
}
KFREE(secassoc);
break;
case KEY_GET:
DPRINTF(IDL_EVENT,("key_output got KEY_GET msg\n"));
if (key_xdata(km, &keyinfo, 1) < 0)
goto parsefail;
if (key_get(km->type, (SOCKADDR *)keyinfo.src,
(SOCKADDR *)keyinfo.dst,
km->spi, &secassoc) != 0) {
DPRINTF(IDL_EVENT,("keyoutput: can't get key\n"));
senderr(ESRCH);
}
if (secassoc) {
int newlen;
DPRINTF(IDL_EVENT,("keyoutput: Found secassoc!\n"));
newlen = sizeof(struct key_msghdr) + ROUNDUP(secassoc->src->sa_len) +
ROUNDUP(secassoc->dst->sa_len) + ROUNDUP(secassoc->from->sa_len) +
ROUNDUP(secassoc->keylen) + ROUNDUP(secassoc->ivlen);
DPRINTF(IDL_EVENT,("keyoutput: newlen=%d\n", newlen));
if (newlen > km->key_msglen) {
struct key_msghdr *newkm;
DPRINTF(IDL_EVENT,("keyoutput: Allocating new buffer!\n"));
KMALLOC(newkm, struct key_msghdr *, newlen);
if (newkm == 0) {
senderr(ENOBUFS);
}
bcopy((char *)km, (char *)newkm, km->key_msglen);
DPRINTF(IDL_FINISHED,("keyoutput: 1\n"));
KFREE(km);
*kmp = km = newkm;
DPRINTF(IDL_CRITICAL, ("km->key_msglen = %d, newlen = %d\n",
km->key_msglen, newlen));
km->key_msglen = newlen;
}
DPRINTF(IDL_FINISHED,("keyoutput: 2\n"));
if (key_secassoc2msghdr(secassoc, km, &keyinfo)) {
DPRINTF(IDL_CRITICAL,("keyoutput: Can't create msghdr!\n"));
senderr(EINVAL);
}
DPRINTF(IDL_FINISHED,("keyoutput: 3\n"));
}
break;
case KEY_GETSPI:
DPRINTF(IDL_EVENT,("key_output got KEY_GETSPI msg\n"));
if (key_xdata(km, &keyinfo, 1) < 0)
goto parsefail;
if ((keyerror = key_getspi(km->type, keyinfo.src, keyinfo.dst,
km->lifetime1, km->lifetime2,
&(km->spi))) != 0) {
DPRINTF(IDL_CRITICAL,("keyoutput: getspi failed error=%d\n", keyerror));
senderr(keyerror);
}
break;
case KEY_REGISTER:
DPRINTF(IDL_EVENT,("key_output got KEY_REGISTER msg\n"));
key_register(so, km->type);
break;
case KEY_DUMP:
DPRINTF(IDL_EVENT,("key_output got KEY_DUMP msg\n"));
error = key_dump(so);
return(error);
break;
case KEY_FLUSH:
DPRINTF(IDL_EVENT,("key_output got KEY_FLUSH msg\n"));
key_flush();
break;
default:
DPRINTF(IDL_CRITICAL,("key_output got unsupported msg type=%d\n",
km->key_msgtype));
senderr(EOPNOTSUPP);
}
goto flush;
parsefail:
keyinfo.dst = NULL;
error = EINVAL;
flush:
if (km)
km->key_errno = error;
if (dstfamily)
*dstfamily = keyinfo.dst ? keyinfo.dst->sa_family : 0;
DPRINTF(IDL_MAJOR_EVENT, ("key_parse exiting with error=%d\n", error));
return error;
}
/*
* Definitions of protocols supported in the KEY domain.
*/
struct sockaddr key_addr = { 2, PF_KEY, };
struct sockproto key_proto = { PF_KEY, };
#define KEYREAPERINT 120
#define ROUNDUP(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
static int
key_sendup(s, km)
struct socket *s;
struct key_msghdr *km;
{
struct mbuf *m;
MGETHDR(m, M_WAIT, MT_DATA);
m->m_len = m->m_pkthdr.len = 0;
m->m_next = 0;
m->m_nextpkt = 0;
m->m_pkthdr.rcvif = 0;
m_copyback(m, 0, km->key_msglen, (caddr_t)km);
if (sbappendaddr(&(s->so_rcv), &key_addr, m, NULL)) {
sorwakeup(s);
return 1;
} else
m_freem(m);
return(0);
}
#ifdef notyet
/*----------------------------------------------------------------------
* key_reaper():
* Scan key table, nuke unwanted entries
----------------------------------------------------------------------*/
static void
key_reaper(whocares)
void *whocares;
{
DPRINTF(IDL_GROSS_EVENT,("Entering key_reaper()\n"));
timeout(key_reaper, NULL, KEYREAPERINT * HZ);
}
#endif /* notyet */
/*----------------------------------------------------------------------
* key_init():
* Init routine for key socket, key engine
----------------------------------------------------------------------*/
static void
key_init(void)
{
DPRINTF(IDL_EVENT,("Called key_init().\n"));
if (key_inittables())
panic("key_inittables failed!\n");
#ifdef notyet
timeout(key_reaper, NULL, HZ);
#endif /* notyet */
bzero((char *)&keyso_cb, sizeof(keyso_cb));
}
/*----------------------------------------------------------------------
* my_addr():
* Determine if an address belongs to one of my configured interfaces.
* Currently handles only AF_INET, AF_INET6 addresses.
----------------------------------------------------------------------*/
static int
my_addr(sa)
SOCKADDR *sa;
{
struct in6_ifaddr *i6a = 0;
struct in_ifaddr *ia = 0;
switch(sa->sa_family) {
#ifdef INET6
case AF_INET6:
for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) {
if (IN6_ADDR_EQUAL(((struct sockaddr_in6 *)sa)->sin6_addr,
i6a->i6a_addr.sin6_addr))
return(1);
}
break;
#endif /* INET6 */
case AF_INET:
for (ia = in_ifaddr; ia; ia = ia->ia_next) {
if (((struct sockaddr_in *)sa)->sin_addr.s_addr ==
ia->ia_addr.sin_addr.s_addr)
return(1);
}
break;
}
return(0);
}
/*----------------------------------------------------------------------
* key_output():
* Process outbound pf_key message.
----------------------------------------------------------------------*/
static int
key_output(struct mbuf *m, struct socket *so)
{
struct key_msghdr *km = 0;
caddr_t cp, cplimit;
int len;
int error = 0;
int dstfamily = 0;
DPRINTF(IDL_EVENT,("key_output() got a message len=%d.\n", m->m_pkthdr.len));
#undef senderr
#define senderr(e) \
{ error = (e); if (km) km->key_errno = error; goto flush; }
if (m == 0 || ((m->m_len < sizeof(long)) &&
(m = m_pullup(m, sizeof(long))) == 0)) {
DPRINTF(IDL_CRITICAL,("key_output can't pullup mbuf\n"));
return (ENOBUFS);
}
if ((m->m_flags & M_PKTHDR) == 0)
panic("key_output");
DDO(IDL_FINISHED,dump_mbuf(m));
len = m->m_pkthdr.len;
if (len < sizeof(*km) || len != mtod(m, struct key_msghdr *)->key_msglen) {
DPRINTF(IDL_CRITICAL,("keyout: Invalid length field/length mismatch!\n"));
senderr(EINVAL);
}
KMALLOC(km, struct key_msghdr *, len);
if (km == 0) {
DPRINTF(IDL_CRITICAL,("keyoutput: Can't malloc memory!\n"));
senderr(ENOBUFS);
}
m_copydata(m, 0, len, (caddr_t)km);
km->key_errno = error = key_parse(&km, so, &dstfamily);
DPRINTF(IDL_MAJOR_EVENT, ("Back from key_parse\n"));
flush:
key_sendup(so, km);
#if 0
{
struct rawcb *rp = 0;
struct mbuf *m;
if ((so->so_options & SO_USELOOPBACK) == 0) {
if (keyso_cb.any_count <= 1) {
if (km)
KFREE(km);
return (error);
}
rp = sotorawcb(so);
}
DPRINTF(IDL_MAJOR_EVENT, ("key_output: foo\n"));
key_proto.sp_protocol = dstfamily;
if (km) {
m = m_devget(km, len, 0, NULL, NULL);
KFREE(km);
}
DPRINTF(IDL_MAJOR_EVENT, ("key_output: bar\n"));
if (rp)
rp->rcb_proto.sp_family = 0; /* Prevent us from receiving message */
raw_input(m, &key_proto, &key_addr, &key_addr);
if (rp)
rp->rcb_proto.sp_family = PF_KEY;
}
DPRINTF(IDL_MAJOR_EVENT, ("key_output: baz\n"));
#endif /* 0 */
return (error);
}
/*----------------------------------------------------------------------
* key_usrreq():
* Handles PRU_* for pf_key sockets.
----------------------------------------------------------------------*/
static int
key_usrreq(struct socket *so, int req, struct mbuf *m, struct mbuf *nam,
struct mbuf *control)
{
register int error = 0;
register struct rawcb *rp = sotorawcb(so);
int s;
DPRINTF(IDL_EVENT,("Entering key_usrreq, req = %d.\n",req));
if (req == PRU_ATTACH) {
MALLOC(rp, struct rawcb *, sizeof(*rp), M_PCB, M_WAITOK);
if (so->so_pcb = (caddr_t)rp)
bzero(so->so_pcb, sizeof(*rp));
}
if (req == PRU_DETACH && rp) {
int af = rp->rcb_proto.sp_protocol;
if (af == AF_INET)
keyso_cb.ip4_count--;
#ifdef INET6
else if (af == AF_INET6)
keyso_cb.ip6_count--;
#endif /* INET6 */
keyso_cb.any_count--;
}
s = splnet();
error = raw_usrreq(so, req, m, nam, control);
rp = sotorawcb(so);
if (req == PRU_ATTACH && rp) {
int af = rp->rcb_proto.sp_protocol;
if (error) {
free((caddr_t)rp, M_PCB);
splx(s);
return error;
}
if (af == AF_INET)
keyso_cb.ip4_count++;
#ifdef INET6
else if (af == AF_INET6)
keyso_cb.ip6_count++;
#endif /* INET6 */
keyso_cb.any_count++;
rp->rcb_faddr = &key_addr;
soisconnected(so); /* Key socket, like routing socket, must be
connected. */
/* Possibly set other needed flags/options at creation time in here. */
so->so_options |= SO_USELOOPBACK; /* Like routing socket, we turn this */
/* on by default */
}
splx(s);
return error;
}
/*----------------------------------------------------------------------
* key_cbinit():
* Control block init routine for key socket
----------------------------------------------------------------------*/
static void
key_cbinit(void)
{
/*
* This is equivalent to raw_init for the routing socket.
* The key socket uses the same control block as the routing
* socket.
*/
DPRINTF(IDL_EVENT,("Called key_cbinit().\n"));
}
/*
* Protoswitch entry for pf_key
*/
extern struct domain keydomain; /* or at least forward */
struct protosw keysw[] = {
{ SOCK_RAW, &keydomain, 0, PR_ATOMIC|PR_ADDR,
raw_input, key_output, raw_ctlinput, 0,
key_usrreq,
key_cbinit
}
};
struct domain keydomain =
{ PF_KEY, "key", key_init, 0, 0,
keysw, &keysw[sizeof(keysw)/sizeof(keysw[0])] };
DOMAIN_SET(key)