Bluetooth: Move generic mgmt command dispatcher to hci_sock.c
[deliverable/linux.git] / net / bluetooth / hci_sock.c
index 1d65c5be7c823a282bc74a4d9691db53eb37b8d3..85a44a7dc1503b8dfb1f62ded862143b96da596f 100644 (file)
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
 #include <net/bluetooth/hci_mon.h>
+#include <net/bluetooth/mgmt.h>
+
+#include "mgmt_util.h"
+
+static LIST_HEAD(mgmt_chan_list);
+static DEFINE_MUTEX(mgmt_chan_list_lock);
 
 static atomic_t monitor_promisc = ATOMIC_INIT(0);
 
@@ -44,11 +50,32 @@ struct hci_pinfo {
        struct hci_filter filter;
        __u32             cmsg_mask;
        unsigned short    channel;
+       unsigned long     flags;
 };
 
-static inline int hci_test_bit(int nr, void *addr)
+void hci_sock_set_flag(struct sock *sk, int nr)
+{
+       set_bit(nr, &hci_pi(sk)->flags);
+}
+
+void hci_sock_clear_flag(struct sock *sk, int nr)
 {
-       return *((__u32 *) addr + (nr >> 5)) & ((__u32) 1 << (nr & 31));
+       clear_bit(nr, &hci_pi(sk)->flags);
+}
+
+int hci_sock_test_flag(struct sock *sk, int nr)
+{
+       return test_bit(nr, &hci_pi(sk)->flags);
+}
+
+unsigned short hci_sock_get_channel(struct sock *sk)
+{
+       return hci_pi(sk)->channel;
+}
+
+static inline int hci_test_bit(int nr, const void *addr)
+{
+       return *((const __u32 *) addr + (nr >> 5)) & ((__u32) 1 << (nr & 31));
 }
 
 /* Security filter */
@@ -183,54 +210,31 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb)
        kfree_skb(skb_copy);
 }
 
-/* Send frame to control socket */
-void hci_send_to_control(struct sk_buff *skb, struct sock *skip_sk)
+/* Send frame to sockets with specific channel */
+void hci_send_to_channel(unsigned short channel, struct sk_buff *skb,
+                        int flag, struct sock *skip_sk)
 {
        struct sock *sk;
 
-       BT_DBG("len %d", skb->len);
+       BT_DBG("channel %u len %d", channel, skb->len);
 
        read_lock(&hci_sk_list.lock);
 
        sk_for_each(sk, &hci_sk_list.head) {
                struct sk_buff *nskb;
 
-               /* Skip the original socket */
-               if (sk == skip_sk)
-                       continue;
-
-               if (sk->sk_state != BT_BOUND)
-                       continue;
-
-               if (hci_pi(sk)->channel != HCI_CHANNEL_CONTROL)
+               /* Ignore socket without the flag set */
+               if (!hci_sock_test_flag(sk, flag))
                        continue;
 
-               nskb = skb_clone(skb, GFP_ATOMIC);
-               if (!nskb)
+               /* Skip the original socket */
+               if (sk == skip_sk)
                        continue;
 
-               if (sock_queue_rcv_skb(sk, nskb))
-                       kfree_skb(nskb);
-       }
-
-       read_unlock(&hci_sk_list.lock);
-}
-
-static void queue_monitor_skb(struct sk_buff *skb)
-{
-       struct sock *sk;
-
-       BT_DBG("len %d", skb->len);
-
-       read_lock(&hci_sk_list.lock);
-
-       sk_for_each(sk, &hci_sk_list.head) {
-               struct sk_buff *nskb;
-
                if (sk->sk_state != BT_BOUND)
                        continue;
 
-               if (hci_pi(sk)->channel != HCI_CHANNEL_MONITOR)
+               if (hci_pi(sk)->channel != channel)
                        continue;
 
                nskb = skb_clone(skb, GFP_ATOMIC);
@@ -290,7 +294,8 @@ void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb)
        hdr->index = cpu_to_le16(hdev->id);
        hdr->len = cpu_to_le16(skb->len);
 
-       queue_monitor_skb(skb_copy);
+       hci_send_to_channel(HCI_CHANNEL_MONITOR, skb_copy,
+                           HCI_SOCK_TRUSTED, NULL);
        kfree_skb(skb_copy);
 }
 
@@ -397,7 +402,8 @@ void hci_sock_dev_event(struct hci_dev *hdev, int event)
 
                skb = create_monitor_event(hdev, event);
                if (skb) {
-                       queue_monitor_skb(skb);
+                       hci_send_to_channel(HCI_CHANNEL_MONITOR, skb,
+                                           HCI_SOCK_TRUSTED, NULL);
                        kfree_skb(skb);
                }
        }
@@ -428,6 +434,56 @@ void hci_sock_dev_event(struct hci_dev *hdev, int event)
        }
 }
 
+static struct hci_mgmt_chan *__hci_mgmt_chan_find(unsigned short channel)
+{
+       struct hci_mgmt_chan *c;
+
+       list_for_each_entry(c, &mgmt_chan_list, list) {
+               if (c->channel == channel)
+                       return c;
+       }
+
+       return NULL;
+}
+
+static struct hci_mgmt_chan *hci_mgmt_chan_find(unsigned short channel)
+{
+       struct hci_mgmt_chan *c;
+
+       mutex_lock(&mgmt_chan_list_lock);
+       c = __hci_mgmt_chan_find(channel);
+       mutex_unlock(&mgmt_chan_list_lock);
+
+       return c;
+}
+
+int hci_mgmt_chan_register(struct hci_mgmt_chan *c)
+{
+       if (c->channel < HCI_CHANNEL_CONTROL)
+               return -EINVAL;
+
+       mutex_lock(&mgmt_chan_list_lock);
+       if (__hci_mgmt_chan_find(c->channel)) {
+               mutex_unlock(&mgmt_chan_list_lock);
+               return -EALREADY;
+       }
+
+       list_add_tail(&c->list, &mgmt_chan_list);
+
+       mutex_unlock(&mgmt_chan_list_lock);
+
+       return 0;
+}
+EXPORT_SYMBOL(hci_mgmt_chan_register);
+
+void hci_mgmt_chan_unregister(struct hci_mgmt_chan *c)
+{
+       mutex_lock(&mgmt_chan_list_lock);
+       list_del(&c->list);
+       mutex_unlock(&mgmt_chan_list_lock);
+}
+EXPORT_SYMBOL(hci_mgmt_chan_unregister);
+
 static int hci_sock_release(struct socket *sock)
 {
        struct sock *sk = sock->sk;
@@ -448,7 +504,7 @@ static int hci_sock_release(struct socket *sock)
        if (hdev) {
                if (hci_pi(sk)->channel == HCI_CHANNEL_USER) {
                        mgmt_index_added(hdev);
-                       clear_bit(HCI_USER_CHANNEL, &hdev->dev_flags);
+                       hci_dev_clear_flag(hdev, HCI_USER_CHANNEL);
                        hci_dev_close(hdev->id);
                }
 
@@ -508,10 +564,10 @@ static int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd,
        if (!hdev)
                return -EBADFD;
 
-       if (test_bit(HCI_USER_CHANNEL, &hdev->dev_flags))
+       if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL))
                return -EBUSY;
 
-       if (test_bit(HCI_UNCONFIGURED, &hdev->dev_flags))
+       if (hci_dev_test_flag(hdev, HCI_UNCONFIGURED))
                return -EOPNOTSUPP;
 
        if (hdev->dev_type != HCI_BREDR)
@@ -687,14 +743,14 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
 
                if (test_bit(HCI_UP, &hdev->flags) ||
                    test_bit(HCI_INIT, &hdev->flags) ||
-                   test_bit(HCI_SETUP, &hdev->dev_flags) ||
-                   test_bit(HCI_CONFIG, &hdev->dev_flags)) {
+                   hci_dev_test_flag(hdev, HCI_SETUP) ||
+                   hci_dev_test_flag(hdev, HCI_CONFIG)) {
                        err = -EBUSY;
                        hci_dev_put(hdev);
                        goto done;
                }
 
-               if (test_and_set_bit(HCI_USER_CHANNEL, &hdev->dev_flags)) {
+               if (hci_dev_test_and_set_flag(hdev, HCI_USER_CHANNEL)) {
                        err = -EUSERS;
                        hci_dev_put(hdev);
                        goto done;
@@ -704,7 +760,7 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
 
                err = hci_dev_open(hdev->id);
                if (err) {
-                       clear_bit(HCI_USER_CHANNEL, &hdev->dev_flags);
+                       hci_dev_clear_flag(hdev, HCI_USER_CHANNEL);
                        mgmt_index_added(hdev);
                        hci_dev_put(hdev);
                        goto done;
@@ -715,38 +771,62 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
                hci_pi(sk)->hdev = hdev;
                break;
 
-       case HCI_CHANNEL_CONTROL:
+       case HCI_CHANNEL_MONITOR:
                if (haddr.hci_dev != HCI_DEV_NONE) {
                        err = -EINVAL;
                        goto done;
                }
 
-               if (!capable(CAP_NET_ADMIN)) {
+               if (!capable(CAP_NET_RAW)) {
                        err = -EPERM;
                        goto done;
                }
 
+               /* The monitor interface is restricted to CAP_NET_RAW
+                * capabilities and with that implicitly trusted.
+                */
+               hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
+
+               send_monitor_replay(sk);
+
+               atomic_inc(&monitor_promisc);
                break;
 
-       case HCI_CHANNEL_MONITOR:
-               if (haddr.hci_dev != HCI_DEV_NONE) {
+       default:
+               if (!hci_mgmt_chan_find(haddr.hci_channel)) {
                        err = -EINVAL;
                        goto done;
                }
 
-               if (!capable(CAP_NET_RAW)) {
-                       err = -EPERM;
+               if (haddr.hci_dev != HCI_DEV_NONE) {
+                       err = -EINVAL;
                        goto done;
                }
 
-               send_monitor_replay(sk);
-
-               atomic_inc(&monitor_promisc);
+               /* Users with CAP_NET_ADMIN capabilities are allowed
+                * access to all management commands and events. For
+                * untrusted users the interface is restricted and
+                * also only untrusted events are sent.
+                */
+               if (capable(CAP_NET_ADMIN))
+                       hci_sock_set_flag(sk, HCI_SOCK_TRUSTED);
+
+               /* At the moment the index and unconfigured index events
+                * are enabled unconditionally. Setting them on each
+                * socket when binding keeps this functionality. They
+                * however might be cleared later and then sending of these
+                * events will be disabled, but that is then intentional.
+                *
+                * This also enables generic events that are safe to be
+                * received by untrusted users. Example for such events
+                * are changes to settings, class of device, name etc.
+                */
+               if (haddr.hci_channel == HCI_CHANNEL_CONTROL) {
+                       hci_sock_set_flag(sk, HCI_MGMT_INDEX_EVENTS);
+                       hci_sock_set_flag(sk, HCI_MGMT_UNCONF_INDEX_EVENTS);
+                       hci_sock_set_flag(sk, HCI_MGMT_GENERIC_EVENTS);
+               }
                break;
-
-       default:
-               err = -EINVAL;
-               goto done;
        }
 
 
@@ -826,8 +906,8 @@ static void hci_sock_cmsg(struct sock *sk, struct msghdr *msg,
        }
 }
 
-static int hci_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
-                           struct msghdr *msg, size_t len, int flags)
+static int hci_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
+                           int flags)
 {
        int noblock = flags & MSG_DONTWAIT;
        struct sock *sk = sock->sk;
@@ -860,10 +940,13 @@ static int hci_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
                hci_sock_cmsg(sk, msg, skb);
                break;
        case HCI_CHANNEL_USER:
-       case HCI_CHANNEL_CONTROL:
        case HCI_CHANNEL_MONITOR:
                sock_recv_timestamp(msg, sk, skb);
                break;
+       default:
+               if (hci_mgmt_chan_find(hci_pi(sk)->channel))
+                       sock_recv_timestamp(msg, sk, skb);
+               break;
        }
 
        skb_free_datagram(sk, skb);
@@ -871,10 +954,122 @@ static int hci_sock_recvmsg(struct kiocb *iocb, struct socket *sock,
        return err ? : copied;
 }
 
-static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
-                           struct msghdr *msg, size_t len)
+static int hci_mgmt_cmd(struct hci_mgmt_chan *chan, struct sock *sk,
+                       struct msghdr *msg, size_t msglen)
+{
+       void *buf;
+       u8 *cp;
+       struct mgmt_hdr *hdr;
+       u16 opcode, index, len;
+       struct hci_dev *hdev = NULL;
+       const struct hci_mgmt_handler *handler;
+       bool var_len, no_hdev;
+       int err;
+
+       BT_DBG("got %zu bytes", msglen);
+
+       if (msglen < sizeof(*hdr))
+               return -EINVAL;
+
+       buf = kmalloc(msglen, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       if (memcpy_from_msg(buf, msg, msglen)) {
+               err = -EFAULT;
+               goto done;
+       }
+
+       hdr = buf;
+       opcode = __le16_to_cpu(hdr->opcode);
+       index = __le16_to_cpu(hdr->index);
+       len = __le16_to_cpu(hdr->len);
+
+       if (len != msglen - sizeof(*hdr)) {
+               err = -EINVAL;
+               goto done;
+       }
+
+       if (opcode >= chan->handler_count ||
+           chan->handlers[opcode].func == NULL) {
+               BT_DBG("Unknown op %u", opcode);
+               err = mgmt_cmd_status(sk, index, opcode,
+                                     MGMT_STATUS_UNKNOWN_COMMAND);
+               goto done;
+       }
+
+       handler = &chan->handlers[opcode];
+
+       if (!hci_sock_test_flag(sk, HCI_SOCK_TRUSTED) &&
+           !(handler->flags & HCI_MGMT_UNTRUSTED)) {
+               err = mgmt_cmd_status(sk, index, opcode,
+                                     MGMT_STATUS_PERMISSION_DENIED);
+               goto done;
+       }
+
+       if (index != MGMT_INDEX_NONE) {
+               hdev = hci_dev_get(index);
+               if (!hdev) {
+                       err = mgmt_cmd_status(sk, index, opcode,
+                                             MGMT_STATUS_INVALID_INDEX);
+                       goto done;
+               }
+
+               if (hci_dev_test_flag(hdev, HCI_SETUP) ||
+                   hci_dev_test_flag(hdev, HCI_CONFIG) ||
+                   hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) {
+                       err = mgmt_cmd_status(sk, index, opcode,
+                                             MGMT_STATUS_INVALID_INDEX);
+                       goto done;
+               }
+
+               if (hci_dev_test_flag(hdev, HCI_UNCONFIGURED) &&
+                   !(handler->flags & HCI_MGMT_UNCONFIGURED)) {
+                       err = mgmt_cmd_status(sk, index, opcode,
+                                             MGMT_STATUS_INVALID_INDEX);
+                       goto done;
+               }
+       }
+
+       no_hdev = (handler->flags & HCI_MGMT_NO_HDEV);
+       if (no_hdev != !hdev) {
+               err = mgmt_cmd_status(sk, index, opcode,
+                                     MGMT_STATUS_INVALID_INDEX);
+               goto done;
+       }
+
+       var_len = (handler->flags & HCI_MGMT_VAR_LEN);
+       if ((var_len && len < handler->data_len) ||
+           (!var_len && len != handler->data_len)) {
+               err = mgmt_cmd_status(sk, index, opcode,
+                                     MGMT_STATUS_INVALID_PARAMS);
+               goto done;
+       }
+
+       if (hdev && chan->hdev_init)
+               chan->hdev_init(sk, hdev);
+
+       cp = buf + sizeof(*hdr);
+
+       err = handler->func(sk, hdev, cp, len);
+       if (err < 0)
+               goto done;
+
+       err = msglen;
+
+done:
+       if (hdev)
+               hci_dev_put(hdev);
+
+       kfree(buf);
+       return err;
+}
+
+static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
+                           size_t len)
 {
        struct sock *sk = sock->sk;
+       struct hci_mgmt_chan *chan;
        struct hci_dev *hdev;
        struct sk_buff *skb;
        int err;
@@ -896,14 +1091,18 @@ static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
        case HCI_CHANNEL_RAW:
        case HCI_CHANNEL_USER:
                break;
-       case HCI_CHANNEL_CONTROL:
-               err = mgmt_control(sk, msg, len);
-               goto done;
        case HCI_CHANNEL_MONITOR:
                err = -EOPNOTSUPP;
                goto done;
        default:
-               err = -EINVAL;
+               mutex_lock(&mgmt_chan_list_lock);
+               chan = __hci_mgmt_chan_find(hci_pi(sk)->channel);
+               if (chan)
+                       err = hci_mgmt_cmd(chan, sk, msg, len);
+               else
+                       err = -EINVAL;
+
+               mutex_unlock(&mgmt_chan_list_lock);
                goto done;
        }
 
@@ -965,7 +1164,7 @@ static int hci_sock_sendmsg(struct kiocb *iocb, struct socket *sock,
                        /* Stand-alone HCI commands must be flagged as
                         * single-command requests.
                         */
-                       bt_cb(skb)->req.start = true;
+                       bt_cb(skb)->req_start = 1;
 
                        skb_queue_tail(&hdev->cmd_q, skb);
                        queue_work(hdev->workqueue, &hdev->cmd_work);
This page took 0.031055 seconds and 5 git commands to generate.