mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-27 06:34:11 +08:00
f398035f2d
The aalgos/ealgos fields are only 32 bits wide. However, af_key tries to test them with the expression 1 << id where id can be as large as 253. This produces different behaviour on different architectures. The following patch explicitly checks whether ID is greater than 31 and fails the check if that's the case. We cannot easily extend the mask to be longer than 32 bits due to exposure to user-space. Besides, this whole interface is obsolete anyway in favour of the xfrm_user interface which doesn't use this bit mask in templates (well not within the kernel anyway). Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
3841 lines
101 KiB
C
3841 lines
101 KiB
C
/*
|
|
* net/key/af_key.c An implementation of PF_KEYv2 sockets.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
* Authors: Maxim Giryaev <gem@asplinux.ru>
|
|
* David S. Miller <davem@redhat.com>
|
|
* Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
|
|
* Kunihiro Ishiguro <kunihiro@ipinfusion.com>
|
|
* Kazunori MIYAZAWA / USAGI Project <miyazawa@linux-ipv6.org>
|
|
* Derek Atkins <derek@ihtfp.com>
|
|
*/
|
|
|
|
#include <linux/capability.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/socket.h>
|
|
#include <linux/pfkeyv2.h>
|
|
#include <linux/ipsec.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/in.h>
|
|
#include <linux/in6.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/init.h>
|
|
#include <net/net_namespace.h>
|
|
#include <net/xfrm.h>
|
|
|
|
#include <net/sock.h>
|
|
|
|
#define _X2KEY(x) ((x) == XFRM_INF ? 0 : (x))
|
|
#define _KEY2X(x) ((x) == 0 ? XFRM_INF : (x))
|
|
|
|
|
|
/* List of all pfkey sockets. */
|
|
static HLIST_HEAD(pfkey_table);
|
|
static DECLARE_WAIT_QUEUE_HEAD(pfkey_table_wait);
|
|
static DEFINE_RWLOCK(pfkey_table_lock);
|
|
static atomic_t pfkey_table_users = ATOMIC_INIT(0);
|
|
|
|
static atomic_t pfkey_socks_nr = ATOMIC_INIT(0);
|
|
|
|
struct pfkey_sock {
|
|
/* struct sock must be the first member of struct pfkey_sock */
|
|
struct sock sk;
|
|
int registered;
|
|
int promisc;
|
|
};
|
|
|
|
static inline struct pfkey_sock *pfkey_sk(struct sock *sk)
|
|
{
|
|
return (struct pfkey_sock *)sk;
|
|
}
|
|
|
|
static void pfkey_sock_destruct(struct sock *sk)
|
|
{
|
|
skb_queue_purge(&sk->sk_receive_queue);
|
|
|
|
if (!sock_flag(sk, SOCK_DEAD)) {
|
|
printk("Attempt to release alive pfkey socket: %p\n", sk);
|
|
return;
|
|
}
|
|
|
|
BUG_TRAP(!atomic_read(&sk->sk_rmem_alloc));
|
|
BUG_TRAP(!atomic_read(&sk->sk_wmem_alloc));
|
|
|
|
atomic_dec(&pfkey_socks_nr);
|
|
}
|
|
|
|
static void pfkey_table_grab(void)
|
|
{
|
|
write_lock_bh(&pfkey_table_lock);
|
|
|
|
if (atomic_read(&pfkey_table_users)) {
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
add_wait_queue_exclusive(&pfkey_table_wait, &wait);
|
|
for(;;) {
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
if (atomic_read(&pfkey_table_users) == 0)
|
|
break;
|
|
write_unlock_bh(&pfkey_table_lock);
|
|
schedule();
|
|
write_lock_bh(&pfkey_table_lock);
|
|
}
|
|
|
|
__set_current_state(TASK_RUNNING);
|
|
remove_wait_queue(&pfkey_table_wait, &wait);
|
|
}
|
|
}
|
|
|
|
static __inline__ void pfkey_table_ungrab(void)
|
|
{
|
|
write_unlock_bh(&pfkey_table_lock);
|
|
wake_up(&pfkey_table_wait);
|
|
}
|
|
|
|
static __inline__ void pfkey_lock_table(void)
|
|
{
|
|
/* read_lock() synchronizes us to pfkey_table_grab */
|
|
|
|
read_lock(&pfkey_table_lock);
|
|
atomic_inc(&pfkey_table_users);
|
|
read_unlock(&pfkey_table_lock);
|
|
}
|
|
|
|
static __inline__ void pfkey_unlock_table(void)
|
|
{
|
|
if (atomic_dec_and_test(&pfkey_table_users))
|
|
wake_up(&pfkey_table_wait);
|
|
}
|
|
|
|
|
|
static const struct proto_ops pfkey_ops;
|
|
|
|
static void pfkey_insert(struct sock *sk)
|
|
{
|
|
pfkey_table_grab();
|
|
sk_add_node(sk, &pfkey_table);
|
|
pfkey_table_ungrab();
|
|
}
|
|
|
|
static void pfkey_remove(struct sock *sk)
|
|
{
|
|
pfkey_table_grab();
|
|
sk_del_node_init(sk);
|
|
pfkey_table_ungrab();
|
|
}
|
|
|
|
static struct proto key_proto = {
|
|
.name = "KEY",
|
|
.owner = THIS_MODULE,
|
|
.obj_size = sizeof(struct pfkey_sock),
|
|
};
|
|
|
|
static int pfkey_create(struct net *net, struct socket *sock, int protocol)
|
|
{
|
|
struct sock *sk;
|
|
int err;
|
|
|
|
if (net != &init_net)
|
|
return -EAFNOSUPPORT;
|
|
|
|
if (!capable(CAP_NET_ADMIN))
|
|
return -EPERM;
|
|
if (sock->type != SOCK_RAW)
|
|
return -ESOCKTNOSUPPORT;
|
|
if (protocol != PF_KEY_V2)
|
|
return -EPROTONOSUPPORT;
|
|
|
|
err = -ENOMEM;
|
|
sk = sk_alloc(net, PF_KEY, GFP_KERNEL, &key_proto);
|
|
if (sk == NULL)
|
|
goto out;
|
|
|
|
sock->ops = &pfkey_ops;
|
|
sock_init_data(sock, sk);
|
|
|
|
sk->sk_family = PF_KEY;
|
|
sk->sk_destruct = pfkey_sock_destruct;
|
|
|
|
atomic_inc(&pfkey_socks_nr);
|
|
|
|
pfkey_insert(sk);
|
|
|
|
return 0;
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int pfkey_release(struct socket *sock)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
|
|
if (!sk)
|
|
return 0;
|
|
|
|
pfkey_remove(sk);
|
|
|
|
sock_orphan(sk);
|
|
sock->sk = NULL;
|
|
skb_queue_purge(&sk->sk_write_queue);
|
|
sock_put(sk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_broadcast_one(struct sk_buff *skb, struct sk_buff **skb2,
|
|
gfp_t allocation, struct sock *sk)
|
|
{
|
|
int err = -ENOBUFS;
|
|
|
|
sock_hold(sk);
|
|
if (*skb2 == NULL) {
|
|
if (atomic_read(&skb->users) != 1) {
|
|
*skb2 = skb_clone(skb, allocation);
|
|
} else {
|
|
*skb2 = skb;
|
|
atomic_inc(&skb->users);
|
|
}
|
|
}
|
|
if (*skb2 != NULL) {
|
|
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf) {
|
|
skb_orphan(*skb2);
|
|
skb_set_owner_r(*skb2, sk);
|
|
skb_queue_tail(&sk->sk_receive_queue, *skb2);
|
|
sk->sk_data_ready(sk, (*skb2)->len);
|
|
*skb2 = NULL;
|
|
err = 0;
|
|
}
|
|
}
|
|
sock_put(sk);
|
|
return err;
|
|
}
|
|
|
|
/* Send SKB to all pfkey sockets matching selected criteria. */
|
|
#define BROADCAST_ALL 0
|
|
#define BROADCAST_ONE 1
|
|
#define BROADCAST_REGISTERED 2
|
|
#define BROADCAST_PROMISC_ONLY 4
|
|
static int pfkey_broadcast(struct sk_buff *skb, gfp_t allocation,
|
|
int broadcast_flags, struct sock *one_sk)
|
|
{
|
|
struct sock *sk;
|
|
struct hlist_node *node;
|
|
struct sk_buff *skb2 = NULL;
|
|
int err = -ESRCH;
|
|
|
|
/* XXX Do we need something like netlink_overrun? I think
|
|
* XXX PF_KEY socket apps will not mind current behavior.
|
|
*/
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
pfkey_lock_table();
|
|
sk_for_each(sk, node, &pfkey_table) {
|
|
struct pfkey_sock *pfk = pfkey_sk(sk);
|
|
int err2;
|
|
|
|
/* Yes, it means that if you are meant to receive this
|
|
* pfkey message you receive it twice as promiscuous
|
|
* socket.
|
|
*/
|
|
if (pfk->promisc)
|
|
pfkey_broadcast_one(skb, &skb2, allocation, sk);
|
|
|
|
/* the exact target will be processed later */
|
|
if (sk == one_sk)
|
|
continue;
|
|
if (broadcast_flags != BROADCAST_ALL) {
|
|
if (broadcast_flags & BROADCAST_PROMISC_ONLY)
|
|
continue;
|
|
if ((broadcast_flags & BROADCAST_REGISTERED) &&
|
|
!pfk->registered)
|
|
continue;
|
|
if (broadcast_flags & BROADCAST_ONE)
|
|
continue;
|
|
}
|
|
|
|
err2 = pfkey_broadcast_one(skb, &skb2, allocation, sk);
|
|
|
|
/* Error is cleare after succecful sending to at least one
|
|
* registered KM */
|
|
if ((broadcast_flags & BROADCAST_REGISTERED) && err)
|
|
err = err2;
|
|
}
|
|
pfkey_unlock_table();
|
|
|
|
if (one_sk != NULL)
|
|
err = pfkey_broadcast_one(skb, &skb2, allocation, one_sk);
|
|
|
|
if (skb2)
|
|
kfree_skb(skb2);
|
|
kfree_skb(skb);
|
|
return err;
|
|
}
|
|
|
|
static inline void pfkey_hdr_dup(struct sadb_msg *new, struct sadb_msg *orig)
|
|
{
|
|
*new = *orig;
|
|
}
|
|
|
|
static int pfkey_error(struct sadb_msg *orig, int err, struct sock *sk)
|
|
{
|
|
struct sk_buff *skb = alloc_skb(sizeof(struct sadb_msg) + 16, GFP_KERNEL);
|
|
struct sadb_msg *hdr;
|
|
|
|
if (!skb)
|
|
return -ENOBUFS;
|
|
|
|
/* Woe be to the platform trying to support PFKEY yet
|
|
* having normal errnos outside the 1-255 range, inclusive.
|
|
*/
|
|
err = -err;
|
|
if (err == ERESTARTSYS ||
|
|
err == ERESTARTNOHAND ||
|
|
err == ERESTARTNOINTR)
|
|
err = EINTR;
|
|
if (err >= 512)
|
|
err = EINVAL;
|
|
BUG_ON(err <= 0 || err >= 256);
|
|
|
|
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
|
|
pfkey_hdr_dup(hdr, orig);
|
|
hdr->sadb_msg_errno = (uint8_t) err;
|
|
hdr->sadb_msg_len = (sizeof(struct sadb_msg) /
|
|
sizeof(uint64_t));
|
|
|
|
pfkey_broadcast(skb, GFP_KERNEL, BROADCAST_ONE, sk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u8 sadb_ext_min_len[] = {
|
|
[SADB_EXT_RESERVED] = (u8) 0,
|
|
[SADB_EXT_SA] = (u8) sizeof(struct sadb_sa),
|
|
[SADB_EXT_LIFETIME_CURRENT] = (u8) sizeof(struct sadb_lifetime),
|
|
[SADB_EXT_LIFETIME_HARD] = (u8) sizeof(struct sadb_lifetime),
|
|
[SADB_EXT_LIFETIME_SOFT] = (u8) sizeof(struct sadb_lifetime),
|
|
[SADB_EXT_ADDRESS_SRC] = (u8) sizeof(struct sadb_address),
|
|
[SADB_EXT_ADDRESS_DST] = (u8) sizeof(struct sadb_address),
|
|
[SADB_EXT_ADDRESS_PROXY] = (u8) sizeof(struct sadb_address),
|
|
[SADB_EXT_KEY_AUTH] = (u8) sizeof(struct sadb_key),
|
|
[SADB_EXT_KEY_ENCRYPT] = (u8) sizeof(struct sadb_key),
|
|
[SADB_EXT_IDENTITY_SRC] = (u8) sizeof(struct sadb_ident),
|
|
[SADB_EXT_IDENTITY_DST] = (u8) sizeof(struct sadb_ident),
|
|
[SADB_EXT_SENSITIVITY] = (u8) sizeof(struct sadb_sens),
|
|
[SADB_EXT_PROPOSAL] = (u8) sizeof(struct sadb_prop),
|
|
[SADB_EXT_SUPPORTED_AUTH] = (u8) sizeof(struct sadb_supported),
|
|
[SADB_EXT_SUPPORTED_ENCRYPT] = (u8) sizeof(struct sadb_supported),
|
|
[SADB_EXT_SPIRANGE] = (u8) sizeof(struct sadb_spirange),
|
|
[SADB_X_EXT_KMPRIVATE] = (u8) sizeof(struct sadb_x_kmprivate),
|
|
[SADB_X_EXT_POLICY] = (u8) sizeof(struct sadb_x_policy),
|
|
[SADB_X_EXT_SA2] = (u8) sizeof(struct sadb_x_sa2),
|
|
[SADB_X_EXT_NAT_T_TYPE] = (u8) sizeof(struct sadb_x_nat_t_type),
|
|
[SADB_X_EXT_NAT_T_SPORT] = (u8) sizeof(struct sadb_x_nat_t_port),
|
|
[SADB_X_EXT_NAT_T_DPORT] = (u8) sizeof(struct sadb_x_nat_t_port),
|
|
[SADB_X_EXT_NAT_T_OA] = (u8) sizeof(struct sadb_address),
|
|
[SADB_X_EXT_SEC_CTX] = (u8) sizeof(struct sadb_x_sec_ctx),
|
|
};
|
|
|
|
/* Verify sadb_address_{len,prefixlen} against sa_family. */
|
|
static int verify_address_len(void *p)
|
|
{
|
|
struct sadb_address *sp = p;
|
|
struct sockaddr *addr = (struct sockaddr *)(sp + 1);
|
|
struct sockaddr_in *sin;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
int len;
|
|
|
|
switch (addr->sa_family) {
|
|
case AF_INET:
|
|
len = DIV_ROUND_UP(sizeof(*sp) + sizeof(*sin), sizeof(uint64_t));
|
|
if (sp->sadb_address_len != len ||
|
|
sp->sadb_address_prefixlen > 32)
|
|
return -EINVAL;
|
|
break;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
len = DIV_ROUND_UP(sizeof(*sp) + sizeof(*sin6), sizeof(uint64_t));
|
|
if (sp->sadb_address_len != len ||
|
|
sp->sadb_address_prefixlen > 128)
|
|
return -EINVAL;
|
|
break;
|
|
#endif
|
|
default:
|
|
/* It is user using kernel to keep track of security
|
|
* associations for another protocol, such as
|
|
* OSPF/RSVP/RIPV2/MIP. It is user's job to verify
|
|
* lengths.
|
|
*
|
|
* XXX Actually, association/policy database is not yet
|
|
* XXX able to cope with arbitrary sockaddr families.
|
|
* XXX When it can, remove this -EINVAL. -DaveM
|
|
*/
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int pfkey_sec_ctx_len(struct sadb_x_sec_ctx *sec_ctx)
|
|
{
|
|
return DIV_ROUND_UP(sizeof(struct sadb_x_sec_ctx) +
|
|
sec_ctx->sadb_x_ctx_len,
|
|
sizeof(uint64_t));
|
|
}
|
|
|
|
static inline int verify_sec_ctx_len(void *p)
|
|
{
|
|
struct sadb_x_sec_ctx *sec_ctx = (struct sadb_x_sec_ctx *)p;
|
|
int len = sec_ctx->sadb_x_ctx_len;
|
|
|
|
if (len > PAGE_SIZE)
|
|
return -EINVAL;
|
|
|
|
len = pfkey_sec_ctx_len(sec_ctx);
|
|
|
|
if (sec_ctx->sadb_x_sec_len != len)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline struct xfrm_user_sec_ctx *pfkey_sadb2xfrm_user_sec_ctx(struct sadb_x_sec_ctx *sec_ctx)
|
|
{
|
|
struct xfrm_user_sec_ctx *uctx = NULL;
|
|
int ctx_size = sec_ctx->sadb_x_ctx_len;
|
|
|
|
uctx = kmalloc((sizeof(*uctx)+ctx_size), GFP_KERNEL);
|
|
|
|
if (!uctx)
|
|
return NULL;
|
|
|
|
uctx->len = pfkey_sec_ctx_len(sec_ctx);
|
|
uctx->exttype = sec_ctx->sadb_x_sec_exttype;
|
|
uctx->ctx_doi = sec_ctx->sadb_x_ctx_doi;
|
|
uctx->ctx_alg = sec_ctx->sadb_x_ctx_alg;
|
|
uctx->ctx_len = sec_ctx->sadb_x_ctx_len;
|
|
memcpy(uctx + 1, sec_ctx + 1,
|
|
uctx->ctx_len);
|
|
|
|
return uctx;
|
|
}
|
|
|
|
static int present_and_same_family(struct sadb_address *src,
|
|
struct sadb_address *dst)
|
|
{
|
|
struct sockaddr *s_addr, *d_addr;
|
|
|
|
if (!src || !dst)
|
|
return 0;
|
|
|
|
s_addr = (struct sockaddr *)(src + 1);
|
|
d_addr = (struct sockaddr *)(dst + 1);
|
|
if (s_addr->sa_family != d_addr->sa_family)
|
|
return 0;
|
|
if (s_addr->sa_family != AF_INET
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
&& s_addr->sa_family != AF_INET6
|
|
#endif
|
|
)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int parse_exthdrs(struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
char *p = (char *) hdr;
|
|
int len = skb->len;
|
|
|
|
len -= sizeof(*hdr);
|
|
p += sizeof(*hdr);
|
|
while (len > 0) {
|
|
struct sadb_ext *ehdr = (struct sadb_ext *) p;
|
|
uint16_t ext_type;
|
|
int ext_len;
|
|
|
|
ext_len = ehdr->sadb_ext_len;
|
|
ext_len *= sizeof(uint64_t);
|
|
ext_type = ehdr->sadb_ext_type;
|
|
if (ext_len < sizeof(uint64_t) ||
|
|
ext_len > len ||
|
|
ext_type == SADB_EXT_RESERVED)
|
|
return -EINVAL;
|
|
|
|
if (ext_type <= SADB_EXT_MAX) {
|
|
int min = (int) sadb_ext_min_len[ext_type];
|
|
if (ext_len < min)
|
|
return -EINVAL;
|
|
if (ext_hdrs[ext_type-1] != NULL)
|
|
return -EINVAL;
|
|
if (ext_type == SADB_EXT_ADDRESS_SRC ||
|
|
ext_type == SADB_EXT_ADDRESS_DST ||
|
|
ext_type == SADB_EXT_ADDRESS_PROXY ||
|
|
ext_type == SADB_X_EXT_NAT_T_OA) {
|
|
if (verify_address_len(p))
|
|
return -EINVAL;
|
|
}
|
|
if (ext_type == SADB_X_EXT_SEC_CTX) {
|
|
if (verify_sec_ctx_len(p))
|
|
return -EINVAL;
|
|
}
|
|
ext_hdrs[ext_type-1] = p;
|
|
}
|
|
p += ext_len;
|
|
len -= ext_len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint16_t
|
|
pfkey_satype2proto(uint8_t satype)
|
|
{
|
|
switch (satype) {
|
|
case SADB_SATYPE_UNSPEC:
|
|
return IPSEC_PROTO_ANY;
|
|
case SADB_SATYPE_AH:
|
|
return IPPROTO_AH;
|
|
case SADB_SATYPE_ESP:
|
|
return IPPROTO_ESP;
|
|
case SADB_X_SATYPE_IPCOMP:
|
|
return IPPROTO_COMP;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
static uint8_t
|
|
pfkey_proto2satype(uint16_t proto)
|
|
{
|
|
switch (proto) {
|
|
case IPPROTO_AH:
|
|
return SADB_SATYPE_AH;
|
|
case IPPROTO_ESP:
|
|
return SADB_SATYPE_ESP;
|
|
case IPPROTO_COMP:
|
|
return SADB_X_SATYPE_IPCOMP;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
/* BTW, this scheme means that there is no way with PFKEY2 sockets to
|
|
* say specifically 'just raw sockets' as we encode them as 255.
|
|
*/
|
|
|
|
static uint8_t pfkey_proto_to_xfrm(uint8_t proto)
|
|
{
|
|
return (proto == IPSEC_PROTO_ANY ? 0 : proto);
|
|
}
|
|
|
|
static uint8_t pfkey_proto_from_xfrm(uint8_t proto)
|
|
{
|
|
return (proto ? proto : IPSEC_PROTO_ANY);
|
|
}
|
|
|
|
static int pfkey_sadb_addr2xfrm_addr(struct sadb_address *addr,
|
|
xfrm_address_t *xaddr)
|
|
{
|
|
switch (((struct sockaddr*)(addr + 1))->sa_family) {
|
|
case AF_INET:
|
|
xaddr->a4 =
|
|
((struct sockaddr_in *)(addr + 1))->sin_addr.s_addr;
|
|
return AF_INET;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
memcpy(xaddr->a6,
|
|
&((struct sockaddr_in6 *)(addr + 1))->sin6_addr,
|
|
sizeof(struct in6_addr));
|
|
return AF_INET6;
|
|
#endif
|
|
default:
|
|
return 0;
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
static struct xfrm_state *pfkey_xfrm_state_lookup(struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct sadb_sa *sa;
|
|
struct sadb_address *addr;
|
|
uint16_t proto;
|
|
unsigned short family;
|
|
xfrm_address_t *xaddr;
|
|
|
|
sa = (struct sadb_sa *) ext_hdrs[SADB_EXT_SA-1];
|
|
if (sa == NULL)
|
|
return NULL;
|
|
|
|
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
|
|
if (proto == 0)
|
|
return NULL;
|
|
|
|
/* sadb_address_len should be checked by caller */
|
|
addr = (struct sadb_address *) ext_hdrs[SADB_EXT_ADDRESS_DST-1];
|
|
if (addr == NULL)
|
|
return NULL;
|
|
|
|
family = ((struct sockaddr *)(addr + 1))->sa_family;
|
|
switch (family) {
|
|
case AF_INET:
|
|
xaddr = (xfrm_address_t *)&((struct sockaddr_in *)(addr + 1))->sin_addr;
|
|
break;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
xaddr = (xfrm_address_t *)&((struct sockaddr_in6 *)(addr + 1))->sin6_addr;
|
|
break;
|
|
#endif
|
|
default:
|
|
xaddr = NULL;
|
|
}
|
|
|
|
if (!xaddr)
|
|
return NULL;
|
|
|
|
return xfrm_state_lookup(xaddr, sa->sadb_sa_spi, proto, family);
|
|
}
|
|
|
|
#define PFKEY_ALIGN8(a) (1 + (((a) - 1) | (8 - 1)))
|
|
static int
|
|
pfkey_sockaddr_size(sa_family_t family)
|
|
{
|
|
switch (family) {
|
|
case AF_INET:
|
|
return PFKEY_ALIGN8(sizeof(struct sockaddr_in));
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
return PFKEY_ALIGN8(sizeof(struct sockaddr_in6));
|
|
#endif
|
|
default:
|
|
return 0;
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
static inline int pfkey_mode_from_xfrm(int mode)
|
|
{
|
|
switch(mode) {
|
|
case XFRM_MODE_TRANSPORT:
|
|
return IPSEC_MODE_TRANSPORT;
|
|
case XFRM_MODE_TUNNEL:
|
|
return IPSEC_MODE_TUNNEL;
|
|
case XFRM_MODE_BEET:
|
|
return IPSEC_MODE_BEET;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static inline int pfkey_mode_to_xfrm(int mode)
|
|
{
|
|
switch(mode) {
|
|
case IPSEC_MODE_ANY: /*XXX*/
|
|
case IPSEC_MODE_TRANSPORT:
|
|
return XFRM_MODE_TRANSPORT;
|
|
case IPSEC_MODE_TUNNEL:
|
|
return XFRM_MODE_TUNNEL;
|
|
case IPSEC_MODE_BEET:
|
|
return XFRM_MODE_BEET;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static struct sk_buff *__pfkey_xfrm_state2msg(struct xfrm_state *x,
|
|
int add_keys, int hsc)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct sadb_msg *hdr;
|
|
struct sadb_sa *sa;
|
|
struct sadb_lifetime *lifetime;
|
|
struct sadb_address *addr;
|
|
struct sadb_key *key;
|
|
struct sadb_x_sa2 *sa2;
|
|
struct sockaddr_in *sin;
|
|
struct sadb_x_sec_ctx *sec_ctx;
|
|
struct xfrm_sec_ctx *xfrm_ctx;
|
|
int ctx_size = 0;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
int size;
|
|
int auth_key_size = 0;
|
|
int encrypt_key_size = 0;
|
|
int sockaddr_size;
|
|
struct xfrm_encap_tmpl *natt = NULL;
|
|
int mode;
|
|
|
|
/* address family check */
|
|
sockaddr_size = pfkey_sockaddr_size(x->props.family);
|
|
if (!sockaddr_size)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* base, SA, (lifetime (HSC),) address(SD), (address(P),)
|
|
key(AE), (identity(SD),) (sensitivity)> */
|
|
size = sizeof(struct sadb_msg) +sizeof(struct sadb_sa) +
|
|
sizeof(struct sadb_lifetime) +
|
|
((hsc & 1) ? sizeof(struct sadb_lifetime) : 0) +
|
|
((hsc & 2) ? sizeof(struct sadb_lifetime) : 0) +
|
|
sizeof(struct sadb_address)*2 +
|
|
sockaddr_size*2 +
|
|
sizeof(struct sadb_x_sa2);
|
|
|
|
if ((xfrm_ctx = x->security)) {
|
|
ctx_size = PFKEY_ALIGN8(xfrm_ctx->ctx_len);
|
|
size += sizeof(struct sadb_x_sec_ctx) + ctx_size;
|
|
}
|
|
|
|
/* identity & sensitivity */
|
|
|
|
if ((x->props.family == AF_INET &&
|
|
x->sel.saddr.a4 != x->props.saddr.a4)
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
|| (x->props.family == AF_INET6 &&
|
|
memcmp (x->sel.saddr.a6, x->props.saddr.a6, sizeof (struct in6_addr)))
|
|
#endif
|
|
)
|
|
size += sizeof(struct sadb_address) + sockaddr_size;
|
|
|
|
if (add_keys) {
|
|
if (x->aalg && x->aalg->alg_key_len) {
|
|
auth_key_size =
|
|
PFKEY_ALIGN8((x->aalg->alg_key_len + 7) / 8);
|
|
size += sizeof(struct sadb_key) + auth_key_size;
|
|
}
|
|
if (x->ealg && x->ealg->alg_key_len) {
|
|
encrypt_key_size =
|
|
PFKEY_ALIGN8((x->ealg->alg_key_len+7) / 8);
|
|
size += sizeof(struct sadb_key) + encrypt_key_size;
|
|
}
|
|
}
|
|
if (x->encap)
|
|
natt = x->encap;
|
|
|
|
if (natt && natt->encap_type) {
|
|
size += sizeof(struct sadb_x_nat_t_type);
|
|
size += sizeof(struct sadb_x_nat_t_port);
|
|
size += sizeof(struct sadb_x_nat_t_port);
|
|
}
|
|
|
|
skb = alloc_skb(size + 16, GFP_ATOMIC);
|
|
if (skb == NULL)
|
|
return ERR_PTR(-ENOBUFS);
|
|
|
|
/* call should fill header later */
|
|
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
|
|
memset(hdr, 0, size); /* XXX do we need this ? */
|
|
hdr->sadb_msg_len = size / sizeof(uint64_t);
|
|
|
|
/* sa */
|
|
sa = (struct sadb_sa *) skb_put(skb, sizeof(struct sadb_sa));
|
|
sa->sadb_sa_len = sizeof(struct sadb_sa)/sizeof(uint64_t);
|
|
sa->sadb_sa_exttype = SADB_EXT_SA;
|
|
sa->sadb_sa_spi = x->id.spi;
|
|
sa->sadb_sa_replay = x->props.replay_window;
|
|
switch (x->km.state) {
|
|
case XFRM_STATE_VALID:
|
|
sa->sadb_sa_state = x->km.dying ?
|
|
SADB_SASTATE_DYING : SADB_SASTATE_MATURE;
|
|
break;
|
|
case XFRM_STATE_ACQ:
|
|
sa->sadb_sa_state = SADB_SASTATE_LARVAL;
|
|
break;
|
|
default:
|
|
sa->sadb_sa_state = SADB_SASTATE_DEAD;
|
|
break;
|
|
}
|
|
sa->sadb_sa_auth = 0;
|
|
if (x->aalg) {
|
|
struct xfrm_algo_desc *a = xfrm_aalg_get_byname(x->aalg->alg_name, 0);
|
|
sa->sadb_sa_auth = a ? a->desc.sadb_alg_id : 0;
|
|
}
|
|
sa->sadb_sa_encrypt = 0;
|
|
BUG_ON(x->ealg && x->calg);
|
|
if (x->ealg) {
|
|
struct xfrm_algo_desc *a = xfrm_ealg_get_byname(x->ealg->alg_name, 0);
|
|
sa->sadb_sa_encrypt = a ? a->desc.sadb_alg_id : 0;
|
|
}
|
|
/* KAME compatible: sadb_sa_encrypt is overloaded with calg id */
|
|
if (x->calg) {
|
|
struct xfrm_algo_desc *a = xfrm_calg_get_byname(x->calg->alg_name, 0);
|
|
sa->sadb_sa_encrypt = a ? a->desc.sadb_alg_id : 0;
|
|
}
|
|
|
|
sa->sadb_sa_flags = 0;
|
|
if (x->props.flags & XFRM_STATE_NOECN)
|
|
sa->sadb_sa_flags |= SADB_SAFLAGS_NOECN;
|
|
if (x->props.flags & XFRM_STATE_DECAP_DSCP)
|
|
sa->sadb_sa_flags |= SADB_SAFLAGS_DECAP_DSCP;
|
|
if (x->props.flags & XFRM_STATE_NOPMTUDISC)
|
|
sa->sadb_sa_flags |= SADB_SAFLAGS_NOPMTUDISC;
|
|
|
|
/* hard time */
|
|
if (hsc & 2) {
|
|
lifetime = (struct sadb_lifetime *) skb_put(skb,
|
|
sizeof(struct sadb_lifetime));
|
|
lifetime->sadb_lifetime_len =
|
|
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
|
|
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD;
|
|
lifetime->sadb_lifetime_allocations = _X2KEY(x->lft.hard_packet_limit);
|
|
lifetime->sadb_lifetime_bytes = _X2KEY(x->lft.hard_byte_limit);
|
|
lifetime->sadb_lifetime_addtime = x->lft.hard_add_expires_seconds;
|
|
lifetime->sadb_lifetime_usetime = x->lft.hard_use_expires_seconds;
|
|
}
|
|
/* soft time */
|
|
if (hsc & 1) {
|
|
lifetime = (struct sadb_lifetime *) skb_put(skb,
|
|
sizeof(struct sadb_lifetime));
|
|
lifetime->sadb_lifetime_len =
|
|
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
|
|
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT;
|
|
lifetime->sadb_lifetime_allocations = _X2KEY(x->lft.soft_packet_limit);
|
|
lifetime->sadb_lifetime_bytes = _X2KEY(x->lft.soft_byte_limit);
|
|
lifetime->sadb_lifetime_addtime = x->lft.soft_add_expires_seconds;
|
|
lifetime->sadb_lifetime_usetime = x->lft.soft_use_expires_seconds;
|
|
}
|
|
/* current time */
|
|
lifetime = (struct sadb_lifetime *) skb_put(skb,
|
|
sizeof(struct sadb_lifetime));
|
|
lifetime->sadb_lifetime_len =
|
|
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
|
|
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_CURRENT;
|
|
lifetime->sadb_lifetime_allocations = x->curlft.packets;
|
|
lifetime->sadb_lifetime_bytes = x->curlft.bytes;
|
|
lifetime->sadb_lifetime_addtime = x->curlft.add_time;
|
|
lifetime->sadb_lifetime_usetime = x->curlft.use_time;
|
|
/* src address */
|
|
addr = (struct sadb_address*) skb_put(skb,
|
|
sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
|
|
/* "if the ports are non-zero, then the sadb_address_proto field,
|
|
normally zero, MUST be filled in with the transport
|
|
protocol's number." - RFC2367 */
|
|
addr->sadb_address_proto = 0;
|
|
addr->sadb_address_reserved = 0;
|
|
if (x->props.family == AF_INET) {
|
|
addr->sadb_address_prefixlen = 32;
|
|
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = x->props.saddr.a4;
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (x->props.family == AF_INET6) {
|
|
addr->sadb_address_prefixlen = 128;
|
|
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr, x->props.saddr.a6,
|
|
sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
}
|
|
#endif
|
|
else
|
|
BUG();
|
|
|
|
/* dst address */
|
|
addr = (struct sadb_address*) skb_put(skb,
|
|
sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
|
|
addr->sadb_address_proto = 0;
|
|
addr->sadb_address_prefixlen = 32; /* XXX */
|
|
addr->sadb_address_reserved = 0;
|
|
if (x->props.family == AF_INET) {
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = x->id.daddr.a4;
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
|
|
if (x->sel.saddr.a4 != x->props.saddr.a4) {
|
|
addr = (struct sadb_address*) skb_put(skb,
|
|
sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_PROXY;
|
|
addr->sadb_address_proto =
|
|
pfkey_proto_from_xfrm(x->sel.proto);
|
|
addr->sadb_address_prefixlen = x->sel.prefixlen_s;
|
|
addr->sadb_address_reserved = 0;
|
|
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = x->sel.saddr.a4;
|
|
sin->sin_port = x->sel.sport;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (x->props.family == AF_INET6) {
|
|
addr->sadb_address_prefixlen = 128;
|
|
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr, x->id.daddr.a6, sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
|
|
if (memcmp (x->sel.saddr.a6, x->props.saddr.a6,
|
|
sizeof(struct in6_addr))) {
|
|
addr = (struct sadb_address *) skb_put(skb,
|
|
sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_PROXY;
|
|
addr->sadb_address_proto =
|
|
pfkey_proto_from_xfrm(x->sel.proto);
|
|
addr->sadb_address_prefixlen = x->sel.prefixlen_s;
|
|
addr->sadb_address_reserved = 0;
|
|
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = x->sel.sport;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr, x->sel.saddr.a6,
|
|
sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
}
|
|
}
|
|
#endif
|
|
else
|
|
BUG();
|
|
|
|
/* auth key */
|
|
if (add_keys && auth_key_size) {
|
|
key = (struct sadb_key *) skb_put(skb,
|
|
sizeof(struct sadb_key)+auth_key_size);
|
|
key->sadb_key_len = (sizeof(struct sadb_key) + auth_key_size) /
|
|
sizeof(uint64_t);
|
|
key->sadb_key_exttype = SADB_EXT_KEY_AUTH;
|
|
key->sadb_key_bits = x->aalg->alg_key_len;
|
|
key->sadb_key_reserved = 0;
|
|
memcpy(key + 1, x->aalg->alg_key, (x->aalg->alg_key_len+7)/8);
|
|
}
|
|
/* encrypt key */
|
|
if (add_keys && encrypt_key_size) {
|
|
key = (struct sadb_key *) skb_put(skb,
|
|
sizeof(struct sadb_key)+encrypt_key_size);
|
|
key->sadb_key_len = (sizeof(struct sadb_key) +
|
|
encrypt_key_size) / sizeof(uint64_t);
|
|
key->sadb_key_exttype = SADB_EXT_KEY_ENCRYPT;
|
|
key->sadb_key_bits = x->ealg->alg_key_len;
|
|
key->sadb_key_reserved = 0;
|
|
memcpy(key + 1, x->ealg->alg_key,
|
|
(x->ealg->alg_key_len+7)/8);
|
|
}
|
|
|
|
/* sa */
|
|
sa2 = (struct sadb_x_sa2 *) skb_put(skb, sizeof(struct sadb_x_sa2));
|
|
sa2->sadb_x_sa2_len = sizeof(struct sadb_x_sa2)/sizeof(uint64_t);
|
|
sa2->sadb_x_sa2_exttype = SADB_X_EXT_SA2;
|
|
if ((mode = pfkey_mode_from_xfrm(x->props.mode)) < 0) {
|
|
kfree_skb(skb);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
sa2->sadb_x_sa2_mode = mode;
|
|
sa2->sadb_x_sa2_reserved1 = 0;
|
|
sa2->sadb_x_sa2_reserved2 = 0;
|
|
sa2->sadb_x_sa2_sequence = 0;
|
|
sa2->sadb_x_sa2_reqid = x->props.reqid;
|
|
|
|
if (natt && natt->encap_type) {
|
|
struct sadb_x_nat_t_type *n_type;
|
|
struct sadb_x_nat_t_port *n_port;
|
|
|
|
/* type */
|
|
n_type = (struct sadb_x_nat_t_type*) skb_put(skb, sizeof(*n_type));
|
|
n_type->sadb_x_nat_t_type_len = sizeof(*n_type)/sizeof(uint64_t);
|
|
n_type->sadb_x_nat_t_type_exttype = SADB_X_EXT_NAT_T_TYPE;
|
|
n_type->sadb_x_nat_t_type_type = natt->encap_type;
|
|
n_type->sadb_x_nat_t_type_reserved[0] = 0;
|
|
n_type->sadb_x_nat_t_type_reserved[1] = 0;
|
|
n_type->sadb_x_nat_t_type_reserved[2] = 0;
|
|
|
|
/* source port */
|
|
n_port = (struct sadb_x_nat_t_port*) skb_put(skb, sizeof (*n_port));
|
|
n_port->sadb_x_nat_t_port_len = sizeof(*n_port)/sizeof(uint64_t);
|
|
n_port->sadb_x_nat_t_port_exttype = SADB_X_EXT_NAT_T_SPORT;
|
|
n_port->sadb_x_nat_t_port_port = natt->encap_sport;
|
|
n_port->sadb_x_nat_t_port_reserved = 0;
|
|
|
|
/* dest port */
|
|
n_port = (struct sadb_x_nat_t_port*) skb_put(skb, sizeof (*n_port));
|
|
n_port->sadb_x_nat_t_port_len = sizeof(*n_port)/sizeof(uint64_t);
|
|
n_port->sadb_x_nat_t_port_exttype = SADB_X_EXT_NAT_T_DPORT;
|
|
n_port->sadb_x_nat_t_port_port = natt->encap_dport;
|
|
n_port->sadb_x_nat_t_port_reserved = 0;
|
|
}
|
|
|
|
/* security context */
|
|
if (xfrm_ctx) {
|
|
sec_ctx = (struct sadb_x_sec_ctx *) skb_put(skb,
|
|
sizeof(struct sadb_x_sec_ctx) + ctx_size);
|
|
sec_ctx->sadb_x_sec_len =
|
|
(sizeof(struct sadb_x_sec_ctx) + ctx_size) / sizeof(uint64_t);
|
|
sec_ctx->sadb_x_sec_exttype = SADB_X_EXT_SEC_CTX;
|
|
sec_ctx->sadb_x_ctx_doi = xfrm_ctx->ctx_doi;
|
|
sec_ctx->sadb_x_ctx_alg = xfrm_ctx->ctx_alg;
|
|
sec_ctx->sadb_x_ctx_len = xfrm_ctx->ctx_len;
|
|
memcpy(sec_ctx + 1, xfrm_ctx->ctx_str,
|
|
xfrm_ctx->ctx_len);
|
|
}
|
|
|
|
return skb;
|
|
}
|
|
|
|
|
|
static inline struct sk_buff *pfkey_xfrm_state2msg(struct xfrm_state *x)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
skb = __pfkey_xfrm_state2msg(x, 1, 3);
|
|
|
|
return skb;
|
|
}
|
|
|
|
static inline struct sk_buff *pfkey_xfrm_state2msg_expire(struct xfrm_state *x,
|
|
int hsc)
|
|
{
|
|
return __pfkey_xfrm_state2msg(x, 0, hsc);
|
|
}
|
|
|
|
static struct xfrm_state * pfkey_msg2xfrm_state(struct sadb_msg *hdr,
|
|
void **ext_hdrs)
|
|
{
|
|
struct xfrm_state *x;
|
|
struct sadb_lifetime *lifetime;
|
|
struct sadb_sa *sa;
|
|
struct sadb_key *key;
|
|
struct sadb_x_sec_ctx *sec_ctx;
|
|
uint16_t proto;
|
|
int err;
|
|
|
|
|
|
sa = (struct sadb_sa *) ext_hdrs[SADB_EXT_SA-1];
|
|
if (!sa ||
|
|
!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
ext_hdrs[SADB_EXT_ADDRESS_DST-1]))
|
|
return ERR_PTR(-EINVAL);
|
|
if (hdr->sadb_msg_satype == SADB_SATYPE_ESP &&
|
|
!ext_hdrs[SADB_EXT_KEY_ENCRYPT-1])
|
|
return ERR_PTR(-EINVAL);
|
|
if (hdr->sadb_msg_satype == SADB_SATYPE_AH &&
|
|
!ext_hdrs[SADB_EXT_KEY_AUTH-1])
|
|
return ERR_PTR(-EINVAL);
|
|
if (!!ext_hdrs[SADB_EXT_LIFETIME_HARD-1] !=
|
|
!!ext_hdrs[SADB_EXT_LIFETIME_SOFT-1])
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
|
|
if (proto == 0)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
/* default error is no buffer space */
|
|
err = -ENOBUFS;
|
|
|
|
/* RFC2367:
|
|
|
|
Only SADB_SASTATE_MATURE SAs may be submitted in an SADB_ADD message.
|
|
SADB_SASTATE_LARVAL SAs are created by SADB_GETSPI and it is not
|
|
sensible to add a new SA in the DYING or SADB_SASTATE_DEAD state.
|
|
Therefore, the sadb_sa_state field of all submitted SAs MUST be
|
|
SADB_SASTATE_MATURE and the kernel MUST return an error if this is
|
|
not true.
|
|
|
|
However, KAME setkey always uses SADB_SASTATE_LARVAL.
|
|
Hence, we have to _ignore_ sadb_sa_state, which is also reasonable.
|
|
*/
|
|
if (sa->sadb_sa_auth > SADB_AALG_MAX ||
|
|
(hdr->sadb_msg_satype == SADB_X_SATYPE_IPCOMP &&
|
|
sa->sadb_sa_encrypt > SADB_X_CALG_MAX) ||
|
|
sa->sadb_sa_encrypt > SADB_EALG_MAX)
|
|
return ERR_PTR(-EINVAL);
|
|
key = (struct sadb_key*) ext_hdrs[SADB_EXT_KEY_AUTH-1];
|
|
if (key != NULL &&
|
|
sa->sadb_sa_auth != SADB_X_AALG_NULL &&
|
|
((key->sadb_key_bits+7) / 8 == 0 ||
|
|
(key->sadb_key_bits+7) / 8 > key->sadb_key_len * sizeof(uint64_t)))
|
|
return ERR_PTR(-EINVAL);
|
|
key = ext_hdrs[SADB_EXT_KEY_ENCRYPT-1];
|
|
if (key != NULL &&
|
|
sa->sadb_sa_encrypt != SADB_EALG_NULL &&
|
|
((key->sadb_key_bits+7) / 8 == 0 ||
|
|
(key->sadb_key_bits+7) / 8 > key->sadb_key_len * sizeof(uint64_t)))
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
x = xfrm_state_alloc();
|
|
if (x == NULL)
|
|
return ERR_PTR(-ENOBUFS);
|
|
|
|
x->id.proto = proto;
|
|
x->id.spi = sa->sadb_sa_spi;
|
|
x->props.replay_window = sa->sadb_sa_replay;
|
|
if (sa->sadb_sa_flags & SADB_SAFLAGS_NOECN)
|
|
x->props.flags |= XFRM_STATE_NOECN;
|
|
if (sa->sadb_sa_flags & SADB_SAFLAGS_DECAP_DSCP)
|
|
x->props.flags |= XFRM_STATE_DECAP_DSCP;
|
|
if (sa->sadb_sa_flags & SADB_SAFLAGS_NOPMTUDISC)
|
|
x->props.flags |= XFRM_STATE_NOPMTUDISC;
|
|
|
|
lifetime = (struct sadb_lifetime*) ext_hdrs[SADB_EXT_LIFETIME_HARD-1];
|
|
if (lifetime != NULL) {
|
|
x->lft.hard_packet_limit = _KEY2X(lifetime->sadb_lifetime_allocations);
|
|
x->lft.hard_byte_limit = _KEY2X(lifetime->sadb_lifetime_bytes);
|
|
x->lft.hard_add_expires_seconds = lifetime->sadb_lifetime_addtime;
|
|
x->lft.hard_use_expires_seconds = lifetime->sadb_lifetime_usetime;
|
|
}
|
|
lifetime = (struct sadb_lifetime*) ext_hdrs[SADB_EXT_LIFETIME_SOFT-1];
|
|
if (lifetime != NULL) {
|
|
x->lft.soft_packet_limit = _KEY2X(lifetime->sadb_lifetime_allocations);
|
|
x->lft.soft_byte_limit = _KEY2X(lifetime->sadb_lifetime_bytes);
|
|
x->lft.soft_add_expires_seconds = lifetime->sadb_lifetime_addtime;
|
|
x->lft.soft_use_expires_seconds = lifetime->sadb_lifetime_usetime;
|
|
}
|
|
|
|
sec_ctx = (struct sadb_x_sec_ctx *) ext_hdrs[SADB_X_EXT_SEC_CTX-1];
|
|
if (sec_ctx != NULL) {
|
|
struct xfrm_user_sec_ctx *uctx = pfkey_sadb2xfrm_user_sec_ctx(sec_ctx);
|
|
|
|
if (!uctx)
|
|
goto out;
|
|
|
|
err = security_xfrm_state_alloc(x, uctx);
|
|
kfree(uctx);
|
|
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
key = (struct sadb_key*) ext_hdrs[SADB_EXT_KEY_AUTH-1];
|
|
if (sa->sadb_sa_auth) {
|
|
int keysize = 0;
|
|
struct xfrm_algo_desc *a = xfrm_aalg_get_byid(sa->sadb_sa_auth);
|
|
if (!a) {
|
|
err = -ENOSYS;
|
|
goto out;
|
|
}
|
|
if (key)
|
|
keysize = (key->sadb_key_bits + 7) / 8;
|
|
x->aalg = kmalloc(sizeof(*x->aalg) + keysize, GFP_KERNEL);
|
|
if (!x->aalg)
|
|
goto out;
|
|
strcpy(x->aalg->alg_name, a->name);
|
|
x->aalg->alg_key_len = 0;
|
|
if (key) {
|
|
x->aalg->alg_key_len = key->sadb_key_bits;
|
|
memcpy(x->aalg->alg_key, key+1, keysize);
|
|
}
|
|
x->props.aalgo = sa->sadb_sa_auth;
|
|
/* x->algo.flags = sa->sadb_sa_flags; */
|
|
}
|
|
if (sa->sadb_sa_encrypt) {
|
|
if (hdr->sadb_msg_satype == SADB_X_SATYPE_IPCOMP) {
|
|
struct xfrm_algo_desc *a = xfrm_calg_get_byid(sa->sadb_sa_encrypt);
|
|
if (!a) {
|
|
err = -ENOSYS;
|
|
goto out;
|
|
}
|
|
x->calg = kmalloc(sizeof(*x->calg), GFP_KERNEL);
|
|
if (!x->calg)
|
|
goto out;
|
|
strcpy(x->calg->alg_name, a->name);
|
|
x->props.calgo = sa->sadb_sa_encrypt;
|
|
} else {
|
|
int keysize = 0;
|
|
struct xfrm_algo_desc *a = xfrm_ealg_get_byid(sa->sadb_sa_encrypt);
|
|
if (!a) {
|
|
err = -ENOSYS;
|
|
goto out;
|
|
}
|
|
key = (struct sadb_key*) ext_hdrs[SADB_EXT_KEY_ENCRYPT-1];
|
|
if (key)
|
|
keysize = (key->sadb_key_bits + 7) / 8;
|
|
x->ealg = kmalloc(sizeof(*x->ealg) + keysize, GFP_KERNEL);
|
|
if (!x->ealg)
|
|
goto out;
|
|
strcpy(x->ealg->alg_name, a->name);
|
|
x->ealg->alg_key_len = 0;
|
|
if (key) {
|
|
x->ealg->alg_key_len = key->sadb_key_bits;
|
|
memcpy(x->ealg->alg_key, key+1, keysize);
|
|
}
|
|
x->props.ealgo = sa->sadb_sa_encrypt;
|
|
}
|
|
}
|
|
/* x->algo.flags = sa->sadb_sa_flags; */
|
|
|
|
x->props.family = pfkey_sadb_addr2xfrm_addr((struct sadb_address *) ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
&x->props.saddr);
|
|
if (!x->props.family) {
|
|
err = -EAFNOSUPPORT;
|
|
goto out;
|
|
}
|
|
pfkey_sadb_addr2xfrm_addr((struct sadb_address *) ext_hdrs[SADB_EXT_ADDRESS_DST-1],
|
|
&x->id.daddr);
|
|
|
|
if (ext_hdrs[SADB_X_EXT_SA2-1]) {
|
|
struct sadb_x_sa2 *sa2 = (void*)ext_hdrs[SADB_X_EXT_SA2-1];
|
|
int mode = pfkey_mode_to_xfrm(sa2->sadb_x_sa2_mode);
|
|
if (mode < 0) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
x->props.mode = mode;
|
|
x->props.reqid = sa2->sadb_x_sa2_reqid;
|
|
}
|
|
|
|
if (ext_hdrs[SADB_EXT_ADDRESS_PROXY-1]) {
|
|
struct sadb_address *addr = ext_hdrs[SADB_EXT_ADDRESS_PROXY-1];
|
|
|
|
/* Nobody uses this, but we try. */
|
|
x->sel.family = pfkey_sadb_addr2xfrm_addr(addr, &x->sel.saddr);
|
|
x->sel.prefixlen_s = addr->sadb_address_prefixlen;
|
|
}
|
|
|
|
if (!x->sel.family)
|
|
x->sel.family = x->props.family;
|
|
|
|
if (ext_hdrs[SADB_X_EXT_NAT_T_TYPE-1]) {
|
|
struct sadb_x_nat_t_type* n_type;
|
|
struct xfrm_encap_tmpl *natt;
|
|
|
|
x->encap = kmalloc(sizeof(*x->encap), GFP_KERNEL);
|
|
if (!x->encap)
|
|
goto out;
|
|
|
|
natt = x->encap;
|
|
n_type = ext_hdrs[SADB_X_EXT_NAT_T_TYPE-1];
|
|
natt->encap_type = n_type->sadb_x_nat_t_type_type;
|
|
|
|
if (ext_hdrs[SADB_X_EXT_NAT_T_SPORT-1]) {
|
|
struct sadb_x_nat_t_port* n_port =
|
|
ext_hdrs[SADB_X_EXT_NAT_T_SPORT-1];
|
|
natt->encap_sport = n_port->sadb_x_nat_t_port_port;
|
|
}
|
|
if (ext_hdrs[SADB_X_EXT_NAT_T_DPORT-1]) {
|
|
struct sadb_x_nat_t_port* n_port =
|
|
ext_hdrs[SADB_X_EXT_NAT_T_DPORT-1];
|
|
natt->encap_dport = n_port->sadb_x_nat_t_port_port;
|
|
}
|
|
}
|
|
|
|
err = xfrm_init_state(x);
|
|
if (err)
|
|
goto out;
|
|
|
|
x->km.seq = hdr->sadb_msg_seq;
|
|
return x;
|
|
|
|
out:
|
|
x->km.state = XFRM_STATE_DEAD;
|
|
xfrm_state_put(x);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static int pfkey_reserved(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static int pfkey_getspi(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct sk_buff *resp_skb;
|
|
struct sadb_x_sa2 *sa2;
|
|
struct sadb_address *saddr, *daddr;
|
|
struct sadb_msg *out_hdr;
|
|
struct sadb_spirange *range;
|
|
struct xfrm_state *x = NULL;
|
|
int mode;
|
|
int err;
|
|
u32 min_spi, max_spi;
|
|
u32 reqid;
|
|
u8 proto;
|
|
unsigned short family;
|
|
xfrm_address_t *xsaddr = NULL, *xdaddr = NULL;
|
|
|
|
if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
ext_hdrs[SADB_EXT_ADDRESS_DST-1]))
|
|
return -EINVAL;
|
|
|
|
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
|
|
if (proto == 0)
|
|
return -EINVAL;
|
|
|
|
if ((sa2 = ext_hdrs[SADB_X_EXT_SA2-1]) != NULL) {
|
|
mode = pfkey_mode_to_xfrm(sa2->sadb_x_sa2_mode);
|
|
if (mode < 0)
|
|
return -EINVAL;
|
|
reqid = sa2->sadb_x_sa2_reqid;
|
|
} else {
|
|
mode = 0;
|
|
reqid = 0;
|
|
}
|
|
|
|
saddr = ext_hdrs[SADB_EXT_ADDRESS_SRC-1];
|
|
daddr = ext_hdrs[SADB_EXT_ADDRESS_DST-1];
|
|
|
|
family = ((struct sockaddr *)(saddr + 1))->sa_family;
|
|
switch (family) {
|
|
case AF_INET:
|
|
xdaddr = (xfrm_address_t *)&((struct sockaddr_in *)(daddr + 1))->sin_addr.s_addr;
|
|
xsaddr = (xfrm_address_t *)&((struct sockaddr_in *)(saddr + 1))->sin_addr.s_addr;
|
|
break;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
xdaddr = (xfrm_address_t *)&((struct sockaddr_in6 *)(daddr + 1))->sin6_addr;
|
|
xsaddr = (xfrm_address_t *)&((struct sockaddr_in6 *)(saddr + 1))->sin6_addr;
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (hdr->sadb_msg_seq) {
|
|
x = xfrm_find_acq_byseq(hdr->sadb_msg_seq);
|
|
if (x && xfrm_addr_cmp(&x->id.daddr, xdaddr, family)) {
|
|
xfrm_state_put(x);
|
|
x = NULL;
|
|
}
|
|
}
|
|
|
|
if (!x)
|
|
x = xfrm_find_acq(mode, reqid, proto, xdaddr, xsaddr, 1, family);
|
|
|
|
if (x == NULL)
|
|
return -ENOENT;
|
|
|
|
min_spi = 0x100;
|
|
max_spi = 0x0fffffff;
|
|
|
|
range = ext_hdrs[SADB_EXT_SPIRANGE-1];
|
|
if (range) {
|
|
min_spi = range->sadb_spirange_min;
|
|
max_spi = range->sadb_spirange_max;
|
|
}
|
|
|
|
err = xfrm_alloc_spi(x, min_spi, max_spi);
|
|
resp_skb = err ? ERR_PTR(err) : pfkey_xfrm_state2msg(x);
|
|
|
|
if (IS_ERR(resp_skb)) {
|
|
xfrm_state_put(x);
|
|
return PTR_ERR(resp_skb);
|
|
}
|
|
|
|
out_hdr = (struct sadb_msg *) resp_skb->data;
|
|
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
|
|
out_hdr->sadb_msg_type = SADB_GETSPI;
|
|
out_hdr->sadb_msg_satype = pfkey_proto2satype(proto);
|
|
out_hdr->sadb_msg_errno = 0;
|
|
out_hdr->sadb_msg_reserved = 0;
|
|
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
|
|
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
|
|
|
|
xfrm_state_put(x);
|
|
|
|
pfkey_broadcast(resp_skb, GFP_KERNEL, BROADCAST_ONE, sk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_acquire(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct xfrm_state *x;
|
|
|
|
if (hdr->sadb_msg_len != sizeof(struct sadb_msg)/8)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (hdr->sadb_msg_seq == 0 || hdr->sadb_msg_errno == 0)
|
|
return 0;
|
|
|
|
x = xfrm_find_acq_byseq(hdr->sadb_msg_seq);
|
|
if (x == NULL)
|
|
return 0;
|
|
|
|
spin_lock_bh(&x->lock);
|
|
if (x->km.state == XFRM_STATE_ACQ) {
|
|
x->km.state = XFRM_STATE_ERROR;
|
|
wake_up(&km_waitq);
|
|
}
|
|
spin_unlock_bh(&x->lock);
|
|
xfrm_state_put(x);
|
|
return 0;
|
|
}
|
|
|
|
static inline int event2poltype(int event)
|
|
{
|
|
switch (event) {
|
|
case XFRM_MSG_DELPOLICY:
|
|
return SADB_X_SPDDELETE;
|
|
case XFRM_MSG_NEWPOLICY:
|
|
return SADB_X_SPDADD;
|
|
case XFRM_MSG_UPDPOLICY:
|
|
return SADB_X_SPDUPDATE;
|
|
case XFRM_MSG_POLEXPIRE:
|
|
// return SADB_X_SPDEXPIRE;
|
|
default:
|
|
printk("pfkey: Unknown policy event %d\n", event);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int event2keytype(int event)
|
|
{
|
|
switch (event) {
|
|
case XFRM_MSG_DELSA:
|
|
return SADB_DELETE;
|
|
case XFRM_MSG_NEWSA:
|
|
return SADB_ADD;
|
|
case XFRM_MSG_UPDSA:
|
|
return SADB_UPDATE;
|
|
case XFRM_MSG_EXPIRE:
|
|
return SADB_EXPIRE;
|
|
default:
|
|
printk("pfkey: Unknown SA event %d\n", event);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ADD/UPD/DEL */
|
|
static int key_notify_sa(struct xfrm_state *x, struct km_event *c)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct sadb_msg *hdr;
|
|
|
|
skb = pfkey_xfrm_state2msg(x);
|
|
|
|
if (IS_ERR(skb))
|
|
return PTR_ERR(skb);
|
|
|
|
hdr = (struct sadb_msg *) skb->data;
|
|
hdr->sadb_msg_version = PF_KEY_V2;
|
|
hdr->sadb_msg_type = event2keytype(c->event);
|
|
hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
|
|
hdr->sadb_msg_errno = 0;
|
|
hdr->sadb_msg_reserved = 0;
|
|
hdr->sadb_msg_seq = c->seq;
|
|
hdr->sadb_msg_pid = c->pid;
|
|
|
|
pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_ALL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_add(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct xfrm_state *x;
|
|
int err;
|
|
struct km_event c;
|
|
|
|
x = pfkey_msg2xfrm_state(hdr, ext_hdrs);
|
|
if (IS_ERR(x))
|
|
return PTR_ERR(x);
|
|
|
|
xfrm_state_hold(x);
|
|
if (hdr->sadb_msg_type == SADB_ADD)
|
|
err = xfrm_state_add(x);
|
|
else
|
|
err = xfrm_state_update(x);
|
|
|
|
xfrm_audit_state_add(x, err ? 0 : 1,
|
|
audit_get_loginuid(current->audit_context), 0);
|
|
|
|
if (err < 0) {
|
|
x->km.state = XFRM_STATE_DEAD;
|
|
__xfrm_state_put(x);
|
|
goto out;
|
|
}
|
|
|
|
if (hdr->sadb_msg_type == SADB_ADD)
|
|
c.event = XFRM_MSG_NEWSA;
|
|
else
|
|
c.event = XFRM_MSG_UPDSA;
|
|
c.seq = hdr->sadb_msg_seq;
|
|
c.pid = hdr->sadb_msg_pid;
|
|
km_state_notify(x, &c);
|
|
out:
|
|
xfrm_state_put(x);
|
|
return err;
|
|
}
|
|
|
|
static int pfkey_delete(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct xfrm_state *x;
|
|
struct km_event c;
|
|
int err;
|
|
|
|
if (!ext_hdrs[SADB_EXT_SA-1] ||
|
|
!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
ext_hdrs[SADB_EXT_ADDRESS_DST-1]))
|
|
return -EINVAL;
|
|
|
|
x = pfkey_xfrm_state_lookup(hdr, ext_hdrs);
|
|
if (x == NULL)
|
|
return -ESRCH;
|
|
|
|
if ((err = security_xfrm_state_delete(x)))
|
|
goto out;
|
|
|
|
if (xfrm_state_kern(x)) {
|
|
err = -EPERM;
|
|
goto out;
|
|
}
|
|
|
|
err = xfrm_state_delete(x);
|
|
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
c.seq = hdr->sadb_msg_seq;
|
|
c.pid = hdr->sadb_msg_pid;
|
|
c.event = XFRM_MSG_DELSA;
|
|
km_state_notify(x, &c);
|
|
out:
|
|
xfrm_audit_state_delete(x, err ? 0 : 1,
|
|
audit_get_loginuid(current->audit_context), 0);
|
|
xfrm_state_put(x);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int pfkey_get(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
__u8 proto;
|
|
struct sk_buff *out_skb;
|
|
struct sadb_msg *out_hdr;
|
|
struct xfrm_state *x;
|
|
|
|
if (!ext_hdrs[SADB_EXT_SA-1] ||
|
|
!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
ext_hdrs[SADB_EXT_ADDRESS_DST-1]))
|
|
return -EINVAL;
|
|
|
|
x = pfkey_xfrm_state_lookup(hdr, ext_hdrs);
|
|
if (x == NULL)
|
|
return -ESRCH;
|
|
|
|
out_skb = pfkey_xfrm_state2msg(x);
|
|
proto = x->id.proto;
|
|
xfrm_state_put(x);
|
|
if (IS_ERR(out_skb))
|
|
return PTR_ERR(out_skb);
|
|
|
|
out_hdr = (struct sadb_msg *) out_skb->data;
|
|
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
|
|
out_hdr->sadb_msg_type = SADB_GET;
|
|
out_hdr->sadb_msg_satype = pfkey_proto2satype(proto);
|
|
out_hdr->sadb_msg_errno = 0;
|
|
out_hdr->sadb_msg_reserved = 0;
|
|
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
|
|
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
|
|
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, sk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct sk_buff *compose_sadb_supported(struct sadb_msg *orig,
|
|
gfp_t allocation)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct sadb_msg *hdr;
|
|
int len, auth_len, enc_len, i;
|
|
|
|
auth_len = xfrm_count_auth_supported();
|
|
if (auth_len) {
|
|
auth_len *= sizeof(struct sadb_alg);
|
|
auth_len += sizeof(struct sadb_supported);
|
|
}
|
|
|
|
enc_len = xfrm_count_enc_supported();
|
|
if (enc_len) {
|
|
enc_len *= sizeof(struct sadb_alg);
|
|
enc_len += sizeof(struct sadb_supported);
|
|
}
|
|
|
|
len = enc_len + auth_len + sizeof(struct sadb_msg);
|
|
|
|
skb = alloc_skb(len + 16, allocation);
|
|
if (!skb)
|
|
goto out_put_algs;
|
|
|
|
hdr = (struct sadb_msg *) skb_put(skb, sizeof(*hdr));
|
|
pfkey_hdr_dup(hdr, orig);
|
|
hdr->sadb_msg_errno = 0;
|
|
hdr->sadb_msg_len = len / sizeof(uint64_t);
|
|
|
|
if (auth_len) {
|
|
struct sadb_supported *sp;
|
|
struct sadb_alg *ap;
|
|
|
|
sp = (struct sadb_supported *) skb_put(skb, auth_len);
|
|
ap = (struct sadb_alg *) (sp + 1);
|
|
|
|
sp->sadb_supported_len = auth_len / sizeof(uint64_t);
|
|
sp->sadb_supported_exttype = SADB_EXT_SUPPORTED_AUTH;
|
|
|
|
for (i = 0; ; i++) {
|
|
struct xfrm_algo_desc *aalg = xfrm_aalg_get_byidx(i);
|
|
if (!aalg)
|
|
break;
|
|
if (aalg->available)
|
|
*ap++ = aalg->desc;
|
|
}
|
|
}
|
|
|
|
if (enc_len) {
|
|
struct sadb_supported *sp;
|
|
struct sadb_alg *ap;
|
|
|
|
sp = (struct sadb_supported *) skb_put(skb, enc_len);
|
|
ap = (struct sadb_alg *) (sp + 1);
|
|
|
|
sp->sadb_supported_len = enc_len / sizeof(uint64_t);
|
|
sp->sadb_supported_exttype = SADB_EXT_SUPPORTED_ENCRYPT;
|
|
|
|
for (i = 0; ; i++) {
|
|
struct xfrm_algo_desc *ealg = xfrm_ealg_get_byidx(i);
|
|
if (!ealg)
|
|
break;
|
|
if (ealg->available)
|
|
*ap++ = ealg->desc;
|
|
}
|
|
}
|
|
|
|
out_put_algs:
|
|
return skb;
|
|
}
|
|
|
|
static int pfkey_register(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct pfkey_sock *pfk = pfkey_sk(sk);
|
|
struct sk_buff *supp_skb;
|
|
|
|
if (hdr->sadb_msg_satype > SADB_SATYPE_MAX)
|
|
return -EINVAL;
|
|
|
|
if (hdr->sadb_msg_satype != SADB_SATYPE_UNSPEC) {
|
|
if (pfk->registered&(1<<hdr->sadb_msg_satype))
|
|
return -EEXIST;
|
|
pfk->registered |= (1<<hdr->sadb_msg_satype);
|
|
}
|
|
|
|
xfrm_probe_algs();
|
|
|
|
supp_skb = compose_sadb_supported(hdr, GFP_KERNEL);
|
|
if (!supp_skb) {
|
|
if (hdr->sadb_msg_satype != SADB_SATYPE_UNSPEC)
|
|
pfk->registered &= ~(1<<hdr->sadb_msg_satype);
|
|
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
pfkey_broadcast(supp_skb, GFP_KERNEL, BROADCAST_REGISTERED, sk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int key_notify_sa_flush(struct km_event *c)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct sadb_msg *hdr;
|
|
|
|
skb = alloc_skb(sizeof(struct sadb_msg) + 16, GFP_ATOMIC);
|
|
if (!skb)
|
|
return -ENOBUFS;
|
|
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
|
|
hdr->sadb_msg_satype = pfkey_proto2satype(c->data.proto);
|
|
hdr->sadb_msg_type = SADB_FLUSH;
|
|
hdr->sadb_msg_seq = c->seq;
|
|
hdr->sadb_msg_pid = c->pid;
|
|
hdr->sadb_msg_version = PF_KEY_V2;
|
|
hdr->sadb_msg_errno = (uint8_t) 0;
|
|
hdr->sadb_msg_len = (sizeof(struct sadb_msg) / sizeof(uint64_t));
|
|
|
|
pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_ALL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_flush(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
unsigned proto;
|
|
struct km_event c;
|
|
struct xfrm_audit audit_info;
|
|
int err;
|
|
|
|
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
|
|
if (proto == 0)
|
|
return -EINVAL;
|
|
|
|
audit_info.loginuid = audit_get_loginuid(current->audit_context);
|
|
audit_info.secid = 0;
|
|
err = xfrm_state_flush(proto, &audit_info);
|
|
if (err)
|
|
return err;
|
|
c.data.proto = proto;
|
|
c.seq = hdr->sadb_msg_seq;
|
|
c.pid = hdr->sadb_msg_pid;
|
|
c.event = XFRM_MSG_FLUSHSA;
|
|
km_state_notify(NULL, &c);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct pfkey_dump_data
|
|
{
|
|
struct sk_buff *skb;
|
|
struct sadb_msg *hdr;
|
|
struct sock *sk;
|
|
};
|
|
|
|
static int dump_sa(struct xfrm_state *x, int count, void *ptr)
|
|
{
|
|
struct pfkey_dump_data *data = ptr;
|
|
struct sk_buff *out_skb;
|
|
struct sadb_msg *out_hdr;
|
|
|
|
out_skb = pfkey_xfrm_state2msg(x);
|
|
if (IS_ERR(out_skb))
|
|
return PTR_ERR(out_skb);
|
|
|
|
out_hdr = (struct sadb_msg *) out_skb->data;
|
|
out_hdr->sadb_msg_version = data->hdr->sadb_msg_version;
|
|
out_hdr->sadb_msg_type = SADB_DUMP;
|
|
out_hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
|
|
out_hdr->sadb_msg_errno = 0;
|
|
out_hdr->sadb_msg_reserved = 0;
|
|
out_hdr->sadb_msg_seq = count;
|
|
out_hdr->sadb_msg_pid = data->hdr->sadb_msg_pid;
|
|
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, data->sk);
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_dump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
u8 proto;
|
|
struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk };
|
|
|
|
proto = pfkey_satype2proto(hdr->sadb_msg_satype);
|
|
if (proto == 0)
|
|
return -EINVAL;
|
|
|
|
return xfrm_state_walk(proto, dump_sa, &data);
|
|
}
|
|
|
|
static int pfkey_promisc(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct pfkey_sock *pfk = pfkey_sk(sk);
|
|
int satype = hdr->sadb_msg_satype;
|
|
|
|
if (hdr->sadb_msg_len == (sizeof(*hdr) / sizeof(uint64_t))) {
|
|
/* XXX we mangle packet... */
|
|
hdr->sadb_msg_errno = 0;
|
|
if (satype != 0 && satype != 1)
|
|
return -EINVAL;
|
|
pfk->promisc = satype;
|
|
}
|
|
pfkey_broadcast(skb_clone(skb, GFP_KERNEL), GFP_KERNEL, BROADCAST_ALL, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int check_reqid(struct xfrm_policy *xp, int dir, int count, void *ptr)
|
|
{
|
|
int i;
|
|
u32 reqid = *(u32*)ptr;
|
|
|
|
for (i=0; i<xp->xfrm_nr; i++) {
|
|
if (xp->xfrm_vec[i].reqid == reqid)
|
|
return -EEXIST;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static u32 gen_reqid(void)
|
|
{
|
|
u32 start;
|
|
static u32 reqid = IPSEC_MANUAL_REQID_MAX;
|
|
|
|
start = reqid;
|
|
do {
|
|
++reqid;
|
|
if (reqid == 0)
|
|
reqid = IPSEC_MANUAL_REQID_MAX+1;
|
|
if (xfrm_policy_walk(XFRM_POLICY_TYPE_MAIN, check_reqid,
|
|
(void*)&reqid) != -EEXIST)
|
|
return reqid;
|
|
} while (reqid != start);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
parse_ipsecrequest(struct xfrm_policy *xp, struct sadb_x_ipsecrequest *rq)
|
|
{
|
|
struct xfrm_tmpl *t = xp->xfrm_vec + xp->xfrm_nr;
|
|
struct sockaddr_in *sin;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
int mode;
|
|
|
|
if (xp->xfrm_nr >= XFRM_MAX_DEPTH)
|
|
return -ELOOP;
|
|
|
|
if (rq->sadb_x_ipsecrequest_mode == 0)
|
|
return -EINVAL;
|
|
|
|
t->id.proto = rq->sadb_x_ipsecrequest_proto; /* XXX check proto */
|
|
if ((mode = pfkey_mode_to_xfrm(rq->sadb_x_ipsecrequest_mode)) < 0)
|
|
return -EINVAL;
|
|
t->mode = mode;
|
|
if (rq->sadb_x_ipsecrequest_level == IPSEC_LEVEL_USE)
|
|
t->optional = 1;
|
|
else if (rq->sadb_x_ipsecrequest_level == IPSEC_LEVEL_UNIQUE) {
|
|
t->reqid = rq->sadb_x_ipsecrequest_reqid;
|
|
if (t->reqid > IPSEC_MANUAL_REQID_MAX)
|
|
t->reqid = 0;
|
|
if (!t->reqid && !(t->reqid = gen_reqid()))
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
/* addresses present only in tunnel mode */
|
|
if (t->mode == XFRM_MODE_TUNNEL) {
|
|
struct sockaddr *sa;
|
|
sa = (struct sockaddr *)(rq+1);
|
|
switch(sa->sa_family) {
|
|
case AF_INET:
|
|
sin = (struct sockaddr_in*)sa;
|
|
t->saddr.a4 = sin->sin_addr.s_addr;
|
|
sin++;
|
|
if (sin->sin_family != AF_INET)
|
|
return -EINVAL;
|
|
t->id.daddr.a4 = sin->sin_addr.s_addr;
|
|
break;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
sin6 = (struct sockaddr_in6*)sa;
|
|
memcpy(t->saddr.a6, &sin6->sin6_addr, sizeof(struct in6_addr));
|
|
sin6++;
|
|
if (sin6->sin6_family != AF_INET6)
|
|
return -EINVAL;
|
|
memcpy(t->id.daddr.a6, &sin6->sin6_addr, sizeof(struct in6_addr));
|
|
break;
|
|
#endif
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
t->encap_family = sa->sa_family;
|
|
} else
|
|
t->encap_family = xp->family;
|
|
|
|
/* No way to set this via kame pfkey */
|
|
t->aalgos = t->ealgos = t->calgos = ~0;
|
|
xp->xfrm_nr++;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
parse_ipsecrequests(struct xfrm_policy *xp, struct sadb_x_policy *pol)
|
|
{
|
|
int err;
|
|
int len = pol->sadb_x_policy_len*8 - sizeof(struct sadb_x_policy);
|
|
struct sadb_x_ipsecrequest *rq = (void*)(pol+1);
|
|
|
|
while (len >= sizeof(struct sadb_x_ipsecrequest)) {
|
|
if ((err = parse_ipsecrequest(xp, rq)) < 0)
|
|
return err;
|
|
len -= rq->sadb_x_ipsecrequest_len;
|
|
rq = (void*)((u8*)rq + rq->sadb_x_ipsecrequest_len);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int pfkey_xfrm_policy2sec_ctx_size(struct xfrm_policy *xp)
|
|
{
|
|
struct xfrm_sec_ctx *xfrm_ctx = xp->security;
|
|
|
|
if (xfrm_ctx) {
|
|
int len = sizeof(struct sadb_x_sec_ctx);
|
|
len += xfrm_ctx->ctx_len;
|
|
return PFKEY_ALIGN8(len);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_xfrm_policy2msg_size(struct xfrm_policy *xp)
|
|
{
|
|
struct xfrm_tmpl *t;
|
|
int sockaddr_size = pfkey_sockaddr_size(xp->family);
|
|
int socklen = 0;
|
|
int i;
|
|
|
|
for (i=0; i<xp->xfrm_nr; i++) {
|
|
t = xp->xfrm_vec + i;
|
|
socklen += (t->encap_family == AF_INET ?
|
|
sizeof(struct sockaddr_in) :
|
|
sizeof(struct sockaddr_in6));
|
|
}
|
|
|
|
return sizeof(struct sadb_msg) +
|
|
(sizeof(struct sadb_lifetime) * 3) +
|
|
(sizeof(struct sadb_address) * 2) +
|
|
(sockaddr_size * 2) +
|
|
sizeof(struct sadb_x_policy) +
|
|
(xp->xfrm_nr * sizeof(struct sadb_x_ipsecrequest)) +
|
|
(socklen * 2) +
|
|
pfkey_xfrm_policy2sec_ctx_size(xp);
|
|
}
|
|
|
|
static struct sk_buff * pfkey_xfrm_policy2msg_prep(struct xfrm_policy *xp)
|
|
{
|
|
struct sk_buff *skb;
|
|
int size;
|
|
|
|
size = pfkey_xfrm_policy2msg_size(xp);
|
|
|
|
skb = alloc_skb(size + 16, GFP_ATOMIC);
|
|
if (skb == NULL)
|
|
return ERR_PTR(-ENOBUFS);
|
|
|
|
return skb;
|
|
}
|
|
|
|
static int pfkey_xfrm_policy2msg(struct sk_buff *skb, struct xfrm_policy *xp, int dir)
|
|
{
|
|
struct sadb_msg *hdr;
|
|
struct sadb_address *addr;
|
|
struct sadb_lifetime *lifetime;
|
|
struct sadb_x_policy *pol;
|
|
struct sockaddr_in *sin;
|
|
struct sadb_x_sec_ctx *sec_ctx;
|
|
struct xfrm_sec_ctx *xfrm_ctx;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
int i;
|
|
int size;
|
|
int sockaddr_size = pfkey_sockaddr_size(xp->family);
|
|
int socklen = (xp->family == AF_INET ?
|
|
sizeof(struct sockaddr_in) :
|
|
sizeof(struct sockaddr_in6));
|
|
|
|
size = pfkey_xfrm_policy2msg_size(xp);
|
|
|
|
/* call should fill header later */
|
|
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
|
|
memset(hdr, 0, size); /* XXX do we need this ? */
|
|
|
|
/* src address */
|
|
addr = (struct sadb_address*) skb_put(skb,
|
|
sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
|
|
addr->sadb_address_proto = pfkey_proto_from_xfrm(xp->selector.proto);
|
|
addr->sadb_address_prefixlen = xp->selector.prefixlen_s;
|
|
addr->sadb_address_reserved = 0;
|
|
/* src address */
|
|
if (xp->family == AF_INET) {
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = xp->selector.saddr.a4;
|
|
sin->sin_port = xp->selector.sport;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (xp->family == AF_INET6) {
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = xp->selector.sport;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr, xp->selector.saddr.a6,
|
|
sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
}
|
|
#endif
|
|
else
|
|
BUG();
|
|
|
|
/* dst address */
|
|
addr = (struct sadb_address*) skb_put(skb,
|
|
sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
|
|
addr->sadb_address_proto = pfkey_proto_from_xfrm(xp->selector.proto);
|
|
addr->sadb_address_prefixlen = xp->selector.prefixlen_d;
|
|
addr->sadb_address_reserved = 0;
|
|
if (xp->family == AF_INET) {
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = xp->selector.daddr.a4;
|
|
sin->sin_port = xp->selector.dport;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (xp->family == AF_INET6) {
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = xp->selector.dport;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr, xp->selector.daddr.a6,
|
|
sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
}
|
|
#endif
|
|
else
|
|
BUG();
|
|
|
|
/* hard time */
|
|
lifetime = (struct sadb_lifetime *) skb_put(skb,
|
|
sizeof(struct sadb_lifetime));
|
|
lifetime->sadb_lifetime_len =
|
|
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
|
|
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD;
|
|
lifetime->sadb_lifetime_allocations = _X2KEY(xp->lft.hard_packet_limit);
|
|
lifetime->sadb_lifetime_bytes = _X2KEY(xp->lft.hard_byte_limit);
|
|
lifetime->sadb_lifetime_addtime = xp->lft.hard_add_expires_seconds;
|
|
lifetime->sadb_lifetime_usetime = xp->lft.hard_use_expires_seconds;
|
|
/* soft time */
|
|
lifetime = (struct sadb_lifetime *) skb_put(skb,
|
|
sizeof(struct sadb_lifetime));
|
|
lifetime->sadb_lifetime_len =
|
|
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
|
|
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_SOFT;
|
|
lifetime->sadb_lifetime_allocations = _X2KEY(xp->lft.soft_packet_limit);
|
|
lifetime->sadb_lifetime_bytes = _X2KEY(xp->lft.soft_byte_limit);
|
|
lifetime->sadb_lifetime_addtime = xp->lft.soft_add_expires_seconds;
|
|
lifetime->sadb_lifetime_usetime = xp->lft.soft_use_expires_seconds;
|
|
/* current time */
|
|
lifetime = (struct sadb_lifetime *) skb_put(skb,
|
|
sizeof(struct sadb_lifetime));
|
|
lifetime->sadb_lifetime_len =
|
|
sizeof(struct sadb_lifetime)/sizeof(uint64_t);
|
|
lifetime->sadb_lifetime_exttype = SADB_EXT_LIFETIME_CURRENT;
|
|
lifetime->sadb_lifetime_allocations = xp->curlft.packets;
|
|
lifetime->sadb_lifetime_bytes = xp->curlft.bytes;
|
|
lifetime->sadb_lifetime_addtime = xp->curlft.add_time;
|
|
lifetime->sadb_lifetime_usetime = xp->curlft.use_time;
|
|
|
|
pol = (struct sadb_x_policy *) skb_put(skb, sizeof(struct sadb_x_policy));
|
|
pol->sadb_x_policy_len = sizeof(struct sadb_x_policy)/sizeof(uint64_t);
|
|
pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY;
|
|
pol->sadb_x_policy_type = IPSEC_POLICY_DISCARD;
|
|
if (xp->action == XFRM_POLICY_ALLOW) {
|
|
if (xp->xfrm_nr)
|
|
pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC;
|
|
else
|
|
pol->sadb_x_policy_type = IPSEC_POLICY_NONE;
|
|
}
|
|
pol->sadb_x_policy_dir = dir+1;
|
|
pol->sadb_x_policy_id = xp->index;
|
|
pol->sadb_x_policy_priority = xp->priority;
|
|
|
|
for (i=0; i<xp->xfrm_nr; i++) {
|
|
struct sadb_x_ipsecrequest *rq;
|
|
struct xfrm_tmpl *t = xp->xfrm_vec + i;
|
|
int req_size;
|
|
int mode;
|
|
|
|
req_size = sizeof(struct sadb_x_ipsecrequest);
|
|
if (t->mode == XFRM_MODE_TUNNEL)
|
|
req_size += ((t->encap_family == AF_INET ?
|
|
sizeof(struct sockaddr_in) :
|
|
sizeof(struct sockaddr_in6)) * 2);
|
|
else
|
|
size -= 2*socklen;
|
|
rq = (void*)skb_put(skb, req_size);
|
|
pol->sadb_x_policy_len += req_size/8;
|
|
memset(rq, 0, sizeof(*rq));
|
|
rq->sadb_x_ipsecrequest_len = req_size;
|
|
rq->sadb_x_ipsecrequest_proto = t->id.proto;
|
|
if ((mode = pfkey_mode_from_xfrm(t->mode)) < 0)
|
|
return -EINVAL;
|
|
rq->sadb_x_ipsecrequest_mode = mode;
|
|
rq->sadb_x_ipsecrequest_level = IPSEC_LEVEL_REQUIRE;
|
|
if (t->reqid)
|
|
rq->sadb_x_ipsecrequest_level = IPSEC_LEVEL_UNIQUE;
|
|
if (t->optional)
|
|
rq->sadb_x_ipsecrequest_level = IPSEC_LEVEL_USE;
|
|
rq->sadb_x_ipsecrequest_reqid = t->reqid;
|
|
if (t->mode == XFRM_MODE_TUNNEL) {
|
|
switch (t->encap_family) {
|
|
case AF_INET:
|
|
sin = (void*)(rq+1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = t->saddr.a4;
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
sin++;
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = t->id.daddr.a4;
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
break;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
sin6 = (void*)(rq+1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr, t->saddr.a6,
|
|
sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
|
|
sin6++;
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr, t->id.daddr.a6,
|
|
sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* security context */
|
|
if ((xfrm_ctx = xp->security)) {
|
|
int ctx_size = pfkey_xfrm_policy2sec_ctx_size(xp);
|
|
|
|
sec_ctx = (struct sadb_x_sec_ctx *) skb_put(skb, ctx_size);
|
|
sec_ctx->sadb_x_sec_len = ctx_size / sizeof(uint64_t);
|
|
sec_ctx->sadb_x_sec_exttype = SADB_X_EXT_SEC_CTX;
|
|
sec_ctx->sadb_x_ctx_doi = xfrm_ctx->ctx_doi;
|
|
sec_ctx->sadb_x_ctx_alg = xfrm_ctx->ctx_alg;
|
|
sec_ctx->sadb_x_ctx_len = xfrm_ctx->ctx_len;
|
|
memcpy(sec_ctx + 1, xfrm_ctx->ctx_str,
|
|
xfrm_ctx->ctx_len);
|
|
}
|
|
|
|
hdr->sadb_msg_len = size / sizeof(uint64_t);
|
|
hdr->sadb_msg_reserved = atomic_read(&xp->refcnt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int key_notify_policy(struct xfrm_policy *xp, int dir, struct km_event *c)
|
|
{
|
|
struct sk_buff *out_skb;
|
|
struct sadb_msg *out_hdr;
|
|
int err;
|
|
|
|
out_skb = pfkey_xfrm_policy2msg_prep(xp);
|
|
if (IS_ERR(out_skb)) {
|
|
err = PTR_ERR(out_skb);
|
|
goto out;
|
|
}
|
|
err = pfkey_xfrm_policy2msg(out_skb, xp, dir);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
out_hdr = (struct sadb_msg *) out_skb->data;
|
|
out_hdr->sadb_msg_version = PF_KEY_V2;
|
|
|
|
if (c->data.byid && c->event == XFRM_MSG_DELPOLICY)
|
|
out_hdr->sadb_msg_type = SADB_X_SPDDELETE2;
|
|
else
|
|
out_hdr->sadb_msg_type = event2poltype(c->event);
|
|
out_hdr->sadb_msg_errno = 0;
|
|
out_hdr->sadb_msg_seq = c->seq;
|
|
out_hdr->sadb_msg_pid = c->pid;
|
|
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ALL, NULL);
|
|
out:
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int pfkey_spdadd(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
int err = 0;
|
|
struct sadb_lifetime *lifetime;
|
|
struct sadb_address *sa;
|
|
struct sadb_x_policy *pol;
|
|
struct xfrm_policy *xp;
|
|
struct km_event c;
|
|
struct sadb_x_sec_ctx *sec_ctx;
|
|
|
|
if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
ext_hdrs[SADB_EXT_ADDRESS_DST-1]) ||
|
|
!ext_hdrs[SADB_X_EXT_POLICY-1])
|
|
return -EINVAL;
|
|
|
|
pol = ext_hdrs[SADB_X_EXT_POLICY-1];
|
|
if (pol->sadb_x_policy_type > IPSEC_POLICY_IPSEC)
|
|
return -EINVAL;
|
|
if (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
|
|
return -EINVAL;
|
|
|
|
xp = xfrm_policy_alloc(GFP_KERNEL);
|
|
if (xp == NULL)
|
|
return -ENOBUFS;
|
|
|
|
xp->action = (pol->sadb_x_policy_type == IPSEC_POLICY_DISCARD ?
|
|
XFRM_POLICY_BLOCK : XFRM_POLICY_ALLOW);
|
|
xp->priority = pol->sadb_x_policy_priority;
|
|
|
|
sa = ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
xp->family = pfkey_sadb_addr2xfrm_addr(sa, &xp->selector.saddr);
|
|
if (!xp->family) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
xp->selector.family = xp->family;
|
|
xp->selector.prefixlen_s = sa->sadb_address_prefixlen;
|
|
xp->selector.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
|
|
xp->selector.sport = ((struct sockaddr_in *)(sa+1))->sin_port;
|
|
if (xp->selector.sport)
|
|
xp->selector.sport_mask = htons(0xffff);
|
|
|
|
sa = ext_hdrs[SADB_EXT_ADDRESS_DST-1],
|
|
pfkey_sadb_addr2xfrm_addr(sa, &xp->selector.daddr);
|
|
xp->selector.prefixlen_d = sa->sadb_address_prefixlen;
|
|
|
|
/* Amusing, we set this twice. KAME apps appear to set same value
|
|
* in both addresses.
|
|
*/
|
|
xp->selector.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
|
|
|
|
xp->selector.dport = ((struct sockaddr_in *)(sa+1))->sin_port;
|
|
if (xp->selector.dport)
|
|
xp->selector.dport_mask = htons(0xffff);
|
|
|
|
sec_ctx = (struct sadb_x_sec_ctx *) ext_hdrs[SADB_X_EXT_SEC_CTX-1];
|
|
if (sec_ctx != NULL) {
|
|
struct xfrm_user_sec_ctx *uctx = pfkey_sadb2xfrm_user_sec_ctx(sec_ctx);
|
|
|
|
if (!uctx) {
|
|
err = -ENOBUFS;
|
|
goto out;
|
|
}
|
|
|
|
err = security_xfrm_policy_alloc(xp, uctx);
|
|
kfree(uctx);
|
|
|
|
if (err)
|
|
goto out;
|
|
}
|
|
|
|
xp->lft.soft_byte_limit = XFRM_INF;
|
|
xp->lft.hard_byte_limit = XFRM_INF;
|
|
xp->lft.soft_packet_limit = XFRM_INF;
|
|
xp->lft.hard_packet_limit = XFRM_INF;
|
|
if ((lifetime = ext_hdrs[SADB_EXT_LIFETIME_HARD-1]) != NULL) {
|
|
xp->lft.hard_packet_limit = _KEY2X(lifetime->sadb_lifetime_allocations);
|
|
xp->lft.hard_byte_limit = _KEY2X(lifetime->sadb_lifetime_bytes);
|
|
xp->lft.hard_add_expires_seconds = lifetime->sadb_lifetime_addtime;
|
|
xp->lft.hard_use_expires_seconds = lifetime->sadb_lifetime_usetime;
|
|
}
|
|
if ((lifetime = ext_hdrs[SADB_EXT_LIFETIME_SOFT-1]) != NULL) {
|
|
xp->lft.soft_packet_limit = _KEY2X(lifetime->sadb_lifetime_allocations);
|
|
xp->lft.soft_byte_limit = _KEY2X(lifetime->sadb_lifetime_bytes);
|
|
xp->lft.soft_add_expires_seconds = lifetime->sadb_lifetime_addtime;
|
|
xp->lft.soft_use_expires_seconds = lifetime->sadb_lifetime_usetime;
|
|
}
|
|
xp->xfrm_nr = 0;
|
|
if (pol->sadb_x_policy_type == IPSEC_POLICY_IPSEC &&
|
|
(err = parse_ipsecrequests(xp, pol)) < 0)
|
|
goto out;
|
|
|
|
err = xfrm_policy_insert(pol->sadb_x_policy_dir-1, xp,
|
|
hdr->sadb_msg_type != SADB_X_SPDUPDATE);
|
|
|
|
xfrm_audit_policy_add(xp, err ? 0 : 1,
|
|
audit_get_loginuid(current->audit_context), 0);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
if (hdr->sadb_msg_type == SADB_X_SPDUPDATE)
|
|
c.event = XFRM_MSG_UPDPOLICY;
|
|
else
|
|
c.event = XFRM_MSG_NEWPOLICY;
|
|
|
|
c.seq = hdr->sadb_msg_seq;
|
|
c.pid = hdr->sadb_msg_pid;
|
|
|
|
km_policy_notify(xp, pol->sadb_x_policy_dir-1, &c);
|
|
xfrm_pol_put(xp);
|
|
return 0;
|
|
|
|
out:
|
|
security_xfrm_policy_free(xp);
|
|
kfree(xp);
|
|
return err;
|
|
}
|
|
|
|
static int pfkey_spddelete(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
int err;
|
|
struct sadb_address *sa;
|
|
struct sadb_x_policy *pol;
|
|
struct xfrm_policy *xp, tmp;
|
|
struct xfrm_selector sel;
|
|
struct km_event c;
|
|
struct sadb_x_sec_ctx *sec_ctx;
|
|
|
|
if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
ext_hdrs[SADB_EXT_ADDRESS_DST-1]) ||
|
|
!ext_hdrs[SADB_X_EXT_POLICY-1])
|
|
return -EINVAL;
|
|
|
|
pol = ext_hdrs[SADB_X_EXT_POLICY-1];
|
|
if (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir >= IPSEC_DIR_MAX)
|
|
return -EINVAL;
|
|
|
|
memset(&sel, 0, sizeof(sel));
|
|
|
|
sa = ext_hdrs[SADB_EXT_ADDRESS_SRC-1],
|
|
sel.family = pfkey_sadb_addr2xfrm_addr(sa, &sel.saddr);
|
|
sel.prefixlen_s = sa->sadb_address_prefixlen;
|
|
sel.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
|
|
sel.sport = ((struct sockaddr_in *)(sa+1))->sin_port;
|
|
if (sel.sport)
|
|
sel.sport_mask = htons(0xffff);
|
|
|
|
sa = ext_hdrs[SADB_EXT_ADDRESS_DST-1],
|
|
pfkey_sadb_addr2xfrm_addr(sa, &sel.daddr);
|
|
sel.prefixlen_d = sa->sadb_address_prefixlen;
|
|
sel.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
|
|
sel.dport = ((struct sockaddr_in *)(sa+1))->sin_port;
|
|
if (sel.dport)
|
|
sel.dport_mask = htons(0xffff);
|
|
|
|
sec_ctx = (struct sadb_x_sec_ctx *) ext_hdrs[SADB_X_EXT_SEC_CTX-1];
|
|
memset(&tmp, 0, sizeof(struct xfrm_policy));
|
|
|
|
if (sec_ctx != NULL) {
|
|
struct xfrm_user_sec_ctx *uctx = pfkey_sadb2xfrm_user_sec_ctx(sec_ctx);
|
|
|
|
if (!uctx)
|
|
return -ENOMEM;
|
|
|
|
err = security_xfrm_policy_alloc(&tmp, uctx);
|
|
kfree(uctx);
|
|
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
xp = xfrm_policy_bysel_ctx(XFRM_POLICY_TYPE_MAIN, pol->sadb_x_policy_dir-1,
|
|
&sel, tmp.security, 1, &err);
|
|
security_xfrm_policy_free(&tmp);
|
|
|
|
if (xp == NULL)
|
|
return -ENOENT;
|
|
|
|
xfrm_audit_policy_delete(xp, err ? 0 : 1,
|
|
audit_get_loginuid(current->audit_context), 0);
|
|
|
|
if (err)
|
|
goto out;
|
|
|
|
c.seq = hdr->sadb_msg_seq;
|
|
c.pid = hdr->sadb_msg_pid;
|
|
c.event = XFRM_MSG_DELPOLICY;
|
|
km_policy_notify(xp, pol->sadb_x_policy_dir-1, &c);
|
|
|
|
out:
|
|
xfrm_pol_put(xp);
|
|
return err;
|
|
}
|
|
|
|
static int key_pol_get_resp(struct sock *sk, struct xfrm_policy *xp, struct sadb_msg *hdr, int dir)
|
|
{
|
|
int err;
|
|
struct sk_buff *out_skb;
|
|
struct sadb_msg *out_hdr;
|
|
err = 0;
|
|
|
|
out_skb = pfkey_xfrm_policy2msg_prep(xp);
|
|
if (IS_ERR(out_skb)) {
|
|
err = PTR_ERR(out_skb);
|
|
goto out;
|
|
}
|
|
err = pfkey_xfrm_policy2msg(out_skb, xp, dir);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
out_hdr = (struct sadb_msg *) out_skb->data;
|
|
out_hdr->sadb_msg_version = hdr->sadb_msg_version;
|
|
out_hdr->sadb_msg_type = hdr->sadb_msg_type;
|
|
out_hdr->sadb_msg_satype = 0;
|
|
out_hdr->sadb_msg_errno = 0;
|
|
out_hdr->sadb_msg_seq = hdr->sadb_msg_seq;
|
|
out_hdr->sadb_msg_pid = hdr->sadb_msg_pid;
|
|
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, sk);
|
|
err = 0;
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
#ifdef CONFIG_NET_KEY_MIGRATE
|
|
static int pfkey_sockaddr_pair_size(sa_family_t family)
|
|
{
|
|
switch (family) {
|
|
case AF_INET:
|
|
return PFKEY_ALIGN8(sizeof(struct sockaddr_in) * 2);
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
return PFKEY_ALIGN8(sizeof(struct sockaddr_in6) * 2);
|
|
#endif
|
|
default:
|
|
return 0;
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
static int parse_sockaddr_pair(struct sadb_x_ipsecrequest *rq,
|
|
xfrm_address_t *saddr, xfrm_address_t *daddr,
|
|
u16 *family)
|
|
{
|
|
struct sockaddr *sa = (struct sockaddr *)(rq + 1);
|
|
if (rq->sadb_x_ipsecrequest_len <
|
|
pfkey_sockaddr_pair_size(sa->sa_family))
|
|
return -EINVAL;
|
|
|
|
switch (sa->sa_family) {
|
|
case AF_INET:
|
|
{
|
|
struct sockaddr_in *sin;
|
|
sin = (struct sockaddr_in *)sa;
|
|
if ((sin+1)->sin_family != AF_INET)
|
|
return -EINVAL;
|
|
memcpy(&saddr->a4, &sin->sin_addr, sizeof(saddr->a4));
|
|
sin++;
|
|
memcpy(&daddr->a4, &sin->sin_addr, sizeof(daddr->a4));
|
|
*family = AF_INET;
|
|
break;
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
{
|
|
struct sockaddr_in6 *sin6;
|
|
sin6 = (struct sockaddr_in6 *)sa;
|
|
if ((sin6+1)->sin6_family != AF_INET6)
|
|
return -EINVAL;
|
|
memcpy(&saddr->a6, &sin6->sin6_addr,
|
|
sizeof(saddr->a6));
|
|
sin6++;
|
|
memcpy(&daddr->a6, &sin6->sin6_addr,
|
|
sizeof(daddr->a6));
|
|
*family = AF_INET6;
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ipsecrequests_to_migrate(struct sadb_x_ipsecrequest *rq1, int len,
|
|
struct xfrm_migrate *m)
|
|
{
|
|
int err;
|
|
struct sadb_x_ipsecrequest *rq2;
|
|
int mode;
|
|
|
|
if (len <= sizeof(struct sadb_x_ipsecrequest) ||
|
|
len < rq1->sadb_x_ipsecrequest_len)
|
|
return -EINVAL;
|
|
|
|
/* old endoints */
|
|
err = parse_sockaddr_pair(rq1, &m->old_saddr, &m->old_daddr,
|
|
&m->old_family);
|
|
if (err)
|
|
return err;
|
|
|
|
rq2 = (struct sadb_x_ipsecrequest *)((u8 *)rq1 + rq1->sadb_x_ipsecrequest_len);
|
|
len -= rq1->sadb_x_ipsecrequest_len;
|
|
|
|
if (len <= sizeof(struct sadb_x_ipsecrequest) ||
|
|
len < rq2->sadb_x_ipsecrequest_len)
|
|
return -EINVAL;
|
|
|
|
/* new endpoints */
|
|
err = parse_sockaddr_pair(rq2, &m->new_saddr, &m->new_daddr,
|
|
&m->new_family);
|
|
if (err)
|
|
return err;
|
|
|
|
if (rq1->sadb_x_ipsecrequest_proto != rq2->sadb_x_ipsecrequest_proto ||
|
|
rq1->sadb_x_ipsecrequest_mode != rq2->sadb_x_ipsecrequest_mode ||
|
|
rq1->sadb_x_ipsecrequest_reqid != rq2->sadb_x_ipsecrequest_reqid)
|
|
return -EINVAL;
|
|
|
|
m->proto = rq1->sadb_x_ipsecrequest_proto;
|
|
if ((mode = pfkey_mode_to_xfrm(rq1->sadb_x_ipsecrequest_mode)) < 0)
|
|
return -EINVAL;
|
|
m->mode = mode;
|
|
m->reqid = rq1->sadb_x_ipsecrequest_reqid;
|
|
|
|
return ((int)(rq1->sadb_x_ipsecrequest_len +
|
|
rq2->sadb_x_ipsecrequest_len));
|
|
}
|
|
|
|
static int pfkey_migrate(struct sock *sk, struct sk_buff *skb,
|
|
struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
int i, len, ret, err = -EINVAL;
|
|
u8 dir;
|
|
struct sadb_address *sa;
|
|
struct sadb_x_policy *pol;
|
|
struct sadb_x_ipsecrequest *rq;
|
|
struct xfrm_selector sel;
|
|
struct xfrm_migrate m[XFRM_MAX_DEPTH];
|
|
|
|
if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC - 1],
|
|
ext_hdrs[SADB_EXT_ADDRESS_DST - 1]) ||
|
|
!ext_hdrs[SADB_X_EXT_POLICY - 1]) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
pol = ext_hdrs[SADB_X_EXT_POLICY - 1];
|
|
if (!pol) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
if (pol->sadb_x_policy_dir >= IPSEC_DIR_MAX) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
dir = pol->sadb_x_policy_dir - 1;
|
|
memset(&sel, 0, sizeof(sel));
|
|
|
|
/* set source address info of selector */
|
|
sa = ext_hdrs[SADB_EXT_ADDRESS_SRC - 1];
|
|
sel.family = pfkey_sadb_addr2xfrm_addr(sa, &sel.saddr);
|
|
sel.prefixlen_s = sa->sadb_address_prefixlen;
|
|
sel.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
|
|
sel.sport = ((struct sockaddr_in *)(sa + 1))->sin_port;
|
|
if (sel.sport)
|
|
sel.sport_mask = htons(0xffff);
|
|
|
|
/* set destination address info of selector */
|
|
sa = ext_hdrs[SADB_EXT_ADDRESS_DST - 1],
|
|
pfkey_sadb_addr2xfrm_addr(sa, &sel.daddr);
|
|
sel.prefixlen_d = sa->sadb_address_prefixlen;
|
|
sel.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto);
|
|
sel.dport = ((struct sockaddr_in *)(sa + 1))->sin_port;
|
|
if (sel.dport)
|
|
sel.dport_mask = htons(0xffff);
|
|
|
|
rq = (struct sadb_x_ipsecrequest *)(pol + 1);
|
|
|
|
/* extract ipsecrequests */
|
|
i = 0;
|
|
len = pol->sadb_x_policy_len * 8 - sizeof(struct sadb_x_policy);
|
|
|
|
while (len > 0 && i < XFRM_MAX_DEPTH) {
|
|
ret = ipsecrequests_to_migrate(rq, len, &m[i]);
|
|
if (ret < 0) {
|
|
err = ret;
|
|
goto out;
|
|
} else {
|
|
rq = (struct sadb_x_ipsecrequest *)((u8 *)rq + ret);
|
|
len -= ret;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (!i || len > 0) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
return xfrm_migrate(&sel, dir, XFRM_POLICY_TYPE_MAIN, m, i);
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
#else
|
|
static int pfkey_migrate(struct sock *sk, struct sk_buff *skb,
|
|
struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
return -ENOPROTOOPT;
|
|
}
|
|
#endif
|
|
|
|
|
|
static int pfkey_spdget(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
unsigned int dir;
|
|
int err = 0, delete;
|
|
struct sadb_x_policy *pol;
|
|
struct xfrm_policy *xp;
|
|
struct km_event c;
|
|
|
|
if ((pol = ext_hdrs[SADB_X_EXT_POLICY-1]) == NULL)
|
|
return -EINVAL;
|
|
|
|
dir = xfrm_policy_id2dir(pol->sadb_x_policy_id);
|
|
if (dir >= XFRM_POLICY_MAX)
|
|
return -EINVAL;
|
|
|
|
delete = (hdr->sadb_msg_type == SADB_X_SPDDELETE2);
|
|
xp = xfrm_policy_byid(XFRM_POLICY_TYPE_MAIN, dir, pol->sadb_x_policy_id,
|
|
delete, &err);
|
|
if (xp == NULL)
|
|
return -ENOENT;
|
|
|
|
if (delete) {
|
|
xfrm_audit_policy_delete(xp, err ? 0 : 1,
|
|
audit_get_loginuid(current->audit_context), 0);
|
|
|
|
if (err)
|
|
goto out;
|
|
c.seq = hdr->sadb_msg_seq;
|
|
c.pid = hdr->sadb_msg_pid;
|
|
c.data.byid = 1;
|
|
c.event = XFRM_MSG_DELPOLICY;
|
|
km_policy_notify(xp, dir, &c);
|
|
} else {
|
|
err = key_pol_get_resp(sk, xp, hdr, dir);
|
|
}
|
|
|
|
out:
|
|
xfrm_pol_put(xp);
|
|
return err;
|
|
}
|
|
|
|
static int dump_sp(struct xfrm_policy *xp, int dir, int count, void *ptr)
|
|
{
|
|
struct pfkey_dump_data *data = ptr;
|
|
struct sk_buff *out_skb;
|
|
struct sadb_msg *out_hdr;
|
|
int err;
|
|
|
|
out_skb = pfkey_xfrm_policy2msg_prep(xp);
|
|
if (IS_ERR(out_skb))
|
|
return PTR_ERR(out_skb);
|
|
|
|
err = pfkey_xfrm_policy2msg(out_skb, xp, dir);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
out_hdr = (struct sadb_msg *) out_skb->data;
|
|
out_hdr->sadb_msg_version = data->hdr->sadb_msg_version;
|
|
out_hdr->sadb_msg_type = SADB_X_SPDDUMP;
|
|
out_hdr->sadb_msg_satype = SADB_SATYPE_UNSPEC;
|
|
out_hdr->sadb_msg_errno = 0;
|
|
out_hdr->sadb_msg_seq = count;
|
|
out_hdr->sadb_msg_pid = data->hdr->sadb_msg_pid;
|
|
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ONE, data->sk);
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_spddump(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct pfkey_dump_data data = { .skb = skb, .hdr = hdr, .sk = sk };
|
|
|
|
return xfrm_policy_walk(XFRM_POLICY_TYPE_MAIN, dump_sp, &data);
|
|
}
|
|
|
|
static int key_notify_policy_flush(struct km_event *c)
|
|
{
|
|
struct sk_buff *skb_out;
|
|
struct sadb_msg *hdr;
|
|
|
|
skb_out = alloc_skb(sizeof(struct sadb_msg) + 16, GFP_ATOMIC);
|
|
if (!skb_out)
|
|
return -ENOBUFS;
|
|
hdr = (struct sadb_msg *) skb_put(skb_out, sizeof(struct sadb_msg));
|
|
hdr->sadb_msg_type = SADB_X_SPDFLUSH;
|
|
hdr->sadb_msg_seq = c->seq;
|
|
hdr->sadb_msg_pid = c->pid;
|
|
hdr->sadb_msg_version = PF_KEY_V2;
|
|
hdr->sadb_msg_errno = (uint8_t) 0;
|
|
hdr->sadb_msg_len = (sizeof(struct sadb_msg) / sizeof(uint64_t));
|
|
pfkey_broadcast(skb_out, GFP_ATOMIC, BROADCAST_ALL, NULL);
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int pfkey_spdflush(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs)
|
|
{
|
|
struct km_event c;
|
|
struct xfrm_audit audit_info;
|
|
int err;
|
|
|
|
audit_info.loginuid = audit_get_loginuid(current->audit_context);
|
|
audit_info.secid = 0;
|
|
err = xfrm_policy_flush(XFRM_POLICY_TYPE_MAIN, &audit_info);
|
|
if (err)
|
|
return err;
|
|
c.data.type = XFRM_POLICY_TYPE_MAIN;
|
|
c.event = XFRM_MSG_FLUSHPOLICY;
|
|
c.pid = hdr->sadb_msg_pid;
|
|
c.seq = hdr->sadb_msg_seq;
|
|
km_policy_notify(NULL, 0, &c);
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef int (*pfkey_handler)(struct sock *sk, struct sk_buff *skb,
|
|
struct sadb_msg *hdr, void **ext_hdrs);
|
|
static pfkey_handler pfkey_funcs[SADB_MAX + 1] = {
|
|
[SADB_RESERVED] = pfkey_reserved,
|
|
[SADB_GETSPI] = pfkey_getspi,
|
|
[SADB_UPDATE] = pfkey_add,
|
|
[SADB_ADD] = pfkey_add,
|
|
[SADB_DELETE] = pfkey_delete,
|
|
[SADB_GET] = pfkey_get,
|
|
[SADB_ACQUIRE] = pfkey_acquire,
|
|
[SADB_REGISTER] = pfkey_register,
|
|
[SADB_EXPIRE] = NULL,
|
|
[SADB_FLUSH] = pfkey_flush,
|
|
[SADB_DUMP] = pfkey_dump,
|
|
[SADB_X_PROMISC] = pfkey_promisc,
|
|
[SADB_X_PCHANGE] = NULL,
|
|
[SADB_X_SPDUPDATE] = pfkey_spdadd,
|
|
[SADB_X_SPDADD] = pfkey_spdadd,
|
|
[SADB_X_SPDDELETE] = pfkey_spddelete,
|
|
[SADB_X_SPDGET] = pfkey_spdget,
|
|
[SADB_X_SPDACQUIRE] = NULL,
|
|
[SADB_X_SPDDUMP] = pfkey_spddump,
|
|
[SADB_X_SPDFLUSH] = pfkey_spdflush,
|
|
[SADB_X_SPDSETIDX] = pfkey_spdadd,
|
|
[SADB_X_SPDDELETE2] = pfkey_spdget,
|
|
[SADB_X_MIGRATE] = pfkey_migrate,
|
|
};
|
|
|
|
static int pfkey_process(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr)
|
|
{
|
|
void *ext_hdrs[SADB_EXT_MAX];
|
|
int err;
|
|
|
|
pfkey_broadcast(skb_clone(skb, GFP_KERNEL), GFP_KERNEL,
|
|
BROADCAST_PROMISC_ONLY, NULL);
|
|
|
|
memset(ext_hdrs, 0, sizeof(ext_hdrs));
|
|
err = parse_exthdrs(skb, hdr, ext_hdrs);
|
|
if (!err) {
|
|
err = -EOPNOTSUPP;
|
|
if (pfkey_funcs[hdr->sadb_msg_type])
|
|
err = pfkey_funcs[hdr->sadb_msg_type](sk, skb, hdr, ext_hdrs);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static struct sadb_msg *pfkey_get_base_msg(struct sk_buff *skb, int *errp)
|
|
{
|
|
struct sadb_msg *hdr = NULL;
|
|
|
|
if (skb->len < sizeof(*hdr)) {
|
|
*errp = -EMSGSIZE;
|
|
} else {
|
|
hdr = (struct sadb_msg *) skb->data;
|
|
if (hdr->sadb_msg_version != PF_KEY_V2 ||
|
|
hdr->sadb_msg_reserved != 0 ||
|
|
(hdr->sadb_msg_type <= SADB_RESERVED ||
|
|
hdr->sadb_msg_type > SADB_MAX)) {
|
|
hdr = NULL;
|
|
*errp = -EINVAL;
|
|
} else if (hdr->sadb_msg_len != (skb->len /
|
|
sizeof(uint64_t)) ||
|
|
hdr->sadb_msg_len < (sizeof(struct sadb_msg) /
|
|
sizeof(uint64_t))) {
|
|
hdr = NULL;
|
|
*errp = -EMSGSIZE;
|
|
} else {
|
|
*errp = 0;
|
|
}
|
|
}
|
|
return hdr;
|
|
}
|
|
|
|
static inline int aalg_tmpl_set(struct xfrm_tmpl *t, struct xfrm_algo_desc *d)
|
|
{
|
|
unsigned int id = d->desc.sadb_alg_id;
|
|
|
|
if (id >= sizeof(t->aalgos) * 8)
|
|
return 0;
|
|
|
|
return (t->aalgos >> id) & 1;
|
|
}
|
|
|
|
static inline int ealg_tmpl_set(struct xfrm_tmpl *t, struct xfrm_algo_desc *d)
|
|
{
|
|
unsigned int id = d->desc.sadb_alg_id;
|
|
|
|
if (id >= sizeof(t->ealgos) * 8)
|
|
return 0;
|
|
|
|
return (t->ealgos >> id) & 1;
|
|
}
|
|
|
|
static int count_ah_combs(struct xfrm_tmpl *t)
|
|
{
|
|
int i, sz = 0;
|
|
|
|
for (i = 0; ; i++) {
|
|
struct xfrm_algo_desc *aalg = xfrm_aalg_get_byidx(i);
|
|
if (!aalg)
|
|
break;
|
|
if (aalg_tmpl_set(t, aalg) && aalg->available)
|
|
sz += sizeof(struct sadb_comb);
|
|
}
|
|
return sz + sizeof(struct sadb_prop);
|
|
}
|
|
|
|
static int count_esp_combs(struct xfrm_tmpl *t)
|
|
{
|
|
int i, k, sz = 0;
|
|
|
|
for (i = 0; ; i++) {
|
|
struct xfrm_algo_desc *ealg = xfrm_ealg_get_byidx(i);
|
|
if (!ealg)
|
|
break;
|
|
|
|
if (!(ealg_tmpl_set(t, ealg) && ealg->available))
|
|
continue;
|
|
|
|
for (k = 1; ; k++) {
|
|
struct xfrm_algo_desc *aalg = xfrm_aalg_get_byidx(k);
|
|
if (!aalg)
|
|
break;
|
|
|
|
if (aalg_tmpl_set(t, aalg) && aalg->available)
|
|
sz += sizeof(struct sadb_comb);
|
|
}
|
|
}
|
|
return sz + sizeof(struct sadb_prop);
|
|
}
|
|
|
|
static void dump_ah_combs(struct sk_buff *skb, struct xfrm_tmpl *t)
|
|
{
|
|
struct sadb_prop *p;
|
|
int i;
|
|
|
|
p = (struct sadb_prop*)skb_put(skb, sizeof(struct sadb_prop));
|
|
p->sadb_prop_len = sizeof(struct sadb_prop)/8;
|
|
p->sadb_prop_exttype = SADB_EXT_PROPOSAL;
|
|
p->sadb_prop_replay = 32;
|
|
memset(p->sadb_prop_reserved, 0, sizeof(p->sadb_prop_reserved));
|
|
|
|
for (i = 0; ; i++) {
|
|
struct xfrm_algo_desc *aalg = xfrm_aalg_get_byidx(i);
|
|
if (!aalg)
|
|
break;
|
|
|
|
if (aalg_tmpl_set(t, aalg) && aalg->available) {
|
|
struct sadb_comb *c;
|
|
c = (struct sadb_comb*)skb_put(skb, sizeof(struct sadb_comb));
|
|
memset(c, 0, sizeof(*c));
|
|
p->sadb_prop_len += sizeof(struct sadb_comb)/8;
|
|
c->sadb_comb_auth = aalg->desc.sadb_alg_id;
|
|
c->sadb_comb_auth_minbits = aalg->desc.sadb_alg_minbits;
|
|
c->sadb_comb_auth_maxbits = aalg->desc.sadb_alg_maxbits;
|
|
c->sadb_comb_hard_addtime = 24*60*60;
|
|
c->sadb_comb_soft_addtime = 20*60*60;
|
|
c->sadb_comb_hard_usetime = 8*60*60;
|
|
c->sadb_comb_soft_usetime = 7*60*60;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dump_esp_combs(struct sk_buff *skb, struct xfrm_tmpl *t)
|
|
{
|
|
struct sadb_prop *p;
|
|
int i, k;
|
|
|
|
p = (struct sadb_prop*)skb_put(skb, sizeof(struct sadb_prop));
|
|
p->sadb_prop_len = sizeof(struct sadb_prop)/8;
|
|
p->sadb_prop_exttype = SADB_EXT_PROPOSAL;
|
|
p->sadb_prop_replay = 32;
|
|
memset(p->sadb_prop_reserved, 0, sizeof(p->sadb_prop_reserved));
|
|
|
|
for (i=0; ; i++) {
|
|
struct xfrm_algo_desc *ealg = xfrm_ealg_get_byidx(i);
|
|
if (!ealg)
|
|
break;
|
|
|
|
if (!(ealg_tmpl_set(t, ealg) && ealg->available))
|
|
continue;
|
|
|
|
for (k = 1; ; k++) {
|
|
struct sadb_comb *c;
|
|
struct xfrm_algo_desc *aalg = xfrm_aalg_get_byidx(k);
|
|
if (!aalg)
|
|
break;
|
|
if (!(aalg_tmpl_set(t, aalg) && aalg->available))
|
|
continue;
|
|
c = (struct sadb_comb*)skb_put(skb, sizeof(struct sadb_comb));
|
|
memset(c, 0, sizeof(*c));
|
|
p->sadb_prop_len += sizeof(struct sadb_comb)/8;
|
|
c->sadb_comb_auth = aalg->desc.sadb_alg_id;
|
|
c->sadb_comb_auth_minbits = aalg->desc.sadb_alg_minbits;
|
|
c->sadb_comb_auth_maxbits = aalg->desc.sadb_alg_maxbits;
|
|
c->sadb_comb_encrypt = ealg->desc.sadb_alg_id;
|
|
c->sadb_comb_encrypt_minbits = ealg->desc.sadb_alg_minbits;
|
|
c->sadb_comb_encrypt_maxbits = ealg->desc.sadb_alg_maxbits;
|
|
c->sadb_comb_hard_addtime = 24*60*60;
|
|
c->sadb_comb_soft_addtime = 20*60*60;
|
|
c->sadb_comb_hard_usetime = 8*60*60;
|
|
c->sadb_comb_soft_usetime = 7*60*60;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int key_notify_policy_expire(struct xfrm_policy *xp, struct km_event *c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int key_notify_sa_expire(struct xfrm_state *x, struct km_event *c)
|
|
{
|
|
struct sk_buff *out_skb;
|
|
struct sadb_msg *out_hdr;
|
|
int hard;
|
|
int hsc;
|
|
|
|
hard = c->data.hard;
|
|
if (hard)
|
|
hsc = 2;
|
|
else
|
|
hsc = 1;
|
|
|
|
out_skb = pfkey_xfrm_state2msg_expire(x, hsc);
|
|
if (IS_ERR(out_skb))
|
|
return PTR_ERR(out_skb);
|
|
|
|
out_hdr = (struct sadb_msg *) out_skb->data;
|
|
out_hdr->sadb_msg_version = PF_KEY_V2;
|
|
out_hdr->sadb_msg_type = SADB_EXPIRE;
|
|
out_hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
|
|
out_hdr->sadb_msg_errno = 0;
|
|
out_hdr->sadb_msg_reserved = 0;
|
|
out_hdr->sadb_msg_seq = 0;
|
|
out_hdr->sadb_msg_pid = 0;
|
|
|
|
pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_REGISTERED, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_send_notify(struct xfrm_state *x, struct km_event *c)
|
|
{
|
|
switch (c->event) {
|
|
case XFRM_MSG_EXPIRE:
|
|
return key_notify_sa_expire(x, c);
|
|
case XFRM_MSG_DELSA:
|
|
case XFRM_MSG_NEWSA:
|
|
case XFRM_MSG_UPDSA:
|
|
return key_notify_sa(x, c);
|
|
case XFRM_MSG_FLUSHSA:
|
|
return key_notify_sa_flush(c);
|
|
case XFRM_MSG_NEWAE: /* not yet supported */
|
|
break;
|
|
default:
|
|
printk("pfkey: Unknown SA event %d\n", c->event);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pfkey_send_policy_notify(struct xfrm_policy *xp, int dir, struct km_event *c)
|
|
{
|
|
if (xp && xp->type != XFRM_POLICY_TYPE_MAIN)
|
|
return 0;
|
|
|
|
switch (c->event) {
|
|
case XFRM_MSG_POLEXPIRE:
|
|
return key_notify_policy_expire(xp, c);
|
|
case XFRM_MSG_DELPOLICY:
|
|
case XFRM_MSG_NEWPOLICY:
|
|
case XFRM_MSG_UPDPOLICY:
|
|
return key_notify_policy(xp, dir, c);
|
|
case XFRM_MSG_FLUSHPOLICY:
|
|
if (c->data.type != XFRM_POLICY_TYPE_MAIN)
|
|
break;
|
|
return key_notify_policy_flush(c);
|
|
default:
|
|
printk("pfkey: Unknown policy event %d\n", c->event);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 get_acqseq(void)
|
|
{
|
|
u32 res;
|
|
static u32 acqseq;
|
|
static DEFINE_SPINLOCK(acqseq_lock);
|
|
|
|
spin_lock_bh(&acqseq_lock);
|
|
res = (++acqseq ? : ++acqseq);
|
|
spin_unlock_bh(&acqseq_lock);
|
|
return res;
|
|
}
|
|
|
|
static int pfkey_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *t, struct xfrm_policy *xp, int dir)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct sadb_msg *hdr;
|
|
struct sadb_address *addr;
|
|
struct sadb_x_policy *pol;
|
|
struct sockaddr_in *sin;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
int sockaddr_size;
|
|
int size;
|
|
struct sadb_x_sec_ctx *sec_ctx;
|
|
struct xfrm_sec_ctx *xfrm_ctx;
|
|
int ctx_size = 0;
|
|
|
|
sockaddr_size = pfkey_sockaddr_size(x->props.family);
|
|
if (!sockaddr_size)
|
|
return -EINVAL;
|
|
|
|
size = sizeof(struct sadb_msg) +
|
|
(sizeof(struct sadb_address) * 2) +
|
|
(sockaddr_size * 2) +
|
|
sizeof(struct sadb_x_policy);
|
|
|
|
if (x->id.proto == IPPROTO_AH)
|
|
size += count_ah_combs(t);
|
|
else if (x->id.proto == IPPROTO_ESP)
|
|
size += count_esp_combs(t);
|
|
|
|
if ((xfrm_ctx = x->security)) {
|
|
ctx_size = PFKEY_ALIGN8(xfrm_ctx->ctx_len);
|
|
size += sizeof(struct sadb_x_sec_ctx) + ctx_size;
|
|
}
|
|
|
|
skb = alloc_skb(size + 16, GFP_ATOMIC);
|
|
if (skb == NULL)
|
|
return -ENOMEM;
|
|
|
|
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
|
|
hdr->sadb_msg_version = PF_KEY_V2;
|
|
hdr->sadb_msg_type = SADB_ACQUIRE;
|
|
hdr->sadb_msg_satype = pfkey_proto2satype(x->id.proto);
|
|
hdr->sadb_msg_len = size / sizeof(uint64_t);
|
|
hdr->sadb_msg_errno = 0;
|
|
hdr->sadb_msg_reserved = 0;
|
|
hdr->sadb_msg_seq = x->km.seq = get_acqseq();
|
|
hdr->sadb_msg_pid = 0;
|
|
|
|
/* src address */
|
|
addr = (struct sadb_address*) skb_put(skb,
|
|
sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
|
|
addr->sadb_address_proto = 0;
|
|
addr->sadb_address_reserved = 0;
|
|
if (x->props.family == AF_INET) {
|
|
addr->sadb_address_prefixlen = 32;
|
|
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = x->props.saddr.a4;
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (x->props.family == AF_INET6) {
|
|
addr->sadb_address_prefixlen = 128;
|
|
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr,
|
|
x->props.saddr.a6, sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
}
|
|
#endif
|
|
else
|
|
BUG();
|
|
|
|
/* dst address */
|
|
addr = (struct sadb_address*) skb_put(skb,
|
|
sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
|
|
addr->sadb_address_proto = 0;
|
|
addr->sadb_address_reserved = 0;
|
|
if (x->props.family == AF_INET) {
|
|
addr->sadb_address_prefixlen = 32;
|
|
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = x->id.daddr.a4;
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (x->props.family == AF_INET6) {
|
|
addr->sadb_address_prefixlen = 128;
|
|
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr,
|
|
x->id.daddr.a6, sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
}
|
|
#endif
|
|
else
|
|
BUG();
|
|
|
|
pol = (struct sadb_x_policy *) skb_put(skb, sizeof(struct sadb_x_policy));
|
|
pol->sadb_x_policy_len = sizeof(struct sadb_x_policy)/sizeof(uint64_t);
|
|
pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY;
|
|
pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC;
|
|
pol->sadb_x_policy_dir = dir+1;
|
|
pol->sadb_x_policy_id = xp->index;
|
|
|
|
/* Set sadb_comb's. */
|
|
if (x->id.proto == IPPROTO_AH)
|
|
dump_ah_combs(skb, t);
|
|
else if (x->id.proto == IPPROTO_ESP)
|
|
dump_esp_combs(skb, t);
|
|
|
|
/* security context */
|
|
if (xfrm_ctx) {
|
|
sec_ctx = (struct sadb_x_sec_ctx *) skb_put(skb,
|
|
sizeof(struct sadb_x_sec_ctx) + ctx_size);
|
|
sec_ctx->sadb_x_sec_len =
|
|
(sizeof(struct sadb_x_sec_ctx) + ctx_size) / sizeof(uint64_t);
|
|
sec_ctx->sadb_x_sec_exttype = SADB_X_EXT_SEC_CTX;
|
|
sec_ctx->sadb_x_ctx_doi = xfrm_ctx->ctx_doi;
|
|
sec_ctx->sadb_x_ctx_alg = xfrm_ctx->ctx_alg;
|
|
sec_ctx->sadb_x_ctx_len = xfrm_ctx->ctx_len;
|
|
memcpy(sec_ctx + 1, xfrm_ctx->ctx_str,
|
|
xfrm_ctx->ctx_len);
|
|
}
|
|
|
|
return pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_REGISTERED, NULL);
|
|
}
|
|
|
|
static struct xfrm_policy *pfkey_compile_policy(struct sock *sk, int opt,
|
|
u8 *data, int len, int *dir)
|
|
{
|
|
struct xfrm_policy *xp;
|
|
struct sadb_x_policy *pol = (struct sadb_x_policy*)data;
|
|
struct sadb_x_sec_ctx *sec_ctx;
|
|
|
|
switch (sk->sk_family) {
|
|
case AF_INET:
|
|
if (opt != IP_IPSEC_POLICY) {
|
|
*dir = -EOPNOTSUPP;
|
|
return NULL;
|
|
}
|
|
break;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
if (opt != IPV6_IPSEC_POLICY) {
|
|
*dir = -EOPNOTSUPP;
|
|
return NULL;
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
*dir = -EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
*dir = -EINVAL;
|
|
|
|
if (len < sizeof(struct sadb_x_policy) ||
|
|
pol->sadb_x_policy_len*8 > len ||
|
|
pol->sadb_x_policy_type > IPSEC_POLICY_BYPASS ||
|
|
(!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir > IPSEC_DIR_OUTBOUND))
|
|
return NULL;
|
|
|
|
xp = xfrm_policy_alloc(GFP_ATOMIC);
|
|
if (xp == NULL) {
|
|
*dir = -ENOBUFS;
|
|
return NULL;
|
|
}
|
|
|
|
xp->action = (pol->sadb_x_policy_type == IPSEC_POLICY_DISCARD ?
|
|
XFRM_POLICY_BLOCK : XFRM_POLICY_ALLOW);
|
|
|
|
xp->lft.soft_byte_limit = XFRM_INF;
|
|
xp->lft.hard_byte_limit = XFRM_INF;
|
|
xp->lft.soft_packet_limit = XFRM_INF;
|
|
xp->lft.hard_packet_limit = XFRM_INF;
|
|
xp->family = sk->sk_family;
|
|
|
|
xp->xfrm_nr = 0;
|
|
if (pol->sadb_x_policy_type == IPSEC_POLICY_IPSEC &&
|
|
(*dir = parse_ipsecrequests(xp, pol)) < 0)
|
|
goto out;
|
|
|
|
/* security context too */
|
|
if (len >= (pol->sadb_x_policy_len*8 +
|
|
sizeof(struct sadb_x_sec_ctx))) {
|
|
char *p = (char *)pol;
|
|
struct xfrm_user_sec_ctx *uctx;
|
|
|
|
p += pol->sadb_x_policy_len*8;
|
|
sec_ctx = (struct sadb_x_sec_ctx *)p;
|
|
if (len < pol->sadb_x_policy_len*8 +
|
|
sec_ctx->sadb_x_sec_len) {
|
|
*dir = -EINVAL;
|
|
goto out;
|
|
}
|
|
if ((*dir = verify_sec_ctx_len(p)))
|
|
goto out;
|
|
uctx = pfkey_sadb2xfrm_user_sec_ctx(sec_ctx);
|
|
*dir = security_xfrm_policy_alloc(xp, uctx);
|
|
kfree(uctx);
|
|
|
|
if (*dir)
|
|
goto out;
|
|
}
|
|
|
|
*dir = pol->sadb_x_policy_dir-1;
|
|
return xp;
|
|
|
|
out:
|
|
security_xfrm_policy_free(xp);
|
|
kfree(xp);
|
|
return NULL;
|
|
}
|
|
|
|
static int pfkey_send_new_mapping(struct xfrm_state *x, xfrm_address_t *ipaddr, __be16 sport)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct sadb_msg *hdr;
|
|
struct sadb_sa *sa;
|
|
struct sadb_address *addr;
|
|
struct sadb_x_nat_t_port *n_port;
|
|
struct sockaddr_in *sin;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
int sockaddr_size;
|
|
int size;
|
|
__u8 satype = (x->id.proto == IPPROTO_ESP ? SADB_SATYPE_ESP : 0);
|
|
struct xfrm_encap_tmpl *natt = NULL;
|
|
|
|
sockaddr_size = pfkey_sockaddr_size(x->props.family);
|
|
if (!sockaddr_size)
|
|
return -EINVAL;
|
|
|
|
if (!satype)
|
|
return -EINVAL;
|
|
|
|
if (!x->encap)
|
|
return -EINVAL;
|
|
|
|
natt = x->encap;
|
|
|
|
/* Build an SADB_X_NAT_T_NEW_MAPPING message:
|
|
*
|
|
* HDR | SA | ADDRESS_SRC (old addr) | NAT_T_SPORT (old port) |
|
|
* ADDRESS_DST (new addr) | NAT_T_DPORT (new port)
|
|
*/
|
|
|
|
size = sizeof(struct sadb_msg) +
|
|
sizeof(struct sadb_sa) +
|
|
(sizeof(struct sadb_address) * 2) +
|
|
(sockaddr_size * 2) +
|
|
(sizeof(struct sadb_x_nat_t_port) * 2);
|
|
|
|
skb = alloc_skb(size + 16, GFP_ATOMIC);
|
|
if (skb == NULL)
|
|
return -ENOMEM;
|
|
|
|
hdr = (struct sadb_msg *) skb_put(skb, sizeof(struct sadb_msg));
|
|
hdr->sadb_msg_version = PF_KEY_V2;
|
|
hdr->sadb_msg_type = SADB_X_NAT_T_NEW_MAPPING;
|
|
hdr->sadb_msg_satype = satype;
|
|
hdr->sadb_msg_len = size / sizeof(uint64_t);
|
|
hdr->sadb_msg_errno = 0;
|
|
hdr->sadb_msg_reserved = 0;
|
|
hdr->sadb_msg_seq = x->km.seq = get_acqseq();
|
|
hdr->sadb_msg_pid = 0;
|
|
|
|
/* SA */
|
|
sa = (struct sadb_sa *) skb_put(skb, sizeof(struct sadb_sa));
|
|
sa->sadb_sa_len = sizeof(struct sadb_sa)/sizeof(uint64_t);
|
|
sa->sadb_sa_exttype = SADB_EXT_SA;
|
|
sa->sadb_sa_spi = x->id.spi;
|
|
sa->sadb_sa_replay = 0;
|
|
sa->sadb_sa_state = 0;
|
|
sa->sadb_sa_auth = 0;
|
|
sa->sadb_sa_encrypt = 0;
|
|
sa->sadb_sa_flags = 0;
|
|
|
|
/* ADDRESS_SRC (old addr) */
|
|
addr = (struct sadb_address*)
|
|
skb_put(skb, sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
|
|
addr->sadb_address_proto = 0;
|
|
addr->sadb_address_reserved = 0;
|
|
if (x->props.family == AF_INET) {
|
|
addr->sadb_address_prefixlen = 32;
|
|
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = x->props.saddr.a4;
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (x->props.family == AF_INET6) {
|
|
addr->sadb_address_prefixlen = 128;
|
|
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr,
|
|
x->props.saddr.a6, sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
}
|
|
#endif
|
|
else
|
|
BUG();
|
|
|
|
/* NAT_T_SPORT (old port) */
|
|
n_port = (struct sadb_x_nat_t_port*) skb_put(skb, sizeof (*n_port));
|
|
n_port->sadb_x_nat_t_port_len = sizeof(*n_port)/sizeof(uint64_t);
|
|
n_port->sadb_x_nat_t_port_exttype = SADB_X_EXT_NAT_T_SPORT;
|
|
n_port->sadb_x_nat_t_port_port = natt->encap_sport;
|
|
n_port->sadb_x_nat_t_port_reserved = 0;
|
|
|
|
/* ADDRESS_DST (new addr) */
|
|
addr = (struct sadb_address*)
|
|
skb_put(skb, sizeof(struct sadb_address)+sockaddr_size);
|
|
addr->sadb_address_len =
|
|
(sizeof(struct sadb_address)+sockaddr_size)/
|
|
sizeof(uint64_t);
|
|
addr->sadb_address_exttype = SADB_EXT_ADDRESS_DST;
|
|
addr->sadb_address_proto = 0;
|
|
addr->sadb_address_reserved = 0;
|
|
if (x->props.family == AF_INET) {
|
|
addr->sadb_address_prefixlen = 32;
|
|
|
|
sin = (struct sockaddr_in *) (addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
sin->sin_addr.s_addr = ipaddr->a4;
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (x->props.family == AF_INET6) {
|
|
addr->sadb_address_prefixlen = 128;
|
|
|
|
sin6 = (struct sockaddr_in6 *) (addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
memcpy(&sin6->sin6_addr, &ipaddr->a6, sizeof(struct in6_addr));
|
|
sin6->sin6_scope_id = 0;
|
|
}
|
|
#endif
|
|
else
|
|
BUG();
|
|
|
|
/* NAT_T_DPORT (new port) */
|
|
n_port = (struct sadb_x_nat_t_port*) skb_put(skb, sizeof (*n_port));
|
|
n_port->sadb_x_nat_t_port_len = sizeof(*n_port)/sizeof(uint64_t);
|
|
n_port->sadb_x_nat_t_port_exttype = SADB_X_EXT_NAT_T_DPORT;
|
|
n_port->sadb_x_nat_t_port_port = sport;
|
|
n_port->sadb_x_nat_t_port_reserved = 0;
|
|
|
|
return pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_REGISTERED, NULL);
|
|
}
|
|
|
|
#ifdef CONFIG_NET_KEY_MIGRATE
|
|
static int set_sadb_address(struct sk_buff *skb, int sasize, int type,
|
|
struct xfrm_selector *sel)
|
|
{
|
|
struct sadb_address *addr;
|
|
struct sockaddr_in *sin;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
addr = (struct sadb_address *)skb_put(skb, sizeof(struct sadb_address) + sasize);
|
|
addr->sadb_address_len = (sizeof(struct sadb_address) + sasize)/8;
|
|
addr->sadb_address_exttype = type;
|
|
addr->sadb_address_proto = sel->proto;
|
|
addr->sadb_address_reserved = 0;
|
|
|
|
switch (type) {
|
|
case SADB_EXT_ADDRESS_SRC:
|
|
if (sel->family == AF_INET) {
|
|
addr->sadb_address_prefixlen = sel->prefixlen_s;
|
|
sin = (struct sockaddr_in *)(addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
memcpy(&sin->sin_addr.s_addr, &sel->saddr,
|
|
sizeof(sin->sin_addr.s_addr));
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (sel->family == AF_INET6) {
|
|
addr->sadb_address_prefixlen = sel->prefixlen_s;
|
|
sin6 = (struct sockaddr_in6 *)(addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
sin6->sin6_scope_id = 0;
|
|
memcpy(&sin6->sin6_addr.s6_addr, &sel->saddr,
|
|
sizeof(sin6->sin6_addr.s6_addr));
|
|
}
|
|
#endif
|
|
break;
|
|
case SADB_EXT_ADDRESS_DST:
|
|
if (sel->family == AF_INET) {
|
|
addr->sadb_address_prefixlen = sel->prefixlen_d;
|
|
sin = (struct sockaddr_in *)(addr + 1);
|
|
sin->sin_family = AF_INET;
|
|
memcpy(&sin->sin_addr.s_addr, &sel->daddr,
|
|
sizeof(sin->sin_addr.s_addr));
|
|
sin->sin_port = 0;
|
|
memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
|
|
}
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
else if (sel->family == AF_INET6) {
|
|
addr->sadb_address_prefixlen = sel->prefixlen_d;
|
|
sin6 = (struct sockaddr_in6 *)(addr + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
sin6->sin6_scope_id = 0;
|
|
memcpy(&sin6->sin6_addr.s6_addr, &sel->daddr,
|
|
sizeof(sin6->sin6_addr.s6_addr));
|
|
}
|
|
#endif
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_ipsecrequest(struct sk_buff *skb,
|
|
uint8_t proto, uint8_t mode, int level,
|
|
uint32_t reqid, uint8_t family,
|
|
xfrm_address_t *src, xfrm_address_t *dst)
|
|
{
|
|
struct sadb_x_ipsecrequest *rq;
|
|
struct sockaddr_in *sin;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
struct sockaddr_in6 *sin6;
|
|
#endif
|
|
int size_req;
|
|
|
|
size_req = sizeof(struct sadb_x_ipsecrequest) +
|
|
pfkey_sockaddr_pair_size(family);
|
|
|
|
rq = (struct sadb_x_ipsecrequest *)skb_put(skb, size_req);
|
|
memset(rq, 0, size_req);
|
|
rq->sadb_x_ipsecrequest_len = size_req;
|
|
rq->sadb_x_ipsecrequest_proto = proto;
|
|
rq->sadb_x_ipsecrequest_mode = mode;
|
|
rq->sadb_x_ipsecrequest_level = level;
|
|
rq->sadb_x_ipsecrequest_reqid = reqid;
|
|
|
|
switch (family) {
|
|
case AF_INET:
|
|
sin = (struct sockaddr_in *)(rq + 1);
|
|
sin->sin_family = AF_INET;
|
|
memcpy(&sin->sin_addr.s_addr, src,
|
|
sizeof(sin->sin_addr.s_addr));
|
|
sin++;
|
|
sin->sin_family = AF_INET;
|
|
memcpy(&sin->sin_addr.s_addr, dst,
|
|
sizeof(sin->sin_addr.s_addr));
|
|
break;
|
|
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
|
|
case AF_INET6:
|
|
sin6 = (struct sockaddr_in6 *)(rq + 1);
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
sin6->sin6_scope_id = 0;
|
|
memcpy(&sin6->sin6_addr.s6_addr, src,
|
|
sizeof(sin6->sin6_addr.s6_addr));
|
|
sin6++;
|
|
sin6->sin6_family = AF_INET6;
|
|
sin6->sin6_port = 0;
|
|
sin6->sin6_flowinfo = 0;
|
|
sin6->sin6_scope_id = 0;
|
|
memcpy(&sin6->sin6_addr.s6_addr, dst,
|
|
sizeof(sin6->sin6_addr.s6_addr));
|
|
break;
|
|
#endif
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NET_KEY_MIGRATE
|
|
static int pfkey_send_migrate(struct xfrm_selector *sel, u8 dir, u8 type,
|
|
struct xfrm_migrate *m, int num_bundles)
|
|
{
|
|
int i;
|
|
int sasize_sel;
|
|
int size = 0;
|
|
int size_pol = 0;
|
|
struct sk_buff *skb;
|
|
struct sadb_msg *hdr;
|
|
struct sadb_x_policy *pol;
|
|
struct xfrm_migrate *mp;
|
|
|
|
if (type != XFRM_POLICY_TYPE_MAIN)
|
|
return 0;
|
|
|
|
if (num_bundles <= 0 || num_bundles > XFRM_MAX_DEPTH)
|
|
return -EINVAL;
|
|
|
|
/* selector */
|
|
sasize_sel = pfkey_sockaddr_size(sel->family);
|
|
if (!sasize_sel)
|
|
return -EINVAL;
|
|
size += (sizeof(struct sadb_address) + sasize_sel) * 2;
|
|
|
|
/* policy info */
|
|
size_pol += sizeof(struct sadb_x_policy);
|
|
|
|
/* ipsecrequests */
|
|
for (i = 0, mp = m; i < num_bundles; i++, mp++) {
|
|
/* old locator pair */
|
|
size_pol += sizeof(struct sadb_x_ipsecrequest) +
|
|
pfkey_sockaddr_pair_size(mp->old_family);
|
|
/* new locator pair */
|
|
size_pol += sizeof(struct sadb_x_ipsecrequest) +
|
|
pfkey_sockaddr_pair_size(mp->new_family);
|
|
}
|
|
|
|
size += sizeof(struct sadb_msg) + size_pol;
|
|
|
|
/* alloc buffer */
|
|
skb = alloc_skb(size, GFP_ATOMIC);
|
|
if (skb == NULL)
|
|
return -ENOMEM;
|
|
|
|
hdr = (struct sadb_msg *)skb_put(skb, sizeof(struct sadb_msg));
|
|
hdr->sadb_msg_version = PF_KEY_V2;
|
|
hdr->sadb_msg_type = SADB_X_MIGRATE;
|
|
hdr->sadb_msg_satype = pfkey_proto2satype(m->proto);
|
|
hdr->sadb_msg_len = size / 8;
|
|
hdr->sadb_msg_errno = 0;
|
|
hdr->sadb_msg_reserved = 0;
|
|
hdr->sadb_msg_seq = 0;
|
|
hdr->sadb_msg_pid = 0;
|
|
|
|
/* selector src */
|
|
set_sadb_address(skb, sasize_sel, SADB_EXT_ADDRESS_SRC, sel);
|
|
|
|
/* selector dst */
|
|
set_sadb_address(skb, sasize_sel, SADB_EXT_ADDRESS_DST, sel);
|
|
|
|
/* policy information */
|
|
pol = (struct sadb_x_policy *)skb_put(skb, sizeof(struct sadb_x_policy));
|
|
pol->sadb_x_policy_len = size_pol / 8;
|
|
pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY;
|
|
pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC;
|
|
pol->sadb_x_policy_dir = dir + 1;
|
|
pol->sadb_x_policy_id = 0;
|
|
pol->sadb_x_policy_priority = 0;
|
|
|
|
for (i = 0, mp = m; i < num_bundles; i++, mp++) {
|
|
/* old ipsecrequest */
|
|
int mode = pfkey_mode_from_xfrm(mp->mode);
|
|
if (mode < 0)
|
|
return -EINVAL;
|
|
if (set_ipsecrequest(skb, mp->proto, mode,
|
|
(mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
|
|
mp->reqid, mp->old_family,
|
|
&mp->old_saddr, &mp->old_daddr) < 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* new ipsecrequest */
|
|
if (set_ipsecrequest(skb, mp->proto, mode,
|
|
(mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE),
|
|
mp->reqid, mp->new_family,
|
|
&mp->new_saddr, &mp->new_daddr) < 0) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* broadcast migrate message to sockets */
|
|
pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_ALL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int pfkey_send_migrate(struct xfrm_selector *sel, u8 dir, u8 type,
|
|
struct xfrm_migrate *m, int num_bundles)
|
|
{
|
|
return -ENOPROTOOPT;
|
|
}
|
|
#endif
|
|
|
|
static int pfkey_sendmsg(struct kiocb *kiocb,
|
|
struct socket *sock, struct msghdr *msg, size_t len)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct sk_buff *skb = NULL;
|
|
struct sadb_msg *hdr = NULL;
|
|
int err;
|
|
|
|
err = -EOPNOTSUPP;
|
|
if (msg->msg_flags & MSG_OOB)
|
|
goto out;
|
|
|
|
err = -EMSGSIZE;
|
|
if ((unsigned)len > sk->sk_sndbuf - 32)
|
|
goto out;
|
|
|
|
err = -ENOBUFS;
|
|
skb = alloc_skb(len, GFP_KERNEL);
|
|
if (skb == NULL)
|
|
goto out;
|
|
|
|
err = -EFAULT;
|
|
if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len))
|
|
goto out;
|
|
|
|
hdr = pfkey_get_base_msg(skb, &err);
|
|
if (!hdr)
|
|
goto out;
|
|
|
|
mutex_lock(&xfrm_cfg_mutex);
|
|
err = pfkey_process(sk, skb, hdr);
|
|
mutex_unlock(&xfrm_cfg_mutex);
|
|
|
|
out:
|
|
if (err && hdr && pfkey_error(hdr, err, sk) == 0)
|
|
err = 0;
|
|
if (skb)
|
|
kfree_skb(skb);
|
|
|
|
return err ? : len;
|
|
}
|
|
|
|
static int pfkey_recvmsg(struct kiocb *kiocb,
|
|
struct socket *sock, struct msghdr *msg, size_t len,
|
|
int flags)
|
|
{
|
|
struct sock *sk = sock->sk;
|
|
struct sk_buff *skb;
|
|
int copied, err;
|
|
|
|
err = -EINVAL;
|
|
if (flags & ~(MSG_PEEK|MSG_DONTWAIT|MSG_TRUNC|MSG_CMSG_COMPAT))
|
|
goto out;
|
|
|
|
msg->msg_namelen = 0;
|
|
skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &err);
|
|
if (skb == NULL)
|
|
goto out;
|
|
|
|
copied = skb->len;
|
|
if (copied > len) {
|
|
msg->msg_flags |= MSG_TRUNC;
|
|
copied = len;
|
|
}
|
|
|
|
skb_reset_transport_header(skb);
|
|
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
|
|
if (err)
|
|
goto out_free;
|
|
|
|
sock_recv_timestamp(msg, sk, skb);
|
|
|
|
err = (flags & MSG_TRUNC) ? skb->len : copied;
|
|
|
|
out_free:
|
|
skb_free_datagram(sk, skb);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static const struct proto_ops pfkey_ops = {
|
|
.family = PF_KEY,
|
|
.owner = THIS_MODULE,
|
|
/* Operations that make no sense on pfkey sockets. */
|
|
.bind = sock_no_bind,
|
|
.connect = sock_no_connect,
|
|
.socketpair = sock_no_socketpair,
|
|
.accept = sock_no_accept,
|
|
.getname = sock_no_getname,
|
|
.ioctl = sock_no_ioctl,
|
|
.listen = sock_no_listen,
|
|
.shutdown = sock_no_shutdown,
|
|
.setsockopt = sock_no_setsockopt,
|
|
.getsockopt = sock_no_getsockopt,
|
|
.mmap = sock_no_mmap,
|
|
.sendpage = sock_no_sendpage,
|
|
|
|
/* Now the operations that really occur. */
|
|
.release = pfkey_release,
|
|
.poll = datagram_poll,
|
|
.sendmsg = pfkey_sendmsg,
|
|
.recvmsg = pfkey_recvmsg,
|
|
};
|
|
|
|
static struct net_proto_family pfkey_family_ops = {
|
|
.family = PF_KEY,
|
|
.create = pfkey_create,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static int pfkey_read_proc(char *buffer, char **start, off_t offset,
|
|
int length, int *eof, void *data)
|
|
{
|
|
off_t pos = 0;
|
|
off_t begin = 0;
|
|
int len = 0;
|
|
struct sock *s;
|
|
struct hlist_node *node;
|
|
|
|
len += sprintf(buffer,"sk RefCnt Rmem Wmem User Inode\n");
|
|
|
|
read_lock(&pfkey_table_lock);
|
|
|
|
sk_for_each(s, node, &pfkey_table) {
|
|
len += sprintf(buffer+len,"%p %-6d %-6u %-6u %-6u %-6lu",
|
|
s,
|
|
atomic_read(&s->sk_refcnt),
|
|
atomic_read(&s->sk_rmem_alloc),
|
|
atomic_read(&s->sk_wmem_alloc),
|
|
sock_i_uid(s),
|
|
sock_i_ino(s)
|
|
);
|
|
|
|
buffer[len++] = '\n';
|
|
|
|
pos = begin + len;
|
|
if (pos < offset) {
|
|
len = 0;
|
|
begin = pos;
|
|
}
|
|
if(pos > offset + length)
|
|
goto done;
|
|
}
|
|
*eof = 1;
|
|
|
|
done:
|
|
read_unlock(&pfkey_table_lock);
|
|
|
|
*start = buffer + (offset - begin);
|
|
len -= (offset - begin);
|
|
|
|
if (len > length)
|
|
len = length;
|
|
if (len < 0)
|
|
len = 0;
|
|
|
|
return len;
|
|
}
|
|
#endif
|
|
|
|
static struct xfrm_mgr pfkeyv2_mgr =
|
|
{
|
|
.id = "pfkeyv2",
|
|
.notify = pfkey_send_notify,
|
|
.acquire = pfkey_send_acquire,
|
|
.compile_policy = pfkey_compile_policy,
|
|
.new_mapping = pfkey_send_new_mapping,
|
|
.notify_policy = pfkey_send_policy_notify,
|
|
.migrate = pfkey_send_migrate,
|
|
};
|
|
|
|
static void __exit ipsec_pfkey_exit(void)
|
|
{
|
|
xfrm_unregister_km(&pfkeyv2_mgr);
|
|
remove_proc_entry("pfkey", init_net.proc_net);
|
|
sock_unregister(PF_KEY);
|
|
proto_unregister(&key_proto);
|
|
}
|
|
|
|
static int __init ipsec_pfkey_init(void)
|
|
{
|
|
int err = proto_register(&key_proto, 0);
|
|
|
|
if (err != 0)
|
|
goto out;
|
|
|
|
err = sock_register(&pfkey_family_ops);
|
|
if (err != 0)
|
|
goto out_unregister_key_proto;
|
|
#ifdef CONFIG_PROC_FS
|
|
err = -ENOMEM;
|
|
if (create_proc_read_entry("pfkey", 0, init_net.proc_net, pfkey_read_proc, NULL) == NULL)
|
|
goto out_sock_unregister;
|
|
#endif
|
|
err = xfrm_register_km(&pfkeyv2_mgr);
|
|
if (err != 0)
|
|
goto out_remove_proc_entry;
|
|
out:
|
|
return err;
|
|
out_remove_proc_entry:
|
|
#ifdef CONFIG_PROC_FS
|
|
remove_proc_entry("net/pfkey", NULL);
|
|
out_sock_unregister:
|
|
#endif
|
|
sock_unregister(PF_KEY);
|
|
out_unregister_key_proto:
|
|
proto_unregister(&key_proto);
|
|
goto out;
|
|
}
|
|
|
|
module_init(ipsec_pfkey_init);
|
|
module_exit(ipsec_pfkey_exit);
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS_NETPROTO(PF_KEY);
|