/** Send a fake request to a virtual server, managing the eap_session_t of the child * * If eap_session_t has a child, inject that into the fake request. * * If after the request has run, the child eap_session_t is no longer present, * we assume it has been freed, and fixup the parent eap_session_t. * * If the eap_session_t pointer changes, this is considered a fatal error. * * @param request the current (real) request. * @param eap_session representing the outer eap method. * @param fake request we're going to send. * @param virtual_server The default virtual server to send the request to. * @return the rcode of the last executed section in the virtual server. */ rlm_rcode_t eap_virtual_server(REQUEST *request, REQUEST *fake, eap_session_t *eap_session, char const *virtual_server) { eap_session_t *inner_eap; rlm_rcode_t rcode; VALUE_PAIR *vp; vp = fr_pair_find_by_num(request->config, 0, PW_VIRTUAL_SERVER, TAG_ANY); fake->server = vp ? vp->vp_strvalue : virtual_server; if (fake->server) { RDEBUG2("Sending tunneled request to %s", fake->server); } else { RDEBUG2("Sending tunnelled request"); } /* * Add a previously recorded inner eap_session_t back * to the request. This in theory allows infinite * nesting, but this is probably limited somewhere. */ if (eap_session->child) { RDEBUG4("Adding eap_session_t %p to fake request", eap_session->child); request_data_add(fake, NULL, REQUEST_DATA_EAP_SESSION, eap_session->child, false, false, false); } rcode = rad_virtual_server(fake); inner_eap = request_data_get(fake, NULL, REQUEST_DATA_EAP_SESSION); if (inner_eap) { if (!eap_session->child || (eap_session->child != inner_eap)) { RDEBUG4("Binding lifetime of child eap_session %p to parent eap_session %p", inner_eap, eap_session); fr_talloc_link_ctx(eap_session, inner_eap); eap_session->child = inner_eap; } else { RDEBUG4("Got eap_session_t %p back unmolested", eap_session->child); } /* * Assume the inner server freed the * eap_session_t and remove our reference to it. * * If it didn't actually free the child (due to error) * the call to talloc_link_ctx (above) ensures it will * be freed when the parent is. */ } else if (eap_session->child) { RDEBUG4("Inner server freed eap_session %p", eap_session->child); eap_session->child = NULL; } return rcode; }
/* * The main guy. */ int main(int argc, char *argv[]) { int rcode = EXIT_SUCCESS; int argval; const char *input_file = NULL; const char *output_file = NULL; const char *filter_file = NULL; FILE *fp; REQUEST *request = NULL; VALUE_PAIR *vp; VALUE_PAIR *filter_vps = NULL; /* * If the server was built with debugging enabled always install * the basic fatal signal handlers. */ #ifndef NDEBUG if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) { fr_perror("unittest"); exit(EXIT_FAILURE); } #endif if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL) progname = argv[0]; else progname++; debug_flag = 0; set_radius_dir(NULL, RADIUS_DIR); /* * Ensure that the configuration is initialized. */ memset(&main_config, 0, sizeof(main_config)); main_config.myip.af = AF_UNSPEC; main_config.port = 0; main_config.name = "radiusd"; /* * The tests should have only IPs, not host names. */ fr_hostname_lookups = false; /* * We always log to stdout. */ fr_log_fp = stdout; default_log.dst = L_DST_STDOUT; default_log.fd = STDOUT_FILENO; /* Process the options. */ while ((argval = getopt(argc, argv, "d:D:f:hi:mMn:o:xX")) != EOF) { switch (argval) { case 'd': set_radius_dir(NULL, optarg); break; case 'D': main_config.dictionary_dir = talloc_typed_strdup(NULL, optarg); break; case 'f': filter_file = optarg; break; case 'h': usage(0); break; case 'i': input_file = optarg; break; case 'm': main_config.debug_memory = true; break; case 'M': memory_report = true; main_config.debug_memory = true; break; case 'n': main_config.name = optarg; break; case 'o': output_file = optarg; break; case 'X': debug_flag += 2; main_config.log_auth = true; main_config.log_auth_badpass = true; main_config.log_auth_goodpass = true; break; case 'x': debug_flag++; break; default: usage(1); break; } } if (debug_flag) { version(); } fr_debug_flag = debug_flag; /* * Mismatch between the binary and the libraries it depends on */ if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) { fr_perror("radiusd"); exit(EXIT_FAILURE); } /* Read the configuration files, BEFORE doing anything else. */ if (main_config_init() < 0) { rcode = EXIT_FAILURE; goto finish; } /* * Load the modules */ if (modules_init(main_config.config) < 0) { rcode = EXIT_FAILURE; goto finish; } fr_state_init(); /* Set the panic action (if required) */ if (main_config.panic_action && #ifndef NDEBUG !getenv("PANIC_ACTION") && #endif (fr_fault_setup(main_config.panic_action, argv[0]) < 0)) { rcode = EXIT_FAILURE; goto finish; } setlinebuf(stdout); /* unbuffered output */ if (!input_file || (strcmp(input_file, "-") == 0)) { fp = stdin; } else { fp = fopen(input_file, "r"); if (!fp) { fprintf(stderr, "Failed reading %s: %s\n", input_file, strerror(errno)); rcode = EXIT_FAILURE; goto finish; } } /* * Grab the VPs from stdin, or from the file. */ request = request_setup(fp); if (!request) { fprintf(stderr, "Failed reading input: %s\n", fr_strerror()); rcode = EXIT_FAILURE; goto finish; } /* * No filter file, OR there's no more input, OR we're * reading from a file, and it's different from the * filter file. */ if (!filter_file || filedone || ((input_file != NULL) && (strcmp(filter_file, input_file) != 0))) { if (output_file) { fclose(fp); fp = NULL; } filedone = false; } /* * There is a filter file. If necessary, open it. If we * already are reading it via "input_file", then we don't * need to re-open it. */ if (filter_file) { if (!fp) { fp = fopen(filter_file, "r"); if (!fp) { fprintf(stderr, "Failed reading %s: %s\n", filter_file, strerror(errno)); rcode = EXIT_FAILURE; goto finish; } } if (readvp2(request, &filter_vps, fp, &filedone) < 0) { fprintf(stderr, "Failed reading attributes from %s: %s\n", filter_file, fr_strerror()); rcode = EXIT_FAILURE; goto finish; } /* * FIXME: loop over input packets. */ fclose(fp); } rad_virtual_server(request); if (!output_file || (strcmp(output_file, "-") == 0)) { fp = stdout; } else { fp = fopen(output_file, "w"); if (!fp) { fprintf(stderr, "Failed writing %s: %s\n", output_file, strerror(errno)); exit(EXIT_FAILURE); } } print_packet(fp, request->reply); if (output_file) fclose(fp); /* * Update the list with the response type. */ vp = radius_paircreate(request->reply, &request->reply->vps, PW_RESPONSE_PACKET_TYPE, 0); vp->vp_integer = request->reply->code; { VALUE_PAIR const *failed[2]; if (filter_vps && !pairvalidate(failed, filter_vps, request->reply->vps)) { pairvalidate_debug(request, failed); fr_perror("Output file %s does not match attributes in filter %s", output_file ? output_file : input_file, filter_file); rcode = EXIT_FAILURE; goto finish; } } INFO("Exiting normally"); finish: talloc_free(request); /* * Detach any modules. */ modules_free(); xlat_free(); /* modules may have xlat's */ fr_state_delete(); /* * Free the configuration items. */ main_config_free(); if (memory_report) { INFO("Allocated memory at time of report:"); fr_log_talloc_report(NULL); } return rcode; }
/* * The main guy. */ int main(int argc, char *argv[]) { int rcode = EXIT_SUCCESS; int argval; const char *input_file = NULL; const char *output_file = NULL; const char *filter_file = NULL; FILE *fp; REQUEST *request; VALUE_PAIR *filter_vps = NULL; if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL) progname = argv[0]; else progname++; debug_flag = 0; radius_dir = talloc_strdup(NULL, RADIUS_DIR); /* * Ensure that the configuration is initialized. */ memset(&mainconfig, 0, sizeof(mainconfig)); mainconfig.myip.af = AF_UNSPEC; mainconfig.port = -1; mainconfig.name = "radiusd"; /* * We always log to stdout. */ fr_log_fp = stdout; default_log.dest = L_DST_STDOUT; default_log.fd = STDOUT_FILENO; /* Process the options. */ while ((argval = getopt(argc, argv, "d:D:f:hi:mMn:o:xX")) != EOF) { switch(argval) { case 'd': if (radius_dir) { rad_const_free(radius_dir); } radius_dir = talloc_strdup(NULL, optarg); break; case 'D': mainconfig.dictionary_dir = talloc_strdup(NULL, optarg); break; case 'f': filter_file = optarg; break; case 'h': usage(0); break; case 'i': input_file = optarg; break; case 'm': mainconfig.debug_memory = 1; break; case 'M': memory_report = 1; mainconfig.debug_memory = 1; break; case 'n': mainconfig.name = optarg; break; case 'o': output_file = optarg; break; case 'X': debug_flag += 2; mainconfig.log_auth = true; mainconfig.log_auth_badpass = true; mainconfig.log_auth_goodpass = true; break; case 'x': debug_flag++; break; default: usage(1); break; } } if (memory_report) { talloc_enable_null_tracking(); #ifdef WITH_VERIFY_PTR talloc_set_abort_fn(die_horribly); #endif } talloc_set_log_fn(log_talloc); if (debug_flag) { version(); } fr_debug_flag = debug_flag; /* Read the configuration files, BEFORE doing anything else. */ if (read_mainconfig(0) < 0) { exit(EXIT_FAILURE); } setlinebuf(stdout); /* unbuffered output */ if (!input_file || (strcmp(input_file, "-") == 0)) { fp = stdin; } else { fp = fopen(input_file, "r"); if (!fp) { fprintf(stderr, "Failed reading %s: %s\n", input_file, strerror(errno)); exit(EXIT_FAILURE); } } /* * Grab the VPs from stdin, or from the file. */ request = request_setup(fp); if (!request) { fprintf(stderr, "Failed reading input: %s\n", fr_strerror()); exit(EXIT_FAILURE); } /* * No filter file, OR there's no more input, OR we're * reading from a file, and it's different from the * filter file. */ if (!filter_file || filedone || ((input_file != NULL) && (strcmp(filter_file, input_file) != 0))) { if (output_file) { fclose(fp); fp = NULL; } filedone = 0; } /* * There is a filter file. If necessary, open it. If we * already are reading it via "input_file", then we don't * need to re-open it. */ if (filter_file) { if (!fp) { fp = fopen(filter_file, "r"); if (!fp) { fprintf(stderr, "Failed reading %s: %s\n", filter_file, strerror(errno)); exit(EXIT_FAILURE); } } filter_vps = readvp2(request, fp, &filedone, "radiusd"); if (!filter_vps) { fprintf(stderr, "Failed reading attributes from %s: %s\n", filter_file, fr_strerror()); exit(EXIT_FAILURE); } /* * FIXME: loop over input packets. */ fclose(fp); } rad_virtual_server(request); if (!output_file || (strcmp(output_file, "-") == 0)) { fp = stdout; } else { fp = fopen(output_file, "w"); if (!fp) { fprintf(stderr, "Failed writing %s: %s\n", output_file, strerror(errno)); exit(EXIT_FAILURE); } } print_packet(fp, request->reply); if (output_file) fclose(fp); if (filter_vps && !pairvalidate(filter_vps, request->reply->vps)) { fprintf(stderr, "Output file %s does not match attributes in filter %s\n", output_file, filter_file); exit(EXIT_FAILURE); } talloc_free(request); INFO("Exiting normally."); /* * Detach any modules. */ detach_modules(); xlat_free(); /* modules may have xlat's */ /* * Free the configuration items. */ free_mainconfig(); rad_const_free(radius_dir); if (memory_report) { INFO("Allocated memory at time of report:"); log_talloc_report(NULL); } return rcode; }
/* * Do authentication, by letting EAP-TLS do most of the work. */ static int CC_HINT(nonnull) mod_authenticate(void *type_arg, eap_handler_t *handler) { fr_tls_status_t status; tls_session_t *tls_session = (tls_session_t *) handler->opaque; REQUEST *request = handler->request; rlm_eap_tls_t *inst; inst = type_arg; RDEBUG2("Authenticate"); status = eaptls_process(handler); RDEBUG2("eaptls_process returned %d\n", status); switch (status) { /* * EAP-TLS handshake was successful, return an * EAP-TLS-Success packet here. * * If a virtual server was configured, check that * it accepts the certificates, too. */ case FR_TLS_SUCCESS: if (inst->virtual_server) { VALUE_PAIR *vp; REQUEST *fake; /* create a fake request */ fake = request_alloc_fake(request); rad_assert(!fake->packet->vps); fake->packet->vps = paircopy(fake->packet, request->packet->vps); /* set the virtual server to use */ if ((vp = pairfind(request->config_items, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { fake->server = vp->vp_strvalue; } else { fake->server = inst->virtual_server; } RDEBUG("Processing EAP-TLS Certificate check:"); debug_pair_list(fake->packet->vps); RDEBUG("server %s {", fake->server); rad_virtual_server(fake); RDEBUG("} # server %s", fake->server); /* copy the reply vps back to our reply */ pairfilter(request->reply, &request->reply->vps, &fake->reply->vps, 0, 0, TAG_ANY); /* reject if virtual server didn't return accept */ if (fake->reply->code != PW_CODE_AUTHENTICATION_ACK) { RDEBUG2("Certificates were rejected by the virtual server"); request_free(&fake); eaptls_fail(handler, 0); return 0; } request_free(&fake); /* success */ } break; /* * The TLS code is still working on the TLS * exchange, and it's a valid TLS request. * do nothing. */ case FR_TLS_HANDLED: return 1; /* * Handshake is done, proceed with decoding tunneled * data. */ case FR_TLS_OK: RDEBUG2("Received unexpected tunneled data after successful handshake"); #ifndef NDEBUG if ((debug_flag > 2) && fr_log_fp) { unsigned int i; unsigned int data_len; unsigned char buffer[1024]; data_len = (tls_session->record_minus)(&tls_session->dirty_in, buffer, sizeof(buffer)); DEBUG(" Tunneled data (%u bytes)", data_len); for (i = 0; i < data_len; i++) { if ((i & 0x0f) == 0x00) fprintf(fr_log_fp, " %x: ", i); if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); fprintf(fr_log_fp, "%02x ", buffer[i]); } fprintf(fr_log_fp, "\n"); } #endif eaptls_fail(handler, 0); return 0; break; /* * Anything else: fail. * * Also, remove the session from the cache so that * the client can't re-use it. */ default: tls_fail(tls_session); return 0; } /* * Success: Automatically return MPPE keys. */ return eaptls_success(handler, 0); }
/* * The main guy. */ int main(int argc, char *argv[]) { int rcode = EXIT_SUCCESS; int argval; const char *input_file = NULL; const char *output_file = NULL; const char *filter_file = NULL; FILE *fp; REQUEST *request = NULL; VALUE_PAIR *vp; VALUE_PAIR *filter_vps = NULL; bool xlat_only = false; fr_state_tree_t *state = NULL; fr_talloc_fault_setup(); /* * If the server was built with debugging enabled always install * the basic fatal signal handlers. */ #ifndef NDEBUG if (fr_fault_setup(getenv("PANIC_ACTION"), argv[0]) < 0) { fr_perror("unittest"); exit(EXIT_FAILURE); } #endif rad_debug_lvl = 0; set_radius_dir(NULL, RADIUS_DIR); /* * Ensure that the configuration is initialized. */ memset(&main_config, 0, sizeof(main_config)); main_config.name = "unittest"; /* * The tests should have only IPs, not host names. */ fr_hostname_lookups = false; /* * We always log to stdout. */ fr_log_fp = stdout; default_log.dst = L_DST_STDOUT; default_log.fd = STDOUT_FILENO; /* Process the options. */ while ((argval = getopt(argc, argv, "d:D:f:hi:mMn:o:O:xX")) != EOF) { switch (argval) { case 'd': set_radius_dir(NULL, optarg); break; case 'D': main_config.dictionary_dir = talloc_typed_strdup(NULL, optarg); break; case 'f': filter_file = optarg; break; case 'h': usage(0); break; case 'i': input_file = optarg; break; case 'm': main_config.debug_memory = true; break; case 'M': memory_report = true; main_config.debug_memory = true; break; case 'n': main_config.name = optarg; break; case 'o': output_file = optarg; break; case 'O': if (strcmp(optarg, "xlat_only") == 0) { xlat_only = true; break; } fprintf(stderr, "Unknown option '%s'\n", optarg); exit(EXIT_FAILURE); case 'X': rad_debug_lvl += 2; main_config.log_auth = true; main_config.log_auth_badpass = true; main_config.log_auth_goodpass = true; break; case 'x': rad_debug_lvl++; break; default: usage(1); break; } } if (rad_debug_lvl) version_print(); fr_debug_lvl = rad_debug_lvl; /* * Mismatch between the binary and the libraries it depends on */ if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) { fr_perror("%s", main_config.name); exit(EXIT_FAILURE); } /* * Initialising OpenSSL once, here, is safer than having individual modules do it. */ #ifdef HAVE_OPENSSL_CRYPTO_H if (tls_global_init() < 0) { rcode = EXIT_FAILURE; goto finish; } #endif if (xlat_register(NULL, "poke", xlat_poke, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN) < 0) { rcode = EXIT_FAILURE; goto finish; } if (map_proc_register(NULL, "test-fail", mod_map_proc, NULL, NULL, 0) < 0) { rcode = EXIT_FAILURE; goto finish; } /* Read the configuration files, BEFORE doing anything else. */ if (main_config_init() < 0) { exit_failure: rcode = EXIT_FAILURE; goto finish; } /* * Setup dummy virtual server */ cf_section_add(main_config.config, cf_section_alloc(main_config.config, "server", "unit_test")); /* * Initialize Auth-Type, etc. in the virtual servers * before loading the modules. Some modules need those * to be defined. */ if (virtual_servers_bootstrap(main_config.config) < 0) goto exit_failure; /* * Bootstrap the modules. This links to them, and runs * their "bootstrap" routines. * * After this step, all dynamic attributes, xlats, etc. are defined. */ if (modules_bootstrap(main_config.config) < 0) exit(EXIT_FAILURE); /* * Load the modules */ if (modules_init(main_config.config) < 0) goto exit_failure; /* * And then load the virtual servers. */ if (virtual_servers_init(main_config.config) < 0) goto exit_failure; state = fr_state_tree_init(NULL, main_config.max_requests * 2, 10); /* * Set the panic action (if required) */ { char const *panic_action = NULL; panic_action = getenv("PANIC_ACTION"); if (!panic_action) panic_action = main_config.panic_action; if (panic_action && (fr_fault_setup(panic_action, argv[0]) < 0)) { fr_perror("%s", main_config.name); exit(EXIT_FAILURE); } } setlinebuf(stdout); /* unbuffered output */ if (!input_file || (strcmp(input_file, "-") == 0)) { fp = stdin; } else { fp = fopen(input_file, "r"); if (!fp) { fprintf(stderr, "Failed reading %s: %s\n", input_file, strerror(errno)); rcode = EXIT_FAILURE; goto finish; } } /* * For simplicity, read xlat's. */ if (xlat_only) { if (!do_xlats(input_file, fp)) rcode = EXIT_FAILURE; if (input_file) fclose(fp); goto finish; } /* * Grab the VPs from stdin, or from the file. */ request = request_setup(fp); if (!request) { fprintf(stderr, "Failed reading input: %s\n", fr_strerror()); rcode = EXIT_FAILURE; goto finish; } /* * No filter file, OR there's no more input, OR we're * reading from a file, and it's different from the * filter file. */ if (!filter_file || filedone || ((input_file != NULL) && (strcmp(filter_file, input_file) != 0))) { if (output_file) { fclose(fp); fp = NULL; } filedone = false; } /* * There is a filter file. If necessary, open it. If we * already are reading it via "input_file", then we don't * need to re-open it. */ if (filter_file) { if (!fp) { fp = fopen(filter_file, "r"); if (!fp) { fprintf(stderr, "Failed reading %s: %s\n", filter_file, strerror(errno)); rcode = EXIT_FAILURE; goto finish; } } if (fr_pair_list_afrom_file(request, &filter_vps, fp, &filedone) < 0) { fprintf(stderr, "Failed reading attributes from %s: %s\n", filter_file, fr_strerror()); rcode = EXIT_FAILURE; goto finish; } /* * FIXME: loop over input packets. */ fclose(fp); } rad_virtual_server(request); if (!output_file || (strcmp(output_file, "-") == 0)) { fp = stdout; } else { fp = fopen(output_file, "w"); if (!fp) { fprintf(stderr, "Failed writing %s: %s\n", output_file, strerror(errno)); exit(EXIT_FAILURE); } } print_packet(fp, request->reply); if (output_file) fclose(fp); /* * Update the list with the response type. */ vp = radius_pair_create(request->reply, &request->reply->vps, PW_RESPONSE_PACKET_TYPE, 0); vp->vp_integer = request->reply->code; { VALUE_PAIR const *failed[2]; if (filter_vps && !fr_pair_validate(failed, filter_vps, request->reply->vps)) { fr_pair_validate_debug(request, failed); fr_perror("Output file %s does not match attributes in filter %s (%s)", output_file ? output_file : input_file, filter_file, fr_strerror()); rcode = EXIT_FAILURE; goto finish; } } INFO("Exiting normally"); finish: talloc_free(request); talloc_free(state); /* * Free the configuration items. */ main_config_free(); /* * Detach any modules. */ modules_free(); xlat_unregister(NULL, "poke", xlat_poke); xlat_free(); /* modules may have xlat's */ if (memory_report) { INFO("Allocated memory at time of report:"); fr_log_talloc_report(NULL); } return rcode; }
/* * Process the "diameter" contents of the tunneled data. */ PW_CODE eapttls_process(eap_handler_t *handler, tls_session_t *tls_session) { PW_CODE code = PW_CODE_ACCESS_REJECT; rlm_rcode_t rcode; REQUEST *fake; VALUE_PAIR *vp; ttls_tunnel_t *t; uint8_t const *data; size_t data_len; REQUEST *request = handler->request; chbind_packet_t *chbind; /* * Just look at the buffer directly, without doing * record_minus. */ data_len = tls_session->clean_out.used; tls_session->clean_out.used = 0; data = tls_session->clean_out.data; t = (ttls_tunnel_t *) tls_session->opaque; /* * If there's no data, maybe this is an ACK to an * MS-CHAP2-Success. */ if (data_len == 0) { if (t->authenticated) { RDEBUG("Got ACK, and the user was already authenticated"); return PW_CODE_ACCESS_ACCEPT; } /* else no session, no data, die. */ /* * FIXME: Call SSL_get_error() to see what went * wrong. */ RDEBUG2("SSL_read Error"); return PW_CODE_ACCESS_REJECT; } #ifndef NDEBUG if ((rad_debug_lvl > 2) && fr_log_fp) { size_t i; for (i = 0; i < data_len; i++) { if ((i & 0x0f) == 0) fprintf(fr_log_fp, " TTLS tunnel data in %04x: ", (int) i); fprintf(fr_log_fp, "%02x ", data[i]); if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); } if ((data_len & 0x0f) != 0) fprintf(fr_log_fp, "\n"); } #endif if (!diameter_verify(request, data, data_len)) { return PW_CODE_ACCESS_REJECT; } /* * Allocate a fake REQUEST structure. */ fake = request_alloc_fake(request); rad_assert(!fake->packet->vps); /* * Add the tunneled attributes to the fake request. */ fake->packet->vps = diameter2vp(request, fake, tls_session->ssl, data, data_len); if (!fake->packet->vps) { talloc_free(fake); return PW_CODE_ACCESS_REJECT; } /* * Tell the request that it's a fake one. */ pair_make_request("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ); RDEBUG("Got tunneled request"); rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); /* * Update other items in the REQUEST data structure. */ fake->username = fr_pair_find_by_num(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); fake->password = fr_pair_find_by_num(fake->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); /* * No User-Name, try to create one from stored data. */ if (!fake->username) { /* * No User-Name in the stored data, look for * an EAP-Identity, and pull it out of there. */ if (!t->username) { vp = fr_pair_find_by_num(fake->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY); if (vp && (vp->vp_length >= EAP_HEADER_LEN + 2) && (vp->vp_strvalue[0] == PW_EAP_RESPONSE) && (vp->vp_strvalue[EAP_HEADER_LEN] == PW_EAP_IDENTITY) && (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { /* * Create & remember a User-Name */ t->username = fr_pair_make(t, NULL, "User-Name", NULL, T_OP_EQ); rad_assert(t->username != NULL); fr_pair_value_bstrncpy(t->username, vp->vp_octets + 5, vp->vp_length - 5); RDEBUG("Got tunneled identity of %s", t->username->vp_strvalue); /* * If there's a default EAP type, * set it here. */ if (t->default_method != 0) { RDEBUG("Setting default EAP type for tunneled EAP session"); vp = fr_pair_afrom_num(fake, PW_EAP_TYPE, 0); rad_assert(vp != NULL); vp->vp_integer = t->default_method; fr_pair_add(&fake->config, vp); } } else { /* * Don't reject the request outright, * as it's permitted to do EAP without * user-name. */ RWDEBUG2("No EAP-Identity found to start EAP conversation"); } } /* else there WAS a t->username */ if (t->username) { vp = fr_pair_list_copy(fake->packet, t->username); fr_pair_add(&fake->packet->vps, vp); fake->username = fr_pair_find_by_num(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); } } /* else the request ALREADY had a User-Name */ /* * Add the State attribute, too, if it exists. */ if (t->state) { vp = fr_pair_list_copy(fake->packet, t->state); if (vp) fr_pair_add(&fake->packet->vps, vp); } /* * If this is set, we copy SOME of the request attributes * from outside of the tunnel to inside of the tunnel. * * We copy ONLY those attributes which do NOT already * exist in the tunneled request. */ if (t->copy_request_to_tunnel) { VALUE_PAIR *copy; vp_cursor_t cursor; for (vp = fr_cursor_init(&cursor, &request->packet->vps); vp; vp = fr_cursor_next(&cursor)) { /* * The attribute is a server-side thingy, * don't copy it. */ if ((vp->da->attr > 255) && (vp->da->vendor == 0)) { continue; } /* * The outside attribute is already in the * tunnel, don't copy it. * * This works for BOTH attributes which * are originally in the tunneled request, * AND attributes which are copied there * from below. */ if (fr_pair_find_by_da(fake->packet->vps, vp->da, TAG_ANY)) { continue; } /* * Some attributes are handled specially. */ switch (vp->da->attr) { /* * NEVER copy Message-Authenticator, * EAP-Message, or State. They're * only for outside of the tunnel. */ case PW_USER_NAME: case PW_USER_PASSWORD: case PW_CHAP_PASSWORD: case PW_CHAP_CHALLENGE: case PW_PROXY_STATE: case PW_MESSAGE_AUTHENTICATOR: case PW_EAP_MESSAGE: case PW_STATE: continue; /* * By default, copy it over. */ default: break; } /* * Don't copy from the head, we've already * checked it. */ copy = fr_pair_list_copy_by_num(fake->packet, vp, vp->da->attr, vp->da->vendor, TAG_ANY); fr_pair_add(&fake->packet->vps, copy); } } if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { fake->server = vp->vp_strvalue; } else if (t->virtual_server) { fake->server = t->virtual_server; } /* else fake->server == request->server */ if ((rad_debug_lvl > 0) && fr_log_fp) { RDEBUG("Sending tunneled request"); } /* * Process channel binding. */ chbind = eap_chbind_vp2packet(fake, fake->packet->vps); if (chbind) { PW_CODE chbind_code; CHBIND_REQ *req = talloc_zero(fake, CHBIND_REQ); RDEBUG("received chbind request"); req->request = chbind; if (fake->username) { req->username = fake->username; } else { req->username = NULL; } chbind_code = chbind_process(request, req); /* encapsulate response here */ if (req->response) { RDEBUG("sending chbind response"); fr_pair_add(&fake->reply->vps, eap_chbind_packet2vp(fake, req->response)); } else { RDEBUG("no chbind response"); } /* clean up chbind req */ talloc_free(req); if (chbind_code != PW_CODE_ACCESS_ACCEPT) { return chbind_code; } } /* * Call authentication recursively, which will * do PAP, CHAP, MS-CHAP, etc. */ rad_virtual_server(fake); /* * Decide what to do with the reply. */ switch (fake->reply->code) { case 0: /* No reply code, must be proxied... */ #ifdef WITH_PROXY vp = fr_pair_find_by_num(fake->config, PW_PROXY_TO_REALM, 0, TAG_ANY); if (vp) { eap_tunnel_data_t *tunnel; RDEBUG("Tunneled authentication will be proxied to %s", vp->vp_strvalue); /* * Tell the original request that it's going * to be proxied. */ fr_pair_list_move_by_num(request, &request->config, &fake->config, PW_PROXY_TO_REALM, 0, TAG_ANY); /* * Seed the proxy packet with the * tunneled request. */ rad_assert(!request->proxy); request->proxy = talloc_steal(request, fake->packet); memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); request->proxy->src_port = 0; request->proxy->dst_port = 0; fake->packet = NULL; rad_free(&fake->reply); fake->reply = NULL; /* * Set up the callbacks for the tunnel */ tunnel = talloc_zero(request, eap_tunnel_data_t); tunnel->tls_session = tls_session; tunnel->callback = eapttls_postproxy; /* * Associate the callback with the request. */ code = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, false); rad_assert(code == 0); /* * rlm_eap.c has taken care of associating * the handler with the fake request. * * So we associate the fake request with * this request. */ code = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK, fake, true); rad_assert(code == 0); fake = NULL; /* * Didn't authenticate the packet, but * we're proxying it. */ code = PW_CODE_STATUS_CLIENT; } else #endif /* WITH_PROXY */ { RDEBUG("No tunneled reply was found for request %d , and the request was not proxied: rejecting the user.", request->number); code = PW_CODE_ACCESS_REJECT; } break; default: /* * Returns RLM_MODULE_FOO, and we want to return PW_FOO */ rcode = process_reply(handler, tls_session, request, fake->reply); switch (rcode) { case RLM_MODULE_REJECT: code = PW_CODE_ACCESS_REJECT; break; case RLM_MODULE_HANDLED: code = PW_CODE_ACCESS_CHALLENGE; break; case RLM_MODULE_OK: code = PW_CODE_ACCESS_ACCEPT; break; default: code = PW_CODE_ACCESS_REJECT; break; } break; } talloc_free(fake); return code; }
/* * Process the "diameter" contents of the tunneled data. */ int eapttls_process(EAP_HANDLER *handler, tls_session_t *tls_session) { int rcode = PW_AUTHENTICATION_REJECT; REQUEST *fake; VALUE_PAIR *vp; ttls_tunnel_t *t; const uint8_t *data; size_t data_len; REQUEST *request = handler->request; rad_assert(request != NULL); /* * Just look at the buffer directly, without doing * record_minus. */ data_len = tls_session->clean_out.used; tls_session->clean_out.used = 0; data = tls_session->clean_out.data; t = (ttls_tunnel_t *) tls_session->opaque; /* * If there's no data, maybe this is an ACK to an * MS-CHAP2-Success. */ if (data_len == 0) { if (t->authenticated) { RDEBUG("Got ACK, and the user was already authenticated."); return PW_AUTHENTICATION_ACK; } /* else no session, no data, die. */ /* * FIXME: Call SSL_get_error() to see what went * wrong. */ RDEBUG2("SSL_read Error"); return PW_AUTHENTICATION_REJECT; } #ifndef NDEBUG if ((debug_flag > 2) && fr_log_fp) { size_t i; for (i = 0; i < data_len; i++) { if ((i & 0x0f) == 0) fprintf(fr_log_fp, " TTLS tunnel data in %04x: ", (int) i); fprintf(fr_log_fp, "%02x ", data[i]); if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n"); } if ((data_len & 0x0f) != 0) fprintf(fr_log_fp, "\n"); } #endif if (!diameter_verify(request, data, data_len)) { return PW_AUTHENTICATION_REJECT; } /* * Allocate a fake REQUEST structe. */ fake = request_alloc_fake(request); rad_assert(fake->packet->vps == NULL); /* * Add the tunneled attributes to the fake request. */ fake->packet->vps = diameter2vp(request, tls_session->ssl, data, data_len); if (!fake->packet->vps) { request_free(&fake); return PW_AUTHENTICATION_REJECT; } /* * Tell the request that it's a fake one. */ vp = pairmake("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ); if (vp) { pairadd(&fake->packet->vps, vp); } if ((debug_flag > 0) && fr_log_fp) { RDEBUG("Got tunneled request"); debug_pair_list(fake->packet->vps); } /* * Update other items in the REQUEST data structure. */ fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); fake->password = pairfind(fake->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); /* * No User-Name, try to create one from stored data. */ if (!fake->username) { /* * No User-Name in the stored data, look for * an EAP-Identity, and pull it out of there. */ if (!t->username) { vp = pairfind(fake->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY); if (vp && (vp->length >= EAP_HEADER_LEN + 2) && (vp->vp_strvalue[0] == PW_EAP_RESPONSE) && (vp->vp_strvalue[EAP_HEADER_LEN] == PW_EAP_IDENTITY) && (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { /* * Create & remember a User-Name */ t->username = pairmake("User-Name", "", T_OP_EQ); rad_assert(t->username != NULL); memcpy(t->username->vp_strvalue, vp->vp_strvalue + 5, vp->length - 5); t->username->length = vp->length - 5; t->username->vp_strvalue[t->username->length] = 0; RDEBUG("Got tunneled identity of %s", t->username->vp_strvalue); /* * If there's a default EAP type, * set it here. */ if (t->default_eap_type != 0) { RDEBUG("Setting default EAP type for tunneled EAP session."); vp = paircreate(PW_EAP_TYPE, 0); rad_assert(vp != NULL); vp->vp_integer = t->default_eap_type; pairadd(&fake->config_items, vp); } } else { /* * Don't reject the request outright, * as it's permitted to do EAP without * user-name. */ RDEBUG2W("No EAP-Identity found to start EAP conversation."); } } /* else there WAS a t->username */ if (t->username) { vp = paircopy(t->username); pairadd(&fake->packet->vps, vp); fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY); } } /* else the request ALREADY had a User-Name */ /* * Add the State attribute, too, if it exists. */ if (t->state) { vp = paircopy(t->state); if (vp) pairadd(&fake->packet->vps, vp); } /* * If this is set, we copy SOME of the request attributes * from outside of the tunnel to inside of the tunnel. * * We copy ONLY those attributes which do NOT already * exist in the tunneled request. */ if (t->copy_request_to_tunnel) { VALUE_PAIR *copy; for (vp = request->packet->vps; vp != NULL; vp = vp->next) { /* * The attribute is a server-side thingy, * don't copy it. */ if ((vp->da->attr > 255) && (vp->da->vendor == 0)) { continue; } /* * The outside attribute is already in the * tunnel, don't copy it. * * This works for BOTH attributes which * are originally in the tunneled request, * AND attributes which are copied there * from below. */ if (pairfind(fake->packet->vps, vp->da->attr, vp->da->vendor, TAG_ANY)) { continue; } /* * Some attributes are handled specially. */ switch (vp->da->attr) { /* * NEVER copy Message-Authenticator, * EAP-Message, or State. They're * only for outside of the tunnel. */ case PW_USER_NAME: case PW_USER_PASSWORD: case PW_CHAP_PASSWORD: case PW_CHAP_CHALLENGE: case PW_PROXY_STATE: case PW_MESSAGE_AUTHENTICATOR: case PW_EAP_MESSAGE: case PW_STATE: continue; break; /* * By default, copy it over. */ default: break; } /* * Don't copy from the head, we've already * checked it. */ copy = paircopy2(vp, vp->da->attr, vp->da->vendor, TAG_ANY); pairadd(&fake->packet->vps, copy); } } if ((vp = pairfind(request->config_items, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { fake->server = vp->vp_strvalue; } else if (t->virtual_server) { fake->server = t->virtual_server; } /* else fake->server == request->server */ if ((debug_flag > 0) && fr_log_fp) { RDEBUG("Sending tunneled request"); debug_pair_list(fake->packet->vps); fprintf(fr_log_fp, "server %s {\n", (fake->server == NULL) ? "" : fake->server); } /* * Call authentication recursively, which will * do PAP, CHAP, MS-CHAP, etc. */ rad_virtual_server(fake); /* * Note that we don't do *anything* with the reply * attributes. */ if ((debug_flag > 0) && fr_log_fp) { fprintf(fr_log_fp, "} # server %s\n", (fake->server == NULL) ? "" : fake->server); RDEBUG("Got tunneled reply code %d", fake->reply->code); debug_pair_list(fake->reply->vps); } /* * Decide what to do with the reply. */ switch (fake->reply->code) { case 0: /* No reply code, must be proxied... */ #ifdef WITH_PROXY vp = pairfind(fake->config_items, PW_PROXY_TO_REALM, 0, TAG_ANY); if (vp) { eap_tunnel_data_t *tunnel; RDEBUG("Tunneled authentication will be proxied to %s", vp->vp_strvalue); /* * Tell the original request that it's going * to be proxied. */ pairmove2(&(request->config_items), &(fake->config_items), PW_PROXY_TO_REALM, 0, TAG_ANY); /* * Seed the proxy packet with the * tunneled request. */ rad_assert(request->proxy == NULL); request->proxy = fake->packet; memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); memset(&request->proxy->src_ipaddr, 0, sizeof(request->proxy->src_ipaddr)); request->proxy->src_port = 0; request->proxy->dst_port = 0; fake->packet = NULL; rad_free(&fake->reply); fake->reply = NULL; /* * Set up the callbacks for the tunnel */ tunnel = rad_malloc(sizeof(*tunnel)); memset(tunnel, 0, sizeof(*tunnel)); tunnel->tls_session = tls_session; tunnel->callback = eapttls_postproxy; /* * Associate the callback with the request. */ rcode = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, free); rad_assert(rcode == 0); /* * rlm_eap.c has taken care of associating * the handler with the fake request. * * So we associate the fake request with * this request. */ rcode = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK, fake, my_request_free); rad_assert(rcode == 0); fake = NULL; /* * Didn't authenticate the packet, but * we're proxying it. */ rcode = PW_STATUS_CLIENT; } else #endif /* WITH_PROXY */ { RDEBUG("No tunneled reply was found for request %d , and the request was not proxied: rejecting the user.", request->number); rcode = PW_AUTHENTICATION_REJECT; } break; default: /* * Returns RLM_MODULE_FOO, and we want to return * PW_FOO */ rcode = process_reply(handler, tls_session, request, fake->reply); switch (rcode) { case RLM_MODULE_REJECT: rcode = PW_AUTHENTICATION_REJECT; break; case RLM_MODULE_HANDLED: rcode = PW_ACCESS_CHALLENGE; break; case RLM_MODULE_OK: rcode = PW_AUTHENTICATION_ACK; break; default: rcode = PW_AUTHENTICATION_REJECT; break; } break; } request_free(&fake); return rcode; }