/** Return a pointer to the first UTF8 char in a string. * * @param[out] chr_len Where to write the length of the multibyte char passed in chr (may be NULL). * @param[in] str Haystack. * @param[in] chr Multibyte needle. * @return * - Position of chr in str. * - NULL if not found. */ char const *fr_utf8_strchr(int *chr_len, char const *str, char const *chr) { int cchr; cchr = fr_utf8_char((uint8_t const *)chr, -1); if (cchr == 0) cchr = 1; if (chr_len) *chr_len = cchr; while (*str) { int schr; schr = fr_utf8_char((uint8_t const *) str, -1); if (schr == 0) schr = 1; if (schr != cchr) goto next; if (memcmp(str, chr, schr) == 0) { return (char const *) str; } next: str += schr; } return NULL; }
/* * Reject any non-UTF8 data. */ static rlm_rcode_t utf8_clean(void *instance, REQUEST *request) { size_t i, len; VALUE_PAIR *vp, *next; /* quiet the compiler */ instance = instance; for (vp = request->packet->vps; vp != NULL; vp = next) { next = vp->next; if (vp->da->type != PW_TYPE_STRING) continue; for (i = 0; i < vp->length; i += len) { len = fr_utf8_char(&vp->vp_octets[i]); if (len == 0) return RLM_MODULE_FAIL; } } return RLM_MODULE_NOOP; }
/** Find the length of the buffer required to fully escape a string with fr_snprint * * Were assuming here that's it's cheaper to figure out the length and do one * alloc than repeatedly expand the buffer when we find extra chars which need * to be added. * * @param in string to calculate the escaped length for. * @param inlen length of the input string, if < 0 strlen will be used to check the length. * @param[in] quote the quotation character. * @return the size of buffer required to hold the escaped string including the NULL byte. */ size_t fr_snprint_len(char const *in, ssize_t inlen, char quote) { uint8_t const *p = (uint8_t const *) in; size_t outlen = 1; /* Need one byte for \0 */ int utf8 = 0; if (!in) return outlen; if (inlen < 0) inlen = strlen(in); if (!quote) return inlen + 1; while (inlen > 0) { int sp = 0; /* * Hack: never print trailing zero. Some clients send pings * with an off-by-one length (confused with strings in C). */ if ((inlen == 1) && (*p == '\0')) { inlen--; break; } if (quote && (*p == quote)) { sp = quote; goto do_escape; } if (quote == '\'') { if (*p == '\\') { sp = '\\'; } goto do_escape; } switch (*p) { case '\r': sp = 'r'; break; case '\n': sp = 'n'; break; case '\t': sp = 't'; break; case '\\': sp = '\\'; break; default: sp = '\0'; break; } do_escape: if (sp) { outlen += 2; p++; inlen--; continue; } if (quote != '\'') { utf8 = fr_utf8_char(p, inlen); if (utf8 == 0) { outlen += 4; p++; inlen--; continue; } } else { utf8 = 1; } outlen += utf8; p += utf8; inlen -= utf8; } return outlen; }
/** Escape any non printable or non-UTF8 characters in the input string * * @note Return value should be checked with is_truncated * @note Will always \0 terminate unless outlen == 0. * * @param[in] in string to escape. * @param[in] inlen length of string to escape (lets us deal with embedded NULLs) * @param[out] out where to write the escaped string. * @param[out] outlen the length of the buffer pointed to by out. * @param[in] quote the quotation character * @return * - The number of bytes written to the out buffer. * - A number >= outlen if truncation has occurred. */ size_t fr_snprint(char *out, size_t outlen, char const *in, ssize_t inlen, char quote) { uint8_t const *p = (uint8_t const *) in; int utf8 = 0; size_t freespace = outlen; /* * IF YOU MODIFY THIS FUNCTION, YOU MUST MAKE * EQUIVALENT MODIFICATIONS TO fr_snprint_len */ /* Can't '\0' terminate */ if (freespace == 0) return inlen; /* No input, so no output... */ if (!in) { no_input: *out = '\0'; return 0; } /* Figure out the length of the input string */ if (inlen < 0) inlen = strlen(in); /* Not enough space to hold one char */ if (freespace < 2) { /* And there's input data... */ if (inlen > 0) { *out = '\0'; return inlen; } goto no_input; } /* * No quotation character, just use memcpy, ensuring we * don't overflow the output buffer. */ if (!quote) { if ((size_t)inlen >= outlen) { memcpy(out, in, outlen - 1); out[outlen - 1] = '\0'; } else { memcpy(out, in, inlen); out[inlen] = '\0'; } return inlen; } while (inlen > 0) { int sp = 0; /* * Hack: never print trailing zero. * Some clients send pings with an off-by-one * length (confused with strings in C). */ if ((inlen == 1) && (*p == '\0')) { inlen--; break; } /* * Always escape the quotation character. */ if (*p == quote) { sp = quote; goto do_escape; } /* * Escape the backslash ONLY for single quoted strings. */ if (quote == '\'') { if (*p == '\\') { sp = '\\'; } goto do_escape; } /* * Try to convert 0x0a --> \r, etc. * Backslashes get handled specially. */ switch (*p) { case '\r': sp = 'r'; break; case '\n': sp = 'n'; break; case '\t': sp = 't'; break; case '\\': sp = '\\'; break; default: sp = '\0'; break; } /* escape the character at *p */ do_escape: if (sp) { if (freespace < 3) break; /* \ + <c> + \0 */ *out++ = '\\'; *out++ = sp; freespace -= 2; p++; inlen--; continue; } /* * Double quoted strings have octal escaping for * things. Single quoted strings don't. */ if (quote != '\'') { utf8 = fr_utf8_char(p, inlen); if (utf8 == 0) { if (freespace < 5) break; /* \ + <o><o><o> + \0 */ snprintf(out, freespace, "\\%03o", *p); out += 4; freespace -= 4; p++; inlen--; continue; } } do { if (freespace < 2) goto finish; /* <c> + \0 */ *out++ = *p++; freespace--; inlen--; } while (--utf8 > 0); } finish: *out = '\0'; /* Indicate truncation occurred */ if (inlen > 0) return outlen + inlen; return outlen - freespace; }
/* * Process and reply to an authentication request * * The return value of this function isn't actually used right now, so * it's not entirely clear if it is returning the right things. --Pac. */ int rad_authenticate(REQUEST *request) { VALUE_PAIR *namepair; #ifdef WITH_SESSION_MGMT VALUE_PAIR *check_item; #endif VALUE_PAIR *auth_item = NULL; VALUE_PAIR *module_msg; VALUE_PAIR *tmp = NULL; int result; const char *password; char autz_retry = 0; int autz_type = 0; password = ""; #ifdef WITH_PROXY /* * If this request got proxied to another server, we need * to check whether it authenticated the request or not. */ if (request->proxy_reply) { switch (request->proxy_reply->code) { /* * Reply of ACCEPT means accept, thus set Auth-Type * accordingly. */ case PW_AUTHENTICATION_ACK: tmp = radius_paircreate(request, &request->config_items, PW_AUTH_TYPE, PW_TYPE_INTEGER); if (tmp) tmp->vp_integer = PW_AUTHTYPE_ACCEPT; #ifdef WITH_POST_PROXY_AUTHORIZE if (mainconfig.post_proxy_authorize) break; #endif goto authenticate; /* * Challenges are punted back to the NAS without any * further processing. */ case PW_ACCESS_CHALLENGE: request->reply->code = PW_ACCESS_CHALLENGE; return RLM_MODULE_OK; /* * ALL other replies mean reject. (this is fail-safe) * * Do NOT do any authorization or authentication. They * are being rejected, so we minimize the amount of work * done by the server, by rejecting them here. */ case PW_AUTHENTICATION_REJECT: rad_authlog("Login incorrect (Home Server says so)", request, 0); request->reply->code = PW_AUTHENTICATION_REJECT; return RLM_MODULE_REJECT; default: rad_authlog("Login incorrect (Home Server failed to respond)", request, 0); return RLM_MODULE_REJECT; } } #endif /* * Get the username from the request. * * Note that namepair MAY be NULL, in which case there * is no User-Name attribute in the request. */ namepair = request->username; /* * Look for, and cache, passwords. */ if (!request->password) { request->password = pairfind(request->packet->vps, PW_USER_PASSWORD); } /* * Discover which password we want to use. */ auth_item = request->password; if (auth_item) { password = (const char *)auth_item->vp_strvalue; } else { /* * Maybe there's a CHAP-Password? */ if ((auth_item = pairfind(request->packet->vps, PW_CHAP_PASSWORD)) != NULL) { password = "******"; } else { /* * No password we recognize. */ password = "******"; } } request->password = auth_item; /* * Get the user's authorization information from the database */ autz_redo: result = module_authorize(autz_type, request); switch (result) { case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: break; case RLM_MODULE_HANDLED: return result; case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: if ((module_msg = pairfind(request->packet->vps, PW_MODULE_FAILURE_MESSAGE)) != NULL) { char msg[MAX_STRING_LEN + 16]; snprintf(msg, sizeof(msg), "Invalid user (%s)", module_msg->vp_strvalue); rad_authlog(msg,request,0); } else { rad_authlog("Invalid user", request, 0); } request->reply->code = PW_AUTHENTICATION_REJECT; return result; } if (!autz_retry) { tmp = pairfind(request->config_items, PW_AUTZ_TYPE); if (tmp) { autz_type = tmp->vp_integer; RDEBUG2("Using Autz-Type %s", dict_valnamebyattr(PW_AUTZ_TYPE, autz_type)); autz_retry = 1; goto autz_redo; } } /* * If we haven't already proxied the packet, then check * to see if we should. Maybe one of the authorize * modules has decided that a proxy should be used. If * so, get out of here and send the packet. */ if ( #ifdef WITH_PROXY (request->proxy == NULL) && #endif ((tmp = pairfind(request->config_items, PW_PROXY_TO_REALM)) != NULL)) { REALM *realm; realm = realm_find2(tmp->vp_strvalue); /* * Don't authenticate, as the request is going to * be proxied. */ if (realm && realm->auth_pool) { return RLM_MODULE_OK; } /* * Catch users who set Proxy-To-Realm to a LOCAL * realm (sigh). But don't complain if it is * *the* LOCAL realm. */ if (realm &&(strcmp(realm->name, "LOCAL") != 0)) { RDEBUG2("WARNING: You set Proxy-To-Realm = %s, but it is a LOCAL realm! Cancelling proxy request.", realm->name); } if (!realm) { RDEBUG2("WARNING: You set Proxy-To-Realm = %s, but the realm does not exist! Cancelling invalid proxy request.", tmp->vp_strvalue); } } #ifdef WITH_PROXY authenticate: #endif /* * Perhaps there is a Stripped-User-Name now. */ namepair = request->username; /* * Validate the user */ do { result = rad_check_password(request); if (result > 0) { /* don't reply! */ return RLM_MODULE_HANDLED; } } while(0); /* * Failed to validate the user. * * We PRESUME that the code which failed will clean up * request->reply->vps, to be ONLY the reply items it * wants to send back. */ if (result < 0) { RDEBUG2("Failed to authenticate the user."); request->reply->code = PW_AUTHENTICATION_REJECT; if ((module_msg = pairfind(request->packet->vps,PW_MODULE_FAILURE_MESSAGE)) != NULL){ char msg[MAX_STRING_LEN+19]; snprintf(msg, sizeof(msg), "Login incorrect (%s)", module_msg->vp_strvalue); rad_authlog(msg, request, 0); } else { rad_authlog("Login incorrect", request, 0); } /* double check: maybe the secret is wrong? */ if ((debug_flag > 1) && (auth_item != NULL) && (auth_item->attribute == PW_USER_PASSWORD)) { uint8_t *p; p = (uint8_t *) auth_item->vp_strvalue; while (*p) { int size; size = fr_utf8_char(p); if (!size) { log_debug(" WARNING: Unprintable characters in the password. Double-check the shared secret on the server and the NAS!"); break; } p += size; } } } #ifdef WITH_SESSION_MGMT if (result >= 0 && (check_item = pairfind(request->config_items, PW_SIMULTANEOUS_USE)) != NULL) { int r, session_type = 0; char logstr[1024]; char umsg[MAX_STRING_LEN + 1]; const char *user_msg = NULL; tmp = pairfind(request->config_items, PW_SESSION_TYPE); if (tmp) { session_type = tmp->vp_integer; RDEBUG2("Using Session-Type %s", dict_valnamebyattr(PW_SESSION_TYPE, session_type)); } /* * User authenticated O.K. Now we have to check * for the Simultaneous-Use parameter. */ if (namepair && (r = module_checksimul(session_type, request, check_item->vp_integer)) != 0) { char mpp_ok = 0; if (r == 2){ /* Multilink attempt. Check if port-limit > simultaneous-use */ VALUE_PAIR *port_limit; if ((port_limit = pairfind(request->reply->vps, PW_PORT_LIMIT)) != NULL && port_limit->vp_integer > check_item->vp_integer){ RDEBUG2("MPP is OK"); mpp_ok = 1; } } if (!mpp_ok){ if (check_item->vp_integer > 1) { snprintf(umsg, sizeof(umsg), "\r\nYou are already logged in %d times - access denied\r\n\n", (int)check_item->vp_integer); user_msg = umsg; } else { user_msg = "\r\nYou are already logged in - access denied\r\n\n"; } request->reply->code = PW_AUTHENTICATION_REJECT; /* * They're trying to log in too many times. * Remove ALL reply attributes. */ pairfree(&request->reply->vps); radius_pairmake(request, &request->reply->vps, "Reply-Message", user_msg, T_OP_SET); snprintf(logstr, sizeof(logstr), "Multiple logins (max %d) %s", check_item->vp_integer, r == 2 ? "[MPP attempt]" : ""); rad_authlog(logstr, request, 1); result = -1; } } } #endif /* * Result should be >= 0 here - if not, it means the user * is rejected, so we just process post-auth and return. */ if (result < 0) { return RLM_MODULE_REJECT; } /* * Add the port number to the Framed-IP-Address if * vp->addport is set. */ if (((tmp = pairfind(request->reply->vps, PW_FRAMED_IP_ADDRESS)) != NULL) && (tmp->flags.addport != 0)) { VALUE_PAIR *vpPortId; /* * Find the NAS port ID. */ if ((vpPortId = pairfind(request->packet->vps, PW_NAS_PORT)) != NULL) { unsigned long tvalue = ntohl(tmp->vp_integer); tmp->vp_integer = htonl(tvalue + vpPortId->vp_integer); tmp->flags.addport = 0; ip_ntoa(tmp->vp_strvalue, tmp->vp_integer); } else { RDEBUG2("WARNING: No NAS-Port attribute in request. CANNOT return a Framed-IP-Address + NAS-Port.\n"); pairdelete(&request->reply->vps, PW_FRAMED_IP_ADDRESS); } } /* * Set the reply to Access-Accept, if it hasn't already * been set to something. (i.e. Access-Challenge) */ if (request->reply->code == 0) request->reply->code = PW_AUTHENTICATION_ACK; if ((module_msg = pairfind(request->packet->vps,PW_MODULE_SUCCESS_MESSAGE)) != NULL){ char msg[MAX_STRING_LEN+12]; snprintf(msg, sizeof(msg), "Login OK (%s)", module_msg->vp_strvalue); rad_authlog(msg, request, 1); } else { rad_authlog("Login OK", request, 1); } /* * Run the modules in the 'post-auth' section. */ result = rad_postauth(request); return result; }
/** Escapes the raw string such that it should be safe to use as part of a file path * * This function is designed to produce a string that's still readable but portable * across the majority of file systems. * * For security reasons it cannot remove characters from the name, and must not allow * collisions to occur between different strings. * * With that in mind '-' has been chosen as the escape character, and will be double * escaped '-' -> '--' to avoid collisions. * * Escaping should be reversible if the original string needs to be extracted. * * @note function takes additional arguments so that it may be used as an xlat escape * function but it's fine to call it directly. * * @note OSX/Unix/NTFS/VFAT have a max filename size of 255 bytes. * * @param request Current request (may be NULL). * @param out Output buffer. * @param outlen Size of the output buffer. * @param in string to escape. * @param arg Context arguments (unused, should be NULL). */ size_t rad_filename_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, UNUSED void *arg) { size_t freespace = outlen; while (*in != '\0') { size_t utf8_len; /* * Encode multibyte UTF8 chars */ utf8_len = fr_utf8_char((uint8_t const *) in, -1); if (utf8_len > 1) { if (freespace <= (utf8_len * 3)) break; switch (utf8_len) { case 2: snprintf(out, freespace, "-%x-%x", in[0], in[1]); break; case 3: snprintf(out, freespace, "-%x-%x-%x", in[0], in[1], in[2]); break; case 4: snprintf(out, freespace, "-%x-%x-%x-%x", in[0], in[1], in[2], in[3]); break; } freespace -= (utf8_len * 3); out += (utf8_len * 3); in += utf8_len; continue; } /* * Safe chars */ if (((*in >= 'A') && (*in <= 'Z')) || ((*in >= 'a') && (*in <= 'z')) || ((*in >= '0') && (*in <= '9')) || (*in == '_')) { if (freespace <= 1) break; *out++ = *in++; freespace--; continue; } if (freespace <= 2) break; /* * Double escape '-' (like \\) */ if (*in == '-') { *out++ = '-'; *out++ = '-'; freespace -= 2; in++; continue; } /* * Unsafe chars */ *out++ = '-'; fr_bin2hex(out, (uint8_t const *)in++, 1); out += 2; freespace -= 3; } *out = '\0'; return outlen - freespace; }
/* * Process and reply to an authentication request * * The return value of this function isn't actually used right now, so * it's not entirely clear if it is returning the right things. --Pac. */ int rad_authenticate(REQUEST *request) { #ifdef WITH_SESSION_MGMT VALUE_PAIR *check_item; #endif VALUE_PAIR *module_msg; VALUE_PAIR *tmp = NULL; int result; char autz_retry = 0; int autz_type = 0; #ifdef WITH_PROXY /* * If this request got proxied to another server, we need * to check whether it authenticated the request or not. * * request->proxy gets set only AFTER authorization, so * it's safe to check it here. If it exists, it means * we're doing a second pass through rad_authenticate(). */ if (request->proxy) { int code = 0; if (request->proxy_reply) code = request->proxy_reply->code; switch (code) { /* * Reply of ACCEPT means accept, thus set Auth-Type * accordingly. */ case PW_CODE_ACCESS_ACCEPT: tmp = radius_paircreate(request, &request->config, PW_AUTH_TYPE, 0); if (tmp) tmp->vp_integer = PW_AUTHTYPE_ACCEPT; goto authenticate; /* * Challenges are punted back to the NAS without any * further processing. */ case PW_CODE_ACCESS_CHALLENGE: request->reply->code = PW_CODE_ACCESS_CHALLENGE; fr_state_put_vps(request, request->packet, request->reply); return RLM_MODULE_OK; /* * ALL other replies mean reject. (this is fail-safe) * * Do NOT do any authorization or authentication. They * are being rejected, so we minimize the amount of work * done by the server, by rejecting them here. */ case PW_CODE_ACCESS_REJECT: rad_authlog("Login incorrect (Home Server says so)", request, 0); request->reply->code = PW_CODE_ACCESS_REJECT; fr_state_discard(request, request->packet); return RLM_MODULE_REJECT; default: rad_authlog("Login incorrect (Home Server failed to respond)", request, 0); fr_state_discard(request, request->packet); return RLM_MODULE_REJECT; } } #endif /* * Look for, and cache, passwords. */ if (!request->password) { request->password = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY); } if (!request->password) { request->password = pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY); } /* * Grab the VPS associated with the State attribute. */ fr_state_get_vps(request, request->packet); /* * Get the user's authorization information from the database */ autz_redo: result = process_authorize(autz_type, request); switch (result) { case RLM_MODULE_NOOP: case RLM_MODULE_NOTFOUND: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: break; case RLM_MODULE_HANDLED: return result; case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: if ((module_msg = pairfind(request->packet->vps, PW_MODULE_FAILURE_MESSAGE, 0, TAG_ANY)) != NULL) { char msg[MAX_STRING_LEN + 16]; snprintf(msg, sizeof(msg), "Invalid user (%s)", module_msg->vp_strvalue); rad_authlog(msg,request,0); } else { rad_authlog("Invalid user", request, 0); } request->reply->code = PW_CODE_ACCESS_REJECT; return result; } if (!autz_retry) { tmp = pairfind(request->config, PW_AUTZ_TYPE, 0, TAG_ANY); if (tmp) { autz_type = tmp->vp_integer; RDEBUG2("Using Autz-Type %s", dict_valnamebyattr(PW_AUTZ_TYPE, 0, autz_type)); autz_retry = 1; goto autz_redo; } } /* * If we haven't already proxied the packet, then check * to see if we should. Maybe one of the authorize * modules has decided that a proxy should be used. If * so, get out of here and send the packet. */ if ( #ifdef WITH_PROXY (request->proxy == NULL) && #endif ((tmp = pairfind(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY)) != NULL)) { REALM *realm; realm = realm_find2(tmp->vp_strvalue); /* * Don't authenticate, as the request is going to * be proxied. */ if (realm && realm->auth_pool) { return RLM_MODULE_OK; } /* * Catch users who set Proxy-To-Realm to a LOCAL * realm (sigh). But don't complain if it is * *the* LOCAL realm. */ if (realm &&(strcmp(realm->name, "LOCAL") != 0)) { RWDEBUG2("You set Proxy-To-Realm = %s, but it is a LOCAL realm! Cancelling proxy request.", realm->name); } if (!realm) { RWDEBUG2("You set Proxy-To-Realm = %s, but the realm does not exist! Cancelling invalid proxy request.", tmp->vp_strvalue); } } #ifdef WITH_PROXY authenticate: #endif /* * Validate the user */ do { result = rad_check_password(request); if (result > 0) { return RLM_MODULE_HANDLED; } } while(0); /* * Failed to validate the user. * * We PRESUME that the code which failed will clean up * request->reply->vps, to be ONLY the reply items it * wants to send back. */ if (result < 0) { RDEBUG2("Failed to authenticate the user"); request->reply->code = PW_CODE_ACCESS_REJECT; if ((module_msg = pairfind(request->packet->vps, PW_MODULE_FAILURE_MESSAGE, 0, TAG_ANY)) != NULL){ char msg[MAX_STRING_LEN+19]; snprintf(msg, sizeof(msg), "Login incorrect (%s)", module_msg->vp_strvalue); rad_authlog(msg, request, 0); } else { rad_authlog("Login incorrect", request, 0); } if (request->password) { VERIFY_VP(request->password); /* double check: maybe the secret is wrong? */ if ((debug_flag > 1) && (request->password->da->attr == PW_USER_PASSWORD)) { uint8_t const *p; p = (uint8_t const *) request->password->vp_strvalue; while (*p) { int size; size = fr_utf8_char(p); if (!size) { RWDEBUG("Unprintable characters in the password. Double-check the " "shared secret on the server and the NAS!"); break; } p += size; } } } } #ifdef WITH_SESSION_MGMT if (result >= 0 && (check_item = pairfind(request->config, PW_SIMULTANEOUS_USE, 0, TAG_ANY)) != NULL) { int r, session_type = 0; char logstr[1024]; char umsg[MAX_STRING_LEN + 1]; tmp = pairfind(request->config, PW_SESSION_TYPE, 0, TAG_ANY); if (tmp) { session_type = tmp->vp_integer; RDEBUG2("Using Session-Type %s", dict_valnamebyattr(PW_SESSION_TYPE, 0, session_type)); } /* * User authenticated O.K. Now we have to check * for the Simultaneous-Use parameter. */ if (request->username && (r = process_checksimul(session_type, request, check_item->vp_integer)) != 0) { char mpp_ok = 0; if (r == 2){ /* Multilink attempt. Check if port-limit > simultaneous-use */ VALUE_PAIR *port_limit; if ((port_limit = pairfind(request->reply->vps, PW_PORT_LIMIT, 0, TAG_ANY)) != NULL && port_limit->vp_integer > check_item->vp_integer){ RDEBUG2("MPP is OK"); mpp_ok = 1; } } if (!mpp_ok){ if (check_item->vp_integer > 1) { snprintf(umsg, sizeof(umsg), "%s (%u)", main_config.denied_msg, check_item->vp_integer); } else { strlcpy(umsg, main_config.denied_msg, sizeof(umsg)); } request->reply->code = PW_CODE_ACCESS_REJECT; /* * They're trying to log in too many times. * Remove ALL reply attributes. */ pairfree(&request->reply->vps); pairmake_reply("Reply-Message", umsg, T_OP_SET); snprintf(logstr, sizeof(logstr), "Multiple logins (max %d) %s", check_item->vp_integer, r == 2 ? "[MPP attempt]" : ""); rad_authlog(logstr, request, 1); result = -1; } } } #endif /* * Result should be >= 0 here - if not, it means the user * is rejected, so we just process post-auth and return. */ if (result < 0) { return RLM_MODULE_REJECT; } /* * Set the reply to Access-Accept, if it hasn't already * been set to something. (i.e. Access-Challenge) */ if (request->reply->code == 0) request->reply->code = PW_CODE_ACCESS_ACCEPT; if ((module_msg = pairfind(request->packet->vps, PW_MODULE_SUCCESS_MESSAGE, 0, TAG_ANY)) != NULL){ char msg[MAX_STRING_LEN+12]; snprintf(msg, sizeof(msg), "Login OK (%s)", module_msg->vp_strvalue); rad_authlog(msg, request, 1); } else { rad_authlog("Login OK", request, 1); } return result; }
static size_t sql_utf8_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, const char *in, void *arg) { rlm_sql_log_t *inst = (rlm_sql_log_t *)arg; size_t len = 0; size_t utf8 = 0; while (in[0]) { /* * Skip over UTF8 characters */ utf8 = fr_utf8_char((const uint8_t *)in); if (utf8) { if (outlen <= utf8) { break; } while (utf8-- > 0) { *out = *in; out++; in++; outlen--; len++; } continue; } /* * Non-printable characters get replaced with their * mime-encoded equivalents. */ if ((in[0] < 32) || strchr(inst->allowed_chars, *in) == NULL) { /* * Only 3 or less bytes available. */ if (outlen <= 3) { break; } snprintf(out, outlen, "=%02X", (unsigned char) in[0]); in++; out += 3; outlen -= 3; len += 3; continue; } /* * Only one byte left. */ if (outlen <= 1) { break; } /* * Allowed character. */ *out = *in; out++; in++; outlen--; len++; } *out = '\0'; return len; }
/* * Translate the SQL queries. */ static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, char const *in, void *arg) { rlm_sql_t *inst = arg; size_t len = 0; while (in[0]) { size_t utf8_len; /* * Allow all multi-byte UTF8 characters. */ utf8_len = fr_utf8_char((uint8_t const *) in); if (utf8_len > 1) { if (outlen <= utf8_len) break; memcpy(out, in, utf8_len); in += utf8_len; out += utf8_len; outlen -= utf8_len; len += utf8_len; continue; } /* * Because we register our own escape function * we're now responsible for escaping all special * chars in an xlat expansion or attribute value. */ switch (in[0]) { case '\n': if (outlen <= 2) break; out[0] = '\''; out[1] = 'n'; in++; out += 2; outlen -= 2; len += 2; break; case '\r': if (outlen <= 2) break; out[0] = '\''; out[1] = 'r'; in++; out += 2; outlen -= 2; len += 2; break; case '\t': if (outlen <= 2) break; out[0] = '\''; out[1] = 't'; in++; out += 2; outlen -= 2; len += 2; break; } /* * Non-printable characters get replaced with their * mime-encoded equivalents. */ if ((in[0] < 32) || strchr(inst->config->allowed_chars, *in) == NULL) { /* * Only 3 or less bytes available. */ if (outlen <= 3) { break; } snprintf(out, outlen, "=%02X", (unsigned char) in[0]); in++; out += 3; outlen -= 3; len += 3; continue; } /* * Only one byte left. */ if (outlen <= 1) { break; } /* * Allowed character. */ *out = *in; out++; in++; outlen--; len++; } *out = '\0'; return len; }
/** Escape any non printable or non-UTF8 characters in the input string * * @note Return value should be checked with is_truncated * @note Will always \0 terminate unless outlen == 0. * * @param[in] in string to escape. * @param[in] inlen length of string to escape (lets us deal with embedded NULs) * @param[out] out where to write the escaped string. * @param[out] outlen the length of the buffer pointed to by out. * @param[in] quote the quotation character * @return the number of bytes it WOULD HAVE written to the buffer, not including the trailing NUL */ size_t fr_prints(char *out, size_t outlen, char const *in, ssize_t inlen, char quote) { uint8_t const *p = (uint8_t const *) in; size_t utf8; size_t used; size_t freespace; /* No input, so no output... */ if (!in) { if (out && outlen) *out = '\0'; return 0; } /* Figure out the length of the input string */ if (inlen < 0) inlen = strlen(in); /* * No quotation character, just use memcpy, ensuring we * don't overflow the output buffer. */ if (!quote) { if (!out) return inlen; if ((size_t)inlen >= outlen) { memcpy(out, in, outlen - 1); out[outlen - 1] = '\0'; } else { memcpy(out, in, inlen); out[inlen] = '\0'; } return inlen; } /* * Check the output buffer and length. Zero both of them * out if either are zero. */ freespace = outlen; if (freespace == 0) out = NULL; if (!out) freespace = 0; used = 0; while (inlen > 0) { int sp = 0; /* * Hack: never print trailing zero. * Some clients send pings with an off-by-one * length (confused with strings in C). */ if ((inlen == 1) && (*p == '\0')) { inlen--; break; } /* * Always escape the quotation character. */ if (*p == quote) { sp = quote; goto do_escape; } /* * Escape the backslash ONLY for single quoted strings. */ if (quote == '\'') { if (*p == '\\') { sp = '\\'; } goto do_escape; } /* * Try to convert 0x0a --> \r, etc. * Backslashes get handled specially. */ switch (*p) { case '\r': sp = 'r'; break; case '\n': sp = 'n'; break; case '\t': sp = 't'; break; case '\\': sp = '\\'; break; default: sp = '\0'; break; } /* escape the character at *p */ do_escape: if (sp) { if ((freespace > 0) && (freespace <= 2)) { if (out) out[used] = '\0'; out = NULL; freespace = 0; } else if (freespace > 2) { /* room for char AND trailing zero */ if (out) { out[used] = '\\'; out[used + 1] = sp; } freespace -= 2; } used += 2; p++; inlen--; continue; } /* * All strings are UTF-8 clean. */ utf8 = fr_utf8_char(p, inlen); /* * If we have an invalid UTF-8 character, it gets * copied over as a 1-byte character for single * quoted strings. Which means that the output * isn't strictly UTF-8, but oh well... * * For double quoted strints, the invalid * characters get escaped as octal encodings. */ if (utf8 == 0) { if (quote == '\'') { utf8 = 1; } else { if ((freespace > 0) && (freespace <= 4)) { if (out) out[used] = '\0'; out = NULL; freespace = 0; } else if (freespace > 4) { /* room for char AND trailing zero */ if (out) snprintf(out + used, freespace, "\\%03o", *p); freespace -= 4; } used += 4; p++; inlen--; continue; } } if ((freespace > 0) && (freespace <= utf8)) { if (out) out[used] = '\0'; out = NULL; freespace = 0; } else if (freespace > utf8) { /* room for char AND trailing zero */ if (out) memcpy(out + used, p, utf8); freespace -= utf8; } used += utf8; p += utf8; inlen -= utf8; } /* * Ensure that the output buffer is always zero terminated. */ if (out && freespace) out[used] = '\0'; return used; }
/* * Log the message to the logfile. Include the severity and * a time stamp. */ int vradlog(log_type_t type, char const *fmt, va_list ap) { unsigned char *p; char buffer[10240]; /* The largest config item size, then extra for prefixes and suffixes */ char *unsan; size_t len; int colourise = default_log.colourise; /* * NOT debugging, and trying to log debug messages. * * Throw the message away. */ if (!debug_flag && ((type & L_DBG) != 0)) { return 0; } /* * If we don't want any messages, then * throw them away. */ if (default_log.dst == L_DST_NULL) { return 0; } buffer[0] = '\0'; len = 0; if (colourise) { len += strlcpy(buffer + len, fr_int2str(colours, type, ""), sizeof(buffer) - len) ; if (len == 0) { colourise = false; } } /* * Mark the point where we treat the buffer as unsanitized. */ unsan = buffer + len; /* * Don't print timestamps to syslog, it does that for us. * Don't print timestamps and error types for low levels * of debugging. * * Print timestamps for non-debugging, and for high levels * of debugging. */ if (default_log.dst != L_DST_SYSLOG) { if ((debug_flag != 1) && (debug_flag != 2)) { time_t timeval; timeval = time(NULL); CTIME_R(&timeval, buffer + len, sizeof(buffer) - len - 1); len = strlen(buffer); len += strlcpy(buffer + len, fr_int2str(levels, type, ": "), sizeof(buffer) - len); } else goto add_prefix; } else { add_prefix: if (len < sizeof(buffer)) switch (type) { case L_DBG_WARN: len += strlcpy(buffer + len, "WARNING: ", sizeof(buffer) - len); break; case L_DBG_ERR: len += strlcpy(buffer + len, "ERROR: ", sizeof(buffer) - len); break; default: break; } } if (len < sizeof(buffer)) { len += vsnprintf(buffer + len, sizeof(buffer) - len - 1, fmt, ap); } /* * Filter out control chars and non UTF8 chars */ for (p = (unsigned char *)unsan; *p != '\0'; p++) { int clen; switch (*p) { case '\r': case '\n': *p = ' '; break; case '\t': continue; default: clen = fr_utf8_char(p); if (!clen) { *p = '?'; continue; } p += (clen - 1); break; } } if (colourise && (len < sizeof(buffer))) { len += strlcpy(buffer + len, VTC_RESET, sizeof(buffer) - len); } if (len < (sizeof(buffer) - 2)) { buffer[len] = '\n'; buffer[len + 1] = '\0'; } else { buffer[sizeof(buffer) - 2] = '\n'; buffer[sizeof(buffer) - 1] = '\0'; } switch (default_log.dst) { #ifdef HAVE_SYSLOG_H case L_DST_SYSLOG: switch(type) { case L_DBG: case L_WARN: case L_DBG_WARN: case L_DBG_ERR: case L_DBG_ERR_REQ: case L_DBG_WARN_REQ: type = LOG_DEBUG; break; case L_AUTH: case L_PROXY: case L_ACCT: type = LOG_NOTICE; break; case L_INFO: type = LOG_INFO; break; case L_ERR: type = LOG_ERR; break; } syslog(type, "%s", buffer); break; #endif case L_DST_FILES: case L_DST_STDOUT: case L_DST_STDERR: return write(default_log.fd, buffer, strlen(buffer)); default: case L_DST_NULL: /* should have been caught above */ break; } return 0; }
/* * Convert a string to something printable. The output string * has to be larger than the input string by at least 5 bytes. * If not, the output is silently truncated... */ void fr_print_string(const char *in, size_t inlen, char *out, size_t outlen) { const uint8_t *str = (const uint8_t *) in; int sp = 0; int utf8 = 0; if (inlen == 0) inlen = strlen(in); /* * */ while ((inlen > 0) && (outlen > 4)) { /* * Hack: never print trailing zero. * Some clients send strings with an off-by-one * length (confused with strings in C). */ if ((inlen == 1) && (*str == 0)) break; switch (*str) { case '\\': sp = '\\'; break; case '\r': sp = 'r'; break; case '\n': sp = 'n'; break; case '\t': sp = 't'; break; case '"': sp = '"'; break; default: sp = 0; break; } if (sp) { *out++ = '\\'; *out++ = sp; outlen -= 2; str++; inlen--; continue; } utf8 = fr_utf8_char(str); if (!utf8) { snprintf(out, outlen, "\\%03o", *str); out += 4; outlen -= 4; str++; inlen--; continue; } do { *out++ = *str++; outlen--; inlen--; } while (--utf8 > 0); } *out = 0; }