Commit 3a00df57 authored by Avraham Stern's avatar Avraham Stern Committed by Johannes Berg

cfg80211: support 4-way handshake offloading for 802.1X

Add API for setting the PMK to the driver. For FT support, allow
setting also the PMK-R0 Name.

This can be used by drivers that support 4-Way handshake offload
while IEEE802.1X authentication is managed by upper layers.
Signed-off-by: default avatarAvraham Stern <avraham.stern@intel.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
[arend.vanspriel@broadcom.com: add WANT_1X_4WAY_HS attribute]
Signed-off-by: default avatarArend van Spriel <arend.vanspriel@broadcom.com>
[reword NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X docs a bit to
say that the device may require it]
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 91b5ab62
...@@ -2400,8 +2400,11 @@ enum ieee80211_sa_query_action { ...@@ -2400,8 +2400,11 @@ enum ieee80211_sa_query_action {
#define WLAN_MAX_KEY_LEN 32 #define WLAN_MAX_KEY_LEN 32
#define WLAN_PMK_NAME_LEN 16
#define WLAN_PMKID_LEN 16 #define WLAN_PMKID_LEN 16
#define WLAN_PMK_LEN_EAP_LEAP 16
#define WLAN_PMK_LEN 32 #define WLAN_PMK_LEN 32
#define WLAN_PMK_LEN_SUITE_B_192 48
#define WLAN_OUI_WFA 0x506f9a #define WLAN_OUI_WFA 0x506f9a
#define WLAN_OUI_TYPE_WFA_P2P 9 #define WLAN_OUI_TYPE_WFA_P2P 9
......
...@@ -2112,6 +2112,8 @@ struct cfg80211_bss_selection { ...@@ -2112,6 +2112,8 @@ struct cfg80211_bss_selection {
* @fils_erp_rrk: ERP re-authentication Root Key (rRK) used to derive additional * @fils_erp_rrk: ERP re-authentication Root Key (rRK) used to derive additional
* keys in FILS or %NULL if not specified. * keys in FILS or %NULL if not specified.
* @fils_erp_rrk_len: Length of @fils_erp_rrk in octets. * @fils_erp_rrk_len: Length of @fils_erp_rrk in octets.
* @want_1x: indicates user-space supports and wants to use 802.1X driver
* offload of 4-way handshake.
*/ */
struct cfg80211_connect_params { struct cfg80211_connect_params {
struct ieee80211_channel *channel; struct ieee80211_channel *channel;
...@@ -2144,6 +2146,7 @@ struct cfg80211_connect_params { ...@@ -2144,6 +2146,7 @@ struct cfg80211_connect_params {
u16 fils_erp_next_seq_num; u16 fils_erp_next_seq_num;
const u8 *fils_erp_rrk; const u8 *fils_erp_rrk;
size_t fils_erp_rrk_len; size_t fils_erp_rrk_len;
bool want_1x;
}; };
/** /**
...@@ -2565,6 +2568,23 @@ struct cfg80211_nan_func { ...@@ -2565,6 +2568,23 @@ struct cfg80211_nan_func {
u64 cookie; u64 cookie;
}; };
/**
* struct cfg80211_pmk_conf - PMK configuration
*
* @aa: authenticator address
* @pmk_len: PMK length in bytes.
* @pmk: the PMK material
* @pmk_r0_name: PMK-R0 Name. NULL if not applicable (i.e., the PMK
* is not PMK-R0). When pmk_r0_name is not NULL, the pmk field
* holds PMK-R0.
*/
struct cfg80211_pmk_conf {
const u8 *aa;
u8 pmk_len;
const u8 *pmk;
const u8 *pmk_r0_name;
};
/** /**
* struct cfg80211_ops - backend description for wireless configuration * struct cfg80211_ops - backend description for wireless configuration
* *
...@@ -2881,6 +2901,13 @@ struct cfg80211_nan_func { ...@@ -2881,6 +2901,13 @@ struct cfg80211_nan_func {
* All other parameters must be ignored. * All other parameters must be ignored.
* *
* @set_multicast_to_unicast: configure multicast to unicast conversion for BSS * @set_multicast_to_unicast: configure multicast to unicast conversion for BSS
*
* @set_pmk: configure the PMK to be used for offloaded 802.1X 4-Way handshake.
* If not deleted through @del_pmk the PMK remains valid until disconnect
* upon which the driver should clear it.
* (invoked with the wireless_dev mutex held)
* @del_pmk: delete the previously configured PMK for the given authenticator.
* (invoked with the wireless_dev mutex held)
*/ */
struct cfg80211_ops { struct cfg80211_ops {
int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow); int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
...@@ -3169,6 +3196,11 @@ struct cfg80211_ops { ...@@ -3169,6 +3196,11 @@ struct cfg80211_ops {
int (*set_multicast_to_unicast)(struct wiphy *wiphy, int (*set_multicast_to_unicast)(struct wiphy *wiphy,
struct net_device *dev, struct net_device *dev,
const bool enabled); const bool enabled);
int (*set_pmk)(struct wiphy *wiphy, struct net_device *dev,
const struct cfg80211_pmk_conf *conf);
int (*del_pmk)(struct wiphy *wiphy, struct net_device *dev,
const u8 *aa);
}; };
/* /*
......
...@@ -182,6 +182,17 @@ ...@@ -182,6 +182,17 @@
* this offload may reject the %NL80211_CMD_CONNECT when no preshared * this offload may reject the %NL80211_CMD_CONNECT when no preshared
* key material is provided, for example when that driver does not * key material is provided, for example when that driver does not
* support setting the temporal keys through %CMD_NEW_KEY. * support setting the temporal keys through %CMD_NEW_KEY.
*
* Similarly @NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X flag can be
* set by drivers indicating offload support of the PTK/GTK EAPOL
* handshakes during 802.1X authentication. In order to use the offload
* the %NL80211_CMD_CONNECT should have %NL80211_ATTR_WANT_1X_4WAY_HS
* attribute flag. Drivers supporting this offload may reject the
* %NL80211_CMD_CONNECT when the attribute flag is not present.
*
* For 802.1X the PMK or PMK-R0 are set by providing %NL80211_ATTR_PMK
* using %NL80211_CMD_SET_PMK. For offloaded FT support also
* %NL80211_ATTR_PMKR0_NAME must be provided.
*/ */
/** /**
...@@ -959,6 +970,14 @@ ...@@ -959,6 +970,14 @@
* does not result in a change for the current association. Currently, * does not result in a change for the current association. Currently,
* only the %NL80211_ATTR_IE data is used and updated with this command. * only the %NL80211_ATTR_IE data is used and updated with this command.
* *
* @NL80211_CMD_SET_PMK: For offloaded 4-Way handshake, set the PMK or PMK-R0
* for the given authenticator address (specified with &NL80211_ATTR_MAC).
* When &NL80211_ATTR_PMKR0_NAME is set, &NL80211_ATTR_PMK specifies the
* PMK-R0, otherwise it specifies the PMK.
* @NL80211_CMD_DEL_PMK: For offloaded 4-Way handshake, delete the previously
* configured PMK for the authenticator address identified by
* &NL80211_ATTR_MAC.
*
* @NL80211_CMD_MAX: highest used command number * @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use * @__NL80211_CMD_AFTER_LAST: internal use
*/ */
...@@ -1158,6 +1177,9 @@ enum nl80211_commands { ...@@ -1158,6 +1177,9 @@ enum nl80211_commands {
NL80211_CMD_UPDATE_CONNECT_PARAMS, NL80211_CMD_UPDATE_CONNECT_PARAMS,
NL80211_CMD_SET_PMK,
NL80211_CMD_DEL_PMK,
/* add new commands above here */ /* add new commands above here */
/* used to define NL80211_CMD_MAX below */ /* used to define NL80211_CMD_MAX below */
...@@ -2095,13 +2117,20 @@ enum nl80211_commands { ...@@ -2095,13 +2117,20 @@ enum nl80211_commands {
* @NL80211_ATTR_PMK: attribute for passing PMK key material. Used with * @NL80211_ATTR_PMK: attribute for passing PMK key material. Used with
* %NL80211_CMD_SET_PMKSA for the PMKSA identified by %NL80211_ATTR_PMKID. * %NL80211_CMD_SET_PMKSA for the PMKSA identified by %NL80211_ATTR_PMKID.
* For %NL80211_CMD_CONNECT it is used to provide PSK for offloading 4-way * For %NL80211_CMD_CONNECT it is used to provide PSK for offloading 4-way
* handshake for WPA/WPA2-PSK networks. * handshake for WPA/WPA2-PSK networks. For 802.1X authentication it is
* used with %NL80211_CMD_SET_PMK. For offloaded FT support this attribute
* specifies the PMK-R0 if NL80211_ATTR_PMKR0_NAME is included as well.
* *
* @NL80211_ATTR_SCHED_SCAN_MULTI: flag attribute which user-space shall use to * @NL80211_ATTR_SCHED_SCAN_MULTI: flag attribute which user-space shall use to
* indicate that it supports multiple active scheduled scan requests. * indicate that it supports multiple active scheduled scan requests.
* @NL80211_ATTR_SCHED_SCAN_MAX_REQS: indicates maximum number of scheduled * @NL80211_ATTR_SCHED_SCAN_MAX_REQS: indicates maximum number of scheduled
* scan request that may be active for the device (u32). * scan request that may be active for the device (u32).
* *
* @NL80211_ATTR_WANT_1X_4WAY_HS: flag attribute which user-space can include
* in %NL80211_CMD_CONNECT to indicate that for 802.1X authentication it
* wants to use the supported offload of the 4-way handshake.
* @NL80211_ATTR_PMKR0_NAME: PMK-R0 Name for offloaded FT.
*
* @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined * @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use * @__NL80211_ATTR_AFTER_LAST: internal use
...@@ -2524,6 +2553,9 @@ enum nl80211_attrs { ...@@ -2524,6 +2553,9 @@ enum nl80211_attrs {
NL80211_ATTR_SCHED_SCAN_MULTI, NL80211_ATTR_SCHED_SCAN_MULTI,
NL80211_ATTR_SCHED_SCAN_MAX_REQS, NL80211_ATTR_SCHED_SCAN_MAX_REQS,
NL80211_ATTR_WANT_1X_4WAY_HS,
NL80211_ATTR_PMKR0_NAME,
/* add attributes here, update the policy in nl80211.c */ /* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST, __NL80211_ATTR_AFTER_LAST,
...@@ -4869,6 +4901,10 @@ enum nl80211_feature_flags { ...@@ -4869,6 +4901,10 @@ enum nl80211_feature_flags {
* @NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK: Device wants to do 4-way * @NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK: Device wants to do 4-way
* handshake with PSK in station mode (PSK is passed as part of the connect * handshake with PSK in station mode (PSK is passed as part of the connect
* and associate commands), doing it in the host might not be supported. * and associate commands), doing it in the host might not be supported.
* @NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X: Device wants to do doing 4-way
* handshake with 802.1X in station mode (will pass EAP frames to the host
* and accept the set_pmk/del_pmk commands), doing it in the host might not
* be supported.
* *
* @NUM_NL80211_EXT_FEATURES: number of extended features. * @NUM_NL80211_EXT_FEATURES: number of extended features.
* @MAX_NL80211_EXT_FEATURES: highest extended feature index. * @MAX_NL80211_EXT_FEATURES: highest extended feature index.
...@@ -4890,6 +4926,7 @@ enum nl80211_ext_feature_index { ...@@ -4890,6 +4926,7 @@ enum nl80211_ext_feature_index {
NL80211_EXT_FEATURE_CQM_RSSI_LIST, NL80211_EXT_FEATURE_CQM_RSSI_LIST,
NL80211_EXT_FEATURE_FILS_SK_OFFLOAD, NL80211_EXT_FEATURE_FILS_SK_OFFLOAD,
NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_PSK,
NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X,
/* add new features before the definition below */ /* add new features before the definition below */
NUM_NL80211_EXT_FEATURES, NUM_NL80211_EXT_FEATURES,
......
...@@ -711,6 +711,11 @@ int wiphy_register(struct wiphy *wiphy) ...@@ -711,6 +711,11 @@ int wiphy_register(struct wiphy *wiphy)
(wiphy->bss_select_support & ~(BIT(__NL80211_BSS_SELECT_ATTR_AFTER_LAST) - 2)))) (wiphy->bss_select_support & ~(BIT(__NL80211_BSS_SELECT_ATTR_AFTER_LAST) - 2))))
return -EINVAL; return -EINVAL;
if (WARN_ON(wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X) &&
(!rdev->ops->set_pmk || !rdev->ops->del_pmk)))
return -EINVAL;
if (wiphy->addresses) if (wiphy->addresses)
memcpy(wiphy->perm_addr, wiphy->addresses[0].addr, ETH_ALEN); memcpy(wiphy->perm_addr, wiphy->addresses[0].addr, ETH_ALEN);
......
...@@ -8881,6 +8881,12 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info) ...@@ -8881,6 +8881,12 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info)
connect.privacy = info->attrs[NL80211_ATTR_PRIVACY]; connect.privacy = info->attrs[NL80211_ATTR_PRIVACY];
if (info->attrs[NL80211_ATTR_WANT_1X_4WAY_HS] &&
!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X))
return -EINVAL;
connect.want_1x = info->attrs[NL80211_ATTR_WANT_1X_4WAY_HS];
err = nl80211_crypto_settings(rdev, info, &connect.crypto, err = nl80211_crypto_settings(rdev, info, &connect.crypto,
NL80211_MAX_NR_CIPHER_SUITES); NL80211_MAX_NR_CIPHER_SUITES);
if (err) if (err)
...@@ -12265,6 +12271,90 @@ static int nl80211_set_multicast_to_unicast(struct sk_buff *skb, ...@@ -12265,6 +12271,90 @@ static int nl80211_set_multicast_to_unicast(struct sk_buff *skb,
return rdev_set_multicast_to_unicast(rdev, dev, enabled); return rdev_set_multicast_to_unicast(rdev, dev, enabled);
} }
static int nl80211_set_pmk(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct net_device *dev = info->user_ptr[1];
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_pmk_conf pmk_conf = {};
int ret;
if (wdev->iftype != NL80211_IFTYPE_STATION &&
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
return -EOPNOTSUPP;
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X))
return -EOPNOTSUPP;
if (!info->attrs[NL80211_ATTR_MAC] || !info->attrs[NL80211_ATTR_PMK])
return -EINVAL;
wdev_lock(wdev);
if (!wdev->current_bss) {
ret = -ENOTCONN;
goto out;
}
pmk_conf.aa = nla_data(info->attrs[NL80211_ATTR_MAC]);
if (memcmp(pmk_conf.aa, wdev->current_bss->pub.bssid, ETH_ALEN)) {
ret = -EINVAL;
goto out;
}
pmk_conf.pmk = nla_data(info->attrs[NL80211_ATTR_PMK]);
pmk_conf.pmk_len = nla_len(info->attrs[NL80211_ATTR_PMK]);
if (pmk_conf.pmk_len != WLAN_PMK_LEN &&
pmk_conf.pmk_len != WLAN_PMK_LEN_SUITE_B_192) {
ret = -EINVAL;
goto out;
}
if (info->attrs[NL80211_ATTR_PMKR0_NAME]) {
int r0_name_len = nla_len(info->attrs[NL80211_ATTR_PMKR0_NAME]);
if (r0_name_len != WLAN_PMK_NAME_LEN) {
ret = -EINVAL;
goto out;
}
pmk_conf.pmk_r0_name =
nla_data(info->attrs[NL80211_ATTR_PMKR0_NAME]);
}
ret = rdev_set_pmk(rdev, dev, &pmk_conf);
out:
wdev_unlock(wdev);
return ret;
}
static int nl80211_del_pmk(struct sk_buff *skb, struct genl_info *info)
{
struct cfg80211_registered_device *rdev = info->user_ptr[0];
struct net_device *dev = info->user_ptr[1];
struct wireless_dev *wdev = dev->ieee80211_ptr;
const u8 *aa;
int ret;
if (wdev->iftype != NL80211_IFTYPE_STATION &&
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
return -EOPNOTSUPP;
if (!wiphy_ext_feature_isset(&rdev->wiphy,
NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X))
return -EOPNOTSUPP;
if (!info->attrs[NL80211_ATTR_MAC])
return -EINVAL;
wdev_lock(wdev);
aa = nla_data(info->attrs[NL80211_ATTR_MAC]);
ret = rdev_del_pmk(rdev, dev, aa);
wdev_unlock(wdev);
return ret;
}
#define NL80211_FLAG_NEED_WIPHY 0x01 #define NL80211_FLAG_NEED_WIPHY 0x01
#define NL80211_FLAG_NEED_NETDEV 0x02 #define NL80211_FLAG_NEED_NETDEV 0x02
#define NL80211_FLAG_NEED_RTNL 0x04 #define NL80211_FLAG_NEED_RTNL 0x04
...@@ -13140,6 +13230,21 @@ static const struct genl_ops nl80211_ops[] = { ...@@ -13140,6 +13230,21 @@ static const struct genl_ops nl80211_ops[] = {
.internal_flags = NL80211_FLAG_NEED_NETDEV | .internal_flags = NL80211_FLAG_NEED_NETDEV |
NL80211_FLAG_NEED_RTNL, NL80211_FLAG_NEED_RTNL,
}, },
{
.cmd = NL80211_CMD_SET_PMK,
.doit = nl80211_set_pmk,
.policy = nl80211_policy,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
{
.cmd = NL80211_CMD_DEL_PMK,
.doit = nl80211_del_pmk,
.policy = nl80211_policy,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
}; };
static struct genl_family nl80211_fam __ro_after_init = { static struct genl_family nl80211_fam __ro_after_init = {
......
...@@ -1164,4 +1164,29 @@ rdev_set_coalesce(struct cfg80211_registered_device *rdev, ...@@ -1164,4 +1164,29 @@ rdev_set_coalesce(struct cfg80211_registered_device *rdev,
trace_rdev_return_int(&rdev->wiphy, ret); trace_rdev_return_int(&rdev->wiphy, ret);
return ret; return ret;
} }
static inline int rdev_set_pmk(struct cfg80211_registered_device *rdev,
struct net_device *dev,
struct cfg80211_pmk_conf *pmk_conf)
{
int ret = -EOPNOTSUPP;
trace_rdev_set_pmk(&rdev->wiphy, dev, pmk_conf);
if (rdev->ops->set_pmk)
ret = rdev->ops->set_pmk(&rdev->wiphy, dev, pmk_conf);
trace_rdev_return_int(&rdev->wiphy, ret);
return ret;
}
static inline int rdev_del_pmk(struct cfg80211_registered_device *rdev,
struct net_device *dev, const u8 *aa)
{
int ret = -EOPNOTSUPP;
trace_rdev_del_pmk(&rdev->wiphy, dev, aa);
if (rdev->ops->del_pmk)
ret = rdev->ops->del_pmk(&rdev->wiphy, dev, aa);
trace_rdev_return_int(&rdev->wiphy, ret);
return ret;
}
#endif /* __CFG80211_RDEV_OPS */ #endif /* __CFG80211_RDEV_OPS */
...@@ -2258,6 +2258,66 @@ TRACE_EVENT(rdev_tdls_cancel_channel_switch, ...@@ -2258,6 +2258,66 @@ TRACE_EVENT(rdev_tdls_cancel_channel_switch,
WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(addr)) WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(addr))
); );
TRACE_EVENT(rdev_set_pmk,
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
struct cfg80211_pmk_conf *pmk_conf),
TP_ARGS(wiphy, netdev, pmk_conf),
TP_STRUCT__entry(
WIPHY_ENTRY
NETDEV_ENTRY
MAC_ENTRY(aa)
__field(u8, pmk_len)
__field(u8, pmk_r0_name_len)
__dynamic_array(u8, pmk, pmk_conf->pmk_len)
__dynamic_array(u8, pmk_r0_name, WLAN_PMK_NAME_LEN)
),
TP_fast_assign(
WIPHY_ASSIGN;
NETDEV_ASSIGN;
MAC_ASSIGN(aa, pmk_conf->aa);
__entry->pmk_len = pmk_conf->pmk_len;
__entry->pmk_r0_name_len =
pmk_conf->pmk_r0_name ? WLAN_PMK_NAME_LEN : 0;
memcpy(__get_dynamic_array(pmk), pmk_conf->pmk,
pmk_conf->pmk_len);
memcpy(__get_dynamic_array(pmk_r0_name), pmk_conf->pmk_r0_name,
pmk_conf->pmk_r0_name ? WLAN_PMK_NAME_LEN : 0);
),
TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " MAC_PR_FMT
"pmk_len=%u, pmk: %s pmk_r0_name: %s", WIPHY_PR_ARG,
NETDEV_PR_ARG, MAC_PR_ARG(aa), __entry->pmk_len,
__print_array(__get_dynamic_array(pmk),
__get_dynamic_array_len(pmk), 1),
__entry->pmk_r0_name_len ?
__print_array(__get_dynamic_array(pmk_r0_name),
__get_dynamic_array_len(pmk_r0_name), 1) : "")
);
TRACE_EVENT(rdev_del_pmk,
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, const u8 *aa),
TP_ARGS(wiphy, netdev, aa),
TP_STRUCT__entry(
WIPHY_ENTRY
NETDEV_ENTRY
MAC_ENTRY(aa)
),
TP_fast_assign(
WIPHY_ASSIGN;
NETDEV_ASSIGN;
MAC_ASSIGN(aa, aa);
),
TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " MAC_PR_FMT,
WIPHY_PR_ARG, NETDEV_PR_ARG, MAC_PR_ARG(aa))
);
/************************************************************* /*************************************************************
* cfg80211 exported functions traces * * cfg80211 exported functions traces *
*************************************************************/ *************************************************************/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment