/* * First, look for Exec-Program && Exec-Program-Wait. * * Then, call exec_dispatch. */ static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request) { int result; int exec_wait = 0; VALUE_PAIR *vp, *tmp; rlm_exec_t *inst = (rlm_exec_t *) instance; vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM, 0, TAG_ANY); if (vp) { exec_wait = 0; } else if ((vp = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT, 0, TAG_ANY)) != NULL) { exec_wait = 1; } if (!vp) { if (!inst->program) return RLM_MODULE_NOOP; return exec_dispatch(instance, request); } tmp = NULL; result = radius_exec_program(vp->vp_strvalue, request, exec_wait, NULL, 0, request->packet->vps, &tmp, inst->shell_escape); /* * Always add the value-pairs to the reply. */ pairmove(request->reply, &request->reply->vps, &tmp); pairfree(&tmp); if (result < 0) { RDEBUGE("Login incorrect (external check failed)"); request->reply->code = PW_AUTHENTICATION_REJECT; return RLM_MODULE_REJECT; } if (result > 0) { /* * Reject. radius_exec_program() returns >0 * if the exec'ed program had a non-zero * exit status. */ request->reply->code = PW_AUTHENTICATION_REJECT; RDEBUGE("Login incorrect (external check said so)"); return RLM_MODULE_REJECT; } return RLM_MODULE_OK; }
/* * Zap all users on a NAS from the radutmp file. */ static int radutmp_zap(REQUEST *request, const char *filename, uint32_t nasaddr, time_t t) { struct radutmp u; int fd; if (t == 0) time(&t); fd = open(filename, O_RDWR); if (fd < 0) { RDEBUGE("Error accessing file %s: %s", filename, strerror(errno)); return RLM_MODULE_FAIL; } /* * Lock the utmp file, prefer lockf() over flock(). */ if (rad_lockfd(fd, LOCK_LEN) < 0) { RDEBUGE("Failed to acquire lock on file %s:" " %s", filename, strerror(errno)); close(fd); return RLM_MODULE_FAIL; } /* * Find the entry for this NAS / portno combination. */ while (read(fd, &u, sizeof(u)) == sizeof(u)) { if ((nasaddr != 0 && nasaddr != u.nas_address) || u.type != P_LOGIN) continue; /* * Match. Zap it. */ if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) { RDEBUGE("radutmp_zap: negative lseek!"); lseek(fd, (off_t)0, SEEK_SET); } u.type = P_IDLE; u.time = t; write(fd, &u, sizeof(u)); } close(fd); /* and implicitely release the locks */ return 0; }
/* * Do xlat of strings! */ static size_t expr_xlat(UNUSED void *instance, REQUEST *request, const char *fmt, char *out, size_t outlen) { int rcode; int64_t result; const char *p; char buffer[256]; /* * Do an xlat on the provided string (nice recursive operation). */ if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) { RDEBUGE("xlat failed."); *out = '\0'; return 0; } p = buffer; rcode = get_number(request, &p, &result); if (rcode < 0) { return 0; } /* * We MUST have eaten the entire input string. */ if (*p != '\0') { RDEBUG2("Failed at %s", p); return 0; } snprintf(out, outlen, "%ld", (long int) result); return strlen(out); }
/** * @brief Generate a random integer value * */ static size_t rand_xlat(UNUSED void *instance, REQUEST *request, const char *fmt, char *out, size_t outlen) { int64_t result; char buffer[256]; /* * Do an xlat on the provided string (nice recursive operation). */ if (!radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL)) { RDEBUGE("xlat failed."); *out = '\0'; return 0; } result = atoi(buffer); /* * Too small or too big. */ if (result <= 0) return 0; if (result >= (1 << 30)) result = (1 << 30); result *= fr_rand(); /* 0..2^32-1 */ result >>= 32; snprintf(out, outlen, "%ld", (long int) result); return strlen(out); }
/** * @brief Convert base64 to hex * * Example: "%{base64tohex:Zm9v}" == "666f6f" */ static size_t base64_to_hex_xlat(UNUSED void *instance, REQUEST *request, const char *fmt, char *out, size_t outlen) { char buffer[1024]; uint8_t decbuf[1024], *p; ssize_t declen; size_t freespace = outlen; size_t len; len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL); if (!len) { RDEBUGE("xlat failed."); *out = '\0'; return 0; } declen = fr_base64_decode(buffer, len, decbuf, sizeof(decbuf)); if (declen < 0) { RDEBUGE("base64 string invalid"); *out = '\0'; return 0; } p = decbuf; while ((declen-- > 0) && (--freespace > 0)) { if (freespace < 3) break; snprintf(out, 3, "%02x", *p++); /* Already decremented */ freespace -= 1; out += 2; } return outlen - freespace; }
/* * Check if account has expired, and if user may login now. */ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) { rlm_expiration_t *inst = instance; VALUE_PAIR *vp, *check_item = NULL; char msg[MAX_STRING_LEN]; if ((check_item = pairfind(request->config_items, PW_EXPIRATION, 0, TAG_ANY)) != NULL){ /* * Has this user's password expired? * * If so, remove ALL reply attributes, * and add our own Reply-Message, saying * why they're being rejected. */ RDEBUG("Checking Expiration time: '%s'",check_item->vp_strvalue); if (((time_t) check_item->vp_date) <= request->timestamp) { RDEBUG("Account has expired"); if (inst->msg && inst->msg[0]){ if (!radius_xlat(msg, sizeof(msg), inst->msg, request, NULL, NULL)) { radlog(L_ERR, "rlm_expiration: xlat failed."); return RLM_MODULE_FAIL; } pairfree(&request->reply->vps); pairmake_reply("Reply-Message", msg, T_OP_ADD); } RDEBUGE("Account has expired [Expiration %s]",check_item->vp_strvalue); return RLM_MODULE_USERLOCK; } /* * Else the account hasn't expired, but it may do so * in the future. Set Session-Timeout. */ vp = pairfind(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY); if (!vp) { vp = radius_paircreate(request, &request->reply->vps, PW_SESSION_TIMEOUT, 0); vp->vp_date = (uint32_t) (((time_t) check_item->vp_date) - request->timestamp); } else if (vp->vp_date > ((uint32_t) (((time_t) check_item->vp_date) - request->timestamp))) { vp->vp_date = (uint32_t) (((time_t) check_item->vp_date) - request->timestamp); } } else return RLM_MODULE_NOOP; return RLM_MODULE_OK; }
void cbtls_info(const SSL *s, int where, int ret) { const char *str, *state; int w; REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST); char buffer[1024]; w = where & ~SSL_ST_MASK; if (w & SSL_ST_CONNECT) str=" TLS_connect"; else if (w & SSL_ST_ACCEPT) str=" TLS_accept"; else str=" (other)"; state = SSL_state_string_long(s); state = state ? state : "NULL"; buffer[0] = '\0'; if (where & SSL_CB_LOOP) { RDEBUG2("%s: %s", str, state); } else if (where & SSL_CB_HANDSHAKE_START) { RDEBUG2("%s: %s", str, state); } else if (where & SSL_CB_HANDSHAKE_DONE) { RDEBUG2("%s: %s", str, state); } else if (where & SSL_CB_ALERT) { str=(where & SSL_CB_READ)?"read":"write"; snprintf(buffer, sizeof(buffer), "TLS Alert %s:%s:%s", str, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); } else if (where & SSL_CB_EXIT) { if (ret == 0) { snprintf(buffer, sizeof(buffer), "%s: failed in %s", str, state); } else if (ret < 0) { if (SSL_want_read(s)) { RDEBUG2("%s: Need to read more data: %s", str, state); } else { snprintf(buffer, sizeof(buffer), "%s: error in %s", str, state); } } } if (buffer[0] && request) { RDEBUGE("SSL says: %s", buffer); } }
/* * Add raw hex data to the reply. */ void eap_add_reply(REQUEST *request, const char *name, const uint8_t *value, int len) { VALUE_PAIR *vp; vp = pairmake_reply(name, "", T_OP_EQ); if (!vp) { RDEBUGE("Did not create attribute %s: %s\n", name, fr_strerror()); return; } memcpy(vp->vp_octets, value, len); vp->length = len; }
/** * @brief URLencode special characters * * Example: "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47" */ static size_t urlquote_xlat(UNUSED void *instance, REQUEST *request, const char *fmt, char *out, size_t outlen) { char *p; char buffer[1024]; size_t freespace = outlen; size_t len; if (outlen <= 1) return 0; len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL); if (!len) { RDEBUGE("xlat failed."); *out = '\0'; return 0; } p = buffer; while ((len-- > 0) && (--freespace > 0)) { if (isalnum(*p)) { *out++ = *p++; continue; } switch (*p) { case '-': case '_': case '.': case '~': *out++ = *p++; break; default: if (freespace < 3) break; snprintf(out, 4, "%%%02x", *p++); /* %xx */ /* Already decremented */ freespace -= 2; out += 3; } } *out = '\0'; return outlen - freespace; }
/** * @brief Equivalent to the old safe-characters functionality in rlm_sql * * Example: "%{escape:<img>foo.jpg</img>}" == "=60img=62foo.jpg=60=/img=62" */ static size_t escape_xlat(UNUSED void *instance, REQUEST *request, const char *fmt, char *out, size_t outlen) { rlm_expr_t *inst = instance; char *p; char buffer[1024]; size_t freespace = outlen; size_t len; if (outlen <= 1) return 0; len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL); if (!len) { RDEBUGE("xlat failed."); *out = '\0'; return 0; } p = buffer; while ((len-- > 0) && (--freespace > 0)) { /* * Non-printable characters get replaced with their * mime-encoded equivalents. */ if ((*p > 31) && strchr(inst->allowed_chars, *p)) { *out++ = *p++; continue; } if (freespace < 3) break; snprintf(out, 4, "=%02X", *p++); /* Already decremented */ freespace -= 2; out += 3; } *out = '\0'; return outlen - freespace; }
/** * @brief Encode string as base64 * * Example: "%{tobase64:foo}" == "Zm9v" */ static size_t base64_xlat(UNUSED void *instance, REQUEST *request, const char *fmt, char *out, size_t outlen) { size_t len; char buffer[1024]; len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL); /* * We can accurately calculate the length of the output string * if it's larger than outlen, the output would be useless so abort. */ if (!len || ((FR_BASE64_ENC_LENGTH(len) + 1) > outlen)) { RDEBUGE("xlat failed."); *out = '\0'; return 0; } return fr_base64_encode((uint8_t *) buffer, len, out, outlen); }
/** 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; }
/* * For backwards compatibility. */ static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) { rlm_eap_t *inst; eap_handler_t *handler; eap_packet_raw_t *eap_packet; eap_rcode_t status; rlm_rcode_t rcode; inst = (rlm_eap_t *) instance; if (!pairfind(request->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY)) { RDEBUGE("You set 'Auth-Type = EAP' for a request that does " "not contain an EAP-Message attribute!"); return RLM_MODULE_INVALID; } /* * Get the eap packet to start with */ eap_packet = eap_vp2packet(request, request->packet->vps); if (!eap_packet) { radlog_request(L_ERR, 0, request, "Malformed EAP Message"); return RLM_MODULE_FAIL; } /* * Create the eap handler. The eap_packet will end up being * "swallowed" into the handler, so we can't access it after * this call. */ handler = eap_handler(inst, &eap_packet, request); if (!handler) { RDEBUG2("Failed in handler"); return RLM_MODULE_INVALID; } /* * Select the appropriate method or default to the * configured one */ status = eap_method_select(inst, handler); /* * If it failed, die. */ if (status == EAP_INVALID) { eap_fail(handler); eap_handler_free(inst, handler); RDEBUG2("Failed in EAP select"); return RLM_MODULE_INVALID; } #ifdef WITH_PROXY /* * If we're doing horrible tunneling work, remember it. */ if ((request->options & RAD_REQUEST_OPTION_PROXY_EAP) != 0) { RDEBUG2(" Not-EAP proxy set. Not composing EAP"); /* * Add the handle to the proxied list, so that we * can retrieve it in the post-proxy stage, and * send a response. */ handler->inst_holder = inst; status = request_data_add(request, inst, REQUEST_DATA_eap_handler_t, handler, (void *) eap_opaque_free); rad_assert(status == 0); return RLM_MODULE_HANDLED; } #endif #ifdef WITH_PROXY /* * Maybe the request was marked to be proxied. If so, * proxy it. */ if (request->proxy != NULL) { VALUE_PAIR *vp = NULL; rad_assert(!request->proxy_reply); /* * Add the handle to the proxied list, so that we * can retrieve it in the post-proxy stage, and * send a response. */ handler->inst_holder = inst; status = request_data_add(request, inst, REQUEST_DATA_eap_handler_t, handler, (void *) eap_opaque_free); rad_assert(status == 0); /* * Some simple sanity checks. These should really * be handled by the radius library... */ vp = pairfind(request->proxy->vps, PW_EAP_MESSAGE, 0, TAG_ANY); if (vp) { vp = pairfind(request->proxy->vps, PW_MESSAGE_AUTHENTICATOR, 0, TAG_ANY); if (!vp) { pairmake(request->proxy, &request->proxy->vps, "Message-Authenticator", "0x00", T_OP_EQ); } } /* * Delete the "proxied to" attribute, as it's * set to 127.0.0.1 for tunneled requests, and * we don't want to tell the world that... */ pairdelete(&request->proxy->vps, PW_FREERADIUS_PROXIED_TO, VENDORPEC_FREERADIUS, TAG_ANY); RDEBUG2(" Tunneled session will be proxied. Not doing EAP."); return RLM_MODULE_HANDLED; } #endif /* * We are done, wrap the EAP-request in RADIUS to send * with all other required radius attributes */ rcode = eap_compose(handler); /* * Add to the list only if it is EAP-Request, OR if * it's LEAP, and a response. */ if (((handler->eap_ds->request->code == PW_EAP_REQUEST) && (handler->eap_ds->request->type.num >= PW_EAP_MD5)) || /* * LEAP is a little different. At Stage 4, * it sends an EAP-Success message, but we still * need to keep the State attribute & session * data structure around for the AP Challenge. * * At stage 6, LEAP sends an EAP-Response, which * isn't put into the list. */ ((handler->eap_ds->response->code == PW_EAP_RESPONSE) && (handler->eap_ds->response->type.num == PW_EAP_LEAP) && (handler->eap_ds->request->code == PW_EAP_SUCCESS) && (handler->eap_ds->request->type.num == 0))) { /* * Return FAIL if we can't remember the handler. * This is actually disallowed by the * specification, as unexpected FAILs could have * been forged. However, we want to signal to * everyone else involved that we are * intentionally failing the session, as opposed * to accidentally failing it. */ if (!eaplist_add(inst, handler)) { RDEBUG("Failed adding handler to the list"); eap_fail(handler); eap_handler_free(inst, handler); return RLM_MODULE_FAIL; } } else { RDEBUG2("Freeing handler"); /* handler is not required any more, free it now */ eap_handler_free(inst, handler); } /* * If it's an Access-Accept, RFC 2869, Section 2.3.1 * says that we MUST include a User-Name attribute in the * Access-Accept. */ if ((request->reply->code == PW_AUTHENTICATION_ACK) && request->username) { VALUE_PAIR *vp; /* * Doesn't exist, add it in. */ vp = pairfind(request->reply->vps, PW_USER_NAME, 0, TAG_ANY); if (!vp) { vp = pairmake_reply("User-Name", "", T_OP_EQ); strlcpy(vp->vp_strvalue, request->username->vp_strvalue, sizeof(vp->vp_strvalue)); vp->length = request->username->length; } /* * Cisco AP1230 has a bug and needs a zero * terminated string in Access-Accept. */ if ((inst->mod_accounting_username_bug) && (vp->length < (int) sizeof(vp->vp_strvalue))) { vp->vp_strvalue[vp->length] = '\0'; vp->length++; } } return rcode; }
/* * Check if account has expired, and if user may login now. */ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) { rlm_logintime_t *inst = instance; VALUE_PAIR *ends, *timeout; int left; ends = pairfind(request->config_items, PW_LOGIN_TIME, 0, TAG_ANY); if (!ends) { return RLM_MODULE_NOOP; } /* * Authentication is OK. Now see if this user may login at this time of the day. */ RDEBUG("Checking Login-Time"); /* * Compare the time the request was received with the current Login-Time value */ left = timestr_match(ends->vp_strvalue, request->timestamp); /* * Do nothing, login time is not controlled (unendsed). */ if (left == 0) { return RLM_MODULE_OK; } /* * The min_time setting is to deal with NAS that won't allow Session-Timeout values below a certain value * For example some Alcatel Lucent products won't allow a Session-Timeout < 300 (5 minutes). * * We don't know were going to get another chance to lock out the user, so we need to do it now. */ if (left < inst->min_time) { RDEBUGE("Login outside of allowed time-slot (session end %s, with lockout %i seconds before)", ends->vp_strvalue, inst->min_time); return RLM_MODULE_USERLOCK; } /* else left > inst->min_time */ /* * There's time left in the users session, inform the NAS by including a Session-Timeout * attribute in the reply, or modifying the existing one. */ RDEBUG("Login within allowed time-slot, %i seconds left in this session", left); timeout = pairfind(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY); if (timeout) { /* just update... */ if (timeout->vp_integer > (unsigned int) left) { timeout->vp_integer = left; } } else { timeout = radius_paircreate(request, &request->reply->vps, PW_SESSION_TIMEOUT, 0); timeout->vp_integer = left; } RDEBUG("reply:Session-Timeout set to %i", left); return RLM_MODULE_OK; }
/* * 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; }
/* * Do post-proxy processing, * 0 = fail * 1 = OK. * * Called from rlm_eap.c, eap_postproxy(). */ static int mschap_postproxy(eap_handler_t *handler, UNUSED void *tunnel_data) { VALUE_PAIR *response = NULL; mschapv2_opaque_t *data; REQUEST *request = handler->request; data = (mschapv2_opaque_t *) handler->opaque; rad_assert(data != NULL); RDEBUG2("Passing reply from proxy back into the tunnel %d.", request->reply->code); /* * There is only a limited number of possibilities. */ switch (request->reply->code) { case PW_AUTHENTICATION_ACK: RDEBUG2("Proxied authentication succeeded."); /* * Move the attribute, so it doesn't go into * the reply. */ pairfilter(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY); break; default: case PW_AUTHENTICATION_REJECT: RDEBUG("Proxied authentication did not succeed."); return 0; } /* * No response, die. */ if (!response) { RDEBUGE("Proxied reply contained no MS-CHAP-Success or MS-CHAP-Error"); return 0; } /* * Done doing EAP proxy stuff. */ request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; eapmschapv2_compose(handler, response); data->code = PW_EAP_MSCHAPV2_SUCCESS; /* * Delete MPPE keys & encryption policy * * FIXME: Use intelligent names... */ fix_mppe_keys(handler, data); /* * save any other attributes for re-use in the final * access-accept e.g. vlan, etc. This lets the PEAP * use_tunneled_reply code work */ data->reply = paircopy(data, request->reply->vps); /* * And we need to challenge the user, not ack/reject them, * so we re-write the ACK to a challenge. Yuck. */ request->reply->code = PW_ACCESS_CHALLENGE; pairfree(&response); return 1; }
/** Execute a program. * * @param cmd Command to execute. This is parsed into argv[] parts, * then each individual argv part is xlat'ed. * @param request current request. * @param exec_wait set to 1 if you want to read from or write to child * @param user_msg buffer to append plaintext (non valuepair) output. * @param msg_len length of user_msg buffer. * @param input_pairs list of value pairs - these will be put into * the environment variables of the child. * @param[out] output_pairs list of value pairs - child stdout will be * parsed and added into this list of value pairs. * @param shell_escape * @return 0 if exec_wait==0, exit code if exec_wait!=0, -1 on error. */ int radius_exec_program(const char *cmd, REQUEST *request, int exec_wait, char *user_msg, int msg_len, VALUE_PAIR *input_pairs, VALUE_PAIR **output_pairs, int shell_escape) { pid_t pid; int from_child; #ifndef __MINGW32__ VALUE_PAIR *vp; char *p; pid_t child_pid; int comma = 0; int status; int n, done; char answer[4096]; #endif pid = radius_start_program(cmd, request, exec_wait, NULL, &from_child, input_pairs, shell_escape); if (pid < 0) { return -1; } if (!exec_wait) return 0; #ifndef __MINGW32__ done = radius_readfrom_program(request, from_child, pid, 10, answer, sizeof(answer)); if (done < 0) { /* * failure - radius_readfrom_program will * have called close(from_child) for us */ DEBUG("failed to read from child output"); return 1; } answer[done] = 0; /* * Make sure that the writer can't block while writing to * a pipe that no one is reading from anymore. */ close(from_child); DEBUG2("Exec: Program output is %s", answer); /* * Parse the output, if any. */ if (done) { n = T_OP_INVALID; if (output_pairs) { /* * For backwards compatibility, first check * for plain text (user_msg). */ vp = NULL; n = userparse(answer, &vp); if (vp) { pairfree(&vp); } } if (n == T_OP_INVALID) { if (user_msg) { strlcpy(user_msg, answer, msg_len); } } else { /* * HACK: Replace '\n' with ',' so that * userparse() can parse the buffer in * one go (the proper way would be to * fix userparse(), but oh well). */ for (p = answer; *p; p++) { if (*p == '\n') { *p = comma ? ' ' : ','; p++; comma = 0; } if (*p == ',') comma++; } /* * Replace any trailing comma by a NUL. */ if (answer[strlen(answer) - 1] == ',') { answer[strlen(answer) - 1] = '\0'; } if (userparse(answer, &vp) == T_OP_INVALID) { RDEBUGE("Exec: Unparsable reply from '%s'", cmd); } else { /* * Tell the caller about the value * pairs. */ *output_pairs = vp; } } /* else the answer was a set of VP's, not a text message */ } /* else we didn't read anything from the child */ /* * Call rad_waitpid (should map to waitpid on non-threaded * or single-server systems). */ child_pid = rad_waitpid(pid, &status); if (child_pid == 0) { RDEBUGE("Exec: Timeout waiting for child"); return 2; } if (child_pid == pid) { if (WIFEXITED(status)) { status = WEXITSTATUS(status); RDEBUGE("Exec: child returned %d", status); return status; } } RDEBUG("Exec:Abnormal child exit: %s", strerror(errno)); #endif /* __MINGW32__ */ return 1; }
/* * Authenticate a previously sent challenge. */ static int mschapv2_authenticate(void *arg, eap_handler_t *handler) { int rcode, ccode; mschapv2_opaque_t *data; EAP_DS *eap_ds = handler->eap_ds; VALUE_PAIR *challenge, *response, *name; rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg; REQUEST *request = handler->request; rad_assert(request != NULL); rad_assert(handler->stage == AUTHENTICATE); data = (mschapv2_opaque_t *) handler->opaque; /* * Sanity check the response. */ if (eap_ds->response->length <= 5) { RDEBUGE("corrupted data"); return 0; } ccode = eap_ds->response->type.data[0]; switch (data->code) { case PW_EAP_MSCHAPV2_FAILURE: if (ccode == PW_EAP_MSCHAPV2_RESPONSE) { RDEBUG2("authentication re-try from client after we sent a failure"); break; } /* * if we sent error 648 (password expired) to the client * we might get an MSCHAP-CPW packet here; turn it into a * regular MS-CHAP2-CPW packet and pass it to rlm_mschap * (or proxy it, I guess) */ if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) { VALUE_PAIR *cpw; int mschap_id = eap_ds->response->type.data[1]; int copied=0,seq=1; RDEBUG2("password change packet received"); challenge = pairmake_packet("MS-CHAP-Challenge", "0x00", T_OP_EQ); if (!challenge) { return 0; } challenge->length = MSCHAPV2_CHALLENGE_LEN; memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN); cpw = pairmake_packet("MS-CHAP2-CPW", "", T_OP_EQ); cpw->vp_octets[0] = 7; cpw->vp_octets[1] = mschap_id; memcpy(cpw->vp_octets+2, eap_ds->response->type.data + 520, 66); cpw->length = 68; /* * break the encoded password into VPs (3 of them) */ while (copied < 516) { VALUE_PAIR *nt_enc; int to_copy = 516 - copied; if (to_copy > 243) to_copy = 243; nt_enc = pairmake_packet("MS-CHAP-NT-Enc-PW", "", T_OP_ADD); nt_enc->vp_octets[0] = 6; nt_enc->vp_octets[1] = mschap_id; nt_enc->vp_octets[2] = 0; nt_enc->vp_octets[3] = seq++; memcpy(nt_enc->vp_octets + 4, eap_ds->response->type.data + 4 + copied, to_copy); copied += to_copy; nt_enc->length = 4 + to_copy; } RDEBUG2("built change password packet"); debug_pair_list(request->packet->vps); /* * jump to "authentication" */ goto packet_ready; } /* * we sent a failure and are expecting a failure back */ if (ccode != PW_EAP_MSCHAPV2_FAILURE) { RDEBUGE("Sent FAILURE expecting FAILURE but got %d", ccode); return 0; } failure: request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; eap_ds->request->code = PW_EAP_FAILURE; return 1; case PW_EAP_MSCHAPV2_SUCCESS: /* * we sent a success to the client; some clients send a * success back as-per the RFC, some send an ACK. Permit * both, I guess... */ switch (ccode) { case PW_EAP_MSCHAPV2_SUCCESS: eap_ds->request->code = PW_EAP_SUCCESS; pairfilter(request->reply, &request->reply->vps, &data->mppe_keys, 0, 0, TAG_ANY); /* fall through... */ case PW_EAP_MSCHAPV2_ACK: #ifdef WITH_PROXY /* * It's a success. Don't proxy it. */ request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; #endif pairfilter(request->reply, &request->reply->vps, &data->reply, 0, 0, TAG_ANY); return 1; } RDEBUGE("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode); return 0; case PW_EAP_MSCHAPV2_CHALLENGE: if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure; /* * we sent a challenge, expecting a response */ if (ccode != PW_EAP_MSCHAPV2_RESPONSE) { RDEBUGE("Sent CHALLENGE expecting RESPONSE but got %d", ccode); return 0; } /* authentication happens below */ break; default: /* should never happen */ RDEBUGE("unknown state %d", data->code); return 0; } /* * Ensure that we have at least enough data * to do the following checks. * * EAP header (4), EAP type, MS-CHAP opcode, * MS-CHAP ident, MS-CHAP data length (2), * MS-CHAP value length. */ if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) { RDEBUGE("Response is too short"); return 0; } /* * The 'value_size' is the size of the response, * which is supposed to be the response (48 * bytes) plus 1 byte of flags at the end. */ if (eap_ds->response->type.data[4] != 49) { RDEBUGE("Response is of incorrect length %d", eap_ds->response->type.data[4]); return 0; } /* * The MS-Length field is 5 + value_size + length * of name, which is put after the response. */ if (((eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3]) < (5 + 49)) { RDEBUGE("Response contains contradictory length %d %d", (eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3], 5 + 49); return 0; } /* * We now know that the user has sent us a response * to the challenge. Let's try to authenticate it. * * We do this by taking the challenge from 'data', * the response from the EAP packet, and creating VALUE_PAIR's * to pass to the 'mschap' module. This is a little wonky, * but it works. */ challenge = pairmake_packet("MS-CHAP-Challenge", "0x00", T_OP_EQ); if (!challenge) { return 0; } challenge->length = MSCHAPV2_CHALLENGE_LEN; memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN); response = pairmake_packet("MS-CHAP2-Response", "0x00", T_OP_EQ); if (!response) { return 0; } response->length = MSCHAPV2_RESPONSE_LEN; memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2); response->vp_strvalue[0] = eap_ds->response->type.data[1]; response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN]; name = pairmake_packet("NTLM-User-Name", "", T_OP_EQ); if (!name) { return 0; } /* * MS-Length - MS-Value - 5. */ name->length = (((eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3]) - eap_ds->response->type.data[4] - 5); if (name->length >= sizeof(name->vp_strvalue)) { name->length = sizeof(name->vp_strvalue) - 1; } memcpy(name->vp_strvalue, &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN], name->length); name->vp_strvalue[name->length] = '\0'; packet_ready: #ifdef WITH_PROXY /* * If this options is set, then we do NOT authenticate the * user here. Instead, now that we've added the MS-CHAP * attributes to the request, we STOP, and let the outer * tunnel code handle it. * * This means that the outer tunnel code will DELETE the * EAP attributes, and proxy the MS-CHAP attributes to a * home server. */ if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) { char *username = NULL; eap_tunnel_data_t *tunnel; RDEBUG2("cancelling authentication and letting it be proxied"); /* * Set up the callbacks for the tunnel */ tunnel = talloc_zero(request, eap_tunnel_data_t); tunnel->tls_session = arg; tunnel->callback = mschap_postproxy; /* * Associate the callback with the request. */ rcode = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, NULL); rad_assert(rcode == 0); /* * The State attribute is NOT supposed to * go into the proxied packet, it will confuse * other RADIUS servers, and they will discard * the request. * * The PEAP module will take care of adding * the State attribute back, before passing * the handler & request back into the tunnel. */ pairdelete(&request->packet->vps, PW_STATE, 0, TAG_ANY); /* * Fix the User-Name when proxying, to strip off * the NT Domain, if we're told to, and a User-Name * exists, and there's a \\, meaning an NT-Domain * in the user name, THEN discard the user name. */ if (inst->with_ntdomain_hack && ((challenge = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) && ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) { /* * Wipe out the NT domain. * * FIXME: Put it into MS-CHAP-Domain? */ username++; /* skip the \\ */ memmove(challenge->vp_strvalue, username, strlen(username) + 1); /* include \0 */ challenge->length = strlen(challenge->vp_strvalue); } /* * Remember that in the post-proxy stage, we've got * to do the work below, AFTER the call to MS-CHAP * authentication... */ return 1; } #endif /* * This is a wild & crazy hack. */ rcode = process_authenticate(PW_AUTHTYPE_MS_CHAP, request); /* * Delete MPPE keys & encryption policy. We don't * want these here. */ fix_mppe_keys(handler, data); /* * Take the response from the mschap module, and * return success or failure, depending on the result. */ response = NULL; if (rcode == RLM_MODULE_OK) { pairfilter(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY); data->code = PW_EAP_MSCHAPV2_SUCCESS; } else if (inst->send_error) { pairfilter(data, &response, &request->reply->vps, PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY); if (response) { int n,err,retry; char buf[34]; RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue); /* * Pxarse the new challenge out of the * MS-CHAP-Error, so that if the client * issues a re-try, we will know which * challenge value that they used. */ n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]); if (n == 3) { DEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf); fr_hex2bin(buf, data->challenge, 16); } else { DEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n); } } data->code = PW_EAP_MSCHAPV2_FAILURE; } else { eap_ds->request->code = PW_EAP_FAILURE; return 1; } /* * No response, die. */ if (!response) { RDEBUGE("No MS-CHAP-Success or MS-CHAP-Error was found."); return 0; } /* * Compose the response (whatever it is), * and return it to the over-lying EAP module. */ eapmschapv2_compose(handler, response); pairfree(&response); return 1; }
/* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t mod_authorize(UNUSED void *instance, UNUSED REQUEST *request) { rlm_sqlcounter_t *inst = instance; int rcode = RLM_MODULE_NOOP; unsigned int counter; const DICT_ATTR *dattr; VALUE_PAIR *key_vp, *check_vp; VALUE_PAIR *reply_item; char msg[128]; char querystr[MAX_QUERY_LEN]; char sqlxlat[MAX_QUERY_LEN]; /* * Before doing anything else, see if we have to reset * the counters. */ if (inst->reset_time && (inst->reset_time <= request->timestamp)) { /* * Re-set the next time and prev_time for this counters range */ inst->last_reset = inst->reset_time; find_next_reset(inst,request->timestamp); } /* * Look for the key. User-Name is special. It means * The REAL username, after stripping. */ DEBUG2("rlm_sqlcounter: Entering module authorize code"); key_vp = ((inst->key_attr->vendor == 0) && (inst->key_attr->attr == PW_USER_NAME)) ? request->username : pairfind(request->packet->vps, inst->key_attr->attr, inst->key_attr->vendor, TAG_ANY); if (!key_vp) { DEBUG2("rlm_sqlcounter: Could not find Key value pair"); return rcode; } /* * Look for the check item */ if ((dattr = dict_attrbyname(inst->check_name)) == NULL) { return rcode; } /* DEBUG2("rlm_sqlcounter: Found Check item attribute %d", dattr->attr); */ if ((check_vp= pairfind(request->config_items, dattr->attr, dattr->vendor, TAG_ANY)) == NULL) { DEBUG2("rlm_sqlcounter: Could not find Check item value pair"); return rcode; } /* first, expand %k, %b and %e in query */ sqlcounter_expand(querystr, MAX_QUERY_LEN, inst->query, inst); /* next, wrap query with sql module & expand */ snprintf(sqlxlat, sizeof(sqlxlat), "%%{%s:%s}", inst->sqlmod_inst, querystr); /* Finally, xlat resulting SQL query */ radius_xlat(querystr, MAX_QUERY_LEN, sqlxlat, request, NULL, NULL); if (sscanf(querystr, "%u", &counter) != 1) { DEBUG2("rlm_sqlcounter: No integer found in string \"%s\"", querystr); return RLM_MODULE_NOOP; } /* * Check if check item > counter */ if (check_vp->vp_integer > counter) { unsigned int res = check_vp->vp_integer - counter; DEBUG2("rlm_sqlcounter: Check item is greater than query result"); /* * We are assuming that simultaneous-use=1. But * even if that does not happen then our user * could login at max for 2*max-usage-time Is * that acceptable? */ /* * If we are near a reset then add the next * limit, so that the user will not need to login * again. Do this only for Session-Timeout. */ if ((inst->reply_attr->attr == PW_SESSION_TIMEOUT) && inst->reset_time && (res >= (inst->reset_time - request->timestamp))) { res = inst->reset_time - request->timestamp; res += check_vp->vp_integer; } /* * Limit the reply attribute to the minimum of * the existing value, or this new one. */ reply_item = pairfind(request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor, TAG_ANY); if (reply_item) { if (reply_item->vp_integer > res) reply_item->vp_integer = res; } else { reply_item = radius_paircreate(request, &request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor); reply_item->vp_integer = res; } rcode = RLM_MODULE_OK; DEBUG2("rlm_sqlcounter: Authorized user %s, check_item=%u, counter=%u", key_vp->vp_strvalue,check_vp->vp_integer,counter); DEBUG2("rlm_sqlcounter: Sent Reply-Item for user %s, Type=%s, value=%u", key_vp->vp_strvalue,inst->reply_name,reply_item->vp_integer); } else { DEBUG2("rlm_sqlcounter: (Check item - counter) is less than zero"); /* * User is denied access, send back a reply message */ snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", inst->reset); pairmake_reply("Reply-Message", msg, T_OP_EQ); RDEBUGE("Maximum %s usage time reached", inst->reset); rcode = RLM_MODULE_REJECT; DEBUG2("rlm_sqlcounter: Rejected user %s, check_item=%u, counter=%u", key_vp->vp_strvalue,check_vp->vp_integer,counter); } return rcode; }
/* * 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; }
/* * 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; }
/** * @brief Generate a string of random chars * * Build strings of random chars, useful for generating tokens and passcodes * Format similar to String::Random. */ static size_t randstr_xlat(UNUSED void *instance, REQUEST *request, const char *fmt, char *out, size_t outlen) { char *p; char buffer[1024]; unsigned int result; size_t freespace = outlen; size_t len; if (outlen <= 1) return 0; /* * Do an xlat on the provided string (nice recursive operation). */ len = radius_xlat(buffer, sizeof(buffer), fmt, request, NULL, NULL); if (!len) { RDEBUGE("xlat failed."); *out = '\0'; return 0; } p = buffer; while ((len-- > 0) && (--freespace > 0)) { result = fr_rand(); switch (*p) { /* * Lowercase letters */ case 'c': *out++ = 'a' + (result % 26); break; /* * Uppercase letters */ case 'C': *out++ = 'A' + (result % 26); break; /* * Numbers */ case 'n': *out++ = '0' + (result % 10); break; /* * Alpha numeric */ case 'a': *out++ = randstr_salt[result % (sizeof(randstr_salt) - 3)]; break; /* * Punctuation */ case '!': *out++ = randstr_punc[result % (sizeof(randstr_punc) - 1)]; break; /* * Alpa numeric + punctuation */ case '.': *out++ = '!' + (result % 95); break; /* * Alpha numeric + salt chars './' */ case 's': *out++ = randstr_salt[result % (sizeof(randstr_salt) - 1)]; break; /* * Binary data as hexits (we don't really support * non printable chars). */ case 'h': if (freespace < 2) break; snprintf(out, 3, "%02x", result % 256); /* Already decremented */ freespace -= 1; out += 2; break; default: radlog(L_ERR, "rlm_expr: invalid character class '%c'", *p); return 0; break; } p++; } *out++ = '\0'; return outlen - freespace; }
/* * Dispatch an exec method */ static rlm_rcode_t exec_dispatch(void *instance, REQUEST *request) { rlm_exec_t *inst = (rlm_exec_t *) instance; int result; VALUE_PAIR **input_pairs, **output_pairs; VALUE_PAIR *answer = NULL; char msg[1024]; size_t len; /* * We need a program to execute. */ if (!inst->program) { radlog(L_ERR, "rlm_exec (%s): We require a program to execute", inst->xlat_name); return RLM_MODULE_FAIL; } /* * See if we're supposed to execute it now. */ if (!((inst->packet_code == 0) || (request->packet->code == inst->packet_code) || (request->reply->code == inst->packet_code) #ifdef WITH_PROXY || (request->proxy && (request->proxy->code == inst->packet_code)) || (request->proxy_reply && (request->proxy_reply->code == inst->packet_code)) #endif )) { RDEBUG2("Packet type is not %s. Not executing.", inst->packet_type); return RLM_MODULE_NOOP; } /* * Decide what input/output the program takes. */ input_pairs = decode_string(request, inst->input); output_pairs = decode_string(request, inst->output); if (!input_pairs) { RDEBUG2W("Possible parse error in %s", inst->input); return RLM_MODULE_NOOP; } /* * It points to the attribute list, but the attribute * list is empty. */ if (!*input_pairs) { RDEBUG2W("Input pairs are empty. No attributes will be passed to the script"); } /* * This function does it's own xlat of the input program * to execute. * * FIXME: if inst->program starts with %{, then * do an xlat ourselves. This will allow us to do * program = %{Exec-Program}, which this module * xlat's into it's string value, and then the * exec program function xlat's it's string value * into something else. */ result = radius_exec_program(inst->program, request, inst->wait, msg, sizeof(msg), *input_pairs, &answer, inst->shell_escape); if (result < 0) { radlog(L_ERR, "rlm_exec (%s): External script failed", inst->xlat_name); return RLM_MODULE_FAIL; } /* * Move the answer over to the output pairs. * * If we're not waiting, then there are no output pairs. */ if (output_pairs) pairmove(request, output_pairs, &answer); pairfree(&answer); if (result == 0) { return RLM_MODULE_OK; } if (result > RLM_MODULE_NUMCODES) { return RLM_MODULE_FAIL; } /* * Write any exec output to module failure message */ if (*msg) { /* Trim off returns and newlines */ len = strlen(msg); if (msg[len - 1] == '\n' || msg[len - 1] == '\r') { msg[len - 1] = '\0'; } RDEBUGE("%s", msg); } return result-1; }