nl80211: bounce scan request back to userspace
[safe/jmp/linux-2.6] / net / wireless / nl80211.c
index d33cab0..909ebd6 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This is the new netlink-based wireless configuration interface.
  *
- * Copyright 2006, 2007        Johannes Berg <johannes@sipsolutions.net>
+ * Copyright 2006-2009 Johannes Berg <johannes@sipsolutions.net>
  */
 
 #include <linux/if.h>
@@ -57,10 +57,14 @@ static int get_drv_dev_by_info_ifindex(struct nlattr **attrs,
 static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
        [NL80211_ATTR_WIPHY] = { .type = NLA_U32 },
        [NL80211_ATTR_WIPHY_NAME] = { .type = NLA_NUL_STRING,
-                                     .len = BUS_ID_SIZE-1 },
+                                     .len = 20-1 },
        [NL80211_ATTR_WIPHY_TXQ_PARAMS] = { .type = NLA_NESTED },
        [NL80211_ATTR_WIPHY_FREQ] = { .type = NLA_U32 },
        [NL80211_ATTR_WIPHY_CHANNEL_TYPE] = { .type = NLA_U32 },
+       [NL80211_ATTR_WIPHY_RETRY_SHORT] = { .type = NLA_U8 },
+       [NL80211_ATTR_WIPHY_RETRY_LONG] = { .type = NLA_U8 },
+       [NL80211_ATTR_WIPHY_FRAG_THRESHOLD] = { .type = NLA_U32 },
+       [NL80211_ATTR_WIPHY_RTS_THRESHOLD] = { .type = NLA_U32 },
 
        [NL80211_ATTR_IFTYPE] = { .type = NLA_U32 },
        [NL80211_ATTR_IFINDEX] = { .type = NLA_U32 },
@@ -73,6 +77,7 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
        [NL80211_ATTR_KEY_IDX] = { .type = NLA_U8 },
        [NL80211_ATTR_KEY_CIPHER] = { .type = NLA_U32 },
        [NL80211_ATTR_KEY_DEFAULT] = { .type = NLA_FLAG },
+       [NL80211_ATTR_KEY_SEQ] = { .type = NLA_BINARY, .len = 8 },
 
        [NL80211_ATTR_BEACON_INTERVAL] = { .type = NLA_U32 },
        [NL80211_ATTR_DTIM_PERIOD] = { .type = NLA_U32 },
@@ -116,6 +121,13 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
                                .len = IEEE80211_MAX_SSID_LEN },
        [NL80211_ATTR_AUTH_TYPE] = { .type = NLA_U32 },
        [NL80211_ATTR_REASON_CODE] = { .type = NLA_U16 },
+       [NL80211_ATTR_FREQ_FIXED] = { .type = NLA_FLAG },
+       [NL80211_ATTR_TIMED_OUT] = { .type = NLA_FLAG },
+       [NL80211_ATTR_USE_MFP] = { .type = NLA_U32 },
+       [NL80211_ATTR_STA_FLAGS2] = {
+               .len = sizeof(struct nl80211_sta_flag_update),
+       },
+       [NL80211_ATTR_CONTROL_PORT] = { .type = NLA_FLAG },
 };
 
 /* IE validation */
@@ -203,6 +215,16 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
 
        NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, dev->wiphy_idx);
        NLA_PUT_STRING(msg, NL80211_ATTR_WIPHY_NAME, wiphy_name(&dev->wiphy));
+
+       NLA_PUT_U8(msg, NL80211_ATTR_WIPHY_RETRY_SHORT,
+                  dev->wiphy.retry_short);
+       NLA_PUT_U8(msg, NL80211_ATTR_WIPHY_RETRY_LONG,
+                  dev->wiphy.retry_long);
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FRAG_THRESHOLD,
+                   dev->wiphy.frag_threshold);
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_RTS_THRESHOLD,
+                   dev->wiphy.rts_threshold);
+
        NLA_PUT_U8(msg, NL80211_ATTR_MAX_NUM_SCAN_SSIDS,
                   dev->wiphy.max_scan_ssids);
        NLA_PUT_U16(msg, NL80211_ATTR_MAX_SCAN_IE_LEN,
@@ -322,6 +344,7 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags,
        CMD(assoc, ASSOCIATE);
        CMD(deauth, DEAUTHENTICATE);
        CMD(disassoc, DISASSOCIATE);
+       CMD(join_ibss, JOIN_IBSS);
 
 #undef CMD
        nla_nest_end(msg, nl_cmds);
@@ -366,7 +389,7 @@ static int nl80211_get_wiphy(struct sk_buff *skb, struct genl_info *info)
        if (IS_ERR(dev))
                return PTR_ERR(dev);
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                goto out_err;
 
@@ -414,6 +437,9 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
        struct cfg80211_registered_device *rdev;
        int result = 0, rem_txq_params = 0;
        struct nlattr *nl_txq_params;
+       u32 changed;
+       u8 retry_short = 0, retry_long = 0;
+       u32 frag_threshold = 0, rts_threshold = 0;
 
        rtnl_lock();
 
@@ -467,7 +493,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
                enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
                struct ieee80211_channel *chan;
                struct ieee80211_sta_ht_cap *ht_cap;
-               u32 freq, sec_freq;
+               u32 freq;
 
                if (!rdev->ops->set_channel) {
                        result = -EOPNOTSUPP;
@@ -493,33 +519,28 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
                if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
                        goto bad_res;
 
-               if (channel_type == NL80211_CHAN_HT40MINUS)
-                       sec_freq = freq - 20;
-               else if (channel_type == NL80211_CHAN_HT40PLUS)
-                       sec_freq = freq + 20;
-               else
-                       sec_freq = 0;
-
-               ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;
-
-               /* no HT capabilities */
-               if (channel_type != NL80211_CHAN_NO_HT &&
-                   !ht_cap->ht_supported)
+               if (channel_type == NL80211_CHAN_HT40MINUS &&
+                   (chan->flags & IEEE80211_CHAN_NO_HT40MINUS))
                        goto bad_res;
+               else if (channel_type == NL80211_CHAN_HT40PLUS &&
+                        (chan->flags & IEEE80211_CHAN_NO_HT40PLUS))
+                       goto bad_res;
+
+               /*
+                * At this point we know if that if HT40 was requested
+                * we are allowed to use it and the extension channel
+                * exists.
+                */
 
-               if (sec_freq) {
-                       struct ieee80211_channel *schan;
+               ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;
 
-                       /* no 40 MHz capabilities */
+               /* no HT capabilities or intolerant */
+               if (channel_type != NL80211_CHAN_NO_HT) {
+                       if (!ht_cap->ht_supported)
+                               goto bad_res;
                        if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
                            (ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT))
                                goto bad_res;
-
-                       schan = ieee80211_get_channel(&rdev->wiphy, sec_freq);
-
-                       /* Secondary channel not allowed */
-                       if (!schan || schan->flags & IEEE80211_CHAN_DISABLED)
-                               goto bad_res;
                }
 
                result = rdev->ops->set_channel(&rdev->wiphy, chan,
@@ -528,6 +549,84 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
                        goto bad_res;
        }
 
+       changed = 0;
+
+       if (info->attrs[NL80211_ATTR_WIPHY_RETRY_SHORT]) {
+               retry_short = nla_get_u8(
+                       info->attrs[NL80211_ATTR_WIPHY_RETRY_SHORT]);
+               if (retry_short == 0) {
+                       result = -EINVAL;
+                       goto bad_res;
+               }
+               changed |= WIPHY_PARAM_RETRY_SHORT;
+       }
+
+       if (info->attrs[NL80211_ATTR_WIPHY_RETRY_LONG]) {
+               retry_long = nla_get_u8(
+                       info->attrs[NL80211_ATTR_WIPHY_RETRY_LONG]);
+               if (retry_long == 0) {
+                       result = -EINVAL;
+                       goto bad_res;
+               }
+               changed |= WIPHY_PARAM_RETRY_LONG;
+       }
+
+       if (info->attrs[NL80211_ATTR_WIPHY_FRAG_THRESHOLD]) {
+               frag_threshold = nla_get_u32(
+                       info->attrs[NL80211_ATTR_WIPHY_FRAG_THRESHOLD]);
+               if (frag_threshold < 256) {
+                       result = -EINVAL;
+                       goto bad_res;
+               }
+               if (frag_threshold != (u32) -1) {
+                       /*
+                        * Fragments (apart from the last one) are required to
+                        * have even length. Make the fragmentation code
+                        * simpler by stripping LSB should someone try to use
+                        * odd threshold value.
+                        */
+                       frag_threshold &= ~0x1;
+               }
+               changed |= WIPHY_PARAM_FRAG_THRESHOLD;
+       }
+
+       if (info->attrs[NL80211_ATTR_WIPHY_RTS_THRESHOLD]) {
+               rts_threshold = nla_get_u32(
+                       info->attrs[NL80211_ATTR_WIPHY_RTS_THRESHOLD]);
+               changed |= WIPHY_PARAM_RTS_THRESHOLD;
+       }
+
+       if (changed) {
+               u8 old_retry_short, old_retry_long;
+               u32 old_frag_threshold, old_rts_threshold;
+
+               if (!rdev->ops->set_wiphy_params) {
+                       result = -EOPNOTSUPP;
+                       goto bad_res;
+               }
+
+               old_retry_short = rdev->wiphy.retry_short;
+               old_retry_long = rdev->wiphy.retry_long;
+               old_frag_threshold = rdev->wiphy.frag_threshold;
+               old_rts_threshold = rdev->wiphy.rts_threshold;
+
+               if (changed & WIPHY_PARAM_RETRY_SHORT)
+                       rdev->wiphy.retry_short = retry_short;
+               if (changed & WIPHY_PARAM_RETRY_LONG)
+                       rdev->wiphy.retry_long = retry_long;
+               if (changed & WIPHY_PARAM_FRAG_THRESHOLD)
+                       rdev->wiphy.frag_threshold = frag_threshold;
+               if (changed & WIPHY_PARAM_RTS_THRESHOLD)
+                       rdev->wiphy.rts_threshold = rts_threshold;
+
+               result = rdev->ops->set_wiphy_params(&rdev->wiphy, changed);
+               if (result) {
+                       rdev->wiphy.retry_short = old_retry_short;
+                       rdev->wiphy.retry_long = old_retry_long;
+                       rdev->wiphy.frag_threshold = old_frag_threshold;
+                       rdev->wiphy.rts_threshold = old_rts_threshold;
+               }
+       }
 
  bad_res:
        mutex_unlock(&rdev->mtx);
@@ -538,6 +637,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
 
 
 static int nl80211_send_iface(struct sk_buff *msg, u32 pid, u32 seq, int flags,
+                             struct cfg80211_registered_device *rdev,
                              struct net_device *dev)
 {
        void *hdr;
@@ -547,6 +647,7 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 pid, u32 seq, int flags,
                return -1;
 
        NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, dev->ifindex);
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
        NLA_PUT_STRING(msg, NL80211_ATTR_IFNAME, dev->name);
        NLA_PUT_U32(msg, NL80211_ATTR_IFTYPE, dev->ieee80211_ptr->iftype);
        return genlmsg_end(msg, hdr);
@@ -581,7 +682,7 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback *
                        }
                        if (nl80211_send_iface(skb, NETLINK_CB(cb->skb).pid,
                                               cb->nlh->nlmsg_seq, NLM_F_MULTI,
-                                              wdev->netdev) < 0) {
+                                              dev, wdev->netdev) < 0) {
                                mutex_unlock(&dev->devlist_mtx);
                                goto out;
                        }
@@ -611,11 +712,12 @@ static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info)
        if (err)
                return err;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                goto out_err;
 
-       if (nl80211_send_iface(msg, info->snd_pid, info->snd_seq, 0, netdev) < 0)
+       if (nl80211_send_iface(msg, info->snd_pid, info->snd_seq, 0,
+                              dev, netdev) < 0)
                goto out_free;
 
        dev_put(netdev);
@@ -665,7 +767,7 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
        struct cfg80211_registered_device *drv;
        struct vif_params params;
        int err, ifindex;
-       enum nl80211_iftype type;
+       enum nl80211_iftype otype, ntype;
        struct net_device *dev;
        u32 _flags, *flags = NULL;
        bool change = false;
@@ -679,30 +781,27 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
                goto unlock_rtnl;
 
        ifindex = dev->ifindex;
-       type = dev->ieee80211_ptr->iftype;
+       otype = ntype = dev->ieee80211_ptr->iftype;
        dev_put(dev);
 
        if (info->attrs[NL80211_ATTR_IFTYPE]) {
-               enum nl80211_iftype ntype;
-
                ntype = nla_get_u32(info->attrs[NL80211_ATTR_IFTYPE]);
-               if (type != ntype)
+               if (otype != ntype)
                        change = true;
-               type = ntype;
-               if (type > NL80211_IFTYPE_MAX) {
+               if (ntype > NL80211_IFTYPE_MAX) {
                        err = -EINVAL;
                        goto unlock;
                }
        }
 
        if (!drv->ops->change_virtual_intf ||
-           !(drv->wiphy.interface_modes & (1 << type))) {
+           !(drv->wiphy.interface_modes & (1 << ntype))) {
                err = -EOPNOTSUPP;
                goto unlock;
        }
 
        if (info->attrs[NL80211_ATTR_MESH_ID]) {
-               if (type != NL80211_IFTYPE_MESH_POINT) {
+               if (ntype != NL80211_IFTYPE_MESH_POINT) {
                        err = -EINVAL;
                        goto unlock;
                }
@@ -712,7 +811,7 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
        }
 
        if (info->attrs[NL80211_ATTR_MNTR_FLAGS]) {
-               if (type != NL80211_IFTYPE_MONITOR) {
+               if (ntype != NL80211_IFTYPE_MONITOR) {
                        err = -EINVAL;
                        goto unlock;
                }
@@ -727,12 +826,17 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
 
        if (change)
                err = drv->ops->change_virtual_intf(&drv->wiphy, ifindex,
-                                                   type, flags, &params);
+                                                   ntype, flags, &params);
        else
                err = 0;
 
        dev = __dev_get_by_index(&init_net, ifindex);
-       WARN_ON(!dev || (!err && dev->ieee80211_ptr->iftype != type));
+       WARN_ON(!dev || (!err && dev->ieee80211_ptr->iftype != ntype));
+
+       if (dev && !err && (ntype != otype)) {
+               if (otype == NL80211_IFTYPE_ADHOC)
+                       cfg80211_clear_ibss(dev, false);
+       }
 
  unlock:
        cfg80211_put_dev(drv);
@@ -881,7 +985,7 @@ static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info)
                goto out;
        }
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg) {
                err = -ENOMEM;
                goto out;
@@ -969,6 +1073,14 @@ static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info)
        }
 
        err = func(&drv->wiphy, dev, key_idx);
+#ifdef CONFIG_WIRELESS_EXT
+       if (!err) {
+               if (func == drv->ops->set_default_key)
+                       dev->ieee80211_ptr->wext.default_key = key_idx;
+               else
+                       dev->ieee80211_ptr->wext.default_mgmt_key = key_idx;
+       }
+#endif
 
  out:
        cfg80211_put_dev(drv);
@@ -999,6 +1111,11 @@ static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
                params.key_len = nla_len(info->attrs[NL80211_ATTR_KEY_DATA]);
        }
 
+       if (info->attrs[NL80211_ATTR_KEY_SEQ]) {
+               params.seq = nla_data(info->attrs[NL80211_ATTR_KEY_SEQ]);
+               params.seq_len = nla_len(info->attrs[NL80211_ATTR_KEY_SEQ]);
+       }
+
        if (info->attrs[NL80211_ATTR_KEY_IDX])
                key_idx = nla_get_u8(info->attrs[NL80211_ATTR_KEY_IDX]);
 
@@ -1007,44 +1124,8 @@ static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
        if (info->attrs[NL80211_ATTR_MAC])
                mac_addr = nla_data(info->attrs[NL80211_ATTR_MAC]);
 
-       if (key_idx > 5)
-               return -EINVAL;
-
-       /*
-        * Disallow pairwise keys with non-zero index unless it's WEP
-        * (because current deployments use pairwise WEP keys with
-        * non-zero indizes but 802.11i clearly specifies to use zero)
-        */
-       if (mac_addr && key_idx &&
-           params.cipher != WLAN_CIPHER_SUITE_WEP40 &&
-           params.cipher != WLAN_CIPHER_SUITE_WEP104)
-               return -EINVAL;
-
-       /* TODO: add definitions for the lengths to linux/ieee80211.h */
-       switch (params.cipher) {
-       case WLAN_CIPHER_SUITE_WEP40:
-               if (params.key_len != 5)
-                       return -EINVAL;
-               break;
-       case WLAN_CIPHER_SUITE_TKIP:
-               if (params.key_len != 32)
-                       return -EINVAL;
-               break;
-       case WLAN_CIPHER_SUITE_CCMP:
-               if (params.key_len != 16)
-                       return -EINVAL;
-               break;
-       case WLAN_CIPHER_SUITE_WEP104:
-               if (params.key_len != 13)
-                       return -EINVAL;
-               break;
-       case WLAN_CIPHER_SUITE_AES_CMAC:
-               if (params.key_len != 16)
-                       return -EINVAL;
-               break;
-       default:
+       if (cfg80211_validate_key_settings(&params, key_idx, mac_addr))
                return -EINVAL;
-       }
 
        rtnl_lock();
 
@@ -1106,6 +1187,15 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info)
 
        err = drv->ops->del_key(&drv->wiphy, dev, key_idx, mac_addr);
 
+#ifdef CONFIG_WIRELESS_EXT
+       if (!err) {
+               if (key_idx == dev->ieee80211_ptr->wext.default_key)
+                       dev->ieee80211_ptr->wext.default_key = -1;
+               else if (key_idx == dev->ieee80211_ptr->wext.default_mgmt_key)
+                       dev->ieee80211_ptr->wext.default_mgmt_key = -1;
+       }
+#endif
+
  out:
        cfg80211_put_dev(drv);
        dev_put(dev);
@@ -1246,15 +1336,36 @@ static const struct nla_policy sta_flags_policy[NL80211_STA_FLAG_MAX + 1] = {
        [NL80211_STA_FLAG_AUTHORIZED] = { .type = NLA_FLAG },
        [NL80211_STA_FLAG_SHORT_PREAMBLE] = { .type = NLA_FLAG },
        [NL80211_STA_FLAG_WME] = { .type = NLA_FLAG },
+       [NL80211_STA_FLAG_MFP] = { .type = NLA_FLAG },
 };
 
-static int parse_station_flags(struct nlattr *nla, u32 *staflags)
+static int parse_station_flags(struct genl_info *info,
+                              struct station_parameters *params)
 {
        struct nlattr *flags[NL80211_STA_FLAG_MAX + 1];
+       struct nlattr *nla;
        int flag;
 
-       *staflags = 0;
+       /*
+        * Try parsing the new attribute first so userspace
+        * can specify both for older kernels.
+        */
+       nla = info->attrs[NL80211_ATTR_STA_FLAGS2];
+       if (nla) {
+               struct nl80211_sta_flag_update *sta_flags;
+
+               sta_flags = nla_data(nla);
+               params->sta_flags_mask = sta_flags->mask;
+               params->sta_flags_set = sta_flags->set;
+               if ((params->sta_flags_mask |
+                    params->sta_flags_set) & BIT(__NL80211_STA_FLAG_INVALID))
+                       return -EINVAL;
+               return 0;
+       }
+
+       /* if present, parse the old attribute */
 
+       nla = info->attrs[NL80211_ATTR_STA_FLAGS];
        if (!nla)
                return 0;
 
@@ -1262,11 +1373,12 @@ static int parse_station_flags(struct nlattr *nla, u32 *staflags)
                             nla, sta_flags_policy))
                return -EINVAL;
 
-       *staflags = STATION_FLAG_CHANGED;
+       params->sta_flags_mask = (1 << __NL80211_STA_FLAG_AFTER_LAST) - 1;
+       params->sta_flags_mask &= ~1;
 
        for (flag = 1; flag <= NL80211_STA_FLAG_MAX; flag++)
                if (flags[flag])
-                       *staflags |= (1<<flag);
+                       params->sta_flags_set |= (1<<flag);
 
        return 0;
 }
@@ -1484,7 +1596,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
        if (err)
                goto out;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                goto out;
 
@@ -1562,8 +1674,7 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                params.ht_capa =
                        nla_data(info->attrs[NL80211_ATTR_HT_CAPABILITY]);
 
-       if (parse_station_flags(info->attrs[NL80211_ATTR_STA_FLAGS],
-                               &params.station_flags))
+       if (parse_station_flags(info, &params))
                return -EINVAL;
 
        if (info->attrs[NL80211_ATTR_STA_PLINK_ACTION])
@@ -1627,13 +1738,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
                nla_len(info->attrs[NL80211_ATTR_STA_SUPPORTED_RATES]);
        params.listen_interval =
                nla_get_u16(info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL]);
+
        params.aid = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]);
+       if (!params.aid || params.aid > IEEE80211_MAX_AID)
+               return -EINVAL;
+
        if (info->attrs[NL80211_ATTR_HT_CAPABILITY])
                params.ht_capa =
                        nla_data(info->attrs[NL80211_ATTR_HT_CAPABILITY]);
 
-       if (parse_station_flags(info->attrs[NL80211_ATTR_STA_FLAGS],
-                               &params.station_flags))
+       if (parse_station_flags(info, &params))
                return -EINVAL;
 
        rtnl_lock();
@@ -1642,6 +1756,12 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
        if (err)
                goto out_rtnl;
 
+       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
+           dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN) {
+               err = -EINVAL;
+               goto out;
+       }
+
        err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], drv, &params.vlan);
        if (err)
                goto out;
@@ -1685,6 +1805,12 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info)
        if (err)
                goto out_rtnl;
 
+       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
+           dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN) {
+               err = -EINVAL;
+               goto out;
+       }
+
        if (!drv->ops->del_station) {
                err = -EOPNOTSUPP;
                goto out;
@@ -1868,7 +1994,7 @@ static int nl80211_get_mpath(struct sk_buff *skb, struct genl_info *info)
        if (err)
                goto out;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                goto out;
 
@@ -2184,7 +2310,7 @@ static int nl80211_get_mesh_params(struct sk_buff *skb,
                goto out;
 
        /* Draw up a netlink message to send back */
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg) {
                err = -ENOBUFS;
                goto out;
@@ -2362,7 +2488,7 @@ static int nl80211_get_reg(struct sk_buff *skb, struct genl_info *info)
        if (!cfg80211_regdomain)
                goto out;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg) {
                err = -ENOBUFS;
                goto out;
@@ -2445,18 +2571,24 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
                        rem_reg_rules) {
                num_rules++;
                if (num_rules > NL80211_MAX_SUPP_REG_RULES)
-                       goto bad_reg;
+                       return -EINVAL;
        }
 
-       if (!reg_is_valid_request(alpha2))
-               return -EINVAL;
+       mutex_lock(&cfg80211_mutex);
+
+       if (!reg_is_valid_request(alpha2)) {
+               r = -EINVAL;
+               goto bad_reg;
+       }
 
        size_of_regd = sizeof(struct ieee80211_regdomain) +
                (num_rules * sizeof(struct ieee80211_reg_rule));
 
        rd = kzalloc(size_of_regd, GFP_KERNEL);
-       if (!rd)
-               return -ENOMEM;
+       if (!rd) {
+               r = -ENOMEM;
+               goto bad_reg;
+       }
 
        rd->n_reg_rules = num_rules;
        rd->alpha2[0] = alpha2[0];
@@ -2473,20 +2605,24 @@ static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
 
                rule_idx++;
 
-               if (rule_idx > NL80211_MAX_SUPP_REG_RULES)
+               if (rule_idx > NL80211_MAX_SUPP_REG_RULES) {
+                       r = -EINVAL;
                        goto bad_reg;
+               }
        }
 
        BUG_ON(rule_idx != num_rules);
 
-       mutex_lock(&cfg80211_mutex);
        r = set_regdom(rd);
+
        mutex_unlock(&cfg80211_mutex);
+
        return r;
 
  bad_reg:
+       mutex_unlock(&cfg80211_mutex);
        kfree(rd);
-       return -EINVAL;
+       return r;
 }
 
 static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
@@ -2909,6 +3045,19 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
                req.ie_len = nla_len(info->attrs[NL80211_ATTR_IE]);
        }
 
+       if (info->attrs[NL80211_ATTR_USE_MFP]) {
+               enum nl80211_mfp use_mfp =
+                       nla_get_u32(info->attrs[NL80211_ATTR_USE_MFP]);
+               if (use_mfp == NL80211_MFP_REQUIRED)
+                       req.use_mfp = true;
+               else if (use_mfp != NL80211_MFP_NO) {
+                       err = -EINVAL;
+                       goto out;
+               }
+       }
+
+       req.control_port = info->attrs[NL80211_ATTR_CONTROL_PORT];
+
        err = drv->ops->assoc(&drv->wiphy, dev, &req);
 
 out:
@@ -3049,6 +3198,124 @@ unlock_rtnl:
        return err;
 }
 
+static int nl80211_join_ibss(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       struct net_device *dev;
+       struct cfg80211_ibss_params ibss;
+       struct wiphy *wiphy;
+       int err;
+
+       memset(&ibss, 0, sizeof(ibss));
+
+       if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_IE]))
+               return -EINVAL;
+
+       if (!info->attrs[NL80211_ATTR_WIPHY_FREQ] ||
+           !info->attrs[NL80211_ATTR_SSID] ||
+           !nla_len(info->attrs[NL80211_ATTR_SSID]))
+               return -EINVAL;
+
+       ibss.beacon_interval = 100;
+
+       if (info->attrs[NL80211_ATTR_BEACON_INTERVAL]) {
+               ibss.beacon_interval =
+                       nla_get_u32(info->attrs[NL80211_ATTR_BEACON_INTERVAL]);
+               if (ibss.beacon_interval < 1 || ibss.beacon_interval > 10000)
+                       return -EINVAL;
+       }
+
+       rtnl_lock();
+
+       err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev);
+       if (err)
+               goto unlock_rtnl;
+
+       if (!drv->ops->join_ibss) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_ADHOC) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (!netif_running(dev)) {
+               err = -ENETDOWN;
+               goto out;
+       }
+
+       wiphy = &drv->wiphy;
+
+       if (info->attrs[NL80211_ATTR_MAC])
+               ibss.bssid = nla_data(info->attrs[NL80211_ATTR_MAC]);
+       ibss.ssid = nla_data(info->attrs[NL80211_ATTR_SSID]);
+       ibss.ssid_len = nla_len(info->attrs[NL80211_ATTR_SSID]);
+
+       if (info->attrs[NL80211_ATTR_IE]) {
+               ibss.ie = nla_data(info->attrs[NL80211_ATTR_IE]);
+               ibss.ie_len = nla_len(info->attrs[NL80211_ATTR_IE]);
+       }
+
+       ibss.channel = ieee80211_get_channel(wiphy,
+               nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]));
+       if (!ibss.channel ||
+           ibss.channel->flags & IEEE80211_CHAN_NO_IBSS ||
+           ibss.channel->flags & IEEE80211_CHAN_DISABLED) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       ibss.channel_fixed = !!info->attrs[NL80211_ATTR_FREQ_FIXED];
+
+       err = cfg80211_join_ibss(drv, dev, &ibss);
+
+out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+unlock_rtnl:
+       rtnl_unlock();
+       return err;
+}
+
+static int nl80211_leave_ibss(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *drv;
+       struct net_device *dev;
+       int err;
+
+       rtnl_lock();
+
+       err = get_drv_dev_by_info_ifindex(info->attrs, &drv, &dev);
+       if (err)
+               goto unlock_rtnl;
+
+       if (!drv->ops->leave_ibss) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_ADHOC) {
+               err = -EOPNOTSUPP;
+               goto out;
+       }
+
+       if (!netif_running(dev)) {
+               err = -ENETDOWN;
+               goto out;
+       }
+
+       err = cfg80211_leave_ibss(drv, dev, false);
+
+out:
+       cfg80211_put_dev(drv);
+       dev_put(dev);
+unlock_rtnl:
+       rtnl_unlock();
+       return err;
+}
+
 static struct genl_ops nl80211_ops[] = {
        {
                .cmd = NL80211_CMD_GET_WIPHY,
@@ -3250,6 +3517,18 @@ static struct genl_ops nl80211_ops[] = {
                .policy = nl80211_policy,
                .flags = GENL_ADMIN_PERM,
        },
+       {
+               .cmd = NL80211_CMD_JOIN_IBSS,
+               .doit = nl80211_join_ibss,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
+       {
+               .cmd = NL80211_CMD_LEAVE_IBSS,
+               .doit = nl80211_leave_ibss,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+       },
 };
 static struct genl_multicast_group nl80211_mlme_mcgrp = {
        .name = "mlme",
@@ -3272,7 +3551,7 @@ void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev)
 {
        struct sk_buff *msg;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return;
 
@@ -3284,11 +3563,43 @@ void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev)
        genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL);
 }
 
+static int nl80211_add_scan_req(struct sk_buff *msg,
+                               struct cfg80211_registered_device *rdev)
+{
+       struct cfg80211_scan_request *req = rdev->scan_req;
+       struct nlattr *nest;
+       int i;
+
+       if (WARN_ON(!req))
+               return 0;
+
+       nest = nla_nest_start(msg, NL80211_ATTR_SCAN_SSIDS);
+       if (!nest)
+               goto nla_put_failure;
+       for (i = 0; i < req->n_ssids; i++)
+               NLA_PUT(msg, i, req->ssids[i].ssid_len, req->ssids[i].ssid);
+       nla_nest_end(msg, nest);
+
+       nest = nla_nest_start(msg, NL80211_ATTR_SCAN_FREQUENCIES);
+       if (!nest)
+               goto nla_put_failure;
+       for (i = 0; i < req->n_channels; i++)
+               NLA_PUT_U32(msg, i, req->channels[i]->center_freq);
+       nla_nest_end(msg, nest);
+
+       if (req->ie)
+               NLA_PUT(msg, NL80211_ATTR_IE, req->ie_len, req->ie);
+
+       return 0;
+ nla_put_failure:
+       return -ENOBUFS;
+}
+
 static int nl80211_send_scan_donemsg(struct sk_buff *msg,
-                                   struct cfg80211_registered_device *rdev,
-                                   struct net_device *netdev,
-                                   u32 pid, u32 seq, int flags,
-                                   u32 cmd)
+                                    struct cfg80211_registered_device *rdev,
+                                    struct net_device *netdev,
+                                    u32 pid, u32 seq, int flags,
+                                    u32 cmd)
 {
        void *hdr;
 
@@ -3299,7 +3610,8 @@ static int nl80211_send_scan_donemsg(struct sk_buff *msg,
        NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
        NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
 
-       /* XXX: we should probably bounce back the request? */
+       /* ignore errors and send incomplete event anyway */
+       nl80211_add_scan_req(msg, rdev);
 
        return genlmsg_end(msg, hdr);
 
@@ -3313,7 +3625,7 @@ void nl80211_send_scan_done(struct cfg80211_registered_device *rdev,
 {
        struct sk_buff *msg;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return;
 
@@ -3331,7 +3643,7 @@ void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev,
 {
        struct sk_buff *msg;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return;
 
@@ -3353,7 +3665,7 @@ void nl80211_send_reg_change_event(struct regulatory_request *request)
        struct sk_buff *msg;
        void *hdr;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return;
 
@@ -3407,7 +3719,7 @@ static void nl80211_send_mlme_event(struct cfg80211_registered_device *rdev,
        struct sk_buff *msg;
        void *hdr;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
        if (!msg)
                return;
 
@@ -3463,6 +3775,88 @@ void nl80211_send_disassoc(struct cfg80211_registered_device *rdev,
                                NL80211_CMD_DISASSOCIATE);
 }
 
+static void nl80211_send_mlme_timeout(struct cfg80211_registered_device *rdev,
+                                     struct net_device *netdev, int cmd,
+                                     const u8 *addr)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
+       if (!msg)
+               return;
+
+       hdr = nl80211hdr_put(msg, 0, 0, 0, cmd);
+       if (!hdr) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
+       NLA_PUT_FLAG(msg, NL80211_ATTR_TIMED_OUT);
+       NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, addr);
+
+       if (genlmsg_end(msg, hdr) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, GFP_ATOMIC);
+       return;
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       nlmsg_free(msg);
+}
+
+void nl80211_send_auth_timeout(struct cfg80211_registered_device *rdev,
+                              struct net_device *netdev, const u8 *addr)
+{
+       nl80211_send_mlme_timeout(rdev, netdev, NL80211_CMD_AUTHENTICATE,
+                                 addr);
+}
+
+void nl80211_send_assoc_timeout(struct cfg80211_registered_device *rdev,
+                               struct net_device *netdev, const u8 *addr)
+{
+       nl80211_send_mlme_timeout(rdev, netdev, NL80211_CMD_ASSOCIATE, addr);
+}
+
+void nl80211_send_ibss_bssid(struct cfg80211_registered_device *rdev,
+                            struct net_device *netdev, const u8 *bssid,
+                            gfp_t gfp)
+{
+       struct sk_buff *msg;
+       void *hdr;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+       if (!msg)
+               return;
+
+       hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_JOIN_IBSS);
+       if (!hdr) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
+       NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
+       NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, bssid);
+
+       if (genlmsg_end(msg, hdr) < 0) {
+               nlmsg_free(msg);
+               return;
+       }
+
+       genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, gfp);
+       return;
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       nlmsg_free(msg);
+}
+
 void nl80211_michael_mic_failure(struct cfg80211_registered_device *rdev,
                                 struct net_device *netdev, const u8 *addr,
                                 enum nl80211_key_type key_type, int key_id,
@@ -3471,7 +3865,7 @@ void nl80211_michael_mic_failure(struct cfg80211_registered_device *rdev,
        struct sk_buff *msg;
        void *hdr;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return;
 
@@ -3511,7 +3905,7 @@ void nl80211_send_beacon_hint_event(struct wiphy *wiphy,
        void *hdr;
        struct nlattr *nl_freq;
 
-       msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
        if (!msg)
                return;
 
@@ -3561,18 +3955,13 @@ nla_put_failure:
 
 int nl80211_init(void)
 {
-       int err, i;
+       int err;
 
-       err = genl_register_family(&nl80211_fam);
+       err = genl_register_family_with_ops(&nl80211_fam,
+               nl80211_ops, ARRAY_SIZE(nl80211_ops));
        if (err)
                return err;
 
-       for (i = 0; i < ARRAY_SIZE(nl80211_ops); i++) {
-               err = genl_register_ops(&nl80211_fam, &nl80211_ops[i]);
-               if (err)
-                       goto err_out;
-       }
-
        err = genl_register_mc_group(&nl80211_fam, &nl80211_config_mcgrp);
        if (err)
                goto err_out;