hostapd: Allow addition of arbitrary RADIUS attributes

New configuration parameters radius_auth_req_attr and
radius_acct_req_attr can now be used to add (or override) RADIUS
attributes in Access-Request and Accounting-Request packets.

Signed-hostap: Jouni Malinen <j@w1.fi>
This commit is contained in:
Jouni Malinen 2012-05-05 20:19:56 +03:00
parent 86f6053aa2
commit af35e7af7f
6 changed files with 241 additions and 17 deletions

View file

@ -491,6 +491,76 @@ hostapd_config_read_radius_addr(struct hostapd_radius_server **server,
return ret; return ret;
} }
static struct hostapd_radius_attr *
hostapd_parse_radius_attr(const char *value)
{
const char *pos;
char syntax;
struct hostapd_radius_attr *attr;
size_t len;
attr = os_zalloc(sizeof(*attr));
if (attr == NULL)
return NULL;
attr->type = atoi(value);
pos = os_strchr(value, ':');
if (pos == NULL) {
attr->val = wpabuf_alloc(1);
if (attr->val == NULL) {
os_free(attr);
return NULL;
}
wpabuf_put_u8(attr->val, 0);
return attr;
}
pos++;
if (pos[0] == '\0' || pos[1] != ':') {
os_free(attr);
return NULL;
}
syntax = *pos++;
pos++;
switch (syntax) {
case 's':
attr->val = wpabuf_alloc_copy(pos, os_strlen(pos));
break;
case 'x':
len = os_strlen(pos);
if (len & 1)
break;
len /= 2;
attr->val = wpabuf_alloc(len);
if (attr->val == NULL)
break;
if (hexstr2bin(pos, wpabuf_put(attr->val, len), len) < 0) {
wpabuf_free(attr->val);
os_free(attr);
return NULL;
}
break;
case 'd':
attr->val = wpabuf_alloc(4);
if (attr->val)
wpabuf_put_be32(attr->val, atoi(pos));
break;
default:
os_free(attr);
return NULL;
}
if (attr->val == NULL) {
os_free(attr);
return NULL;
}
return attr;
}
#endif /* CONFIG_NO_RADIUS */ #endif /* CONFIG_NO_RADIUS */
@ -1557,6 +1627,36 @@ static int hostapd_config_fill(struct hostapd_config *conf,
bss->acct_interim_interval = atoi(pos); bss->acct_interim_interval = atoi(pos);
} else if (os_strcmp(buf, "radius_request_cui") == 0) { } else if (os_strcmp(buf, "radius_request_cui") == 0) {
bss->radius_request_cui = atoi(pos); bss->radius_request_cui = atoi(pos);
} else if (os_strcmp(buf, "radius_auth_req_attr") == 0) {
struct hostapd_radius_attr *attr, *a;
attr = hostapd_parse_radius_attr(pos);
if (attr == NULL) {
wpa_printf(MSG_ERROR, "Line %d: invalid "
"radius_auth_req_attr", line);
errors++;
} else if (bss->radius_auth_req_attr == NULL) {
bss->radius_auth_req_attr = attr;
} else {
a = bss->radius_auth_req_attr;
while (a->next)
a = a->next;
a->next = attr;
}
} else if (os_strcmp(buf, "radius_acct_req_attr") == 0) {
struct hostapd_radius_attr *attr, *a;
attr = hostapd_parse_radius_attr(pos);
if (attr == NULL) {
wpa_printf(MSG_ERROR, "Line %d: invalid "
"radius_acct_req_attr", line);
errors++;
} else if (bss->radius_acct_req_attr == NULL) {
bss->radius_acct_req_attr = attr;
} else {
a = bss->radius_acct_req_attr;
while (a->next)
a = a->next;
a->next = attr;
}
#endif /* CONFIG_NO_RADIUS */ #endif /* CONFIG_NO_RADIUS */
} else if (os_strcmp(buf, "auth_algs") == 0) { } else if (os_strcmp(buf, "auth_algs") == 0) {
bss->auth_algs = atoi(pos); bss->auth_algs = atoi(pos);

View file

@ -665,6 +665,36 @@ own_ip_addr=127.0.0.1
# to the bridge. # to the bridge.
#vlan_tagged_interface=eth0 #vlan_tagged_interface=eth0
# Arbitrary RADIUS attributes can be added into Access-Request and
# Accounting-Request packets by specifying the contents of the attributes with
# the following configuration parameters. There can be multiple of these to
# add multiple attributes. These parameters can also be used to override some
# of the attributes added automatically by hostapd.
# Format: <attr_id>[:<syntax:value>]
# attr_id: RADIUS attribute type (e.g., 26 = Vendor-Specific)
# syntax: s = string (UTF-8), d = integer, x = octet string
# value: attribute value in format indicated by the syntax
# If syntax and value parts are omitted, a null value (single 0x00 octet) is
# used.
#
# Additional Access-Request attributes
# radius_auth_req_attr=<attr_id>[:<syntax:value>]
# Examples:
# Operator-Name = "Operator"
#radius_auth_req_attr=126:s:Operator
# Service-Type = Framed (2)
#radius_auth_req_attr=6:d:2
# Connect-Info = "testing" (this overrides the automatically generated value)
#radius_auth_req_attr=77:s:testing
# Same Connect-Info value set as a hexdump
#radius_auth_req_attr=77:x:74657374696e67
#
# Additional Accounting-Request attributes
# radius_acct_req_attr=<attr_id>[:<syntax:value>]
# Examples:
# Operator-Name = "Operator"
#radius_acct_req_attr=126:s:Operator
##### RADIUS authentication server configuration ############################## ##### RADIUS authentication server configuration ##############################

View file

@ -40,6 +40,7 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
size_t len; size_t len;
int i; int i;
struct wpabuf *b; struct wpabuf *b;
struct hostapd_radius_attr *attr;
msg = radius_msg_new(RADIUS_CODE_ACCOUNTING_REQUEST, msg = radius_msg_new(RADIUS_CODE_ACCOUNTING_REQUEST,
radius_client_get_id(hapd->radius)); radius_client_get_id(hapd->radius));
@ -68,7 +69,9 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
goto fail; goto fail;
} }
if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_AUTHENTIC, if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr,
RADIUS_ATTR_ACCT_AUTHENTIC) &&
!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_AUTHENTIC,
hapd->conf->ieee802_1x ? hapd->conf->ieee802_1x ?
RADIUS_ACCT_AUTHENTIC_RADIUS : RADIUS_ACCT_AUTHENTIC_RADIUS :
RADIUS_ACCT_AUTHENTIC_LOCAL)) { RADIUS_ACCT_AUTHENTIC_LOCAL)) {
@ -92,7 +95,9 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
} }
} }
if (hapd->conf->own_ip_addr.af == AF_INET && if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr,
RADIUS_ATTR_NAS_IP_ADDRESS) &&
hapd->conf->own_ip_addr.af == AF_INET &&
!radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS,
(u8 *) &hapd->conf->own_ip_addr.u.v4, 4)) { (u8 *) &hapd->conf->own_ip_addr.u.v4, 4)) {
printf("Could not add NAS-IP-Address\n"); printf("Could not add NAS-IP-Address\n");
@ -100,7 +105,9 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
} }
#ifdef CONFIG_IPV6 #ifdef CONFIG_IPV6
if (hapd->conf->own_ip_addr.af == AF_INET6 && if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr,
RADIUS_ATTR_NAS_IPV6_ADDRESS) &&
hapd->conf->own_ip_addr.af == AF_INET6 &&
!radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS, !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
(u8 *) &hapd->conf->own_ip_addr.u.v6, 16)) { (u8 *) &hapd->conf->own_ip_addr.u.v6, 16)) {
printf("Could not add NAS-IPv6-Address\n"); printf("Could not add NAS-IPv6-Address\n");
@ -108,7 +115,9 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
} }
#endif /* CONFIG_IPV6 */ #endif /* CONFIG_IPV6 */
if (hapd->conf->nas_identifier && if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr,
RADIUS_ATTR_NAS_IDENTIFIER) &&
hapd->conf->nas_identifier &&
!radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER, !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
(u8 *) hapd->conf->nas_identifier, (u8 *) hapd->conf->nas_identifier,
os_strlen(hapd->conf->nas_identifier))) { os_strlen(hapd->conf->nas_identifier))) {
@ -116,7 +125,9 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
goto fail; goto fail;
} }
if (sta && if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr,
RADIUS_ATTR_NAS_PORT) &&
sta &&
!radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT, sta->aid)) { !radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT, sta->aid)) {
printf("Could not add NAS-Port\n"); printf("Could not add NAS-Port\n");
goto fail; goto fail;
@ -124,7 +135,9 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT ":%s", os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT ":%s",
MAC2STR(hapd->own_addr), hapd->conf->ssid.ssid); MAC2STR(hapd->own_addr), hapd->conf->ssid.ssid);
if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLED_STATION_ID, if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr,
RADIUS_ATTR_CALLED_STATION_ID) &&
!radius_msg_add_attr(msg, RADIUS_ATTR_CALLED_STATION_ID,
(u8 *) buf, os_strlen(buf))) { (u8 *) buf, os_strlen(buf))) {
printf("Could not add Called-Station-Id\n"); printf("Could not add Called-Station-Id\n");
goto fail; goto fail;
@ -139,7 +152,10 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
goto fail; goto fail;
} }
if (!radius_msg_add_attr_int32( if (!hostapd_config_get_radius_attr(
hapd->conf->radius_acct_req_attr,
RADIUS_ATTR_NAS_PORT_TYPE) &&
!radius_msg_add_attr_int32(
msg, RADIUS_ATTR_NAS_PORT_TYPE, msg, RADIUS_ATTR_NAS_PORT_TYPE,
RADIUS_NAS_PORT_TYPE_IEEE_802_11)) { RADIUS_NAS_PORT_TYPE_IEEE_802_11)) {
printf("Could not add NAS-Port-Type\n"); printf("Could not add NAS-Port-Type\n");
@ -150,7 +166,10 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
radius_sta_rate(hapd, sta) / 2, radius_sta_rate(hapd, sta) / 2,
(radius_sta_rate(hapd, sta) & 1) ? ".5" : "", (radius_sta_rate(hapd, sta) & 1) ? ".5" : "",
radius_mode_txt(hapd)); radius_mode_txt(hapd));
if (!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO, if (!hostapd_config_get_radius_attr(
hapd->conf->radius_acct_req_attr,
RADIUS_ATTR_CONNECT_INFO) &&
!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO,
(u8 *) buf, os_strlen(buf))) { (u8 *) buf, os_strlen(buf))) {
printf("Could not add Connect-Info\n"); printf("Could not add Connect-Info\n");
goto fail; goto fail;
@ -179,6 +198,17 @@ static struct radius_msg * accounting_msg(struct hostapd_data *hapd,
} }
} }
for (attr = hapd->conf->radius_acct_req_attr; attr; attr = attr->next)
{
if (!radius_msg_add_attr(msg, attr->type,
wpabuf_head(attr->val),
wpabuf_len(attr->val))) {
wpa_printf(MSG_ERROR, "Could not add RADIUS "
"attribute");
goto fail;
}
}
return msg; return msg;
fail: fail:

View file

@ -1,6 +1,6 @@
/* /*
* hostapd / Configuration helper functions * hostapd / Configuration helper functions
* Copyright (c) 2003-2009, Jouni Malinen <j@w1.fi> * Copyright (c) 2003-2012, Jouni Malinen <j@w1.fi>
* *
* This software may be distributed under the terms of the BSD license. * This software may be distributed under the terms of the BSD license.
* See README for more details. * See README for more details.
@ -336,6 +336,30 @@ static void hostapd_config_free_radius(struct hostapd_radius_server *servers,
} }
struct hostapd_radius_attr *
hostapd_config_get_radius_attr(struct hostapd_radius_attr *attr, u8 type)
{
for (; attr; attr = attr->next) {
if (attr->type == type)
return attr;
}
return NULL;
}
static void hostapd_config_free_radius_attr(struct hostapd_radius_attr *attr)
{
struct hostapd_radius_attr *prev;
while (attr) {
prev = attr;
attr = attr->next;
wpabuf_free(prev->val);
os_free(prev);
}
}
static void hostapd_config_free_eap_user(struct hostapd_eap_user *user) static void hostapd_config_free_eap_user(struct hostapd_eap_user *user)
{ {
os_free(user->identity); os_free(user->identity);
@ -392,6 +416,8 @@ static void hostapd_config_free_bss(struct hostapd_bss_config *conf)
conf->radius->num_auth_servers); conf->radius->num_auth_servers);
hostapd_config_free_radius(conf->radius->acct_servers, hostapd_config_free_radius(conf->radius->acct_servers,
conf->radius->num_acct_servers); conf->radius->num_acct_servers);
hostapd_config_free_radius_attr(conf->radius_auth_req_attr);
hostapd_config_free_radius_attr(conf->radius_acct_req_attr);
os_free(conf->rsn_preauth_interfaces); os_free(conf->rsn_preauth_interfaces);
os_free(conf->ctrl_interface); os_free(conf->ctrl_interface);
os_free(conf->ca_cert); os_free(conf->ca_cert);

View file

@ -116,6 +116,12 @@ struct hostapd_eap_user {
int ttls_auth; /* EAP_TTLS_AUTH_* bitfield */ int ttls_auth; /* EAP_TTLS_AUTH_* bitfield */
}; };
struct hostapd_radius_attr {
u8 type;
struct wpabuf *val;
struct hostapd_radius_attr *next;
};
#define NUM_TX_QUEUES 4 #define NUM_TX_QUEUES 4
@ -178,6 +184,8 @@ struct hostapd_bss_config {
struct hostapd_radius_servers *radius; struct hostapd_radius_servers *radius;
int acct_interim_interval; int acct_interim_interval;
int radius_request_cui; int radius_request_cui;
struct hostapd_radius_attr *radius_auth_req_attr;
struct hostapd_radius_attr *radius_acct_req_attr;
struct hostapd_ssid ssid; struct hostapd_ssid ssid;
@ -455,5 +463,7 @@ const char * hostapd_get_vlan_id_ifname(struct hostapd_vlan *vlan,
const struct hostapd_eap_user * const struct hostapd_eap_user *
hostapd_get_eap_user(const struct hostapd_bss_config *conf, const u8 *identity, hostapd_get_eap_user(const struct hostapd_bss_config *conf, const u8 *identity,
size_t identity_len, int phase2); size_t identity_len, int phase2);
struct hostapd_radius_attr *
hostapd_config_get_radius_attr(struct hostapd_radius_attr *attr, u8 type);
#endif /* HOSTAPD_CONFIG_H */ #endif /* HOSTAPD_CONFIG_H */

View file

@ -416,6 +416,7 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
struct radius_msg *msg; struct radius_msg *msg;
char buf[128]; char buf[128];
struct eapol_state_machine *sm = sta->eapol_sm; struct eapol_state_machine *sm = sta->eapol_sm;
struct hostapd_radius_attr *attr;
if (sm == NULL) if (sm == NULL)
return; return;
@ -442,7 +443,9 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
goto fail; goto fail;
} }
if (hapd->conf->own_ip_addr.af == AF_INET && if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr,
RADIUS_ATTR_NAS_IP_ADDRESS) &&
hapd->conf->own_ip_addr.af == AF_INET &&
!radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS,
(u8 *) &hapd->conf->own_ip_addr.u.v4, 4)) { (u8 *) &hapd->conf->own_ip_addr.u.v4, 4)) {
printf("Could not add NAS-IP-Address\n"); printf("Could not add NAS-IP-Address\n");
@ -450,7 +453,9 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
} }
#ifdef CONFIG_IPV6 #ifdef CONFIG_IPV6
if (hapd->conf->own_ip_addr.af == AF_INET6 && if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr,
RADIUS_ATTR_NAS_IPV6_ADDRESS) &&
hapd->conf->own_ip_addr.af == AF_INET6 &&
!radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS, !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
(u8 *) &hapd->conf->own_ip_addr.u.v6, 16)) { (u8 *) &hapd->conf->own_ip_addr.u.v6, 16)) {
printf("Could not add NAS-IPv6-Address\n"); printf("Could not add NAS-IPv6-Address\n");
@ -458,7 +463,9 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
} }
#endif /* CONFIG_IPV6 */ #endif /* CONFIG_IPV6 */
if (hapd->conf->nas_identifier && if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr,
RADIUS_ATTR_NAS_IDENTIFIER) &&
hapd->conf->nas_identifier &&
!radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER, !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
(u8 *) hapd->conf->nas_identifier, (u8 *) hapd->conf->nas_identifier,
os_strlen(hapd->conf->nas_identifier))) { os_strlen(hapd->conf->nas_identifier))) {
@ -466,7 +473,9 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
goto fail; goto fail;
} }
if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT, sta->aid)) { if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr,
RADIUS_ATTR_NAS_PORT) &&
!radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT, sta->aid)) {
printf("Could not add NAS-Port\n"); printf("Could not add NAS-Port\n");
goto fail; goto fail;
} }
@ -474,7 +483,9 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT ":%s", os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT ":%s",
MAC2STR(hapd->own_addr), hapd->conf->ssid.ssid); MAC2STR(hapd->own_addr), hapd->conf->ssid.ssid);
buf[sizeof(buf) - 1] = '\0'; buf[sizeof(buf) - 1] = '\0';
if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLED_STATION_ID, if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr,
RADIUS_ATTR_CALLED_STATION_ID) &&
!radius_msg_add_attr(msg, RADIUS_ATTR_CALLED_STATION_ID,
(u8 *) buf, os_strlen(buf))) { (u8 *) buf, os_strlen(buf))) {
printf("Could not add Called-Station-Id\n"); printf("Could not add Called-Station-Id\n");
goto fail; goto fail;
@ -492,12 +503,16 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
/* TODO: should probably check MTU from driver config; 2304 is max for /* TODO: should probably check MTU from driver config; 2304 is max for
* IEEE 802.11, but use 1400 to avoid problems with too large packets * IEEE 802.11, but use 1400 to avoid problems with too large packets
*/ */
if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_FRAMED_MTU, 1400)) { if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr,
RADIUS_ATTR_FRAMED_MTU) &&
!radius_msg_add_attr_int32(msg, RADIUS_ATTR_FRAMED_MTU, 1400)) {
printf("Could not add Framed-MTU\n"); printf("Could not add Framed-MTU\n");
goto fail; goto fail;
} }
if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT_TYPE, if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr,
RADIUS_ATTR_NAS_PORT_TYPE) &&
!radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT_TYPE,
RADIUS_NAS_PORT_TYPE_IEEE_802_11)) { RADIUS_NAS_PORT_TYPE_IEEE_802_11)) {
printf("Could not add NAS-Port-Type\n"); printf("Could not add NAS-Port-Type\n");
goto fail; goto fail;
@ -513,7 +528,9 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
radius_mode_txt(hapd)); radius_mode_txt(hapd));
buf[sizeof(buf) - 1] = '\0'; buf[sizeof(buf) - 1] = '\0';
} }
if (!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO, if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr,
RADIUS_ATTR_CONNECT_INFO) &&
!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO,
(u8 *) buf, os_strlen(buf))) { (u8 *) buf, os_strlen(buf))) {
printf("Could not add Connect-Info\n"); printf("Could not add Connect-Info\n");
goto fail; goto fail;
@ -560,6 +577,17 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd,
} }
} }
for (attr = hapd->conf->radius_auth_req_attr; attr; attr = attr->next)
{
if (!radius_msg_add_attr(msg, attr->type,
wpabuf_head(attr->val),
wpabuf_len(attr->val))) {
wpa_printf(MSG_ERROR, "Could not add RADIUS "
"attribute");
goto fail;
}
}
if (radius_client_send(hapd->radius, msg, RADIUS_AUTH, sta->addr) < 0) if (radius_client_send(hapd->radius, msg, RADIUS_AUTH, sta->addr) < 0)
goto fail; goto fail;