# HG changeset patch # User Sebastien Decugis # Date 1245724365 -32400 # Node ID fc0d723c1f8b1dc4d746121fc697868bd62246fc # Parent 8155408c6dc5e143b99bf1575d3e3d4c8ff04a08 Added conversion of Diameter answers to RADIUS diff -r 8155408c6dc5 -r fc0d723c1f8b extensions/radius_gw/radius_gw.h --- a/extensions/radius_gw/radius_gw.h Tue Jun 16 15:38:18 2009 +0900 +++ b/extensions/radius_gw/radius_gw.h Tue Jun 23 11:32:45 2009 +0900 @@ -111,7 +111,7 @@ int rgw_clients_search(struct sockaddr * ip_port, struct rgw_client ** ref); int rgw_clients_check_dup(struct rgw_radius_msg_meta **msg, struct rgw_client *cli); int rgw_clients_check_origin(struct rgw_radius_msg_meta *msg, struct rgw_client *cli); -int rgw_clients_get_origin(struct rgw_client *cli, char * oh, size_t oh_len, char * or, size_t or_len, char **fqdn, char **realm, int strict); +int rgw_clients_get_origin(struct rgw_client *cli, char **fqdn, char **realm); int rgw_client_finish_send(struct radius_msg ** msg, struct rgw_radius_msg_meta * req, struct rgw_client * cli); void rgw_clients_dispose(struct rgw_client ** ref); void rgw_clients_dump(void); diff -r 8155408c6dc5 -r fc0d723c1f8b extensions/radius_gw/rg_common.h --- a/extensions/radius_gw/rg_common.h Tue Jun 16 15:38:18 2009 +0900 +++ b/extensions/radius_gw/rg_common.h Tue Jun 23 11:32:45 2009 +0900 @@ -128,7 +128,83 @@ RADIUS_ATTR_FRAMED_IPV6_PREFIX = 97, RADIUS_ATTR_LOGIN_IPV6_HOST = 98, RADIUS_ATTR_FRAMED_IPV6_ROUTE = 99, - RADIUS_ATTR_FRAMED_IPV6_POOL = 100 + RADIUS_ATTR_FRAMED_IPV6_POOL = 100, + RADIUS_ATTR_ERROR_CAUSE = 101, + RADIUS_ATTR_EAP_KEY_NAME = 102 +}; + +enum { DIAM_ATTR_USER_NAME = 1, + DIAM_ATTR_USER_PASSWORD = 2, + DIAM_ATTR_SERVICE_TYPE = 6, + DIAM_ATTR_FRAMED_PROTOCOL = 7, + DIAM_ATTR_FRAMED_IP_ADDRESS = 8, + DIAM_ATTR_FRAMED_IP_NETMASK = 9, + DIAM_ATTR_FRAMED_ROUTING = 10, + DIAM_ATTR_FILTER_ID = 11, + DIAM_ATTR_FRAMED_MTU = 12, + DIAM_ATTR_FRAMED_COMPRESSION = 13, + DIAM_ATTR_LOGIN_IP_HOST = 14, + DIAM_ATTR_LOGIN_SERVICE = 15, + DIAM_ATTR_LOGIN_TCP_PORT = 16, + DIAM_ATTR_REPLY_MESSAGE = 18, + DIAM_ATTR_CALLBACK_NUMBER = 19, + DIAM_ATTR_CALLBACK_ID = 20, + DIAM_ATTR_FRAMED_ROUTE = 22, + DIAM_ATTR_FRAMED_IPX_NETWORK = 23, + DIAM_ATTR_STATE = 24, + DIAM_ATTR_CLASS = 25, + DIAM_ATTR_IDLE_TIMEOUT = 28, + DIAM_ATTR_LOGIN_LAT_SERVICE = 34, + DIAM_ATTR_LOGIN_LAT_NODE = 35, + DIAM_ATTR_LOGIN_LAT_GROUP = 36, + DIAM_ATTR_FRAMED_APPLETALK_LINK = 37, + DIAM_ATTR_FRAMED_APPLETALK_NETWORK = 38, + DIAM_ATTR_FRAMED_APPLETALK_ZONE = 39, + DIAM_ATTR_PORT_LIMIT = 62, + DIAM_ATTR_LOGIN_LAT_PORT = 63, + DIAM_ATTR_TUNNEL_TYPE = 64, + DIAM_ATTR_TUNNEL_MEDIUM_TYPE = 65, + DIAM_ATTR_TUNNEL_CLIENT_ENDPOINT = 66, + DIAM_ATTR_TUNNEL_SERVER_ENDPOINT = 67, + DIAM_ATTR_TUNNEL_PASSWORD = 69, + DIAM_ATTR_ARAP_FEATURES = 71, + DIAM_ATTR_ARAP_ZONE_ACCESS = 72, + DIAM_ATTR_ARAP_SECURITY = 73, + DIAM_ATTR_ARAP_SECURITY_DATA = 74, + DIAM_ATTR_PASSWORD_RETRY = 75, + DIAM_ATTR_PROMPT = 76, + DIAM_ATTR_CONFIGURATION_TOKEN = 78, + DIAM_ATTR_TUNNEL_PRIVATE_GROUP_ID = 81, + DIAM_ATTR_TUNNEL_ASSIGNEMENT_ID = 82, + DIAM_ATTR_TUNNEL_PREFERENCE = 83, + DIAM_ATTR_ARAP_CHALLENGE_RESPONSE = 84, + DIAM_ATTR_ACCT_INTERIM_INTERVAL = 85, + DIAM_ATTR_FRAMED_POOL = 88, + DIAM_ATTR_TUNNEL_CLIENT_AUTH_ID = 90, + DIAM_ATTR_TUNNEL_SERVER_AUTH_ID = 91, + DIAM_ATTR_FRAMED_INTERFACE_ID = 96, + DIAM_ATTR_FRAMED_IPV6_PREFIX = 97, + DIAM_ATTR_LOGIN_IPV6_HOST = 98, + DIAM_ATTR_FRAMED_IPV6_ROUTE = 99, + DIAM_ATTR_FRAMED_IPV6_POOL = 100, + DIAM_ATTR_EAP_KEY_NAME = 102, + DIAM_ATTR_AUTH_APPLICATION_ID = 258, + DIAM_ATTR_MULTI_ROUND_TIMEOUT = 272, + DIAM_ATTR_AUTH_REQUEST_TYPE = 274, + DIAM_ATTR_AUTH_GRACE_PERIOD = 276, + DIAM_ATTR_AUTH_SESSION_STATE = 277, + DIAM_ATTR_ORIGIN_STATE_ID = 278, + DIAM_ATTR_FAILED_AVP = 279, + DIAM_ATTR_ERROR_MESSAGE = 281, + DIAM_ATTR_ERROR_REPORTING_HOST = 294, + DIAM_ATTR_NAS_FILTER_RULE = 400, + DIAM_ATTR_TUNNELING = 401, + DIAM_ATTR_QOS_FILTER_RULE = 407, + DIAM_ATTR_ORIGIN_AAA_PROTOCOL = 408, + DIAM_ATTR_EAP_PAYLOAD = 462, + DIAM_ATTR_EAP_REISSUED_PAYLOAD = 463, + DIAM_ATTR_EAP_MASTER_SESSION_KEY = 464, + DIAM_ATTR_ACCOUNTING_EAP_AUTH_METHOD = 465 }; diff -r 8155408c6dc5 -r fc0d723c1f8b extensions/radius_gw/rgw_clients.c --- a/extensions/radius_gw/rgw_clients.c Tue Jun 16 15:38:18 2009 +0900 +++ b/extensions/radius_gw/rgw_clients.c Tue Jun 23 11:32:45 2009 +0900 @@ -306,6 +306,7 @@ /* Check that the NAS-IP-Adress or NAS-Identifier is coherent with the IP the packet was received from */ /* Also update the client list of aliases if needed */ +/* NOTE: This function will require changes to allow RADIUS Proxy on the path... */ int rgw_clients_check_origin(struct rgw_radius_msg_meta *msg, struct rgw_client *cli) { int idx; @@ -462,27 +463,11 @@ return 0; } -int rgw_clients_get_origin(struct rgw_client *cli, char * oh, size_t oh_len, char * or, size_t or_len, char **fqdn, char **realm, int strict) +int rgw_clients_get_origin(struct rgw_client *cli, char **fqdn, char **realm) { - TRACE_ENTRY("%p %p %g %p %g %p %p", cli, oh, oh_len, or, or_len, fqdn, realm); + TRACE_ENTRY("%p %p %p", cli, fqdn, realm); CHECK_PARAMS(cli && fqdn && realm); - if (oh && oh_len) { - if (strncasecmp(oh, cli->fqdn, oh_len)) { - TRACE_DEBUG(INFO, "Received unexpected '%.*s' Origin-Host in RADIUS State attribute, replacing with '%s'", oh_len, oh, cli->fqdn); - if (strict) - return EINVAL; - } - } - - if (or && or_len) { - if (strncasecmp(or, cli->realm, or_len)) { - TRACE_DEBUG(INFO, "Received unexpected '%.*s' Origin-Realm in RADIUS State attribute, replacing with '%s'", or_len, or, cli->realm); - if (strict) - return EINVAL; - } - } - *fqdn = cli->fqdn; *realm= cli->realm; return 0; diff -r 8155408c6dc5 -r fc0d723c1f8b extensions/radius_gw/rgw_extensions.c --- a/extensions/radius_gw/rgw_extensions.c Tue Jun 16 15:38:18 2009 +0900 +++ b/extensions/radius_gw/rgw_extensions.c Tue Jun 23 11:32:45 2009 +0900 @@ -443,7 +443,7 @@ if (ret > 0) { /* Critical error, log and exit */ - log_error("An error occurred while handling a DIAMETER answer to a converted RADIUS request, turn on DEBUG for details: %s\n", strerror(ret)); + log_error("(radius_gw) An error occurred while handling a DIAMETER answer to a converted RADIUS request, turn on DEBUG for details: %s\n", strerror(ret)); return ret; } diff -r 8155408c6dc5 -r fc0d723c1f8b extensions/radius_gw/rgw_msg.c --- a/extensions/radius_gw/rgw_msg.c Tue Jun 16 15:38:18 2009 +0900 +++ b/extensions/radius_gw/rgw_msg.c Tue Jun 23 11:32:45 2009 +0900 @@ -140,6 +140,8 @@ } static dict_object_t * rm_sess_id; +static dict_object_t * rm_dest_host; +static dict_object_t * rm_dest_realm; static dict_object_t * rm_orig_host; static dict_object_t * rm_orig_realm; @@ -149,25 +151,26 @@ CHECK_FCT( dict_search(DICT_AVP, AVP_BY_NAME, "Session-Id", &rm_sess_id, ENOENT) ); CHECK_FCT( dict_search(DICT_AVP, AVP_BY_NAME, "Origin-Host", &rm_orig_host, ENOENT) ); CHECK_FCT( dict_search(DICT_AVP, AVP_BY_NAME, "Origin-Realm", &rm_orig_realm, ENOENT) ); + CHECK_FCT( dict_search(DICT_AVP, AVP_BY_NAME, "Destination-Host", &rm_dest_host, ENOENT) ); + CHECK_FCT( dict_search(DICT_AVP, AVP_BY_NAME, "Destination-Realm", &rm_dest_realm, ENOENT) ); return 0; } /* Create a msg with origin-host & realm, and session-id, and a session object from a RADIUS request message */ int rgw_msg_create_base(struct rgw_radius_msg_meta * msg, struct rgw_client * cli, sess_id_t ** session, msg_t ** diam) { - int idx; + int idx, i; const char * prefix = "Diameter/"; size_t pref_len; - char * oh = NULL; - size_t oh_len = 0; - char * or = NULL; - size_t or_len = 0; + char * dh = NULL; + size_t dh_len = 0; + char * dr = NULL; + size_t dr_len = 0; char * si = NULL; size_t si_len = 0; char * un = NULL; size_t un_len = 0; - char * fqdn; char * realm; char * sess_str = NULL; @@ -180,7 +183,8 @@ pref_len = strlen(prefix); - /* Is there a State attribute with prefix "Diameter/" in the message? (in that case: Diameter/Origin-Host/Origin-Realm/Session-Id) */ + /* Is there a State attribute with prefix "Diameter/" in the message? (in that case: Diameter/Destination-Host/Destination-Realm/Session-Id) */ + /* NOTE: RFC4005 says "Origin-Host" here, but it's not coherent with the rules for answers. Destination-Host makes more sense */ /* Is there a Class attribute with prefix "Diameter/" in the message? (in that case: Diameter/Session-Id) */ for (idx = 0; idx < msg->radius.attr_used; idx++) { struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(msg->radius.buf + msg->radius.attr_pos[idx]); @@ -205,22 +209,26 @@ /* Now parse the value and check its content is valid. Unfortunately we cannot use strchr here since strings are not \0-terminated */ i = start = pref_len; - oh = attr_val + i; + dh = attr_val + i; for (; (i < attr_len - 2) && (attr_val[i] != '/'); i++) /* loop */; if ( i >= attr_len - 2 ) continue; /* the attribute format is not good */ - oh_len = i - 1 - start; + dh_len = i - 1 - start; start = ++i; - or = attr_val + i; + dr = attr_val + i; for (; (i < attr_len - 1) && (attr_val[i] != '/'); i++) /* loop */; if ( i >= attr_len - 1 ) continue; /* the attribute format is not good */ - or_len = i - 1 - start; + dr_len = i - 1 - start; i++; si = attr_val + i; si_len = attr_len - i; - TRACE_DEBUG(ANNOYING, "Attribute parsed successfully: OH:'%.*s' OR:'%.*s' SI:'%.*s'.", oh_len, oh, or_len, or, si_len, si); + TRACE_DEBUG(ANNOYING, "Attribute parsed successfully: DH:'%.*s' DR:'%.*s' SI:'%.*s'.", dh_len, dh, dr_len, dr, si_len, si); + /* Remove from the message */ + for (i = idx + 1; i < msg->radius.attr_used; i++) + msg->radius.attr_pos[i - 1] = msg->radius.attr_pos[i]; + msg->radius.attr_used -= 1; break; } @@ -230,12 +238,17 @@ si = attr_val + pref_len; si_len = attr_len - pref_len; TRACE_DEBUG(ANNOYING, "Found Class attribute with '%s' prefix (attr #%d), SI:'%.*s'.", prefix, idx, si_len, si); + /* Remove from the message */ + for (i = idx + 1; i < msg->radius.attr_used; i++) + msg->radius.attr_pos[i - 1] = msg->radius.attr_pos[i]; + msg->radius.attr_used -= 1; break; } + } /* Get information on this peer */ - CHECK_FCT( rgw_clients_get_origin(cli, oh, oh_len, or, or_len, &fqdn, &realm, 0) ); + CHECK_FCT( rgw_clients_get_origin(cli, &fqdn, &realm) ); /* Create the session object */ if (si_len) { @@ -270,6 +283,45 @@ TRACE_DEBUG(FULL, "No session has been created for this message"); } + /* Add the Destination-Realm as next AVP */ + CHECK_FCT( msg_avp_new ( rm_dest_realm, 0, &avp ) ); + memset(&avp_val, 0, sizeof(avp_val)); + if (dr) { + avp_val.os.data = (unsigned char *)dr; + avp_val.os.len = dr_len; + } else { + int i = 0; + if (un) { + /* Is there an '@' in the user name? We don't care for decorated NAI here */ + for (i = un_len - 2; i > 0; i--) { + if (un[i] == '@') { + i++; + break; + } + } + } + if (i == 0) { + /* Not found in the User-Name => local domain (of this gateway) */ + avp_val.os.data = g_pconf->diameter_realm; + avp_val.os.len = strlen(g_pconf->diameter_realm); + } else { + avp_val.os.data = un + i; + avp_val.os.len = un_len - i; + } + } + CHECK_FCT( msg_avp_setvalue ( avp, &avp_val ) ); + CHECK_FCT( msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); + + /* Add the Destination-Host as next AVP */ + if (dh) { + CHECK_FCT( msg_avp_new ( rm_dest_host, 0, &avp ) ); + memset(&avp_val, 0, sizeof(avp_val)); + avp_val.os.data = (unsigned char *)dh; + avp_val.os.len = dh_len; + CHECK_FCT( msg_avp_setvalue ( avp, &avp_val ) ); + CHECK_FCT( msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); + } + /* Add the Origin-Host as next AVP */ CHECK_FCT( msg_avp_new ( rm_orig_host, 0, &avp ) ); memset(&avp_val, 0, sizeof(avp_val)); diff -r 8155408c6dc5 -r fc0d723c1f8b extensions/radius_gw/rgw_work.c --- a/extensions/radius_gw/rgw_work.c Tue Jun 16 15:38:18 2009 +0900 +++ b/extensions/radius_gw/rgw_work.c Tue Jun 23 11:32:45 2009 +0900 @@ -70,14 +70,15 @@ TRACE_ENTRY("%p %p", pa, ans); CHECK_PARAMS_DO( pa && ans, return ); - TRACE_DEBUG(INFO, "Handling Diameter answer: Not implemented yet..."); - /* Create an empty RADIUS answer message */ - CHECK_MALLOC_DO( rad_ans = radius_msg_new(RADIUS_CODE_ACCESS_REJECT, pa->rad->radius.hdr->identifier), goto out ); + CHECK_MALLOC_DO( rad_ans = radius_msg_new(0, pa->rad->radius.hdr->identifier), goto out ); /* Pass the Diameter answer to the same extensions as the request */ CHECK_FCT_DO( rgw_extensions_loop_ans(pa->rad, pa->sess, ans, &rad_ans, pa->cli), goto out ); + /* Now check what AVPs remain in the diameter answer. If AVPs with the 'M' flag are here, we have a problem... */ + ASSERT(0); + /* Now try and send the RADIUS answer */ if (rad_ans) { CHECK_FCT_DO( rgw_client_finish_send(&rad_ans, pa->rad, pa->cli), goto out); diff -r 8155408c6dc5 -r fc0d723c1f8b extensions/radius_gw/sub_auth.c --- a/extensions/radius_gw/sub_auth.c Tue Jun 16 15:38:18 2009 +0900 +++ b/extensions/radius_gw/sub_auth.c Tue Jun 23 11:32:45 2009 +0900 @@ -55,6 +55,7 @@ struct rga_conf_state { char * conffile; + sess_reg_t * sess_hdl; void * dl_handle; int (*rgw_clients_getkey)(void * cli, unsigned char **key, size_t *key_len); }; @@ -68,6 +69,8 @@ CHECK_MALLOC_DO( cs = malloc(sizeof(struct rga_conf_state)), return NULL ); memset(cs, 0, sizeof(struct rga_conf_state)); + CHECK_FCT_DO( sess_regext( &cs->sess_hdl ), { free(cs); return NULL; } ); + cs->conffile = conffile; if (conffile) { @@ -85,7 +88,8 @@ static void auth_conf_free(struct rga_conf_state * cs) { TRACE_ENTRY("%p", cs); - CHECK_PARAMS_DO( cs, ); + CHECK_PARAMS_DO( cs, return ); + CHECK_FCT_DO( sess_deregext( cs->sess_hdl ), ); rg_pointers_fini(&cs->dl_handle); free(cs); return; @@ -107,6 +111,14 @@ TRACE_ENTRY("%p %p %p %p %p %p", cs, session, rad_req, rad_ans, diam_fw, cli); CHECK_PARAMS(rad_req && (rad_req->hdr->code == RADIUS_CODE_ACCESS_REQUEST) && rad_ans && diam_fw && *diam_fw); + /* We store the identifier in the session, if any */ + if (session) { + unsigned char * req_auth; + CHECK_MALLOC(req_auth = malloc(16)); + memcpy(req_auth, &rad_req->hdr->authenticator[0], 16); + CHECK_FCT( sess_data_reg(session, cs->sess_hdl, req_auth, free) ); + } + /* Guidelines: http://tools.ietf.org/html/rfc4005#section-9.1 @@ -348,32 +360,10 @@ /* - The Destination-Realm AVP is created from the information found in the RADIUS User-Name attribute. + -> done in rgw_msg_create_base */ case RADIUS_ATTR_USER_NAME: CONV_STR( "User-Name" ); - { - /* In addition, extract the destination-realm, if any */ - /* We suppose the format is anything@dest-realm */ - /* We don't care about decorated NAI here */ - int i; - for (i = value.os.len - 2; i > 0; i--) { - if (value.os.data[i] == '@') { - break; - } - } - CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Destination-Realm", &avp_dict, ENOENT)); - CHECK_FCT( msg_avp_new ( avp_dict, 0, &avp ) ); - if (i == 0) { - /* Not found in the User-Name => local domain */ - value.os.data = g_pconf->diameter_realm; - value.os.len = strlen(g_pconf->diameter_realm); - } else { - value.os.data += i + 1; - value.os.len -= i + 1; - } - CHECK_FCT( msg_avp_setvalue ( avp, &value ) ); - CHECK_FCT( msg_avp_add ( *diam_fw, MSG_BRW_LAST_CHILD, avp) ); - } break; /* @@ -821,10 +811,765 @@ static int auth_diam_ans(struct rga_conf_state * cs, sess_id_t * session, msg_t ** diam_ans, struct radius_msg ** rad_fw, void * cli ) { + msg_data_t *mdata; + msg_avp_t *avp, *next, *avp_x, *avp_y, *asid, *aoh; + msg_avp_data_t *adata, *sid, *oh; + dict_object_t * avp_dict; + char buf[254]; /* to store some attributes values (with final '\0') */ + int ta_set = 0; + uint8_t tuntag = 0; + unsigned char * req_auth = NULL; + TRACE_ENTRY("%p %p %p %p %p", cs, session, diam_ans, rad_fw, cli); - CHECK_PARAMS(cs); + CHECK_PARAMS(cs && session && diam_ans && *diam_ans && rad_fw && *rad_fw); + + if (session) { + int ret = sess_data_dereg( session, cs->sess_hdl, (void *)&req_auth ); + if (ret == ENOENT) { + TRACE_DEBUG(FULL, "No data saved in the session"); + } else { + CHECK_FCT(ret); /* Return if another error occurred */ + } + } + + CHECK_FCT( msg_data( *diam_ans, &mdata ) ); + + /* + - If the Diameter Command-Code is set to AA-Answer and the + Result-Code AVP is set to DIAMETER_MULTI_ROUND_AUTH, the + gateway must send a RADIUS Access-Challenge. This must have + the Origin-Host, Origin-Realm, and Diameter Session-Id AVPs + encapsulated in the RADIUS State attribute, with the prefix + "Diameter/", concatenated in the above order separated with "/" + characters, in UTF-8 [UTF-8]. This is necessary to ensure that + the Translation Agent receiving the subsequent RADIUS Access- + Request will have access to the Session Identifier and be able + to set the Destination-Host to the correct value. + -> done here bellow + + - If the Command-Code is set to AA-Answer, the Diameter Session- + Id AVP is saved in a new RADIUS Class attribute whose format + consists of the string "Diameter/" followed by the Diameter + Session Identifier. This will ensure that the subsequent + Accounting messages, which could be received by any Translation + Agent, would have access to the original Diameter Session + Identifier. + -> done here but only for Access-Accept messages (Result-Code = success) + */ + + /* _trunc_ = 0 => error; _trunc_ = 1 => truncate; _trunc = 2 => create several attributes */ +#define CONV2_STR( _attr_, _data_, _len_, _trunc_) { \ + size_t __l = (size_t)(_len_); \ + size_t __off = 0; \ + if ((_trunc_) == 0) { \ + CHECK_PARAMS( __l <= 253 ); \ + } \ + if ((__l > 253) && (_trunc_ == 1)) { \ + TRACE_DEBUG(FULL, "Attribute truncated!"); \ + __l = 253; \ + } \ + do { \ + size_t __w = (__l > 253) ? 253 : __l; \ + CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (_data_) + __off, __w)); \ + __off += __w; \ + __l -= __w; \ + } while (__l); \ +} + +#define CONV2_32B( _attr_, _data_) { \ + uint32_t __v = htonl((uint32_t)(_data_)); \ + CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (uint8_t *)&__v, sizeof(__v))); \ +} + +#define CONV2_64B( _attr_, _data_) { /* Some systems do not provide htonll */ \ + uint64_t __v = ((uint64_t)htonl((uint32_t)(((uint64_t)(_data_)) >> 32))) << 32; \ + __v |= htonl((uint32_t)(_data_)); \ + CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (uint8_t *)&__v, sizeof(__v))); \ +} + /* Search the different AVPs we handle here */ + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Session-Id", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &asid) ); + CHECK_FCT( msg_avp_data ( asid, &sid ) ); + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Origin-Host", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &aoh) ); + CHECK_FCT( msg_avp_data ( aoh, &oh ) ); + + /* Check the Diameter error code */ + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Result-Code", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &avp) ); + CHECK_FCT( msg_avp_data ( avp, &adata ) ); + switch (adata->avp_data->u32) { + case 1001: /* DIAMETER_MULTI_ROUND_AUTH */ + (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_CHALLENGE; + break; + case 2001: /* DIAMETER_SUCCESS */ + case 2002: /* DIAMETER_LIMITED_SUCCESS */ + (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_ACCEPT; + break; + + default: + (*rad_fw)->hdr->code = RADIUS_CODE_ACCESS_REJECT; + log_error("Received Diameter answer with error code '%d' from server '%.*s', session %.*s, sending Access-Reject\n", + adata->avp_data->u32, + oh->avp_data->os.len, oh->avp_data->os.data, + sid->avp_data->os.len, sid->avp_data->os.data); + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Error-Message", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &avp) ); + if (avp) { + CHECK_FCT( msg_avp_data ( avp, &adata ) ); + log_error(" Error-Message content: '%.*s'\n", + adata->avp_data->os.len, adata->avp_data->os.data); + } + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Error-Reporting-Host", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &avp) ); + if (avp) { + CHECK_FCT( msg_avp_data ( avp, &adata ) ); + log_error(" Error-Reporting-Host: '%.*s'\n", + adata->avp_data->os.len, adata->avp_data->os.data); + } + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Failed-AVP", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &avp) ); + if (avp) { + log_error(" Failed-AVP was present in the message\n"); + /* Dump its content ? */ + } + return 0; + } + /* Remove this Result-Code avp */ + CHECK_FCT( msg_free( avp, 1 ) ); + + /* Process creation of the State or Class attribute with session information */ + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Origin-Realm", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &avp) ); + CHECK_FCT( msg_avp_data ( avp, &adata ) ); + + /* Now, save the session-id and eventually server info in a STATE or CLASS attribute */ + if ((*rad_fw)->hdr->code == RADIUS_CODE_ACCESS_CHALLENGE) { + if (sizeof(buf) < snprintf(buf, sizeof(buf), "Diameter/%.*s/%.*s/%.*s", + oh->avp_data->os.len, oh->avp_data->os.data, + adata->avp_data->os.len, adata->avp_data->os.data, + sid->avp_data->os.len, sid->avp_data->os.data)) { + TRACE_DEBUG(INFO, "Data truncated in State attribute: %s", buf); + } + CONV2_STR(RADIUS_ATTR_STATE, buf, strlen(buf), 0); + } + /* The RFC text says that this should always be the case, but it seems odd... */ + if ((*rad_fw)->hdr->code == RADIUS_CODE_ACCESS_ACCEPT) { + if (sizeof(buf) < snprintf(buf, sizeof(buf), "Diameter/%.*s", + sid->avp_data->os.len, sid->avp_data->os.data)) { + TRACE_DEBUG(INFO, "Data truncated in Class attribute: %s", buf); + } + CONV2_STR(RADIUS_ATTR_CLASS, buf, strlen(buf), 0); + } + + /* Unlink the Origin-Realm now; the others are unlinked at the end of this function */ + CHECK_FCT( msg_free( avp, 1 ) ); + + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Session-Timeout", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &avp) ); + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Authorization-Lifetime", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &avp_x) ); + CHECK_FCT( dict_search( DICT_AVP, AVP_BY_NAME, "Re-Auth-Request-Type", &avp_dict, ENOENT)); + CHECK_FCT( msg_search_avp (*diam_ans, avp_dict, &avp_y) ); + /* + When translating a Diameter AA-Answer (with successful result code) + to RADIUS Access-Accept that contains a Session-Timeout or + Authorization-Lifetime AVP, take the following steps: + + - If the Diameter message contains a Session-Timeout AVP but no + Authorization-Lifetime AVP, translate it to a Session-Timeout + attribute (not a Termination-Action). + */ + if ((avp != NULL) && (avp_x == NULL)) { + CHECK_FCT( msg_avp_data ( avp, &adata ) ); + CONV2_32B( RADIUS_ATTR_SESSION_TIMEOUT, adata->avp_data->u32 ); + } + + /* + - If the Diameter message contains an Authorization-Lifetime AVP + but no Session-Timeout AVP, translate it to a Session-Timeout + attribute and a Termination-Action set to AA-REQUEST. (Remove + Authorization-Lifetime and Re-Auth-Request-Type.) + */ + if ((avp == NULL) && (avp_x != NULL)) { + CHECK_FCT( msg_avp_data ( avp_x, &adata ) ); + CONV2_32B( RADIUS_ATTR_SESSION_TIMEOUT, adata->avp_data->u32 ); + CONV2_32B( RADIUS_ATTR_TERMINATION_ACTION, RADIUS_TERMINATION_ACTION_RADIUS_REQUEST ); + ta_set = 1; + } + + /* + - If the Diameter message has both, the Session-Timeout must be + greater than or equal to the Authorization-Lifetime (required + by [BASE]). Translate it to a Session-Timeout value (with + value from Authorization-Lifetime AVP, the smaller one) and + with the Termination-Action set to AA-REQUEST. (Remove the + Authorization-Lifetime and Re-Auth-Request-Type.) + */ + if ((avp != NULL) && (avp_x != NULL)) { + CHECK_FCT( msg_avp_data ( avp_x, &adata ) ); + CONV2_32B( RADIUS_ATTR_SESSION_TIMEOUT, adata->avp_data->u32 ); + CONV2_32B( RADIUS_ATTR_TERMINATION_ACTION, RADIUS_TERMINATION_ACTION_RADIUS_REQUEST ); + ta_set = 1; + } + + /* -> Not too sure about Auth-Grace-Period... we'll just discard it for now */ + + if (avp) { + CHECK_FCT( msg_free( avp, 1 ) ); + } + if (avp_x) { + CHECK_FCT( msg_free( avp_x, 1 ) ); + } + if (avp_y) { + CHECK_FCT( msg_free( avp_y, 1 ) ); + } + + + /* + - If a Proxy-State attribute was present in the RADIUS request, + the same attribute is added in the response. This information + may be found in the Proxy-Info AVP group, or in a local state + table. + -> handled by sub_echo_drop - return ENOTSUP; + - If state information regarding the RADIUS request was saved in + a Proxy-Info AVP or local state table, the RADIUS Identifier + and UDP IP Address and port number are extracted and used in + issuing the RADIUS reply. + -> was saved with the full request + */ + + + /* Now loop in the list of AVPs and convert those that we know how */ + CHECK_FCT( msg_browse(*diam_ans, MSG_BRW_FIRST_CHILD, &next, NULL) ); + + while (next) { + int handled = 1; + avp = next; + CHECK_FCT( msg_browse(avp, MSG_BRW_NEXT, &next, NULL) ); + + CHECK_FCT( msg_avp_data ( avp, &adata ) ); + + if (adata->avp_flags & AVP_FLAG_VENDOR == 0) { + switch (adata->avp_code) { + + /* RFC 4005 (AVP in the order of the AA-Request/Answer AVP Table) */ + case DIAM_ATTR_ACCT_INTERIM_INTERVAL: + CONV2_32B(RADIUS_ATTR_ACCT_INTERIM_INTERVAL, adata->avp_data->u32); + break; + + case DIAM_ATTR_ARAP_CHALLENGE_RESPONSE: + CONV2_STR(RADIUS_ATTR_ARAP_CHALLENGE_RESPONSE, adata->avp_data->os.data, adata->avp_data->os.len, 0); + break; + + case DIAM_ATTR_ARAP_FEATURES: + CONV2_STR(RADIUS_ATTR_ARAP_FEATURES, adata->avp_data->os.data, adata->avp_data->os.len, 0); + break; + + /* ARAP-Password is not present in answers */ + + case DIAM_ATTR_ARAP_SECURITY: + CONV2_32B(RADIUS_ATTR_ARAP_SECURITY, adata->avp_data->u32); + break; + + case DIAM_ATTR_ARAP_SECURITY_DATA: + CONV2_STR(RADIUS_ATTR_ARAP_SECURITY_DATA, adata->avp_data->os.data, adata->avp_data->os.len, 2); + break; + + case DIAM_ATTR_ARAP_ZONE_ACCESS: + CONV2_32B(RADIUS_ATTR_ARAP_ZONE_ACCESS, adata->avp_data->u32); + break; + + case DIAM_ATTR_AUTH_APPLICATION_ID: + /* We just remove this AVP */ + break; + + case DIAM_ATTR_AUTH_GRACE_PERIOD: + /* We just remove this AVP (?) */ + break; + + case DIAM_ATTR_AUTH_REQUEST_TYPE: + /* We only check the value */ + if (adata->avp_data->u32 != 3) { + log_normal("Received Diameter answer with Auth-Request-Type set to %d (%s) from server %.*s, session %.*s.\n" + " This may cause interoperability with RADIUS.\n", + adata->avp_data->u32, + (adata->avp_data->u32 == 1) ? "AUTHENTICATE_ONLY" : + ((adata->avp_data->u32 == 2) ? "AUTHORIZE_ONLY" : "???"), + oh->avp_data->os.len, oh->avp_data->os.data, + sid->avp_data->os.len, sid->avp_data->os.len); + } + break; + + case DIAM_ATTR_AUTH_SESSION_STATE: + if ((!ta_set) && (adata->avp_data->u32 == 0 /* STATE_MAINTAINED */)) { + CONV2_32B( RADIUS_ATTR_TERMINATION_ACTION, RADIUS_TERMINATION_ACTION_RADIUS_REQUEST ); + } + break; + + /* Authorization-Lifetime already handled */ + + case DIAM_ATTR_CALLBACK_ID: + CONV2_STR(RADIUS_ATTR_CALLBACK_ID, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_CALLBACK_NUMBER: + CONV2_STR(RADIUS_ATTR_CALLBACK_NUMBER, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + /* Called-Station-Id is not present in answers */ + /* Calling-Station-Id is not present in answers */ + /* CHAP-Auth is not present in answers */ + /* CHAP-Challenge is not present in answers */ + + case DIAM_ATTR_CLASS: + CONV2_STR(RADIUS_ATTR_CLASS, adata->avp_data->os.data, adata->avp_data->os.len, 2); + break; + + case DIAM_ATTR_CONFIGURATION_TOKEN: + /* We might as well remove it since it's not supposed to be sent to the NAS... */ + CONV2_STR(RADIUS_ATTR_CONFIGURATION_TOKEN, adata->avp_data->os.data, adata->avp_data->os.len, 2); + break; + + /* Connect-Info is not present in answers */ + + case DIAM_ATTR_FILTER_ID: + CONV2_STR(RADIUS_ATTR_FILTER_ID, adata->avp_data->os.data, adata->avp_data->os.len, 2); + break; + + case DIAM_ATTR_FRAMED_APPLETALK_LINK: + CONV2_32B(RADIUS_ATTR_FRAMED_APPLETALK_LINK, adata->avp_data->u32); + break; + + case DIAM_ATTR_FRAMED_APPLETALK_NETWORK: + CONV2_32B(RADIUS_ATTR_FRAMED_APPLETALK_NETWORK, adata->avp_data->u32); + break; + + case DIAM_ATTR_FRAMED_APPLETALK_ZONE: + CONV2_STR(RADIUS_ATTR_FRAMED_APPLETALK_ZONE, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_FRAMED_COMPRESSION: + CONV2_32B(RADIUS_ATTR_FRAMED_COMPRESSION, adata->avp_data->u32); + break; + + case DIAM_ATTR_FRAMED_INTERFACE_ID: + CONV2_64B(RADIUS_ATTR_FRAMED_INTERFACE_ID, adata->avp_data->u64); + break; + + case DIAM_ATTR_FRAMED_IP_ADDRESS: + CONV2_STR(RADIUS_ATTR_FRAMED_IP_ADDRESS, adata->avp_data->os.data, adata->avp_data->os.len, 0); + break; + + case DIAM_ATTR_FRAMED_IP_NETMASK: + CONV2_STR(RADIUS_ATTR_FRAMED_IP_NETMASK, adata->avp_data->os.data, adata->avp_data->os.len, 0); + break; + + case DIAM_ATTR_FRAMED_IPV6_PREFIX: + CONV2_STR(RADIUS_ATTR_FRAMED_IPV6_PREFIX, adata->avp_data->os.data, adata->avp_data->os.len, 0); + break; + + case DIAM_ATTR_FRAMED_IPV6_POOL: + CONV2_STR(RADIUS_ATTR_FRAMED_IPV6_POOL, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_FRAMED_IPV6_ROUTE: + CONV2_STR(RADIUS_ATTR_FRAMED_IPV6_ROUTE, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_FRAMED_IPX_NETWORK: + CONV2_32B(RADIUS_ATTR_FRAMED_IPX_NETWORK, adata->avp_data->u32); + break; + + case DIAM_ATTR_FRAMED_MTU: + CONV2_32B(RADIUS_ATTR_FRAMED_MTU, adata->avp_data->u32); + break; + + case DIAM_ATTR_FRAMED_POOL: + CONV2_STR(RADIUS_ATTR_FRAMED_POOL, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_FRAMED_PROTOCOL: + CONV2_32B(RADIUS_ATTR_FRAMED_PROTOCOL, adata->avp_data->u32); + break; + + case DIAM_ATTR_FRAMED_ROUTE: + CONV2_STR(RADIUS_ATTR_FRAMED_ROUTE, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_FRAMED_ROUTING: + CONV2_32B(RADIUS_ATTR_FRAMED_ROUTING, adata->avp_data->u32); + break; + + case DIAM_ATTR_IDLE_TIMEOUT: + CONV2_32B(RADIUS_ATTR_IDLE_TIMEOUT, adata->avp_data->u32); + break; + + case DIAM_ATTR_LOGIN_IP_HOST: + CONV2_STR(RADIUS_ATTR_LOGIN_IP_HOST, adata->avp_data->os.data, adata->avp_data->os.len, 0); + break; + + case DIAM_ATTR_LOGIN_IPV6_HOST: + CONV2_STR(RADIUS_ATTR_LOGIN_IPV6_HOST, adata->avp_data->os.data, adata->avp_data->os.len, 0); + break; + + case DIAM_ATTR_LOGIN_LAT_GROUP: + CONV2_STR(RADIUS_ATTR_LOGIN_LAT_GROUP, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_LOGIN_LAT_NODE: + CONV2_STR(RADIUS_ATTR_LOGIN_LAT_NODE, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_LOGIN_LAT_PORT: + CONV2_STR(RADIUS_ATTR_LOGIN_LAT_PORT, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_LOGIN_LAT_SERVICE: + CONV2_STR(RADIUS_ATTR_LOGIN_LAT_SERVICE, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_LOGIN_SERVICE: + CONV2_32B(RADIUS_ATTR_LOGIN_SERVICE, adata->avp_data->u32); + break; + + case DIAM_ATTR_LOGIN_TCP_PORT: + CONV2_32B(RADIUS_ATTR_LOGIN_TCP_PORT, adata->avp_data->u32); + break; + + /* + - If the + Multi-Round-Time-Out AVP is present, the value of the AVP MUST + be inserted in the RADIUS Session-Timeout AVP. + + o As described in [NASREQ], if the Result-Code AVP set to + DIAMETER_MULTI_ROUND_AUTH and the Multi-Round-Time-Out AVP is + present, it is translated to the RADIUS Session-Timeout attribute. + */ + case DIAM_ATTR_MULTI_ROUND_TIMEOUT: + CONV2_32B(RADIUS_ATTR_SESSION_TIMEOUT, adata->avp_data->u32); + break; + + case DIAM_ATTR_NAS_FILTER_RULE: + /* This is not translatable to RADIUS */ + log_normal("Received Diameter answer with non-translatable NAS-Filter-Rule AVP from '%.*s' (session: '%.*s'), ignoring.\n", + oh->avp_data->os.len, oh->avp_data->os.data, + sid->avp_data->os.len, sid->avp_data->os.data); + handled = 0; + break; + + /* NAS-Identifier is not present in answers */ + /* NAS-IP-Address is not present in answers */ + /* NAS-IPv6-Address is not present in answers */ + /* NAS-Port is not present in answers */ + /* NAS-Port-Id is not present in answers */ + /* NAS-Port-Type is not present in answers */ + + case DIAM_ATTR_ORIGIN_AAA_PROTOCOL: + /* We just remove this AVP */ + break; + + /* Originating-Line-Info is not present in answers */ + + case DIAM_ATTR_PASSWORD_RETRY: + CONV2_32B(RADIUS_ATTR_PASSWORD_RETRY, adata->avp_data->u32); + break; + + case DIAM_ATTR_PORT_LIMIT: + CONV2_32B(RADIUS_ATTR_PORT_LIMIT, adata->avp_data->u32); + break; + + case DIAM_ATTR_PROMPT: + CONV2_32B(RADIUS_ATTR_PROMPT, adata->avp_data->u32); + break; + + case DIAM_ATTR_QOS_FILTER_RULE: + /* This is not translatable to RADIUS */ + log_normal("Received Diameter answer with non-translatable QoS-Filter-Rule AVP from '%.*s' (session: '%.*s'), ignoring.\n", + oh->avp_data->os.len, oh->avp_data->os.data, + sid->avp_data->os.len, sid->avp_data->os.data); + handled = 0; + break; + + /* Re-Auth-Request-Type already handled */ + + case DIAM_ATTR_REPLY_MESSAGE: + CONV2_STR(RADIUS_ATTR_REPLY_MESSAGE, adata->avp_data->os.data, adata->avp_data->os.len, 2); + break; + + case DIAM_ATTR_SERVICE_TYPE: + CONV2_32B(RADIUS_ATTR_SERVICE_TYPE, adata->avp_data->u32); + break; + + case DIAM_ATTR_STATE: + CONV2_STR(RADIUS_ATTR_STATE, adata->avp_data->os.data, adata->avp_data->os.len, 2); + break; + + case DIAM_ATTR_TUNNELING: + { +#define CONV2_TUN_STR( _attr_, _data_, _len_, _trunc_) { \ + size_t __l = (size_t)(_len_); \ + size_t __w = (__l > 252) ? 252 : __l; \ + size_t __off = 0; \ + if ((_trunc_) == 0) { \ + CHECK_PARAMS( __l <= 252 ); \ + } \ + if ((__l > 252) && (_trunc_ == 1)) { \ + TRACE_DEBUG(FULL, "Attribute truncated!"); \ + __l = 252; \ + } \ + buf[0] = tuntag; \ + memcpy(&buf[1], (_data_), __w); \ + CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), &buf[0], __w + 1)); \ + while (__l -= __w) { \ + __off += __w; \ + __w = (__l > 253) ? 253 : __l; \ + CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (_data_) + __off, __w)); \ + } \ +} + +#define CONV2_TUN_32B( _attr_, _data_) { \ + uint32_t __v = htonl((uint32_t)(_data_)); \ + __v |= tuntag << 24; \ + CHECK_MALLOC(radius_msg_add_attr(*rad_fw, (_attr_), (uint8_t *)&__v, sizeof(__v))); \ +} + msg_avp_t *inavp, *innext; + tuntag++; + CHECK_FCT( msg_browse(avp, MSG_BRW_FIRST_CHILD, &innext, NULL) ); + while (innext) { + inavp = innext; + CHECK_FCT( msg_browse(inavp, MSG_BRW_NEXT, &innext, NULL) ); + CHECK_FCT( msg_avp_data ( inavp, &adata ) ); + + if (adata->avp_flags & AVP_FLAG_VENDOR == 0) { + switch (adata->avp_code) { + case DIAM_ATTR_TUNNEL_TYPE: + CONV2_TUN_32B( RADIUS_ATTR_TUNNEL_TYPE, adata->avp_data->u32); + break; + + case DIAM_ATTR_TUNNEL_MEDIUM_TYPE: + CONV2_TUN_32B( RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, adata->avp_data->u32); + break; + + case DIAM_ATTR_TUNNEL_CLIENT_ENDPOINT: + CONV2_TUN_STR(RADIUS_ATTR_TUNNEL_CLIENT_ENDPOINT, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_TUNNEL_SERVER_ENDPOINT: + CONV2_TUN_STR(RADIUS_ATTR_TUNNEL_SERVER_ENDPOINT, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_TUNNEL_PREFERENCE: + CONV2_TUN_32B( RADIUS_ATTR_TUNNEL_PREFERENCE, adata->avp_data->u32); + break; + + case DIAM_ATTR_TUNNEL_CLIENT_AUTH_ID: + CONV2_TUN_STR(RADIUS_ATTR_TUNNEL_CLIENT_AUTH_ID, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_TUNNEL_SERVER_AUTH_ID: + CONV2_TUN_STR(RADIUS_ATTR_TUNNEL_SERVER_AUTH_ID, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_TUNNEL_ASSIGNEMENT_ID: + CONV2_TUN_STR(RADIUS_ATTR_TUNNEL_ASSIGNEMENT_ID, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + case DIAM_ATTR_TUNNEL_PASSWORD: + { + /* This AVP must be encoded for RADIUS (similar to radius_msg_add_attr_user_password) + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Length | Tag | Salt + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + Salt (cont) | String ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + size_t pos; + int i; + size_t buflen; + uint8_t * secret; /* S */ + size_t secret_len; + uint8_t hash[16]; /* b(i) */ + const uint8_t *addr[3]; + size_t len[3]; + + /* We need the request authenticator */ + CHECK_PARAMS(req_auth); + + /* Retrieve the shared secret */ + CHECK_FCT((*cs->rgw_clients_getkey)(cli, &secret, &secret_len)); + + /* Beginning of the buffer */ + buf[0] = tuntag; + buf[1] = (uint8_t)(lrand48()); /* A (hi bits) */ + buf[2] = (uint8_t)(lrand48()); /* A (low bits) */ + + /* The plain text string P */ + CHECK_PARAM(adata->avp_data->os.len < 240); + buf[3] = adata->avp_data->os.len; + memcpy(&buf[4], adata->avp_data->os.data, adata->avp_data->os.len); + memset(&buf[4 + adata->avp_data->os.len], 0, sizeof(buf) - 4 - adata->avp_data->os.len); + + /* Initial b1 = MD5(S + R + A) */ + addr[0] = secret; + len[0] = secret_len; + addr[1] = req_auth; + len[1] = 16; + addr[2] = &buf[1]; + len[2] = 2; + md5_vector(3, addr, len, hash); + + /* Initial c(1) = p(1) xor b(1) */ + for (i = 0; i < 16; i++) { + buf[i + 3] ^= hash[i]; + } + pos = 16; + + /* loop */ + while (pos < adata->avp_data->os.len + 1) { + addr[0] = secret; + len[0] = secret_len; + addr[1] = &buf[pos - 13]; + len[1] = 16; + /* b(i) = MD5( S + c(i-1) */ + md5_vector(2, addr, len, hash); + + /* c(i) = p(i) xor b(i) */ + for (i = 0; i < 16; i++) + buf[pos + i + 3] ^= hash[i]; + + pos += 16; + } + + CONV2_STR(RADIUS_ATTR_TUNNEL_PASSWORD, &buf[0], pos + 3, 0); + } + break; + + case DIAM_ATTR_TUNNEL_PRIVATE_GROUP_ID: + CONV2_TUN_STR(RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + default: + TRACE_DEBUG(FULL, "Ignored unknown AVP inside Tunneling (%d)", adata->avp_code); + } + } else { + TRACE_DEBUG(FULL, "Ignored unknown Vendor AVP inside Tunneling (%d, %d)", adata->avp_vendor, adata->avp_code); + } + } + } + break; + + case DIAM_ATTR_USER_NAME: + CONV2_STR(RADIUS_ATTR_USER_NAME, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + /* User-Password never present in answers */ + + /* RFC 4072 (AVP in the order of the EAP Command AVP Table) */ + /* + o Diameter Accounting-EAP-Auth-Method AVPs, if present, are + discarded. + */ + case DIAM_ATTR_ACCOUNTING_EAP_AUTH_METHOD: + break; + + /* + o Diameter EAP-Master-Session-Key AVP can be translated to the + vendor-specific RADIUS MS-MPPE-Recv-Key and MS-MPPE-Send-Key + attributes [RFC2548]. The first up to 32 octets of the key is + stored into MS-MPPE-Recv-Key, and the next up to 32 octets (if + present) are stored into MS-MPPE-Send-Key. The encryption of this + attribute is described in [RFC2548]. + */ + case DIAM_ATTR_EAP_MASTER_SESSION_KEY: + { + uint8_t * secret; /* S */ + size_t secret_len; + size_t recv_len, send_len; + + /* We need the request authenticator */ + CHECK_PARAMS(req_auth); + + /* Retrieve the shared secret */ + CHECK_FCT((*cs->rgw_clients_getkey)(cli, &secret, &secret_len)); + + if (adata->avp_data->os.len != 64) { + TRACE_DEBUG(INFO, "Received EAP-Master-Session-Key attribute with length %d != 64.\n", adata->avp_data->os.len) + } + + CHECK_PARAMS(adata->avp_data->os.len <= 64); + recv_len = adata->avp_data->os.len >= 32 ? 32 : adata->avp_data->os.len; + send_len = adata->avp_data->os.len - recv_len; + + if ( ! radius_msg_add_mppe_keys(*rad_fw, req_auth, secret, secret_len, + adata->avp_data->os.data + recv_len, send_len, + adata->avp_data->os.data, recv_len) ) { + TRACE_DEBUG(INFO, "Error while converting EAP-Master-Session-Key to RADIUS message"); + return ENOMEM; + } + } + break; + + case DIAM_ATTR_EAP_KEY_NAME: + CONV2_STR(RADIUS_ATTR_EAP_KEY_NAME, adata->avp_data->os.data, adata->avp_data->os.len, 1); + break; + + /* + o Diameter EAP-Payload AVP is translated to RADIUS EAP-Message + attribute(s). If necessary, the value is split into multiple + RADIUS EAP-Message attributes. + */ + case DIAM_ATTR_EAP_PAYLOAD: + if ( ! radius_msg_add_eap(*rad_fw, adata->avp_data->os.data, adata->avp_data->os.len) ) { + TRACE_DEBUG(INFO, "Error while converting EAP payload to RADIUS message"); + return ENOMEM; + } + break; + + /* + o Diameter EAP-Reissued-Payload AVP is translated to a message that + contains RADIUS EAP-Message attribute(s), and a RADIUS Error-Cause + attribute [RFC3576] with value 202 (decimal), "Invalid EAP Packet + (Ignored)" [RFC3579]. + */ + case DIAM_ATTR_EAP_REISSUED_PAYLOAD: + if ( ! radius_msg_add_eap(*rad_fw, adata->avp_data->os.data, adata->avp_data->os.len) ) { + TRACE_DEBUG(INFO, "Error while converting EAP reissued payload to RADIUS message"); + return ENOMEM; + } + + if ( ! radius_msg_add_attr_int32(*rad_fw, RADIUS_ATTR_ERROR_CAUSE, 202) ) { + TRACE_DEBUG(INFO, "Error while adding Error-Cause attribute in RADIUS message"); + return ENOMEM; + } + break; + + default: + /* Leave the AVP in the message for further treatment */ + handled = 0; + } + } else { + /* Vendor-specific AVPs */ + switch (adata->avp_vendor) { + + default: /* unknown vendor */ + handled = 0; + } + } + + if (handled) { + CHECK_FCT( msg_free( avp, 1 ) ); + } + } + + CHECK_FCT( msg_free( asid, 1 ) ); + CHECK_FCT( msg_free( aoh, 1 ) ); + free(req_auth); + + return 0; } int rga_register(int version, waaad_api_t * waaad_api, struct radius_gw_api * api)