int eaptls_success(EAP_HANDLER *handler, int peap_flag) { EAPTLS_PACKET reply; REQUEST *request = handler->request; tls_session_t *tls_session = handler->opaque; handler->finished = TRUE; reply.code = FR_TLS_SUCCESS; reply.length = TLS_HEADER_LEN; reply.flags = peap_flag; reply.data = NULL; reply.dlen = 0; tls_success(tls_session, request); /* * Call compose AFTER checking for cached data. */ eaptls_compose(handler->eap_ds, &reply); /* * Automatically generate MPPE keying material. */ if (tls_session->prf_label) { eaptls_gen_mppe_keys(&handler->request->reply->vps, tls_session->ssl, tls_session->prf_label); } else { RDEBUGW("Not adding MPPE keys because there is no PRF label"); } eaptls_gen_eap_key(tls_session->ssl, handler->eap_type, &handler->request->reply->vps); return 1; }
/** Start a process * * @param cmd Command to execute. This is parsed into argv[] parts, * then each individual argv part is xlat'ed. * @param request Current reuqest * @param exec_wait set to 1 if you want to read from or write to child * @param[in,out] input_fd pointer to int, receives the stdin file. * descriptor. Set to NULL and the child will have /dev/null on stdin * @param[in,out] output_fd pinter to int, receives the stdout file * descriptor. Set to NULL and child will have /dev/null on stdout. * @param input_pairs list of value pairs - these will be put into * the environment variables of the child. * @param shell_escape * @return PID of the child process, -1 on error. */ pid_t radius_start_program(const char *cmd, REQUEST *request, int exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, int shell_escape) { #ifndef __MINGW32__ char *p; VALUE_PAIR *vp; int n; int to_child[2] = {-1, -1}; int from_child[2] = {-1, -1}; pid_t pid; #endif int argc; int i; char *argv[MAX_ARGV]; char argv_buf[4096]; #define MAX_ENVP 1024 char *envp[MAX_ENVP]; argc = rad_expand_xlat(request, cmd, MAX_ARGV, (const char **) argv, 1, sizeof(argv_buf), argv_buf); if (argc <= 0) { RDEBUG("Exec: invalid command line '%s'.", cmd); return -1; } #ifndef __MINGW32__ /* * Open a pipe for child/parent communication, if necessary. */ if (exec_wait) { if (input_fd) { if (pipe(to_child) != 0) { RDEBUG("Exec: Couldn't open pipe to child: %s", strerror(errno)); return -1; } } if (output_fd) { if (pipe(from_child) != 0) { RDEBUG("Exec: Couldn't open pipe from child: %s", strerror(errno)); /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); return -1; } } } envp[0] = NULL; if (input_pairs) { int envlen; char buffer[1024]; /* * Set up the environment variables in the * parent, so we don't call libc functions that * hold mutexes. They might be locked when we fork, * and will remain locked in the child. */ envlen = 0; for (vp = input_pairs; vp != NULL; vp = vp->next) { /* * Hmm... maybe we shouldn't pass the * user's password in an environment * variable... */ snprintf(buffer, sizeof(buffer), "%s=", vp->da->name); if (shell_escape) { for (p = buffer; *p != '='; p++) { if (*p == '-') { *p = '_'; } else if (isalpha((int) *p)) { *p = toupper(*p); } } } n = strlen(buffer); vp_prints_value(buffer+n, sizeof(buffer) - n, vp, shell_escape); envp[envlen++] = strdup(buffer); /* * Don't add too many attributes. */ if (envlen == (MAX_ENVP - 1)) break; } envp[envlen] = NULL; } if (exec_wait) { pid = rad_fork(); /* remember PID */ } else { pid = fork(); /* don't wait */ } if (pid == 0) { int devnull; /* * Child process. * * We try to be fail-safe here. So if ANYTHING * goes wrong, we exit with status 1. */ /* * Open STDIN to /dev/null */ devnull = open("/dev/null", O_RDWR); if (devnull < 0) { RDEBUG("Exec: Failed opening /dev/null: %s\n", strerror(errno)); exit(1); } /* * Only massage the pipe handles if the parent * has created them. */ if (exec_wait) { if (input_fd) { close(to_child[1]); dup2(to_child[0], STDIN_FILENO); } else { dup2(devnull, STDIN_FILENO); } if (output_fd) { close(from_child[0]); dup2(from_child[1], STDOUT_FILENO); } else { dup2(devnull, STDOUT_FILENO); } } else { /* no pipe, STDOUT should be /dev/null */ dup2(devnull, STDIN_FILENO); dup2(devnull, STDOUT_FILENO); } /* * If we're not debugging, then we can't do * anything with the error messages, so we throw * them away. * * If we are debugging, then we want the error * messages to go to the STDERR of the server. */ if (debug_flag == 0) { dup2(devnull, STDERR_FILENO); } close(devnull); /* * The server may have MANY FD's open. We don't * want to leave dangling FD's for the child process * to play funky games with, so we close them. */ closefrom(3); execve(argv[0], argv, envp); RDEBUGW("Exec: failed to execute %s: %s", argv[0], strerror(errno)); exit(1); } /* * Free child environment variables */ for (i = 0; envp[i] != NULL; i++) { free(envp[i]); } /* * Parent process. */ if (pid < 0) { RDEBUG("Exec: Couldn't fork %s: %s", argv[0], strerror(errno)); if (exec_wait) { /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); close(from_child[0]); close(from_child[0]); } return -1; } /* * We're not waiting, exit, and ignore any child's status. */ if (exec_wait) { /* * Close the ends of the pipe(s) the child is using * return the ends of the pipe(s) our caller wants * */ if (input_fd) { *input_fd = to_child[1]; close(to_child[0]); } if (output_fd) { *output_fd = from_child[0]; close(from_child[1]); } } return pid; #else if (exec_wait) { RDEBUG("Exec: Wait is not supported"); return -1; } { /* * The _spawn and _exec families of functions are * found in Windows compiler libraries for * portability from UNIX. There is a variety of * functions, including the ability to pass * either a list or array of parameters, to * search in the PATH or otherwise, and whether * or not to pass an environment (a set of * environment variables). Using _spawn, you can * also specify whether you want the new process * to close your program (_P_OVERLAY), to wait * until the new process is finished (_P_WAIT) or * for the two to run concurrently (_P_NOWAIT). * _spawn and _exec are useful for instances in * which you have simple requirements for running * the program, don't want the overhead of the * Windows header file, or are interested * primarily in portability. */ /* * FIXME: check return code... what is it? */ _spawnve(_P_NOWAIT, argv[0], argv, envp); } return 0; #endif }
/** Copy packet to multiple servers * * Create a duplicate of the packet and send it to a list of realms * defined by the presence of the Replicate-To-Realm VP in the control * list of the current request. * * This is pretty hacky and is 100% fire and forget. If you're looking * to forward authentication requests to multiple realms and process * the responses, this function will not allow you to do that. * * @param[in] instance of this module. * @param[in] request The current request. * @param[in] list of attributes to copy to the duplicate packet. * @param[in] code to write into the code field of the duplicate packet. * @return RCODE fail on error, invalid if list does not exist, noop if no * replications succeeded, else ok. */ static int replicate_packet(UNUSED void *instance, REQUEST *request, pair_lists_t list, unsigned int code) { int rcode = RLM_MODULE_NOOP; VALUE_PAIR *vp, **vps, *last; home_server *home; REALM *realm; home_pool_t *pool; RADIUS_PACKET *packet = NULL; last = request->config_items; /* * Send as many packets as necessary to different * destinations. */ while (1) { vp = pairfind(last, PW_REPLICATE_TO_REALM, 0, TAG_ANY); if (!vp) break; last = vp->next; realm = realm_find2(vp->vp_strvalue); if (!realm) { RDEBUG2E("Cannot Replicate to unknown realm %s", realm); continue; } /* * We shouldn't really do this on every loop. */ switch (request->packet->code) { default: RDEBUG2E("Cannot replicate unknown packet code %d", request->packet->code); cleanup(packet); return RLM_MODULE_FAIL; case PW_AUTHENTICATION_REQUEST: pool = realm->auth_pool; break; #ifdef WITH_ACCOUNTING case PW_ACCOUNTING_REQUEST: pool = realm->acct_pool; break; #endif #ifdef WITH_COA case PW_COA_REQUEST: case PW_DISCONNECT_REQUEST: pool = realm->acct_pool; break; #endif } if (!pool) { RDEBUG2W("Cancelling replication to Realm %s, as the realm is local.", realm->name); continue; } home = home_server_ldb(realm->name, pool, request); if (!home) { RDEBUG2E("Failed to find live home server for realm %s", realm->name); continue; } /* * For replication to multiple servers we re-use the packet * we built here. */ if (!packet) { packet = rad_alloc(NULL, 1); if (!packet) return RLM_MODULE_FAIL; packet->sockfd = -1; packet->code = code; packet->id = fr_rand() & 0xff; packet->sockfd = fr_socket(&home->src_ipaddr, 0); if (packet->sockfd < 0) { RDEBUGE("Failed opening socket: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } vps = radius_list(request, list); if (!vps) { RDEBUGW("List '%s' doesn't exist for " "this packet", fr_int2str(pair_lists, list, "?unknown?")); rcode = RLM_MODULE_INVALID; goto done; } /* * Don't assume the list actually contains any * attributes. */ if (*vps) { packet->vps = paircopy(packet, *vps); if (!packet->vps) { rcode = RLM_MODULE_FAIL; goto done; } } /* * For CHAP, create the CHAP-Challenge if * it doesn't exist. */ if ((code == PW_AUTHENTICATION_REQUEST) && (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) != NULL) && (pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL)) { vp = radius_paircreate(request, &packet->vps, PW_CHAP_CHALLENGE, 0); vp->length = AUTH_VECTOR_LEN; memcpy(vp->vp_strvalue, request->packet->vector, AUTH_VECTOR_LEN); } } else { size_t i; for (i = 0; i < sizeof(packet->vector); i++) { packet->vector[i] = fr_rand() & 0xff; } packet->id++; free(packet->data); packet->data = NULL; packet->data_len = 0; } /* * (Re)-Write these. */ packet->dst_ipaddr = home->ipaddr; packet->dst_port = home->port; memset(&packet->src_ipaddr, 0, sizeof(packet->src_ipaddr)); packet->src_port = 0; /* * Encode, sign and then send the packet. */ RDEBUG("Replicating list '%s' to Realm '%s'", fr_int2str(pair_lists, list, "¿unknown?"),realm->name); if (rad_send(packet, NULL, home->secret) < 0) { RDEBUGE("Failed replicating packet: %s", fr_strerror()); rcode = RLM_MODULE_FAIL; goto done; } /* * We've sent it to at least one destination. */ rcode = RLM_MODULE_OK; } done: cleanup(packet); return rcode; }
/* * See if a user is already logged in. Sets request->simul_count to the * current session count for this user and sets request->simul_mpp to 2 * if it looks like a multilink attempt based on the requested IP * address, otherwise leaves request->simul_mpp alone. * * Check twice. If on the first pass the user exceeds his * max. number of logins, do a second pass and validate all * logins by querying the terminal server (using eg. SNMP). */ static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) { struct radutmp u; int fd; VALUE_PAIR *vp; uint32_t ipno = 0; char *call_num = NULL; int rcode; rlm_radutmp_t *inst = instance; char login[256]; char filename[1024]; /* * Get the filename, via xlat. */ radius_xlat(filename, sizeof(filename), inst->filename, request, NULL, NULL); if ((fd = open(filename, O_RDWR)) < 0) { /* * If the file doesn't exist, then no users * are logged in. */ if (errno == ENOENT) { request->simul_count=0; return RLM_MODULE_OK; } /* * Error accessing the file. */ radlog(L_ERR, "rlm_radumtp: Error accessing file %s: %s", filename, strerror(errno)); return RLM_MODULE_FAIL; } *login = '******'; radius_xlat(login, sizeof(login), inst->username, request, NULL, NULL); if (!*login) { close(fd); return RLM_MODULE_NOOP; } /* * WTF? This is probably wrong... we probably want to * be able to check users across multiple session accounting * methods. */ request->simul_count = 0; /* * Loop over utmp, counting how many people MAY be logged in. */ while (read(fd, &u, sizeof(u)) == sizeof(u)) { if (((strncmp(login, u.login, RUT_NAMESIZE) == 0) || (!inst->case_sensitive && (strncasecmp(login, u.login, RUT_NAMESIZE) == 0))) && (u.type == P_LOGIN)) { ++request->simul_count; } } /* * The number of users logged in is OK, * OR, we've been told to not check the NAS. */ if ((request->simul_count < request->simul_max) || !inst->check_nas) { close(fd); return RLM_MODULE_OK; } lseek(fd, (off_t)0, SEEK_SET); /* * Setup some stuff, like for MPP detection. */ if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) ipno = vp->vp_ipaddr; if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) call_num = vp->vp_strvalue; /* * lock the file while reading/writing. */ rad_lockfd(fd, LOCK_LEN); /* * FIXME: If we get a 'Start' for a user/nas/port which is * listed, but for which we did NOT get a 'Stop', then * it's not a duplicate session. This happens with * static IP's like DSL. */ request->simul_count = 0; while (read(fd, &u, sizeof(u)) == sizeof(u)) { if (((strncmp(login, u.login, RUT_NAMESIZE) == 0) || (!inst->case_sensitive && (strncasecmp(login, u.login, RUT_NAMESIZE) == 0))) && (u.type == P_LOGIN)) { char session_id[sizeof(u.session_id) + 1]; char utmp_login[sizeof(u.login) + 1]; strlcpy(session_id, u.session_id, sizeof(session_id)); /* * The login name MAY fill the whole field, * and thus won't be zero-filled. * * Note that we take the user name from * the utmp file, as that's the canonical * form. The 'login' variable may contain * a string which is an upper/lowercase * version of u.login. When we call the * routine to check the terminal server, * the NAS may be case sensitive. * * e.g. We ask if "bob" is using a port, * and the NAS says "no", because "BOB" * is using the port. */ strlcpy(utmp_login, u.login, sizeof(u.login)); /* * rad_check_ts may take seconds * to return, and we don't want * to block everyone else while * that's happening. */ rad_unlockfd(fd, LOCK_LEN); rcode = rad_check_ts(u.nas_address, u.nas_port, utmp_login, session_id); rad_lockfd(fd, LOCK_LEN); if (rcode == 0) { /* * Stale record - zap it. */ session_zap(request, u.nas_address, u.nas_port, login, session_id, u.framed_address, u.proto,0); } else if (rcode == 1) { /* * User is still logged in. */ ++request->simul_count; /* * Does it look like a MPP attempt? */ if (strchr("SCPA", u.proto) && ipno && u.framed_address == ipno) request->simul_mpp = 2; else if (strchr("SCPA", u.proto) && call_num && !strncmp(u.caller_id,call_num,16)) request->simul_mpp = 2; } else { /* * Failed to check the terminal * server for duplicate logins: * Return an error. */ close(fd); RDEBUGW("Failed to check the terminal server for user '%s'.", utmp_login); return RLM_MODULE_FAIL; } } } close(fd); /* and implicitely release the locks */ return RLM_MODULE_OK; }
/* * Store logins in the RADIUS utmp file. */ static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) { struct radutmp ut, u; VALUE_PAIR *vp; int status = -1; int protocol = -1; time_t t; int fd; int port_seen = 0; int off; rlm_radutmp_t *inst = instance; char buffer[256]; char filename[1024]; char ip_name[32]; /* 255.255.255.255 */ const char *nas; NAS_PORT *cache; int r; if (request->packet->src_ipaddr.af != AF_INET) { DEBUG("rlm_radutmp: IPv6 not supported!"); return RLM_MODULE_NOOP; } /* * Which type is this. */ if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) { RDEBUG("No Accounting-Status-Type record."); return RLM_MODULE_NOOP; } status = vp->vp_integer; /* * Look for weird reboot packets. * * ComOS (up to and including 3.5.1b20) does not send * standard PW_STATUS_ACCOUNTING_XXX messages. * * Check for: o no Acct-Session-Time, or time of 0 * o Acct-Session-Id of "00000000". * * We could also check for NAS-Port, that attribute * should NOT be present (but we don't right now). */ if ((status != PW_STATUS_ACCOUNTING_ON) && (status != PW_STATUS_ACCOUNTING_OFF)) do { int check1 = 0; int check2 = 0; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY)) == NULL || vp->vp_date == 0) check1 = 1; if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID, 0, TAG_ANY)) != NULL && vp->length == 8 && memcmp(vp->vp_strvalue, "00000000", 8) == 0) check2 = 1; if (check1 == 0 || check2 == 0) { break; } radlog(L_INFO, "rlm_radutmp: converting reboot records."); if (status == PW_STATUS_STOP) status = PW_STATUS_ACCOUNTING_OFF; if (status == PW_STATUS_START) status = PW_STATUS_ACCOUNTING_ON; } while(0); time(&t); memset(&ut, 0, sizeof(ut)); ut.porttype = 'A'; ut.nas_address = htonl(INADDR_NONE); /* * First, find the interesting attributes. */ for (vp = request->packet->vps; vp; vp = vp->next) { if (!vp->da->vendor) switch (vp->da->attr) { case PW_LOGIN_IP_HOST: case PW_FRAMED_IP_ADDRESS: ut.framed_address = vp->vp_ipaddr; break; case PW_FRAMED_PROTOCOL: protocol = vp->vp_integer; break; case PW_NAS_IP_ADDRESS: ut.nas_address = vp->vp_ipaddr; break; case PW_NAS_PORT: ut.nas_port = vp->vp_integer; port_seen = 1; break; case PW_ACCT_DELAY_TIME: ut.delay = vp->vp_integer; break; case PW_ACCT_SESSION_ID: /* * If length > 8, only store the * last 8 bytes. */ off = vp->length - sizeof(ut.session_id); /* * Ascend is br0ken - it adds a \0 * to the end of any string. * Compensate. */ if (vp->length > 0 && vp->vp_strvalue[vp->length - 1] == 0) off--; if (off < 0) off = 0; memcpy(ut.session_id, vp->vp_strvalue + off, sizeof(ut.session_id)); break; case PW_NAS_PORT_TYPE: if (vp->vp_integer <= 4) ut.porttype = porttypes[vp->vp_integer]; break; case PW_CALLING_STATION_ID: if(inst->callerid_ok) strlcpy(ut.caller_id, (char *)vp->vp_strvalue, sizeof(ut.caller_id)); break; } } /* * If we didn't find out the NAS address, use the * originator's IP address. */ if (ut.nas_address == htonl(INADDR_NONE)) { ut.nas_address = request->packet->src_ipaddr.ipaddr.ip4addr.s_addr; nas = request->client->shortname; } else if (request->packet->src_ipaddr.ipaddr.ip4addr.s_addr == ut.nas_address) { /* might be a client, might not be. */ nas = request->client->shortname; } else { /* * The NAS isn't a client, it's behind * a proxy server. In that case, just * get the IP address. */ nas = ip_ntoa(ip_name, ut.nas_address); } /* * Set the protocol field. */ if (protocol == PW_PPP) ut.proto = 'P'; else if (protocol == PW_SLIP) ut.proto = 'S'; else ut.proto = 'T'; ut.time = t - ut.delay; /* * Get the utmp filename, via xlat. */ radius_xlat(filename, sizeof(filename), inst->filename, request, NULL, NULL); /* * See if this was a reboot. * * Hmm... we may not want to zap all of the users when * the NAS comes up, because of issues with receiving * UDP packets out of order. */ if (status == PW_STATUS_ACCOUNTING_ON && (ut.nas_address != htonl(INADDR_NONE))) { radlog(L_INFO, "rlm_radutmp: NAS %s restarted (Accounting-On packet seen)", nas); radutmp_zap(request, filename, ut.nas_address, ut.time); return RLM_MODULE_OK; } if (status == PW_STATUS_ACCOUNTING_OFF && (ut.nas_address != htonl(INADDR_NONE))) { radlog(L_INFO, "rlm_radutmp: NAS %s rebooted (Accounting-Off packet seen)", nas); radutmp_zap(request, filename, ut.nas_address, ut.time); return RLM_MODULE_OK; } /* * If we don't know this type of entry pretend we succeeded. */ if (status != PW_STATUS_START && status != PW_STATUS_STOP && status != PW_STATUS_ALIVE) { RDEBUGE("NAS %s port %u unknown packet type %d)", nas, ut.nas_port, status); return RLM_MODULE_NOOP; } /* * Translate the User-Name attribute, or whatever else * they told us to use. */ *buffer = '\0'; radius_xlat(buffer, sizeof(buffer), inst->username, request, NULL, NULL); /* * Copy the previous translated user name. */ strlcpy(ut.login, buffer, RUT_NAMESIZE); /* * Perhaps we don't want to store this record into * radutmp. We skip records: * * - without a NAS-Port (telnet / tcp access) * - with the username "!root" (console admin login) */ if (!port_seen) { DEBUG2(" rlm_radutmp: No NAS-Port seen. Cannot do anything."); DEBUG2W("checkrad will probably not work!"); return RLM_MODULE_NOOP; } if (strncmp(ut.login, "!root", RUT_NAMESIZE) == 0) { DEBUG2(" rlm_radutmp: Not recording administrative user"); return RLM_MODULE_NOOP; } /* * Enter into the radutmp file. */ fd = open(filename, O_RDWR|O_CREAT, inst->permission); if (fd < 0) { RDEBUGE("Error accessing file %s: %s", filename, strerror(errno)); return RLM_MODULE_FAIL; } /* * Lock the utmp file, prefer lockf() over flock(). */ rad_lockfd(fd, LOCK_LEN); /* * Find the entry for this NAS / portno combination. */ if ((cache = nas_port_find(inst->nas_port_list, ut.nas_address, ut.nas_port)) != NULL) { lseek(fd, (off_t)cache->offset, SEEK_SET); } r = 0; off = 0; while (read(fd, &u, sizeof(u)) == sizeof(u)) { off += sizeof(u); if (u.nas_address != ut.nas_address || u.nas_port != ut.nas_port) continue; /* * Don't compare stop records to unused entries. */ if (status == PW_STATUS_STOP && u.type == P_IDLE) { continue; } if (status == PW_STATUS_STOP && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) != 0) { /* * Don't complain if this is not a * login record (some clients can * send _only_ logout records). */ if (u.type == P_LOGIN) RDEBUGW("Logout entry for NAS %s port %u has wrong ID", nas, u.nas_port); r = -1; break; } if (status == PW_STATUS_START && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) == 0 && u.time >= ut.time) { if (u.type == P_LOGIN) { radlog(L_INFO, "rlm_radutmp: Login entry for NAS %s port %u duplicate", nas, u.nas_port); r = -1; break; } RDEBUGW("Login entry for NAS %s port %u wrong order", nas, u.nas_port); r = -1; break; } /* * FIXME: the ALIVE record could need * some more checking, but anyway I'd * rather rewrite this mess -- miquels. */ if (status == PW_STATUS_ALIVE && strncmp(ut.session_id, u.session_id, sizeof(u.session_id)) == 0 && u.type == P_LOGIN) { /* * Keep the original login time. */ ut.time = u.time; } if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) { RDEBUGW("negative lseek!"); lseek(fd, (off_t)0, SEEK_SET); off = 0; } else off -= sizeof(u); r = 1; break; } /* read the file until we find a match */ /* * Found the entry, do start/update it with * the information from the packet. */ if (r >= 0 && (status == PW_STATUS_START || status == PW_STATUS_ALIVE)) { /* * Remember where the entry was, because it's * easier than searching through the entire file. */ if (!cache) { cache = talloc_zero(inst, NAS_PORT); if (cache) { cache->nasaddr = ut.nas_address; cache->port = ut.nas_port; cache->offset = off; cache->next = inst->nas_port_list; inst->nas_port_list = cache; } } ut.type = P_LOGIN; write(fd, &ut, sizeof(u)); } /* * The user has logged off, delete the entry by * re-writing it in place. */ if (status == PW_STATUS_STOP) { if (r > 0) { u.type = P_IDLE; u.time = ut.time; u.delay = ut.delay; write(fd, &u, sizeof(u)); } else if (r == 0) { RDEBUGW("Logout for NAS %s port %u, but no Login record", nas, ut.nas_port); } } close(fd); /* and implicitely release the locks */ return RLM_MODULE_OK; }
/* * Verify the response entered by the user. */ static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) { rlm_otp_t *inst = instance; const char *username; int rc; otp_pwe_t pwe; VALUE_PAIR *vp; char challenge[OTP_MAX_CHALLENGE_LEN]; /* cf. authorize() */ char passcode[OTP_MAX_PASSCODE_LEN + 1]; challenge[0] = '\0'; /* initialize for otp_pw_valid() */ /* User-Name attribute required. */ if (!request->username) { RDEBUGW("Attribute \"User-Name\" required " "for authentication."); return RLM_MODULE_INVALID; } username = request->username->vp_strvalue; pwe = otp_pwe_present(request); if (pwe == 0) { RDEBUGW("Attribute \"User-Password\" " "or equivalent required for authentication."); return RLM_MODULE_INVALID; } /* * Retrieve the challenge (from State attribute). */ vp = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY); if (vp) { char gen_state[OTP_MAX_RADSTATE_LEN]; //!< State as hexits uint8_t bin_state[OTP_MAX_RADSTATE_LEN]; int32_t then; //!< State timestamp. size_t elen; //!< Expected State length. size_t len; /* * Set expected State length (see otp_gen_state()) */ elen = (inst->challenge_len * 2) + 8 + 8 + 32; if (vp->length != elen) { RDEBUGE("Bad radstate for [%s]: length", username); return RLM_MODULE_INVALID; } /* * Verify the state. */ /* * Convert vp state (ASCII encoded hexits in opaque bin * string) back to binary. * * There are notes in otp_radstate as to why the state * value is encoded as hexits. */ len = fr_hex2bin(vp->vp_strvalue, bin_state, vp->length); if (len != (vp->length / 2)) { RDEBUGE("bad radstate for [%s]: not hex", username); return RLM_MODULE_INVALID; } /* * Extract data from State */ memcpy(challenge, bin_state, inst->challenge_len); /* * Skip flag data */ memcpy(&then, bin_state + inst->challenge_len + 4, 4); /* * Generate new state from returned input data */ otp_gen_state(gen_state, challenge, inst->challenge_len, 0, then, inst->hmac_key); /* * Compare generated state (in hex form) * against generated state (in hex form) * to verify hmac. */ if (memcmp(gen_state, vp->vp_octets, vp->length)) { RDEBUGE("bad radstate for [%s]: hmac", username); return RLM_MODULE_REJECT; } /* * State is valid, but check expiry. */ then = ntohl(then); if (time(NULL) - then > inst->challenge_delay) { RDEBUGE("bad radstate for [%s]: expired",username); return RLM_MODULE_REJECT; } } /* do it */ rc = otp_pw_valid(request, pwe, challenge, inst, passcode); /* Add MPPE data as needed. */ if (rc == RLM_MODULE_OK) { otp_mppe(request, pwe, inst, passcode); } return rc; }
/* * Generate a challenge to be presented to the user. */ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) { rlm_otp_t *inst = (rlm_otp_t *) instance; char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */ int auth_type_found; /* Early exit if Auth-Type != inst->name */ { VALUE_PAIR *vp; auth_type_found = 0; vp = pairfind(request->config_items, PW_AUTHTYPE, 0, TAG_ANY); if (vp) { auth_type_found = 1; if (strcmp(vp->vp_strvalue, inst->name)) { return RLM_MODULE_NOOP; } } } /* The State attribute will be present if this is a response. */ if (pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY) != NULL) { DEBUG("rlm_otp: autz: Found response to Access-Challenge"); return RLM_MODULE_OK; } /* User-Name attribute required. */ if (!request->username) { RDEBUGW("Attribute \"User-Name\" " "required for authentication."); return RLM_MODULE_INVALID; } if (otp_pwe_present(request) == 0) { RDEBUGW("Attribute " "\"User-Password\" or equivalent required " "for authentication."); return RLM_MODULE_INVALID; } /* * We used to check for special "challenge" and "resync" passcodes * here, but these are complicated to explain and application is * limited. More importantly, since we've removed all actual OTP * code (now we ask otpd), it's awkward for us to support them. * Should the need arise to reinstate these options, the most * likely choice is to duplicate some otpd code here. */ if (inst->allow_sync && !inst->allow_async) { /* This is the token sync response. */ if (!auth_type_found) { pairmake_config("Auth-Type", inst->name, T_OP_EQ); } return RLM_MODULE_OK; } /* * Generate a random challenge. */ otp_async_challenge(challenge, inst->challenge_len); /* * Create the State attribute, which will be returned to * us along with the response. * * We will need this to verify the response. * * It must be hmac protected to prevent insertion of arbitrary * State by an inside attacker. * * If we won't actually use the State (server config doesn't * allow async), we just use a trivial State. * * We always create at least a trivial State, so mod_authorize() * can quickly pass on to mod_authenticate(). */ { int32_t now = htonl(time(NULL)); //!< Low-order 32 bits on LP64. char gen_state[OTP_MAX_RADSTATE_LEN]; size_t len; VALUE_PAIR *vp; len = otp_gen_state(gen_state, challenge, inst->challenge_len, 0, now, inst->hmac_key); vp = paircreate(request->reply, PW_STATE, 0); if (!vp) { return RLM_MODULE_FAIL; } memcpy(vp->vp_octets, gen_state, len); vp->length = len; pairadd(&request->reply->vps, vp); } /* * Add the challenge to the reply. */ { VALUE_PAIR *vp; char buff[1024]; size_t len; /* * First add the internal OTP challenge attribute to * the reply list. */ vp = paircreate(request->reply, PW_OTP_CHALLENGE, 0); if (!vp) { return RLM_MODULE_FAIL; } memcpy(vp->vp_strvalue, challenge, inst->challenge_len); vp->length = inst->challenge_len; vp->op = T_OP_SET; pairadd(&request->reply->vps, vp); /* * Then add the message to the user to they known * what the challenge value is. */ len = radius_xlat(buff, sizeof(buff), inst->chal_prompt, request, NULL, NULL); if (len == 0) { return RLM_MODULE_FAIL; } vp = paircreate(request->reply, PW_REPLY_MESSAGE, 0); if (!vp) { return RLM_MODULE_FAIL; } memcpy(vp->vp_strvalue, challenge, len); vp->length = inst->challenge_len; vp->op = T_OP_SET; pairadd(&request->reply->vps, vp); } /* * Mark the packet as an Access-Challenge packet. * The server will take care of sending it to the user. */ request->reply->code = PW_ACCESS_CHALLENGE; DEBUG("rlm_otp: Sending Access-Challenge."); if (!auth_type_found) { pairmake_config("Auth-Type", inst->name, T_OP_EQ); } return RLM_MODULE_HANDLED; }
/* * Authenticate the user with the given password. */ static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) { rlm_yubikey_t *inst = instance; char *passcode; size_t i, len; uint32_t counter; const DICT_ATTR *da; VALUE_PAIR *key, *vp; yubikey_token_st token; char private_id[(YUBIKEY_UID_SIZE * 2) + 1]; /* * Can't do yubikey auth if there's no password. */ if (!request->password || (request->password->da->attr != PW_USER_PASSWORD)) { RDEBUGE("No Clear-Text password in the request. Can't do Yubikey authentication."); return RLM_MODULE_FAIL; } passcode = request->password->vp_strvalue; len = request->password->length; /* * Verify the passcode is the correct length (in its raw * modhex encoded form). * * <public_id (6-16 bytes)> + <aes-block (32 bytes)> */ if (len != (inst->id_len + 32)) { RDEBUGE("User-Password value is not the correct length, expected %u, got %zu", inst->id_len + 32, len); return RLM_MODULE_FAIL; } for (i = inst->id_len; i < len; i++) { if (!is_modhex(*passcode)) { RDEBUG2("User-Password (aes-block) value contains non modhex chars"); return RLM_MODULE_FAIL; } } da = dict_attrbyname("Yubikey-Key"); key = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY); if (!key) { RDEBUGE("Yubikey-Key attribute not found in control list, can't decrypt OTP data"); return RLM_MODULE_FAIL; } if (key->length != YUBIKEY_KEY_SIZE) { RDEBUGE("Yubikey-Key length incorrect, expected %u got %zu", YUBIKEY_KEY_SIZE, key->length); return RLM_MODULE_FAIL; } yubikey_parse(request->password->vp_octets + inst->id_len, key->vp_octets, &token); /* * Apparently this just uses byte offsets... */ if (!yubikey_crc_ok_p((uint8_t *) &token)) { RDEBUGE("Decrypting OTP token data failed, rejecting"); return RLM_MODULE_REJECT; } RDEBUG("Token data decrypted successfully"); if (request->options && request->radlog) { (void) fr_bin2hex((uint8_t*) &token.uid, (char *) &private_id, YUBIKEY_UID_SIZE); RDEBUG2("Private ID : 0x%s", private_id); RDEBUG2("Session counter : %u", yubikey_counter(token.ctr)); RDEBUG2("# used in session : %u", token.use); RDEBUG2("Token timetamp : %u", (token.tstph << 16) | token.tstpl); RDEBUG2("Random data : %u", token.rnd); RDEBUG2("CRC data : 0x%x", token.crc); } /* * Private ID used for validation purposes */ vp = pairmake(request, &request->packet->vps, "Yubikey-Private-ID", NULL, T_OP_SET); memcpy(vp->vp_octets, token.uid, YUBIKEY_UID_SIZE); vp->length = YUBIKEY_UID_SIZE; /* * Token timestamp */ vp = pairmake(request, &request->packet->vps, "Yubikey-Timestamp", NULL, T_OP_SET); vp->vp_integer = (token.tstph << 16) | token.tstpl; vp->length = 4; /* * Token random */ vp = pairmake(request, &request->packet->vps, "Yubikey-Random", NULL, T_OP_SET); vp->vp_integer = token.rnd; vp->length = 4; /* * Combine the two counter fields together so we can do * replay attack checks. */ counter = (yubikey_counter(token.ctr) << 16) | token.use; vp = pairmake(request, &request->packet->vps, "Yubikey-Counter", NULL, T_OP_SET); vp->vp_integer = counter; vp->length = 4; /* * Now we check for replay attacks */ vp = pairfind(request->config_items, vp->da->attr, vp->da->vendor, TAG_ANY); if (!vp) { RDEBUGW("Yubikey-Counter not found in control list, skipping replay attack checks"); return RLM_MODULE_OK; } if (counter <= vp->vp_integer) { RDEBUGE("Replay attack detected! Counter value %u, is lt or eq to last known counter value %u", counter, vp->vp_integer); return RLM_MODULE_REJECT; } return RLM_MODULE_OK; }
/* * Generate the keys after the user has been authenticated. */ static rlm_rcode_t wimax_postauth(void *instance, REQUEST *request) { rlm_wimax_t *inst = instance; VALUE_PAIR *msk, *emsk, *vp; VALUE_PAIR *mn_nai, *ip, *fa_rk; HMAC_CTX hmac; unsigned int rk1_len, rk2_len, rk_len; uint32_t mip_spi; uint8_t usage_data[24]; uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE]; uint8_t mip_rk[2 * EVP_MAX_MD_SIZE]; msk = pairfind(request->reply->vps, 1129, 0, TAG_ANY); emsk = pairfind(request->reply->vps, 1130, 0, TAG_ANY); if (!msk || !emsk) { RDEBUG("No EAP-MSK or EAP-EMSK. Cannot create WiMAX keys."); return RLM_MODULE_NOOP; } /* * If we delete the MS-MPPE-*-Key attributes, then add in * the WiMAX-MSK so that the client has a key available. */ if (inst->delete_mppe_keys) { pairdelete(&request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); pairdelete(&request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); vp = radius_pairmake(request, &request->reply->vps, "WiMAX-MSK", "0x00", T_OP_EQ); if (vp) { memcpy(vp->vp_octets, msk->vp_octets, msk->length); vp->length = msk->length; } } /* * Initialize usage data. */ memcpy(usage_data, "*****@*****.**", 21); /* with trailing \0 */ usage_data[21] = 0x02; usage_data[22] = 0x00; usage_data[23] = 0x01; /* * MIP-RK-1 = HMAC-SSHA256(EMSK, usage-data | 0x01) */ HMAC_CTX_init(&hmac); HMAC_Init_ex(&hmac, emsk->vp_octets, emsk->length, EVP_sha256(), NULL); HMAC_Update(&hmac, &usage_data[0], sizeof(usage_data)); HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len); /* * MIP-RK-2 = HMAC-SSHA256(EMSK, MIP-RK-1 | usage-data | 0x01) */ HMAC_Init_ex(&hmac, emsk->vp_octets, emsk->length, EVP_sha256(), NULL); HMAC_Update(&hmac, (const uint8_t *) &mip_rk_1, rk1_len); HMAC_Update(&hmac, &usage_data[0], sizeof(usage_data)); HMAC_Final(&hmac, &mip_rk_2[0], &rk2_len); memcpy(mip_rk, mip_rk_1, rk1_len); memcpy(mip_rk + rk1_len, mip_rk_2, rk2_len); rk_len = rk1_len + rk2_len; /* * MIP-SPI = HMAC-SSHA256(MIP-RK, "SPI CMIP PMIP"); */ HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha256(), NULL); HMAC_Update(&hmac, (const uint8_t *) "SPI CMIP PMIP", 12); HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len); /* * Take the 4 most significant octets. * If less than 256, add 256. */ mip_spi = ((mip_rk_1[0] << 24) | (mip_rk_1[1] << 16) | (mip_rk_1[2] << 8) | mip_rk_1[3]); if (mip_spi < 256) mip_spi += 256; if (debug_flag) { int len = rk_len; char buffer[512]; if (len > 128) len = 128; /* buffer size */ fr_bin2hex(mip_rk, buffer, len); radlog_request(L_DBG, 0, request, "MIP-RK = 0x%s", buffer); radlog_request(L_DBG, 0, request, "MIP-SPI = %08x", ntohl(mip_spi)); } /* * FIXME: Perform SPI collision prevention */ /* * Calculate mobility keys */ mn_nai = pairfind(request->packet->vps, 1900, 0, TAG_ANY); if (!mn_nai) mn_nai = pairfind(request->reply->vps, 1900, 0, TAG_ANY); if (!mn_nai) { RDEBUGW("WiMAX-MN-NAI was not found in the request or in the reply."); RDEBUGW("We cannot calculate MN-HA keys."); } /* * WiMAX-IP-Technology */ vp = NULL; if (mn_nai) vp = pairfind(request->reply->vps, 23, VENDORPEC_WIMAX, TAG_ANY); if (!vp) { RDEBUGW("WiMAX-IP-Technology not found in reply."); RDEBUGW("Not calculating MN-HA keys"); } if (vp) switch (vp->vp_integer) { case 2: /* PMIP4 */ /* * Look for WiMAX-hHA-IP-MIP4 */ ip = pairfind(request->reply->vps, 6, VENDORPEC_WIMAX, TAG_ANY); if (!ip) { RDEBUGW("WiMAX-hHA-IP-MIP4 not found. Cannot calculate MN-HA-PMIP4 key"); break; } /* * MN-HA-PMIP4 = * H(MIP-RK, "PMIP4 MN HA" | HA-IPv4 | MN-NAI); */ HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha1(), NULL); HMAC_Update(&hmac, (const uint8_t *) "PMIP4 MN HA", 11); HMAC_Update(&hmac, (const uint8_t *) &ip->vp_ipaddr, 4); HMAC_Update(&hmac, (const uint8_t *) &mn_nai->vp_strvalue, mn_nai->length); HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len); /* * Put MN-HA-PMIP4 into WiMAX-MN-hHA-MIP4-Key */ vp = pairfind(request->reply->vps, 10, VENDORPEC_WIMAX, TAG_ANY); if (!vp) { vp = radius_paircreate(request, &request->reply->vps, 10, VENDORPEC_WIMAX); } if (!vp) { RDEBUGW("Failed creating WiMAX-MN-hHA-MIP4-Key"); break; } memcpy(vp->vp_octets, &mip_rk_1[0], rk1_len); vp->length = rk1_len; /* * Put MN-HA-PMIP4-SPI into WiMAX-MN-hHA-MIP4-SPI */ vp = pairfind(request->reply->vps, 11, VENDORPEC_WIMAX, TAG_ANY); if (!vp) { vp = radius_paircreate(request, &request->reply->vps, 11, VENDORPEC_WIMAX); } if (!vp) { RDEBUGW("Failed creating WiMAX-MN-hHA-MIP4-SPI"); break; } vp->vp_integer = mip_spi + 1; break; case 3: /* CMIP4 */ /* * Look for WiMAX-hHA-IP-MIP4 */ ip = pairfind(request->reply->vps, 6, VENDORPEC_WIMAX, TAG_ANY); if (!ip) { RDEBUGW("WiMAX-hHA-IP-MIP4 not found. Cannot calculate MN-HA-CMIP4 key"); break; } /* * MN-HA-CMIP4 = * H(MIP-RK, "CMIP4 MN HA" | HA-IPv4 | MN-NAI); */ HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha1(), NULL); HMAC_Update(&hmac, (const uint8_t *) "CMIP4 MN HA", 11); HMAC_Update(&hmac, (const uint8_t *) &ip->vp_ipaddr, 4); HMAC_Update(&hmac, (const uint8_t *) &mn_nai->vp_strvalue, mn_nai->length); HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len); /* * Put MN-HA-CMIP4 into WiMAX-MN-hHA-MIP4-Key */ vp = pairfind(request->reply->vps, 10, VENDORPEC_WIMAX, TAG_ANY); if (!vp) { vp = radius_paircreate(request, &request->reply->vps, 10, VENDORPEC_WIMAX); } if (!vp) { RDEBUGW("Failed creating WiMAX-MN-hHA-MIP4-Key"); break; } memcpy(vp->vp_octets, &mip_rk_1[0], rk1_len); vp->length = rk1_len; /* * Put MN-HA-CMIP4-SPI into WiMAX-MN-hHA-MIP4-SPI */ vp = pairfind(request->reply->vps, 11, VENDORPEC_WIMAX, TAG_ANY); if (!vp) { vp = radius_paircreate(request, &request->reply->vps, 11, VENDORPEC_WIMAX); } if (!vp) { RDEBUGW("Failed creating WiMAX-MN-hHA-MIP4-SPI"); break; } vp->vp_integer = mip_spi; break; case 4: /* CMIP6 */ /* * Look for WiMAX-hHA-IP-MIP6 */ ip = pairfind(request->reply->vps, 7, VENDORPEC_WIMAX, TAG_ANY); if (!ip) { RDEBUGW("WiMAX-hHA-IP-MIP6 not found. Cannot calculate MN-HA-CMIP6 key"); break; } /* * MN-HA-CMIP6 = * H(MIP-RK, "CMIP6 MN HA" | HA-IPv6 | MN-NAI); */ HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha1(), NULL); HMAC_Update(&hmac, (const uint8_t *) "CMIP6 MN HA", 11); HMAC_Update(&hmac, (const uint8_t *) &ip->vp_ipv6addr, 16); HMAC_Update(&hmac, (const uint8_t *) &mn_nai->vp_strvalue, mn_nai->length); HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len); /* * Put MN-HA-CMIP6 into WiMAX-MN-hHA-MIP6-Key */ vp = pairfind(request->reply->vps, 12, VENDORPEC_WIMAX, TAG_ANY); if (!vp) { vp = radius_paircreate(request, &request->reply->vps, 12, VENDORPEC_WIMAX); } if (!vp) { RDEBUGW("Failed creating WiMAX-MN-hHA-MIP6-Key"); break; } memcpy(vp->vp_octets, &mip_rk_1[0], rk1_len); vp->length = rk1_len; /* * Put MN-HA-CMIP6-SPI into WiMAX-MN-hHA-MIP6-SPI */ vp = pairfind(request->reply->vps, 13, VENDORPEC_WIMAX, TAG_ANY); if (!vp) { vp = radius_paircreate(request, &request->reply->vps, 13, VENDORPEC_WIMAX); } if (!vp) { RDEBUGW("Failed creating WiMAX-MN-hHA-MIP6-SPI"); break; } vp->vp_integer = mip_spi + 2; break; default: break; /* do nothing */ } /* * Generate FA-RK, if requested. * * FA-RK= H(MIP-RK, "FA-RK") */ fa_rk = pairfind(request->reply->vps, 14, VENDORPEC_WIMAX, TAG_ANY); if (fa_rk && (fa_rk->length <= 1)) { HMAC_Init_ex(&hmac, mip_rk, rk_len, EVP_sha1(), NULL); HMAC_Update(&hmac, (const uint8_t *) "FA-RK", 5); HMAC_Final(&hmac, &mip_rk_1[0], &rk1_len); memcpy(fa_rk->vp_octets, &mip_rk_1[0], rk1_len); fa_rk->length = rk1_len; } /* * Create FA-RK-SPI, which is really SPI-CMIP4, which is * really MIP-SPI. Clear? Of course. This is WiMAX. */ if (fa_rk) { vp = pairfind(request->reply->vps, 61, VENDORPEC_WIMAX, TAG_ANY); if (!vp) { vp = radius_paircreate(request, &request->reply->vps, 61, VENDORPEC_WIMAX); } if (!vp) { RDEBUGW("Failed creating WiMAX-FA-RK-SPI"); } else { vp->vp_integer = mip_spi; } } /* * Give additional information about requests && responses * * WiMAX-RRQ-MN-HA-SPI */ vp = pairfind(request->packet->vps, 20, VENDORPEC_WIMAX, TAG_ANY); if (vp) { RDEBUG("Client requested MN-HA key: Should use SPI to look up key from storage."); if (!mn_nai) { RDEBUGW("MN-NAI was not found!"); } /* * WiMAX-RRQ-HA-IP */ if (!pairfind(request->packet->vps, 18, VENDORPEC_WIMAX, TAG_ANY)) { RDEBUGW("HA-IP was not found!"); } /* * WiMAX-HA-RK-Key-Requested */ vp = pairfind(request->packet->vps, 58, VENDORPEC_WIMAX, TAG_ANY); if (vp && (vp->vp_integer == 1)) { RDEBUG("Client requested HA-RK: Should use IP to look it up from storage."); } } /* * Wipe the context of all sensitive information. */ HMAC_CTX_cleanup(&hmac); return RLM_MODULE_UPDATED; }