1189251Ssam/* 2189251Ssam * EAP peer method: EAP-SAKE (RFC 4763) 3189251Ssam * Copyright (c) 2006-2008, Jouni Malinen <j@w1.fi> 4189251Ssam * 5252726Srpaulo * This software may be distributed under the terms of the BSD license. 6252726Srpaulo * See README for more details. 7189251Ssam */ 8189251Ssam 9189251Ssam#include "includes.h" 10189251Ssam 11189251Ssam#include "common.h" 12252726Srpaulo#include "crypto/random.h" 13189251Ssam#include "eap_peer/eap_i.h" 14189251Ssam#include "eap_common/eap_sake_common.h" 15189251Ssam 16189251Ssamstruct eap_sake_data { 17189251Ssam enum { IDENTITY, CHALLENGE, CONFIRM, SUCCESS, FAILURE } state; 18189251Ssam u8 root_secret_a[EAP_SAKE_ROOT_SECRET_LEN]; 19189251Ssam u8 root_secret_b[EAP_SAKE_ROOT_SECRET_LEN]; 20189251Ssam u8 rand_s[EAP_SAKE_RAND_LEN]; 21189251Ssam u8 rand_p[EAP_SAKE_RAND_LEN]; 22189251Ssam struct { 23189251Ssam u8 auth[EAP_SAKE_TEK_AUTH_LEN]; 24189251Ssam u8 cipher[EAP_SAKE_TEK_CIPHER_LEN]; 25189251Ssam } tek; 26189251Ssam u8 msk[EAP_MSK_LEN]; 27189251Ssam u8 emsk[EAP_EMSK_LEN]; 28189251Ssam u8 session_id; 29189251Ssam int session_id_set; 30189251Ssam u8 *peerid; 31189251Ssam size_t peerid_len; 32189251Ssam u8 *serverid; 33189251Ssam size_t serverid_len; 34189251Ssam}; 35189251Ssam 36189251Ssam 37189251Ssamstatic const char * eap_sake_state_txt(int state) 38189251Ssam{ 39189251Ssam switch (state) { 40189251Ssam case IDENTITY: 41189251Ssam return "IDENTITY"; 42189251Ssam case CHALLENGE: 43189251Ssam return "CHALLENGE"; 44189251Ssam case CONFIRM: 45189251Ssam return "CONFIRM"; 46189251Ssam case SUCCESS: 47189251Ssam return "SUCCESS"; 48189251Ssam case FAILURE: 49189251Ssam return "FAILURE"; 50189251Ssam default: 51189251Ssam return "?"; 52189251Ssam } 53189251Ssam} 54189251Ssam 55189251Ssam 56189251Ssamstatic void eap_sake_state(struct eap_sake_data *data, int state) 57189251Ssam{ 58189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: %s -> %s", 59189251Ssam eap_sake_state_txt(data->state), 60189251Ssam eap_sake_state_txt(state)); 61189251Ssam data->state = state; 62189251Ssam} 63189251Ssam 64189251Ssam 65189251Ssamstatic void eap_sake_deinit(struct eap_sm *sm, void *priv); 66189251Ssam 67189251Ssam 68189251Ssamstatic void * eap_sake_init(struct eap_sm *sm) 69189251Ssam{ 70189251Ssam struct eap_sake_data *data; 71189251Ssam const u8 *identity, *password; 72189251Ssam size_t identity_len, password_len; 73189251Ssam 74189251Ssam password = eap_get_config_password(sm, &password_len); 75189251Ssam if (!password || password_len != 2 * EAP_SAKE_ROOT_SECRET_LEN) { 76189251Ssam wpa_printf(MSG_INFO, "EAP-SAKE: No key of correct length " 77189251Ssam "configured"); 78189251Ssam return NULL; 79189251Ssam } 80189251Ssam 81189251Ssam data = os_zalloc(sizeof(*data)); 82189251Ssam if (data == NULL) 83189251Ssam return NULL; 84189251Ssam data->state = IDENTITY; 85189251Ssam 86189251Ssam identity = eap_get_config_identity(sm, &identity_len); 87189251Ssam if (identity) { 88189251Ssam data->peerid = os_malloc(identity_len); 89189251Ssam if (data->peerid == NULL) { 90189251Ssam eap_sake_deinit(sm, data); 91189251Ssam return NULL; 92189251Ssam } 93189251Ssam os_memcpy(data->peerid, identity, identity_len); 94189251Ssam data->peerid_len = identity_len; 95189251Ssam } 96189251Ssam 97189251Ssam os_memcpy(data->root_secret_a, password, EAP_SAKE_ROOT_SECRET_LEN); 98189251Ssam os_memcpy(data->root_secret_b, 99189251Ssam password + EAP_SAKE_ROOT_SECRET_LEN, 100189251Ssam EAP_SAKE_ROOT_SECRET_LEN); 101189251Ssam 102189251Ssam return data; 103189251Ssam} 104189251Ssam 105189251Ssam 106189251Ssamstatic void eap_sake_deinit(struct eap_sm *sm, void *priv) 107189251Ssam{ 108189251Ssam struct eap_sake_data *data = priv; 109189251Ssam os_free(data->serverid); 110189251Ssam os_free(data->peerid); 111189251Ssam os_free(data); 112189251Ssam} 113189251Ssam 114189251Ssam 115189251Ssamstatic struct wpabuf * eap_sake_build_msg(struct eap_sake_data *data, 116189251Ssam int id, size_t length, u8 subtype) 117189251Ssam{ 118189251Ssam struct eap_sake_hdr *sake; 119189251Ssam struct wpabuf *msg; 120189251Ssam size_t plen; 121189251Ssam 122189251Ssam plen = length + sizeof(struct eap_sake_hdr); 123189251Ssam 124189251Ssam msg = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_SAKE, plen, 125189251Ssam EAP_CODE_RESPONSE, id); 126189251Ssam if (msg == NULL) { 127189251Ssam wpa_printf(MSG_ERROR, "EAP-SAKE: Failed to allocate memory " 128189251Ssam "request"); 129189251Ssam return NULL; 130189251Ssam } 131189251Ssam 132189251Ssam sake = wpabuf_put(msg, sizeof(*sake)); 133189251Ssam sake->version = EAP_SAKE_VERSION; 134189251Ssam sake->session_id = data->session_id; 135189251Ssam sake->subtype = subtype; 136189251Ssam 137189251Ssam return msg; 138189251Ssam} 139189251Ssam 140189251Ssam 141189251Ssamstatic struct wpabuf * eap_sake_process_identity(struct eap_sm *sm, 142189251Ssam struct eap_sake_data *data, 143189251Ssam struct eap_method_ret *ret, 144189251Ssam const struct wpabuf *reqData, 145189251Ssam const u8 *payload, 146189251Ssam size_t payload_len) 147189251Ssam{ 148189251Ssam struct eap_sake_parse_attr attr; 149189251Ssam struct wpabuf *resp; 150189251Ssam 151189251Ssam if (data->state != IDENTITY) { 152189251Ssam ret->ignore = TRUE; 153189251Ssam return NULL; 154189251Ssam } 155189251Ssam 156189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Received Request/Identity"); 157189251Ssam 158189251Ssam if (eap_sake_parse_attributes(payload, payload_len, &attr)) 159189251Ssam return NULL; 160189251Ssam 161189251Ssam if (!attr.perm_id_req && !attr.any_id_req) { 162189251Ssam wpa_printf(MSG_INFO, "EAP-SAKE: No AT_PERM_ID_REQ or " 163189251Ssam "AT_ANY_ID_REQ in Request/Identity"); 164189251Ssam return NULL; 165189251Ssam } 166189251Ssam 167189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Sending Response/Identity"); 168189251Ssam 169189251Ssam resp = eap_sake_build_msg(data, eap_get_id(reqData), 170189251Ssam 2 + data->peerid_len, 171189251Ssam EAP_SAKE_SUBTYPE_IDENTITY); 172189251Ssam if (resp == NULL) 173189251Ssam return NULL; 174189251Ssam 175189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: * AT_PEERID"); 176189251Ssam eap_sake_add_attr(resp, EAP_SAKE_AT_PEERID, 177189251Ssam data->peerid, data->peerid_len); 178189251Ssam 179189251Ssam eap_sake_state(data, CHALLENGE); 180189251Ssam 181189251Ssam return resp; 182189251Ssam} 183189251Ssam 184189251Ssam 185189251Ssamstatic struct wpabuf * eap_sake_process_challenge(struct eap_sm *sm, 186189251Ssam struct eap_sake_data *data, 187189251Ssam struct eap_method_ret *ret, 188189251Ssam const struct wpabuf *reqData, 189189251Ssam const u8 *payload, 190189251Ssam size_t payload_len) 191189251Ssam{ 192189251Ssam struct eap_sake_parse_attr attr; 193189251Ssam struct wpabuf *resp; 194189251Ssam u8 *rpos; 195189251Ssam size_t rlen; 196189251Ssam 197189251Ssam if (data->state != IDENTITY && data->state != CHALLENGE) { 198189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Request/Challenge received " 199189251Ssam "in unexpected state (%d)", data->state); 200189251Ssam ret->ignore = TRUE; 201189251Ssam return NULL; 202189251Ssam } 203189251Ssam if (data->state == IDENTITY) 204189251Ssam eap_sake_state(data, CHALLENGE); 205189251Ssam 206189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Received Request/Challenge"); 207189251Ssam 208189251Ssam if (eap_sake_parse_attributes(payload, payload_len, &attr)) 209189251Ssam return NULL; 210189251Ssam 211189251Ssam if (!attr.rand_s) { 212189251Ssam wpa_printf(MSG_INFO, "EAP-SAKE: Request/Challenge did not " 213189251Ssam "include AT_RAND_S"); 214189251Ssam return NULL; 215189251Ssam } 216189251Ssam 217189251Ssam os_memcpy(data->rand_s, attr.rand_s, EAP_SAKE_RAND_LEN); 218189251Ssam wpa_hexdump(MSG_MSGDUMP, "EAP-SAKE: RAND_S (server rand)", 219189251Ssam data->rand_s, EAP_SAKE_RAND_LEN); 220189251Ssam 221252726Srpaulo if (random_get_bytes(data->rand_p, EAP_SAKE_RAND_LEN)) { 222189251Ssam wpa_printf(MSG_ERROR, "EAP-SAKE: Failed to get random data"); 223189251Ssam return NULL; 224189251Ssam } 225189251Ssam wpa_hexdump(MSG_MSGDUMP, "EAP-SAKE: RAND_P (peer rand)", 226189251Ssam data->rand_p, EAP_SAKE_RAND_LEN); 227189251Ssam 228189251Ssam os_free(data->serverid); 229189251Ssam data->serverid = NULL; 230189251Ssam data->serverid_len = 0; 231189251Ssam if (attr.serverid) { 232189251Ssam wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-SAKE: SERVERID", 233189251Ssam attr.serverid, attr.serverid_len); 234189251Ssam data->serverid = os_malloc(attr.serverid_len); 235189251Ssam if (data->serverid == NULL) 236189251Ssam return NULL; 237189251Ssam os_memcpy(data->serverid, attr.serverid, attr.serverid_len); 238189251Ssam data->serverid_len = attr.serverid_len; 239189251Ssam } 240189251Ssam 241189251Ssam eap_sake_derive_keys(data->root_secret_a, data->root_secret_b, 242189251Ssam data->rand_s, data->rand_p, 243189251Ssam (u8 *) &data->tek, data->msk, data->emsk); 244189251Ssam 245189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Sending Response/Challenge"); 246189251Ssam 247189251Ssam rlen = 2 + EAP_SAKE_RAND_LEN + 2 + EAP_SAKE_MIC_LEN; 248189251Ssam if (data->peerid) 249189251Ssam rlen += 2 + data->peerid_len; 250189251Ssam resp = eap_sake_build_msg(data, eap_get_id(reqData), rlen, 251189251Ssam EAP_SAKE_SUBTYPE_CHALLENGE); 252189251Ssam if (resp == NULL) 253189251Ssam return NULL; 254189251Ssam 255189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: * AT_RAND_P"); 256189251Ssam eap_sake_add_attr(resp, EAP_SAKE_AT_RAND_P, 257189251Ssam data->rand_p, EAP_SAKE_RAND_LEN); 258189251Ssam 259189251Ssam if (data->peerid) { 260189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: * AT_PEERID"); 261189251Ssam eap_sake_add_attr(resp, EAP_SAKE_AT_PEERID, 262189251Ssam data->peerid, data->peerid_len); 263189251Ssam } 264189251Ssam 265189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: * AT_MIC_P"); 266189251Ssam wpabuf_put_u8(resp, EAP_SAKE_AT_MIC_P); 267189251Ssam wpabuf_put_u8(resp, 2 + EAP_SAKE_MIC_LEN); 268189251Ssam rpos = wpabuf_put(resp, EAP_SAKE_MIC_LEN); 269189251Ssam if (eap_sake_compute_mic(data->tek.auth, data->rand_s, data->rand_p, 270189251Ssam data->serverid, data->serverid_len, 271189251Ssam data->peerid, data->peerid_len, 1, 272189251Ssam wpabuf_head(resp), wpabuf_len(resp), rpos, 273189251Ssam rpos)) { 274189251Ssam wpa_printf(MSG_INFO, "EAP-SAKE: Failed to compute MIC"); 275189251Ssam wpabuf_free(resp); 276189251Ssam return NULL; 277189251Ssam } 278189251Ssam 279189251Ssam eap_sake_state(data, CONFIRM); 280189251Ssam 281189251Ssam return resp; 282189251Ssam} 283189251Ssam 284189251Ssam 285189251Ssamstatic struct wpabuf * eap_sake_process_confirm(struct eap_sm *sm, 286189251Ssam struct eap_sake_data *data, 287189251Ssam struct eap_method_ret *ret, 288189251Ssam const struct wpabuf *reqData, 289189251Ssam const u8 *payload, 290189251Ssam size_t payload_len) 291189251Ssam{ 292189251Ssam struct eap_sake_parse_attr attr; 293189251Ssam u8 mic_s[EAP_SAKE_MIC_LEN]; 294189251Ssam struct wpabuf *resp; 295189251Ssam u8 *rpos; 296189251Ssam 297189251Ssam if (data->state != CONFIRM) { 298189251Ssam ret->ignore = TRUE; 299189251Ssam return NULL; 300189251Ssam } 301189251Ssam 302189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Received Request/Confirm"); 303189251Ssam 304189251Ssam if (eap_sake_parse_attributes(payload, payload_len, &attr)) 305189251Ssam return NULL; 306189251Ssam 307189251Ssam if (!attr.mic_s) { 308189251Ssam wpa_printf(MSG_INFO, "EAP-SAKE: Request/Confirm did not " 309189251Ssam "include AT_MIC_S"); 310189251Ssam return NULL; 311189251Ssam } 312189251Ssam 313189251Ssam eap_sake_compute_mic(data->tek.auth, data->rand_s, data->rand_p, 314189251Ssam data->serverid, data->serverid_len, 315189251Ssam data->peerid, data->peerid_len, 0, 316189251Ssam wpabuf_head(reqData), wpabuf_len(reqData), 317189251Ssam attr.mic_s, mic_s); 318189251Ssam if (os_memcmp(attr.mic_s, mic_s, EAP_SAKE_MIC_LEN) != 0) { 319189251Ssam wpa_printf(MSG_INFO, "EAP-SAKE: Incorrect AT_MIC_S"); 320189251Ssam eap_sake_state(data, FAILURE); 321189251Ssam ret->methodState = METHOD_DONE; 322189251Ssam ret->decision = DECISION_FAIL; 323189251Ssam ret->allowNotifications = FALSE; 324189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Sending " 325189251Ssam "Response/Auth-Reject"); 326189251Ssam return eap_sake_build_msg(data, eap_get_id(reqData), 0, 327189251Ssam EAP_SAKE_SUBTYPE_AUTH_REJECT); 328189251Ssam } 329189251Ssam 330189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Sending Response/Confirm"); 331189251Ssam 332189251Ssam resp = eap_sake_build_msg(data, eap_get_id(reqData), 333189251Ssam 2 + EAP_SAKE_MIC_LEN, 334189251Ssam EAP_SAKE_SUBTYPE_CONFIRM); 335189251Ssam if (resp == NULL) 336189251Ssam return NULL; 337189251Ssam 338189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: * AT_MIC_P"); 339189251Ssam wpabuf_put_u8(resp, EAP_SAKE_AT_MIC_P); 340189251Ssam wpabuf_put_u8(resp, 2 + EAP_SAKE_MIC_LEN); 341189251Ssam rpos = wpabuf_put(resp, EAP_SAKE_MIC_LEN); 342189251Ssam if (eap_sake_compute_mic(data->tek.auth, data->rand_s, data->rand_p, 343189251Ssam data->serverid, data->serverid_len, 344189251Ssam data->peerid, data->peerid_len, 1, 345189251Ssam wpabuf_head(resp), wpabuf_len(resp), rpos, 346189251Ssam rpos)) { 347189251Ssam wpa_printf(MSG_INFO, "EAP-SAKE: Failed to compute MIC"); 348189251Ssam wpabuf_free(resp); 349189251Ssam return NULL; 350189251Ssam } 351189251Ssam 352189251Ssam eap_sake_state(data, SUCCESS); 353189251Ssam ret->methodState = METHOD_DONE; 354189251Ssam ret->decision = DECISION_UNCOND_SUCC; 355189251Ssam ret->allowNotifications = FALSE; 356189251Ssam 357189251Ssam return resp; 358189251Ssam} 359189251Ssam 360189251Ssam 361189251Ssamstatic struct wpabuf * eap_sake_process(struct eap_sm *sm, void *priv, 362189251Ssam struct eap_method_ret *ret, 363189251Ssam const struct wpabuf *reqData) 364189251Ssam{ 365189251Ssam struct eap_sake_data *data = priv; 366189251Ssam const struct eap_sake_hdr *req; 367189251Ssam struct wpabuf *resp; 368189251Ssam const u8 *pos, *end; 369189251Ssam size_t len; 370189251Ssam u8 subtype, session_id; 371189251Ssam 372189251Ssam pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_SAKE, reqData, &len); 373189251Ssam if (pos == NULL || len < sizeof(struct eap_sake_hdr)) { 374189251Ssam ret->ignore = TRUE; 375189251Ssam return NULL; 376189251Ssam } 377189251Ssam 378189251Ssam req = (const struct eap_sake_hdr *) pos; 379189251Ssam end = pos + len; 380189251Ssam subtype = req->subtype; 381189251Ssam session_id = req->session_id; 382189251Ssam pos = (const u8 *) (req + 1); 383189251Ssam 384189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Received frame: subtype %d " 385189251Ssam "session_id %d", subtype, session_id); 386189251Ssam wpa_hexdump(MSG_DEBUG, "EAP-SAKE: Received attributes", 387189251Ssam pos, end - pos); 388189251Ssam 389189251Ssam if (data->session_id_set && data->session_id != session_id) { 390189251Ssam wpa_printf(MSG_INFO, "EAP-SAKE: Session ID mismatch (%d,%d)", 391189251Ssam session_id, data->session_id); 392189251Ssam ret->ignore = TRUE; 393189251Ssam return NULL; 394189251Ssam } 395189251Ssam data->session_id = session_id; 396189251Ssam data->session_id_set = 1; 397189251Ssam 398189251Ssam ret->ignore = FALSE; 399189251Ssam ret->methodState = METHOD_MAY_CONT; 400189251Ssam ret->decision = DECISION_FAIL; 401189251Ssam ret->allowNotifications = TRUE; 402189251Ssam 403189251Ssam switch (subtype) { 404189251Ssam case EAP_SAKE_SUBTYPE_IDENTITY: 405189251Ssam resp = eap_sake_process_identity(sm, data, ret, reqData, 406189251Ssam pos, end - pos); 407189251Ssam break; 408189251Ssam case EAP_SAKE_SUBTYPE_CHALLENGE: 409189251Ssam resp = eap_sake_process_challenge(sm, data, ret, reqData, 410189251Ssam pos, end - pos); 411189251Ssam break; 412189251Ssam case EAP_SAKE_SUBTYPE_CONFIRM: 413189251Ssam resp = eap_sake_process_confirm(sm, data, ret, reqData, 414189251Ssam pos, end - pos); 415189251Ssam break; 416189251Ssam default: 417189251Ssam wpa_printf(MSG_DEBUG, "EAP-SAKE: Ignoring message with " 418189251Ssam "unknown subtype %d", subtype); 419189251Ssam ret->ignore = TRUE; 420189251Ssam return NULL; 421189251Ssam } 422189251Ssam 423189251Ssam if (ret->methodState == METHOD_DONE) 424189251Ssam ret->allowNotifications = FALSE; 425189251Ssam 426189251Ssam return resp; 427189251Ssam} 428189251Ssam 429189251Ssam 430189251Ssamstatic Boolean eap_sake_isKeyAvailable(struct eap_sm *sm, void *priv) 431189251Ssam{ 432189251Ssam struct eap_sake_data *data = priv; 433189251Ssam return data->state == SUCCESS; 434189251Ssam} 435189251Ssam 436189251Ssam 437189251Ssamstatic u8 * eap_sake_getKey(struct eap_sm *sm, void *priv, size_t *len) 438189251Ssam{ 439189251Ssam struct eap_sake_data *data = priv; 440189251Ssam u8 *key; 441189251Ssam 442189251Ssam if (data->state != SUCCESS) 443189251Ssam return NULL; 444189251Ssam 445189251Ssam key = os_malloc(EAP_MSK_LEN); 446189251Ssam if (key == NULL) 447189251Ssam return NULL; 448189251Ssam os_memcpy(key, data->msk, EAP_MSK_LEN); 449189251Ssam *len = EAP_MSK_LEN; 450189251Ssam 451189251Ssam return key; 452189251Ssam} 453189251Ssam 454189251Ssam 455189251Ssamstatic u8 * eap_sake_get_emsk(struct eap_sm *sm, void *priv, size_t *len) 456189251Ssam{ 457189251Ssam struct eap_sake_data *data = priv; 458189251Ssam u8 *key; 459189251Ssam 460189251Ssam if (data->state != SUCCESS) 461189251Ssam return NULL; 462189251Ssam 463189251Ssam key = os_malloc(EAP_EMSK_LEN); 464189251Ssam if (key == NULL) 465189251Ssam return NULL; 466189251Ssam os_memcpy(key, data->emsk, EAP_EMSK_LEN); 467189251Ssam *len = EAP_EMSK_LEN; 468189251Ssam 469189251Ssam return key; 470189251Ssam} 471189251Ssam 472189251Ssam 473189251Ssamint eap_peer_sake_register(void) 474189251Ssam{ 475189251Ssam struct eap_method *eap; 476189251Ssam int ret; 477189251Ssam 478189251Ssam eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, 479189251Ssam EAP_VENDOR_IETF, EAP_TYPE_SAKE, "SAKE"); 480189251Ssam if (eap == NULL) 481189251Ssam return -1; 482189251Ssam 483189251Ssam eap->init = eap_sake_init; 484189251Ssam eap->deinit = eap_sake_deinit; 485189251Ssam eap->process = eap_sake_process; 486189251Ssam eap->isKeyAvailable = eap_sake_isKeyAvailable; 487189251Ssam eap->getKey = eap_sake_getKey; 488189251Ssam eap->get_emsk = eap_sake_get_emsk; 489189251Ssam 490189251Ssam ret = eap_peer_method_register(eap); 491189251Ssam if (ret) 492189251Ssam eap_peer_method_free(eap); 493189251Ssam return ret; 494189251Ssam} 495