Changeset 516:1c2f5ee38039 in freeDiameter for extensions/app_radgw/rgw_clients.c
- Timestamp:
- Aug 27, 2010, 10:59:51 AM (14 years ago)
- Branch:
- default
- Phase:
- public
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
extensions/app_radgw/rgw_clients.c
r500 r516 40 40 #include "rgw.h" 41 41 42 #define REVERSE_DNS_SIZE_MAX 512 /* length of our buffer for reverse DNS */ 43 42 44 /* Ordered lists of clients. The order relationship is a memcmp on the address zone. 43 45 For same addresses, the port is compared. … … 67 69 /* The FQDN, realm, and optional aliases */ 68 70 int is_local; /* true if the RADIUS client runs on the same host -- we use Diameter Identity in that case */ 71 enum rgw_cli_type type; /* is it a proxy ? */ 69 72 char *fqdn; 70 73 size_t fqdn_len; … … 91 94 92 95 /* create a new rgw_client. the arguments are moved into the structure (to limit malloc & free calls). */ 93 static int client_create(struct rgw_client ** res, struct sockaddr ** ip_port, unsigned char ** key, size_t keylen )96 static int client_create(struct rgw_client ** res, struct sockaddr ** ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type ) 94 97 { 95 98 struct rgw_client *tmp = NULL; … … 117 120 memset(tmp, 0, sizeof(struct rgw_client)); 118 121 fd_list_init(&tmp->chain, NULL); 122 123 tmp->type = type; 119 124 120 125 if (loc) { … … 231 236 } 232 237 238 int rgw_clients_gettype(struct rgw_client * cli, enum rgw_cli_type *type) 239 { 240 CHECK_PARAMS( cli && type ); 241 *type = cli->type; 242 return 0; 243 } 244 245 233 246 int rgw_clients_search(struct sockaddr * ip_port, struct rgw_client ** ref) 234 247 { … … 304 317 /* Check that the NAS-IP-Adress or NAS-Identifier is coherent with the IP the packet was received from */ 305 318 /* Also update the client list of aliases if needed */ 306 /* NOTE: This function will require changes to allow RADIUS Proxy on the path... */ 307 int rgw_clients_check_origin(struct rgw_radius_msg_meta *msg, struct rgw_client *cli) 319 /* NOTE: This function does nothing if the client is a RADIUS Proxy... */ 320 /* Check if the message has a valid authenticator, and update the meta-data accordingly */ 321 int rgw_clients_auth_check(struct rgw_radius_msg_meta * msg, struct rgw_client * cli, uint8_t * req_auth) 322 { 323 unsigned char * key; 324 size_t keylen; 325 int count; 326 327 TRACE_ENTRY("%p %p %p", msg, cli, req_auth); 328 329 CHECK_PARAMS(msg && cli); 330 331 CHECK_FCT(rgw_clients_getkey(cli, &key, &keylen)); 332 333 count = radius_msg_count_attr(&msg->radius, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 0); 334 if (count > 1) { 335 TRACE_DEBUG(INFO, "Too many Message-Authenticator attributes (%d), discarding message.", count); 336 return EINVAL; 337 } 338 if (count == 0) { 339 TRACE_DEBUG(FULL, "Message does not contain a Message-Authenticator attributes."); 340 msg->valid_mac = 0; 341 } else { 342 if (radius_msg_verify_msg_auth( &msg->radius, key, keylen, req_auth )) { 343 TRACE_DEBUG(INFO, "Invalid Message-Authenticator received, discarding message."); 344 return EINVAL; 345 } 346 msg->valid_mac = 1; 347 } 348 349 return 0; 350 } 351 352 static struct dict_object * cache_orig_host = NULL; 353 static struct dict_object * cache_orig_realm = NULL; 354 static struct dict_object * cache_route_record = NULL; 355 356 int rgw_clients_init(void) 357 { 358 TRACE_ENTRY(); 359 CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &cache_orig_host, ENOENT) ); 360 CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &cache_orig_realm, ENOENT) ); 361 CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Route-Record", &cache_route_record, ENOENT) ); 362 return 0; 363 } 364 365 366 /* The following function checks if a RADIUS message contains a valid NAS identifier, and initializes an empty Diameter 367 message with the appropriate routing information */ 368 int rgw_clients_create_origin(struct rgw_radius_msg_meta *msg, struct rgw_client * cli, struct msg ** diam) 308 369 { 309 370 int idx; 371 int valid_nas_info = 0; 310 372 struct radius_attr_hdr *nas_ip = NULL, *nas_ip6 = NULL, *nas_id = NULL; 311 312 TRACE_ENTRY("%p %p", msg, cli); 313 CHECK_PARAMS(msg && cli && !msg->valid_nas_info ); 314 373 char * oh_str = NULL; 374 char * or_str = NULL; 375 char * rr_str = NULL; 376 char buf[REVERSE_DNS_SIZE_MAX]; /* to store DNS lookups results */ 377 378 struct avp *avp = NULL; 379 union avp_value avp_val; 380 381 TRACE_ENTRY("%p %p %p", msg, cli, diam); 382 CHECK_PARAMS(msg && cli && diam && (*diam == NULL)); 383 315 384 /* Find the relevant attributes, if any */ 316 385 for (idx = 0; idx < msg->radius.attr_used; idx++) { … … 336 405 if (!nas_ip && !nas_ip6 && !nas_id) { 337 406 TRACE_DEBUG(FULL, "The message does not contain any NAS identification attribute."); 338 goto end; 407 408 /* Get information on this peer */ 409 CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); 410 411 goto diameter; 339 412 } 340 413 … … 342 415 if (nas_ip && (cli->sa->sa_family == AF_INET) && !memcmp(nas_ip+1, &cli->sin->sin_addr, sizeof(struct in_addr))) { 343 416 TRACE_DEBUG(FULL, "NAS-IP-Address contains the same address as the message was received from."); 344 msg->valid_nas_info |= 1;417 valid_nas_info |= 1; 345 418 } 346 419 if (nas_ip6 && (cli->sa->sa_family == AF_INET6) && !memcmp(nas_ip6+1, &cli->sin6->sin6_addr, sizeof(struct in6_addr))) { 347 420 TRACE_DEBUG(FULL, "NAS-IPv6-Address contains the same address as the message was received from."); 348 msg->valid_nas_info |= 1; 349 } 350 351 /* If these conditions are not met, the message is probably forged (well, this might be false...) */ 352 if ((! msg->valid_nas_info) && (nas_ip || nas_ip6)) { 353 /* 354 In RADIUS it would be possible for a rogue NAS to forge the NAS-IP- 355 Address attribute value. Diameter/RADIUS translation agents MUST 356 check a received NAS-IP-Address or NAS-IPv6-Address attribute against 357 the source address of the RADIUS packet. If they do not match and 358 the Diameter/RADIUS translation agent does not know whether the 359 packet was sent by a RADIUS proxy or NAS (e.g., no Proxy-State 360 attribute), then by default it is assumed that the source address 361 corresponds to a RADIUS proxy, and that the NAS Address is behind 362 that proxy, potentially with some additional RADIUS proxies in 363 between. The Diameter/RADIUS translation agent MUST insert entries 364 in the Route-Record AVP corresponding to the apparent route. This 365 implies doing a reverse lookup on the source address and NAS-IP- 366 Address or NAS-IPv6-Address attributes to determine the corresponding 367 FQDNs. 368 369 If the source address and the NAS-IP-Address or NAS-IPv6-Address do 370 not match, and the Diameter/RADIUS translation agent knows that it is 371 talking directly to the NAS (e.g., there are no RADIUS proxies 372 between it and the NAS), then the error should be logged, and the 373 packet MUST be discarded. 374 375 Diameter agents and servers MUST check whether the NAS-IP-Address AVP 376 corresponds to an entry in the Route-Record AVP. This is done by 377 doing a reverse lookup (PTR RR) for the NAS-IP-Address to retrieve 378 the corresponding FQDN, and by checking for a match with the Route- 379 Record AVP. If no match is found, then an error is logged, but no 380 other action is taken. 381 */ 382 TRACE_DEBUG(INFO, "Message received with a NAS-IP-Address or NAS-IPv6-Address different from the sender's. Discarding..."); 383 return ENOTSUP; 384 } 385 386 /* Now check the nas_id, but only for non-local hosts */ 387 if (nas_id && (! cli->is_local) ) { 388 char * str; 421 valid_nas_info |= 2; 422 } 423 424 425 /* 426 In RADIUS it would be possible for a rogue NAS to forge the NAS-IP- 427 Address attribute value. Diameter/RADIUS translation agents MUST 428 check a received NAS-IP-Address or NAS-IPv6-Address attribute against 429 the source address of the RADIUS packet. If they do not match and 430 the Diameter/RADIUS translation agent does not know whether the 431 packet was sent by a RADIUS proxy or NAS (e.g., no Proxy-State 432 attribute), then by default it is assumed that the source address 433 corresponds to a RADIUS proxy, and that the NAS Address is behind 434 that proxy, potentially with some additional RADIUS proxies in 435 between. The Diameter/RADIUS translation agent MUST insert entries 436 in the Route-Record AVP corresponding to the apparent route. This 437 implies doing a reverse lookup on the source address and NAS-IP- 438 Address or NAS-IPv6-Address attributes to determine the corresponding 439 FQDNs. 440 441 If the source address and the NAS-IP-Address or NAS-IPv6-Address do 442 not match, and the Diameter/RADIUS translation agent knows that it is 443 talking directly to the NAS (e.g., there are no RADIUS proxies 444 between it and the NAS), then the error should be logged, and the 445 packet MUST be discarded. 446 447 Diameter agents and servers MUST check whether the NAS-IP-Address AVP 448 corresponds to an entry in the Route-Record AVP. This is done by 449 doing a reverse lookup (PTR RR) for the NAS-IP-Address to retrieve 450 the corresponding FQDN, and by checking for a match with the Route- 451 Record AVP. If no match is found, then an error is logged, but no 452 other action is taken. 453 */ 454 if (nas_ip || nas_ip6) { 455 if (!valid_nas_info) { 456 if (cli->type == RGW_CLI_NAS) { 457 TRACE_DEBUG(INFO, "Message received with a NAS-IP-Address or NAS-IPv6-Address different \nfrom the sender's. Please configure as Proxy if this is expected.\n Message discarded."); 458 return EINVAL; 459 } else { 460 /* the peer is configured as a proxy, so accept the message */ 461 sSS ss; 462 463 /* In that case, the cli will be stored as Route-Record and the NAS-IP-Address as origin */ 464 if (!cli->is_local) { 465 rr_str = cli->fqdn; 466 } 467 468 /* We must DNS-reverse the NAS-IP*-Address */ 469 memset(&ss, 0 , sizeof(sSS)); 470 if (nas_ip) { 471 sSA4 * sin = (sSA4 *)&ss; 472 sin->sin_family = AF_INET; 473 memcpy(&sin->sin_addr, nas_ip + 1, sizeof(struct in_addr)); 474 } else { 475 sSA6 * sin6 = (sSA6 *)&ss; 476 sin6->sin6_family = AF_INET6; 477 memcpy(&sin6->sin6_addr, nas_ip6 + 1, sizeof(struct in6_addr)); 478 } 479 CHECK_SYS_DO( getnameinfo( (sSA *)&ss, sSAlen(&ss), &buf[0], sizeof(buf), NULL, 0, NI_NAMEREQD), 480 { 481 TRACE_DEBUG(INFO, "The NAS-IP*-Address cannot be DNS reversed in order to create the Origin-Host AVP; rejecting the message (translation is impossible)."); 482 return EINVAL; 483 } ); 484 485 oh_str = &buf[0]; 486 or_str = strchr(oh_str, '.'); 487 if (or_str) { 488 or_str ++; /* move after the first dot */ 489 if (*or_str == '\0') 490 or_str = NULL; /* Discard this realm, we will use the local realm later */ 491 } 492 } 493 } else { 494 /* The attribute matches the source address, just use this in origin-host */ 495 CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); 496 } 497 498 goto diameter; /* we ignore the nas_id in that case */ 499 } 500 501 /* We don't have a NAS-IP*-Address attribute if we are here */ 502 if (cli->is_local) { 503 /* Simple: we use our own configuration */ 504 CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); 505 goto diameter; 506 } 507 508 /* At this point, we only have nas_id, and the client is not local */ 509 ASSERT(nas_id); 510 511 { 389 512 int found, ret; 390 513 struct addrinfo hint, *res, *ptr; … … 410 533 if ((cli->fqdn_len == (nas_id->length - sizeof(struct radius_attr_hdr))) 411 534 && (!strncasecmp((char *)(nas_id + 1), cli->fqdn, nas_id->length - sizeof(struct radius_attr_hdr)))) { 412 TRACE_DEBUG(FULL, "NAS-Identifier contains the fqdn of the NAS");535 TRACE_DEBUG(FULL, "NAS-Identifier contains the fqdn of the client"); 413 536 found = 1; 414 537 } else { … … 424 547 425 548 if (found) { 426 msg->valid_nas_info |= 2; 427 goto end; 428 } 429 430 /* copy the identifier, we try to DNS resolve it */ 431 CHECK_MALLOC( str = malloc(nas_id->length - sizeof(struct radius_attr_hdr) + 1) ); 432 memcpy(str, nas_id + 1, nas_id->length - sizeof(struct radius_attr_hdr)); 433 str[nas_id->length - sizeof(struct radius_attr_hdr)] = '\0'; 549 /* The NAS-Identifier matches the source IP */ 550 CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); 551 552 goto diameter; 553 } 554 555 /* Attempt DNS resolution of the identifier */ 556 ASSERT( nas_id->length - sizeof(struct radius_attr_hdr) < sizeof(buf) ); 557 memcpy(buf, nas_id + 1, nas_id->length - sizeof(struct radius_attr_hdr)); 558 buf[nas_id->length - sizeof(struct radius_attr_hdr)] = '\0'; 434 559 435 560 /* Now check if this alias is valid for this peer */ 436 561 memset(&hint, 0, sizeof(hint)); 437 hint.ai_family = cli->sa->sa_family;438 562 hint.ai_flags = AI_CANONNAME; 439 ret = getaddrinfo( str, NULL, &hint, &res);563 ret = getaddrinfo(buf, NULL, &hint, &res); 440 564 if (ret == 0) { 441 /* The name was resolved correctly, it must match the IP of the client: */ 565 strncpy(buf, res->ai_canonname, sizeof(buf)); 566 /* The name was resolved correctly, does it match the IP of the client? */ 442 567 for (ptr = res; ptr != NULL; ptr = ptr->ai_next) { 443 568 if (cli->sa->sa_family != ptr->ai_family) … … 446 571 continue; 447 572 448 /* It matches: the alias is valid */449 573 found = 1; 450 574 break; … … 453 577 454 578 if (!found) { 455 TRACE_DEBUG(INFO, "The NAS-Identifier value '%s' resolves to a different IP from the NAS's, discarding the message.", str); 456 free(str); 457 return EINVAL; 579 if (cli->type == RGW_CLI_NAS) { 580 TRACE_DEBUG(INFO, "The NAS-Identifier value '%.*s' resolves to a different IP than the client's, discarding the message. \nConfigure this client as a Proxy if this message should be valid.", 581 nas_id->length - sizeof(struct radius_attr_hdr), nas_id + 1); 582 return EINVAL; 583 } else { 584 /* This identifier matches a different IP, assume it is a proxied message */ 585 if (!cli->is_local) { 586 rr_str = cli->fqdn; 587 } 588 oh_str = &buf[0]; /* The canonname resolved */ 589 or_str = strchr(oh_str, '.'); 590 if (or_str) { 591 or_str ++; /* move after the first dot */ 592 if (*or_str == '\0') 593 or_str = NULL; /* Discard this realm, we will use the local realm later */ 594 } 595 } 596 } else { 597 /* It is a valid alias, save it */ 598 CHECK_MALLOC( cli->aliases = realloc(cli->aliases, (cli->aliases_nb + 1) * sizeof(char *)) ); 599 CHECK_MALLOC( cli->aliases[cli->aliases_nb + 1] = malloc( 1 + nas_id->length - sizeof(struct radius_attr_hdr) )); 600 memcpy( cli->aliases[cli->aliases_nb + 1], nas_id + 1, nas_id->length - sizeof(struct radius_attr_hdr)); 601 *(cli->aliases[cli->aliases_nb + 1] + nas_id->length - sizeof(struct radius_attr_hdr)) = '\0'; 602 cli->aliases_nb ++; 603 TRACE_DEBUG(FULL, "Saved valid alias for client: '%s' -> '%s'", cli->aliases[cli->aliases_nb + 1], cli->fqdn); 604 CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); 458 605 } 459 606 } else { 460 607 /* Error resolving the name */ 461 TRACE_DEBUG(INFO, "Error while resolving NAS-Identifier value '%s': %s. Ignoring...", str, gai_strerror(ret)); 462 } 463 464 /* It is a valid alias, save it */ 465 CHECK_MALLOC( cli->aliases = realloc(cli->aliases, (cli->aliases_nb + 1) * sizeof(char *)) ); 466 cli->aliases[cli->aliases_nb + 1] = str; 467 cli->aliases_nb ++; 468 TRACE_DEBUG(FULL, "Saved valid alias for client: '%s' -> '%s'", str, cli->fqdn); 469 msg->valid_nas_info |= 2; 470 } 471 end: 608 TRACE_DEBUG(INFO, "NAS-Identifier '%s' cannot be resolved: %s. Ignoring...", buf, gai_strerror(ret)); 609 /* Assume this is a valid identifier for the client */ 610 CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); 611 } 612 } 613 614 /* Now, let's create the empty Diameter message with Origin-Host, -Realm, and Route-Record if needed. */ 615 diameter: 616 ASSERT(oh_str); /* If it is not defined here, there is a bug... */ 617 if (!or_str) 618 or_str = fd_g_config->cnf_diamrlm; /* Use local realm in that case */ 619 620 /* Create an empty Diameter message so that extensions can store their AVPs */ 621 CHECK_FCT( fd_msg_new ( NULL, MSGFL_ALLOC_ETEID, diam ) ); 622 623 /* Add the Origin-Host as next AVP */ 624 CHECK_FCT( fd_msg_avp_new ( cache_orig_host, 0, &avp ) ); 625 memset(&avp_val, 0, sizeof(avp_val)); 626 avp_val.os.data = (unsigned char *)oh_str; 627 avp_val.os.len = strlen(oh_str); 628 CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); 629 CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); 630 631 /* Add the Origin-Realm as next AVP */ 632 CHECK_FCT( fd_msg_avp_new ( cache_orig_realm, 0, &avp ) ); 633 memset(&avp_val, 0, sizeof(avp_val)); 634 avp_val.os.data = (unsigned char *)or_str; 635 avp_val.os.len = strlen(or_str); 636 CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); 637 CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); 638 639 if (rr_str) { 640 CHECK_FCT( fd_msg_avp_new ( cache_route_record, 0, &avp ) ); 641 memset(&avp_val, 0, sizeof(avp_val)); 642 avp_val.os.data = (unsigned char *)rr_str; 643 avp_val.os.len = strlen(rr_str); 644 CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); 645 CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); 646 } 647 648 /* Done! */ 472 649 return 0; 473 650 } … … 508 685 } 509 686 510 int rgw_clients_add( struct sockaddr * ip_port, unsigned char ** key, size_t keylen )687 int rgw_clients_add( struct sockaddr * ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type ) 511 688 { 512 689 struct rgw_client * prev = NULL, *new = NULL; … … 517 694 CHECK_PARAMS( ip_port && key && *key && keylen ); 518 695 CHECK_PARAMS( (ip_port->sa_family == AF_INET) || (ip_port->sa_family == AF_INET6) ); 696 CHECK_PARAMS( (type == RGW_CLI_NAS) || (type == RGW_CLI_PXY) ); 519 697 520 698 /* Dump the entry in debug mode */ 521 699 if (TRACE_BOOL(FULL + 1 )) { 522 TRACE_DEBUG(FULL, "Adding client:");700 TRACE_DEBUG(FULL, "Adding %s:", (type == RGW_CLI_NAS) ? "NAS" : "PROXY" ); 523 701 TRACE_DEBUG_sSA(FULL, "\tIP : ", ip_port, NI_NUMERICHOST | NI_NUMERICSERV, "" ); 524 702 TRACE_DEBUG_BUFFER(FULL, "\tKey: [", *key, keylen, "]" ); … … 532 710 if (ret == ENOENT) { 533 711 /* No duplicate found, Ok to add */ 534 CHECK_FCT_DO( ret = client_create( &new, &ip_port, key, keylen ), goto end );712 CHECK_FCT_DO( ret = client_create( &new, &ip_port, key, keylen, type ), goto end ); 535 713 fd_list_insert_after(&prev->chain, &new->chain); 536 714 new->refcount++; … … 541 719 if (ret == EEXIST) { 542 720 /* Check if the key is the same, then skip or return an error */ 543 if ((keylen == prev->key.len ) && ( ! memcmp(*key, prev->key.data, keylen) ) ) {721 if ((keylen == prev->key.len ) && ( ! memcmp(*key, prev->key.data, keylen) ) && (type == prev->type)) { 544 722 TRACE_DEBUG(INFO, "Skipping duplicate client description"); 545 723 ret = 0; … … 548 726 549 727 fd_log_debug("ERROR: Conflicting RADIUS clients descriptions!\n"); 550 TRACE_DEBUG(NONE, "Previous entry: ");728 TRACE_DEBUG(NONE, "Previous entry: %s", (prev->type == RGW_CLI_NAS) ? "NAS" : "PROXY"); 551 729 TRACE_DEBUG_sSA(NONE, "\tIP : ", prev->sa, NI_NUMERICHOST | NI_NUMERICSERV, "" ); 552 730 TRACE_DEBUG_BUFFER(NONE, "\tKey: [", prev->key.data, prev->key.len, "]" ); 553 TRACE_DEBUG(NONE, "Conflicting entry: ");731 TRACE_DEBUG(NONE, "Conflicting entry: %s", (type == RGW_CLI_NAS) ? "NAS" : "PROXY"); 554 732 TRACE_DEBUG_sSA(NONE, "\tIP : ", ip_port, NI_NUMERICHOST | NI_NUMERICSERV, "" ); 555 733 TRACE_DEBUG_BUFFER(NONE, "\tKey: [", *key, keylen, "]" );
Note: See TracChangeset
for help on using the changeset viewer.