# HG changeset patch # User Sebastien Decugis # Date 1277966854 -32400 # Node ID e203fc0c95e3845965e07b6fefeacf7d0b1bd94b # Parent 1ae3f2c287377048f5b653283030ddc06242b2f5 Updated the app_radgw extension to allow more souple management of sessions, and stateful gateway features. diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgw.h --- a/extensions/app_radgw/rgw.h Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgw.h Thu Jul 01 15:47:34 2010 +0900 @@ -67,7 +67,7 @@ int rgw_msg_parse(unsigned char * buf, size_t len, struct rgw_radius_msg_meta ** msg); void rgw_msg_dump(struct rgw_radius_msg_meta * msg); int rgw_msg_auth_check(struct rgw_radius_msg_meta * msg, struct rgw_client * cli, uint8_t * req_auth); -int rgw_msg_create_base(struct rgw_radius_msg_meta * msg, struct rgw_client * cli, struct session ** session, struct msg ** diam); +int rgw_msg_create_base(struct rgw_client * cli, struct msg ** diam); int rgw_msg_init(void); /* Local RADIUS server(s) configuration */ @@ -116,7 +116,7 @@ void rgw_plg_dump(void); void rgw_plg_start_cache(void); int rgw_plg_loop_req(struct rgw_radius_msg_meta **rad, struct session **session, struct msg **diam_msg, struct rgw_client * cli); -int rgw_plg_loop_ans(struct rgw_radius_msg_meta *req, struct session *session, struct msg **diam_ans, struct radius_msg ** rad_ans, struct rgw_client * cli); +int rgw_plg_loop_ans(struct rgw_radius_msg_meta *req, struct session *session, struct msg **diam_ans, struct radius_msg ** rad_ans, struct rgw_client * cli, int * stateful); void rgw_plg_fini(void); diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgw_common.h --- a/extensions/app_radgw/rgw_common.h Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgw_common.h Thu Jul 01 15:47:34 2010 +0900 @@ -71,17 +71,18 @@ void (*rgwp_conf_free) (struct rgwp_config * state); /* handle an incoming RADIUS message */ - int (*rgwp_rad_req) ( struct rgwp_config * conf, struct session * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ); - /* ret 0: continue; + int (*rgwp_rad_req) ( struct rgwp_config * conf, struct session ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ); + /* ret >0: critical error (errno), log and exit. + ret 0: continue; ret -1: stop processing this message ret -2: reply the content of rad_ans to the RADIUS client immediatly - ret >0: critical error (errno), log and exit. */ /* handle the corresponding Diameter answer */ - int (*rgwp_diam_ans) ( struct rgwp_config * conf, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli ); + int (*rgwp_diam_ans) ( struct rgwp_config * conf, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli, int * stateful ); /* ret 0: continue; ret >0: error; ret: -1 ... (tbd) */ - + /* if *stateful = 1 on return, the session will not be destroyed after RADIUS answer is sent. The extension must ensure to register a timeout on the session in this case. */ + } rgwp_descriptor; diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgw_msg.c --- a/extensions/app_radgw/rgw_msg.c Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgw_msg.c Thu Jul 01 15:47:34 2010 +0900 @@ -136,196 +136,35 @@ fd_log_debug("-----------------------------\n"); } -static struct dict_object * cache_sess_id = NULL; -static struct dict_object * cache_dest_host = NULL; -static struct dict_object * cache_dest_realm = NULL; static struct dict_object * cache_orig_host = NULL; static struct dict_object * cache_orig_realm = NULL; int rgw_msg_init(void) { TRACE_ENTRY(); - CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &cache_sess_id, ENOENT) ); - CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Host", &cache_dest_host, ENOENT) ); - CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &cache_dest_realm, ENOENT) ); CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &cache_orig_host, ENOENT) ); CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &cache_orig_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, struct session ** session, struct msg ** diam) +/* Create a new Diameter msg with origin-host & realm */ +int rgw_msg_create_base(struct rgw_client * cli, struct msg ** diam) { - int idx, i; - const char * prefix = "Diameter/"; - size_t pref_len; - 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; struct avp *avp = NULL; union avp_value avp_val; - TRACE_ENTRY("%p %p %p %p", msg, cli, session, diam); - CHECK_PARAMS( msg && cli && session && (*session == NULL) && diam && (*diam == NULL) ); - - pref_len = strlen(prefix); - - /* 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]); - char * attr_val = (char *)(attr + 1); - size_t attr_len = attr->length - sizeof(struct radius_attr_hdr); - - if ((attr->type == RADIUS_ATTR_USER_NAME) - && attr_len) { - TRACE_DEBUG(ANNOYING, "Found a User-Name attribute: '%.*s'", attr_len, attr_val); - un = attr_val; - un_len = attr_len; - continue; - } - - if ((attr->type == RADIUS_ATTR_STATE) - && (attr_len > pref_len + 5 /* for the '/'s and non empty strings */ ) - && ! strncmp(attr_val, prefix, pref_len)) { /* should we make it strncasecmp? */ - int i, start; - - TRACE_DEBUG(ANNOYING, "Found a State attribute with '%s' prefix (attr #%d).", prefix, idx); - - /* 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; - 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 */ - dh_len = i - start; - - start = ++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 */ - dr_len = i - start; - - i++; - si = attr_val + i; - si_len = attr_len - i; - - 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; - } - - if ((attr->type == RADIUS_ATTR_CLASS) - && (attr_len > pref_len ) - && ! strncmp(attr_val, prefix, pref_len)) { - 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; - } - - } + TRACE_ENTRY("%p %p", cli, diam); + CHECK_PARAMS( cli && diam && (*diam == NULL) ); /* Get information on this peer */ CHECK_FCT( rgw_clients_get_origin(cli, &fqdn, &realm) ); - /* Create the session object */ - if (si_len) { - CHECK_FCT( fd_sess_fromsid ( si, si_len, session, &idx) ); - } else { - if (un) { - int len; - /* If not found, create a new Session-Id. The format is: {fqdn;hi32;lo32;username;diamid} */ - CHECK_MALLOC( sess_str = malloc(un_len + 1 /* ';' */ + fd_g_config->cnf_diamid_len + 1 /* '\0' */) ); - len = sprintf(sess_str, "%.*s;%s", un_len, un, fd_g_config->cnf_diamid); - CHECK_FCT( fd_sess_new(session, fqdn, sess_str, len) ); - free(sess_str); - idx = 1; - } - } - /* Create an empty Diameter message so that extensions can store their AVPs */ CHECK_FCT( fd_msg_new ( NULL, MSGFL_ALLOC_ETEID, diam ) ); - if (*session) { - CHECK_FCT( fd_sess_getsid(*session, &sess_str) ); - if (idx == 0) { - TRACE_DEBUG(INFO, "Another message was translated for this session ('%s') and not answered yet, discarding the new RADIUS request.", sess_str); - *session = NULL; - return EALREADY; - } - - TRACE_DEBUG(FULL, "Translating new message for session '%s'...", sess_str); - - /* Add the Session-Id AVP as first AVP */ - CHECK_FCT( fd_msg_avp_new ( cache_sess_id, 0, &avp ) ); - memset(&avp_val, 0, sizeof(avp_val)); - avp_val.os.data = (unsigned char *)sess_str; - avp_val.os.len = strlen(sess_str); - CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); - CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_FIRST_CHILD, avp) ); - - } else { - TRACE_DEBUG(FULL, "No session has been created for this message"); - } - - /* Add the Destination-Realm as next AVP */ - CHECK_FCT( fd_msg_avp_new ( cache_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 => we use the local domain of this gateway */ - avp_val.os.data = fd_g_config->cnf_diamrlm; - avp_val.os.len = fd_g_config->cnf_diamrlm_len; - } else { - avp_val.os.data = un + i; - avp_val.os.len = un_len - i; - } - } - CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); - CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); - - /* Add the Destination-Host as next AVP */ - if (dh) { - CHECK_FCT( fd_msg_avp_new ( cache_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( fd_msg_avp_setvalue ( avp, &avp_val ) ); - CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); - } - /* Add the Origin-Host as next AVP */ CHECK_FCT( fd_msg_avp_new ( cache_orig_host, 0, &avp ) ); memset(&avp_val, 0, sizeof(avp_val)); diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgw_plugins.c --- a/extensions/app_radgw/rgw_plugins.c Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgw_plugins.c Thu Jul 01 15:47:34 2010 +0900 @@ -348,7 +348,7 @@ if (plg->descriptor->rgwp_rad_req) { TRACE_DEBUG(ANNOYING, "Calling next plugin: %s", plg->descriptor->rgwp_name); - ret = (*plg->descriptor->rgwp_rad_req)(plg->cs, *session, &(*rad)->radius, &rad_ans, diam_msg, cli); + ret = (*plg->descriptor->rgwp_rad_req)(plg->cs, session, &(*rad)->radius, &rad_ans, diam_msg, cli); if (ret) break; } else { @@ -390,13 +390,15 @@ } /* Loop in the extension list (same as req) to convert data from diam_ans to rad_ans */ -int rgw_plg_loop_ans(struct rgw_radius_msg_meta *req, struct session *session, struct msg **diam_ans, struct radius_msg ** rad_ans, struct rgw_client * cli) +int rgw_plg_loop_ans(struct rgw_radius_msg_meta *req, struct session *session, struct msg **diam_ans, struct radius_msg ** rad_ans, struct rgw_client * cli, int * stateful) { int ret = 0; struct fd_list * head = NULL, *li; - TRACE_ENTRY("%p %p %p %p %p", req, session, diam_ans, rad_ans, cli); - CHECK_PARAMS( req && session && diam_ans && *diam_ans && rad_ans && *rad_ans && cli); + TRACE_ENTRY("%p %p %p %p %p %p", req, session, diam_ans, rad_ans, cli, stateful); + CHECK_PARAMS( req && session && diam_ans && *diam_ans && rad_ans && *rad_ans && cli && stateful); + + *stateful = 0; /* default: stateless gateway */ /* Get the list of extensions of the RADIUS request */ CHECK_POSIX( pthread_rwlock_rdlock( &plg_lock) ); @@ -406,12 +408,14 @@ /* Loop in the list of extensions */ for (li = head->next; li != head; li = li->next) { struct plg_descr * plg = ((struct plg_accel_item *) li)->plg; + int locstateful = 0; if (plg->descriptor->rgwp_diam_ans) { TRACE_DEBUG(ANNOYING, "Calling next plugin: %s", plg->descriptor->rgwp_name); - ret = (*plg->descriptor->rgwp_diam_ans)(plg->cs, session, diam_ans, rad_ans, (void *)cli); + ret = (*plg->descriptor->rgwp_diam_ans)(plg->cs, session, diam_ans, rad_ans, (void *)cli, &locstateful); if (ret) break; + *stateful |= locstateful; } else { TRACE_DEBUG(ANNOYING, "Skipping extension '%s' (NULL callback)", plg->descriptor->rgwp_name); } diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgw_worker.c --- a/extensions/app_radgw/rgw_worker.c Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgw_worker.c Thu Jul 01 15:47:34 2010 +0900 @@ -129,12 +129,10 @@ } ); /* Note: after this point, the radius message buffer may not be consistent with the array of attributes anymore. */ - - session = NULL; diam_msg = NULL; - /* Create the session and an empty message with default common AVPs */ - CHECK_FCT_DO( rgw_msg_create_base(msg, cli, &session, &diam_msg), + /* Create an empty message with only Origin information (no session, no destination -- added by the plugins) */ + CHECK_FCT_DO( rgw_msg_create_base(cli, &diam_msg), { /* An error occurred, discard message */ rgw_msg_free(&msg); @@ -142,6 +140,8 @@ continue; } ); + session = NULL; + /* Pass the message to the list of registered plugins */ CHECK_FCT_DO( rgw_plg_loop_req(&msg, &session, &diam_msg, cli), { @@ -157,26 +157,23 @@ rgw_clients_dispose(&cli); continue; } ); - if (msg == NULL) { + if (msg == NULL) { /* Error or RADIUS answer locally generated */ rgw_clients_dispose(&cli); if (diam_msg) { CHECK_FCT_DO( fd_msg_free(diam_msg), ); diam_msg = NULL; } - if (session) { - CHECK_FCT_DO( fd_sess_destroy(&session), ); - } continue; /* the message was handled already */ } pb = 0; - /* Check the created Diameter message */ + /* Check the created Diameter message -- it will be invalid if no callback has handled the RADIUS message */ if ((diam_msg == NULL) || ( fd_msg_parse_rules(diam_msg, fd_g_config->cnf_dict, NULL) ) ) { fd_log_debug("[radgw] No or invalid Diameter message was generated after processing the RADIUS command %hhd (%s).\n" - " This is likely an implementation problem, please report.\n", + " It may indicate a gateway configuration problem, or implementation issue in a plugin.\n", msg->radius.hdr->code, rgw_msg_code_str(msg->radius.hdr->code)); - /* We might also dump the conflicting rule here if useful */ + /* We should also dump the conflicting rule here to help debug? */ pb++; } @@ -189,9 +186,6 @@ msg->radius.hdr->code, rgw_msg_code_str(msg->radius.hdr->code)); } - /* Check the session is correct (for debug) */ - ASSERT(session != NULL); - if (pb) { /* Something went wrong during the conversion */ if (session) { @@ -252,6 +246,7 @@ struct avp *avp; struct avp_hdr *ahdr; int pb = 0; + int keepsession=0; TRACE_ENTRY("%p %p", pa, ans); CHECK_PARAMS_DO( pa && ans, return ); @@ -260,7 +255,7 @@ 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_plg_loop_ans(pa->rad, pa->sess, ans, &rad_ans, pa->cli), goto out ); + CHECK_FCT_DO( rgw_plg_loop_ans(pa->rad, pa->sess, ans, &rad_ans, pa->cli, &keepsession), goto out ); if (*ans != NULL) { @@ -292,7 +287,7 @@ } if (pb) { - TRACE_DEBUG(INFO, "[radgw] WARNING: %d mandatory AVP in the Diameter answer have not been translated to RADIUS!\n Please use plg_debug.rgwx for more information.", pb); + TRACE_DEBUG(INFO, "[radgw] WARNING: %d mandatory AVP in the Diameter answer have not been translated to RADIUS!\n Please use debug.rgwx for more information.", pb); } } @@ -302,8 +297,10 @@ } out: - /* Destroy remaining session data (stateless gateway) */ - CHECK_FCT_DO( fd_sess_destroy(&pa->sess), ); + if (!keepsession) { + /* Destroy remaining session data (stateless gateway) */ + CHECK_FCT_DO( fd_sess_destroy(&pa->sess), ); + } /* Clear the Diameter message */ if (*ans) { diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgwx_acct.c --- a/extensions/app_radgw/rgwx_acct.c Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgwx_acct.c Thu Jul 01 15:47:34 2010 +0900 @@ -284,7 +284,7 @@ } /* Incoming RADIUS request */ -static int acct_rad_req( struct rgwp_config * cs, struct session * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) +static int acct_rad_req( struct rgwp_config * cs, struct session ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) { int idx; int send_str=0; @@ -299,15 +299,23 @@ union avp_value value; struct avp ** avp_tun = NULL, *avp = NULL; + const char * prefix = "Diameter/"; + size_t pref_len; + char * si = NULL; + size_t si_len = 0; + 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_ACCOUNTING_REQUEST) && rad_ans && diam_fw && *diam_fw); + pref_len = strlen(prefix); + /* Either NAS-IP-Address or NAS-Identifier MUST be present in a RADIUS Accounting-Request. It SHOULD contain a NAS-Port or NAS- Port-Type attribute or both unless the service does not involve a port or the NAS does not distinguish among its ports. */ + /* We also enforce that the message contains a CLASS attribute with Diameter/ prefix containing the Session-Id. */ for (idx = 0; idx < rad_req->attr_used; idx++) { struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]); uint8_t * v = (uint8_t *)(attr + 1); @@ -341,6 +349,25 @@ | (v[2] << 8) | v[3] ; break; + + case RADIUS_ATTR_CLASS: + { + char * attr_val = (char *)(attr + 1); + size_t attr_len = attr->length - sizeof(struct radius_attr_hdr); + if ((attr_len > pref_len ) && ! strncmp(attr_val, prefix, pref_len)) { + int i; + 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 < rad_req->attr_used; i++) + rad_req->attr_pos[i - 1] = rad_req->attr_pos[i]; + rad_req->attr_used -= 1; + break; + } + } + break; + } } @@ -417,6 +444,26 @@ return ENOTSUP; } + /* Check if we got a valid session information, otherwise the server will not be able to handle the data... */ + if (!*session && !si) { + TRACE_DEBUG(INFO, "[acct.rgwx] RADIUS Account-Request from %s did not contain a CLASS attribute with Diameter session information, reject.", rgw_clients_id(cli)); + return EINVAL; + } + + /* Create the Session-Id AVP if needed */ + if (!*session) { + CHECK_FCT( fd_sess_fromsid ( si, si_len, session, NULL) ); + + TRACE_DEBUG(FULL, "[auth.rgwx] Translating new accounting message for session '%.*s'...", si_len, si); + + /* Add the Session-Id AVP as first AVP */ + CHECK_FCT( fd_msg_avp_new ( cs->dict.Session_Id, 0, &avp ) ); + value.os.data = (unsigned char *)si; + value.os.len = si_len; + CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); + CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_FIRST_CHILD, avp) ); + } + /* Add the command code */ { struct msg_hdr * header = NULL; @@ -1091,7 +1138,7 @@ st->send_str = send_str; } st->term_cause = str_cause; - CHECK_FCT( fd_sess_state_store( cs->sess_hdl, session, &st ) ); + CHECK_FCT( fd_sess_state_store( cs->sess_hdl, *session, &st ) ); } return 0; @@ -1127,7 +1174,7 @@ return; } -static int acct_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli ) +static int acct_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli, int * stateful ) { struct sess_state * st = NULL; struct avp *avp, *next; diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgwx_auth.c --- a/extensions/app_radgw/rgwx_auth.c Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgwx_auth.c Thu Jul 01 15:47:34 2010 +0900 @@ -70,6 +70,8 @@ struct dict_object * CHAP_Challenge; /* CHAP-Challenge */ struct dict_object * CHAP_Ident; /* CHAP-Ident */ struct dict_object * CHAP_Response; /* CHAP-Response */ + struct dict_object * Destination_Host; /* Destination-Host */ + struct dict_object * Destination_Realm; /* Destination-Realm */ struct dict_object * Connect_Info; /* Connect-Info */ struct dict_object * EAP_Payload; /* EAP-Payload */ struct dict_object * Error_Message; /* Error-Message */ @@ -153,6 +155,8 @@ CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "CHAP-Ident", &new->dict.CHAP_Ident, ENOENT) ); CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "CHAP-Response", &new->dict.CHAP_Response, ENOENT) ); CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Connect-Info", &new->dict.Connect_Info, ENOENT) ); + CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Host", &new->dict.Destination_Host, ENOENT) ); + CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &new->dict.Destination_Realm, ENOENT) ); CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "EAP-Payload", &new->dict.EAP_Payload, ENOENT) ); CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Error-Message", &new->dict.Error_Message, ENOENT) ); CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Error-Reporting-Host", &new->dict.Error_Reporting_Host, ENOENT) ); @@ -221,7 +225,7 @@ } /* Handle an incoming RADIUS request */ -static int auth_rad_req( struct rgwp_config * cs, struct session * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) +static int auth_rad_req( struct rgwp_config * cs, struct session ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) { int idx; int got_id = 0; @@ -229,13 +233,25 @@ int got_passwd = 0; int got_eap = 0; int got_empty_eap = 0; + const char * prefix = "Diameter/"; + size_t pref_len; + 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; uint32_t status_type; size_t nattr_used = 0; struct avp ** avp_tun = NULL, *avp = NULL; union avp_value value; 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); + CHECK_PARAMS(cs && session && rad_req && (rad_req->hdr->code == RADIUS_CODE_ACCESS_REQUEST) && rad_ans && diam_fw && *diam_fw); + + pref_len = strlen(prefix); /* Guidelines: @@ -269,14 +285,6 @@ and associated with the session state. -> sub_echo_drop should handle the Proxy-State attribute (conf issue) - - If the RADIUS request contained a State attribute and the - prefix of the data is "Diameter/", the data following the - prefix contains the Diameter Origin-Host/Origin-Realm/Session- - Id. If no such attributes are present and the RADIUS command - is an Access-Request, a new Session-Id is created. The - Session-Id is included in the Session-Id AVP. - -> done in rgw_msg_create_base. - - The Diameter Origin-Host and Origin-Realm AVPs MUST be created and added by using the information from an FQDN corresponding to the NAS-IP-Address attribute (preferred if available), @@ -304,9 +312,12 @@ -> the remaining is specific conversion rules */ - /* Check basic information is there */ + /* Check basic information is there, and also retrieve some attribute information */ for (idx = 0; idx < rad_req->attr_used; idx++) { struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(rad_req->buf + rad_req->attr_pos[idx]); + char * attr_val = (char *)(attr + 1); + size_t attr_len = attr->length - sizeof(struct radius_attr_hdr); + switch (attr->type) { case RADIUS_ATTR_NAS_IP_ADDRESS: case RADIUS_ATTR_NAS_IDENTIFIER: @@ -326,6 +337,50 @@ case RADIUS_ATTR_ARAP_PASSWORD: got_passwd += 1; break; + + /* 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 */ + case RADIUS_ATTR_STATE: + if ((attr_len > pref_len + 5 /* for the '/'s and non empty strings */ ) + && ! strncmp(attr_val, prefix, pref_len)) { /* should we make it strncasecmp? */ + int i, start; + + TRACE_DEBUG(ANNOYING, "Found a State attribute with '%s' prefix (attr #%d).", prefix, idx); + + /* 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; + 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 */ + dh_len = i - start; + + start = ++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 */ + dr_len = i - start; + + i++; + si = attr_val + i; + si_len = attr_len - i; + + 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 < rad_req->attr_used; i++) + rad_req->attr_pos[i - 1] = rad_req->attr_pos[i]; + rad_req->attr_used -= 1; + } + break; + + case RADIUS_ATTR_USER_NAME: + if (attr_len) { + TRACE_DEBUG(ANNOYING, "Found a User-Name attribute: '%.*s'", attr_len, attr_val); + un = attr_val; + un_len = attr_len; + } + break; + } } if (!got_id) { @@ -346,6 +401,99 @@ return EINVAL; } + + + /* + - If the RADIUS request contained a State attribute and the + prefix of the data is "Diameter/", the data following the + prefix contains the Diameter Origin-Host/Origin-Realm/Session- + Id. If no such attributes are present and the RADIUS command + is an Access-Request, a new Session-Id is created. The + Session-Id is included in the Session-Id AVP. + */ + + /* Add the Destination-Realm AVP */ + CHECK_FCT( fd_msg_avp_new ( cs->dict.Destination_Realm, 0, &avp ) ); + if (dr) { + value.os.data = (unsigned char *)dr; + value.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 => we use the local domain of this gateway */ + value.os.data = fd_g_config->cnf_diamrlm; + value.os.len = fd_g_config->cnf_diamrlm_len; + } else { + value.os.data = un + i; + value.os.len = un_len - i; + } + } + CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); + CHECK_FCT( fd_msg_avp_add ( *diam_fw, *session ? MSG_BRW_LAST_CHILD : MSG_BRW_FIRST_CHILD, avp) ); + + /* Add the Destination-Host if found */ + if (dh) { + CHECK_FCT( fd_msg_avp_new ( cs->dict.Destination_Host, 0, &avp ) ); + value.os.data = (unsigned char *)dh; + value.os.len = dh_len; + CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); + CHECK_FCT( fd_msg_avp_add ( *diam_fw, *session ? MSG_BRW_LAST_CHILD : MSG_BRW_FIRST_CHILD, avp) ); + } + + /* Create the session if it is not already done */ + if (*session == NULL) { + char * sess_str = NULL; + + if (si_len) { + /* We already have the Session-Id, just use it */ + CHECK_FCT( fd_sess_fromsid ( si, si_len, session, NULL) ); + } else { + /* Create a new Session-Id string */ + + char * fqdn; + char * realm; + + /* Get information on the RADIUS client */ + CHECK_FCT( rgw_clients_get_origin(cli, &fqdn, &realm) ); + + /* If we have a user name, create the new session with it */ + if (un) { + int len; + /* If not found, create a new Session-Id. The format is: {fqdn;hi32;lo32;username;diamid} */ + CHECK_MALLOC( sess_str = malloc(un_len + 1 /* ';' */ + fd_g_config->cnf_diamid_len + 1 /* '\0' */) ); + len = sprintf(sess_str, "%.*s;%s", un_len, un, fd_g_config->cnf_diamid); + CHECK_FCT( fd_sess_new(session, fqdn, sess_str, len) ); + free(sess_str); + } else { + /* We don't have enough information to create the Session-Id, the RADIUS message is probably invalid */ + TRACE_DEBUG(INFO, "RADIUS Access-Request does not contain a User-Name attribute, rejecting."); + return EINVAL; + } + } + + /* Now, add the Session-Id AVP at beginning of Diameter message */ + CHECK_FCT( fd_sess_getsid(*session, &sess_str) ); + + TRACE_DEBUG(FULL, "[auth.rgwx] Translating new message for session '%s'...", sess_str); + + /* Add the Session-Id AVP as first AVP */ + CHECK_FCT( fd_msg_avp_new ( cs->dict.Session_Id, 0, &avp ) ); + value.os.data = (unsigned char *)sess_str; + value.os.len = strlen(sess_str); + CHECK_FCT( fd_msg_avp_setvalue ( avp, &value ) ); + CHECK_FCT( fd_msg_avp_add ( *diam_fw, MSG_BRW_FIRST_CHILD, avp) ); + } + + /* Add the appropriate command code & Auth-Application-Id */ { struct msg_hdr * header = NULL; @@ -907,13 +1055,13 @@ CHECK_MALLOC(req_auth = malloc(16)); memcpy(req_auth, &rad_req->hdr->authenticator[0], 16); - CHECK_FCT( fd_sess_state_store( cs->sess_hdl, session, &req_auth ) ); + CHECK_FCT( fd_sess_state_store( cs->sess_hdl, *session, &req_auth ) ); } return 0; } -static int auth_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli ) +static int auth_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli, int * stateful ) { struct msg_hdr * hdr; struct avp *avp, *next, *avp_x, *avp_y, *asid, *aoh; diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgwx_debug.c --- a/extensions/app_radgw/rgwx_debug.c Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgwx_debug.c Thu Jul 01 15:47:34 2010 +0900 @@ -71,7 +71,7 @@ } /* Function called when a new RADIUS message is being converted to Diameter */ -static int debug_rad_req( struct rgwp_config * cs, struct session * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) +static int debug_rad_req( struct rgwp_config * cs, struct session ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) { TRACE_ENTRY("%p %p %p %p %p %p", cs, session, rad_req, rad_ans, diam_fw, cli); @@ -98,15 +98,24 @@ fd_msg_dump_walk(0, *diam_fw); } + if (!session || ! *session) { + fd_log_debug(" Diameter session: NULL pointer\n"); + } else { + char * str; + CHECK_FCT( fd_sess_getsid(*session, &str) ); + + fd_log_debug(" Diameter session: %s\n", str); + } + fd_log_debug("=========== Debug%s%s%s complete =============\n", cs ? " [" : "", cs ? (char *)cs : "", cs ? "]" : ""); return 0; } /* This one, when Diameter answer is converted to RADIUS */ -static int debug_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli ) +static int debug_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli, int * stateful ) { - TRACE_ENTRY("%p %p %p %p %p", cs, session, diam_ans, rad_fw, cli); + TRACE_ENTRY("%p %p %p %p %p %p", cs, session, diam_ans, rad_fw, cli, stateful); fd_log_debug("------------- RADIUS/Diameter Answer Debug%s%s%s -------------\n", cs ? " [" : "", cs ? (char *)cs : "", cs ? "]" : ""); diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgwx_echodrop.c --- a/extensions/app_radgw/rgwx_echodrop.c Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgwx_echodrop.c Thu Jul 01 15:47:34 2010 +0900 @@ -118,7 +118,7 @@ /* Handle attributes from a RADIUS request as specified in the configuration */ -static int ed_rad_req( struct rgwp_config * cs, struct session * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) +static int ed_rad_req( struct rgwp_config * cs, struct session ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) { size_t nattr_used = 0; int idx; @@ -127,7 +127,7 @@ struct fd_list *li; TRACE_ENTRY("%p %p %p %p %p %p", cs, session, rad_req, rad_ans, diam_fw, cli); - CHECK_PARAMS(cs && rad_req); + CHECK_PARAMS(cs && rad_req && session); /* For each attribute in the original message */ for (idx = 0; idx < rad_req->attr_used; idx++) { @@ -216,7 +216,13 @@ /* Save the echoed values in the session, if any */ if (!FD_IS_LIST_EMPTY(&echo_list)) { - CHECK_PARAMS(session); + CHECK_PARAMS_DO(*session, + { + fd_log_debug( "[echodrop.rgwx] The extension is configured to echo some attributes from this message, but no session object has been created for it (yet).\n" + " Please check your configuration file and include a session-generating extension BEFORE calling echodrop.rgwx to echo attributes.\n" + " Please use debug.rgwx to retrieve more information.\n" ); + return EINVAL; + } ); /* Move the values in a dynamically allocated list */ CHECK_MALLOC( li = malloc(sizeof(struct fd_list)) ); @@ -224,19 +230,19 @@ fd_list_move_end(li, &echo_list); /* Save the list in the session */ - CHECK_FCT( fd_sess_state_store( cs->sess_hdl, session, &li ) ); + CHECK_FCT( fd_sess_state_store( cs->sess_hdl, *session, &li ) ); } return 0; } /* Process an answer: add the ECHO attributes back, if any */ -static int ed_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli ) +static int ed_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli, int * stateful ) { int ret; struct fd_list * list = NULL; - TRACE_ENTRY("%p %p %p %p %p", cs, session, diam_ans, rad_fw, cli); + TRACE_ENTRY("%p %p %p %p %p %p", cs, session, diam_ans, rad_fw, cli, stateful); CHECK_PARAMS(cs); /* If there is no session associated, just give up */ diff -r 1ae3f2c28737 -r e203fc0c95e3 extensions/app_radgw/rgwx_sample.c --- a/extensions/app_radgw/rgwx_sample.c Wed Jun 30 11:24:15 2010 +0900 +++ b/extensions/app_radgw/rgwx_sample.c Thu Jul 01 15:47:34 2010 +0900 @@ -66,7 +66,7 @@ } /* This function is called on incoming RADIUS messages. It should handle (some) RADIUS data and store into the Diameter message. */ -static int sample_rad_req( struct rgwp_config * cs, struct session * session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) +static int sample_rad_req( struct rgwp_config * cs, struct session ** session, struct radius_msg * rad_req, struct radius_msg ** rad_ans, struct msg ** diam_fw, struct rgw_client * cli ) { TRACE_ENTRY("%p %p %p %p %p %p", cs, session, rad_req, rad_ans, diam_fw, cli); CHECK_PARAMS(cs); @@ -75,9 +75,9 @@ } /* This function is called when a Diameter answer is coming back. It should remove the AVPs and add the attributes in the RADIUS message. */ -static int sample_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli ) +static int sample_diam_ans( struct rgwp_config * cs, struct session * session, struct msg ** diam_ans, struct radius_msg ** rad_fw, struct rgw_client * cli, int * stateful ) { - TRACE_ENTRY("%p %p %p %p %p", cs, session, diam_ans, rad_fw, cli); + TRACE_ENTRY("%p %p %p %p %p %p", cs, session, diam_ans, rad_fw, cli, stateful); CHECK_PARAMS(cs); TRACE_DEBUG(INFO, "RADIUS/Diameter Sample plugin received a new Diameter answer."); return 0;