/** Print one attribute and value to a string * * Print a VALUE_PAIR in the format: @verbatim <attribute_name>[:tag] <op> <value> @endverbatim * to a string. * * @param out Where to write the string. * @param outlen Lenth of output buffer. * @param vp to print. * @return * - Length of data written to out. * - value >= outlen on truncation. */ size_t vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp) { char const *token = NULL; size_t len, freespace = outlen; if (!out) return 0; *out = '\0'; if (!vp || !vp->da) return 0; VERIFY_VP(vp); if ((vp->op > T_INVALID) && (vp->op < T_TOKEN_LAST)) { token = vp_tokens[vp->op]; } else { token = "<INVALID-TOKEN>"; } if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { len = snprintf(out, freespace, "%s:%d %s ", vp->da->name, vp->tag, token); } else { len = snprintf(out, freespace, "%s %s ", vp->da->name, token); } if (is_truncated(len, freespace)) return len; out += len; freespace -= len; len = vp_prints_value(out, freespace, vp, '"'); if (is_truncated(len, freespace)) return (outlen - freespace) + len; freespace -= len; return (outlen - freespace); }
/** Print the address portion of a #fr_ipaddr_t * * @note Includes the textual scope_id name (eth0, en0 etc...) if supported. * * @param[out] out Where to write the resulting IP string. * Should be at least FR_IPADDR_STRLEN bytes. * @param[in] outlen of output buffer. * @param[in] addr to convert to presentation format. * @return * - NULL on error (use fr_syserror(errno)). * - a pointer to out on success. */ char *fr_inet_ntop(char out[FR_IPADDR_STRLEN], size_t outlen, fr_ipaddr_t const *addr) { char *p; size_t len; out[0] = '\0'; if (inet_ntop(addr->af, &addr->addr, out, outlen) == NULL) { fr_strerror_printf("%s", fr_syserror(errno)); return NULL; } if ((addr->af == AF_INET) || (addr->scope_id == 0)) return out; p = out + strlen(out); #ifdef WITH_IFINDEX_NAME_RESOLUTION { char buffer[IFNAMSIZ]; char *ifname; ifname = fr_ifname_from_ifindex(buffer, addr->scope_id); if (ifname) { len = snprintf(p, outlen - (p - out), "%%%s", ifname); if (is_truncated(len + (p - out), outlen)) { fr_strerror_printf("Address buffer too small, needed %zu bytes, have %zu bytes", (p - out) + len, outlen); return NULL; } return out; } } #endif len = snprintf(p, outlen - (p - out), "%%%u", addr->scope_id); if (is_truncated(len + (p - out), outlen)) { fr_strerror_printf("Address buffer too small, needed %zu bytes, have %zu bytes", (p - out) + len, outlen); return NULL; } return out; }
// ******************************************************************************************** void Block::ReadQualityPlain(BitStream &bit_stream, std::vector<uchar> &qualities, std::vector<HuffmanEncoder*> &Huffman_qua, int32 /*n_qualities*/, bool trucated_hashes, bool /*uses_const_delta*/) { // Quality data for (uint32 i = 0; i < rec_count; ++i) { no_of_amb[i] = 0; uchar *cur_quality = records[i].quality; uint32 cur_quality_len = records[i].quality_len; uint32 trunc_len = 0; if (trucated_hashes) // Truncate # { uint32 is_truncated(0); // skip warning... bit_stream.GetBit(is_truncated); if (is_truncated) { bit_stream.GetBits(trunc_len, BitStream::BitLength(cur_quality_len)); } } for (uint32 j = 0; j < cur_quality_len-trunc_len; ++j) { uint32 bit; int32 h_tmp; bit_stream.GetBits(bit, Huffman_qua[j+1]->GetMinLen()); h_tmp = Huffman_qua[j+1]->DecodeFast(bit); while (h_tmp < 0) { bit_stream.GetBit(bit); h_tmp = Huffman_qua[j+1]->Decode(bit); }; if ((cur_quality[j] = qualities[h_tmp]) >= 128) { no_of_amb[i]++; } } for (uint32 j = cur_quality_len-trunc_len; j < cur_quality_len; ++j) { cur_quality[j] = '#'; } } bit_stream.FlushInputWordBuffer(); }
/** Print a #fr_ipaddr_t as a CIDR style network prefix * * @param[out] out Where to write the resulting prefix string. * Should be at least FR_IPADDR_PREFIX_STRLEN bytes. * @param[in] outlen of output buffer. * @param[in] addr to convert to presentation format. * @return * - NULL on error (use fr_syserror(errno)). * - a pointer to out on success. */ char *fr_inet_ntop_prefix(char out[FR_IPADDR_PREFIX_STRLEN], size_t outlen, fr_ipaddr_t const *addr) { char *p; size_t len; if (fr_inet_ntop(out, outlen, addr) == NULL) return NULL; p = out + strlen(out); len = snprintf(p, outlen - (p - out), "/%i", addr->prefix); if (is_truncated(len + (p - out), outlen)) { fr_strerror_printf("Address buffer too small, needed %zu bytes, have %zu bytes", (p - out) + len, outlen); return NULL; } return out; }
/** Check to see if panic_action file is world writeable * * @return 0 if file is OK, else -1. */ static int fr_fault_check_permissions(void) { char const *p, *q; size_t len; char filename[256]; struct stat statbuf; /* * Try and guess which part of the command is the binary, and check to see if * it's world writeable, to try and save the admin from their own stupidity. * * @fixme we should do this properly and take into account single and double * quotes. */ if ((q = strchr(panic_action, ' '))) { /* * need to use a static buffer, because mallocing memory in a signal handler * is a bad idea and can result in deadlock. */ len = snprintf(filename, sizeof(filename), "%.*s", (int)(q - panic_action), panic_action); if (is_truncated(len, sizeof(filename))) { fr_strerror_printf("Failed writing panic_action to temporary buffer (truncated)"); return -1; } p = filename; } else { p = panic_action; } if (stat(p, &statbuf) == 0) { #ifdef S_IWOTH if ((statbuf.st_mode & S_IWOTH) != 0) { fr_strerror_printf("panic_action file \"%s\" is globally writable", p); return -1; } #endif } return 0; }
/** Write the result of a set operation back to net-snmp * * Writes "DONE\n" on success, or an error as described in man snmpd.conf * on error. * * @param fd to write to. * @param error attribute. * @param head of list of attributes to convert and write. * @return * - 0 on success. * - -1 on failure. */ static int radsnmp_set_response(int fd, fr_dict_attr_t const *error, VALUE_PAIR *head) { VALUE_PAIR *vp; char buffer[64]; size_t len; struct iovec io_vector[2]; char newline[] = "\n"; vp = fr_pair_find_by_da(head, error, TAG_NONE); if (!vp) { if (write(fd, "DONE\n", 5) < 0) { fr_strerror_printf("Failed writing set response: %s", fr_syserror(errno)); return -1; } return 0; } len = fr_pair_value_snprint(buffer, sizeof(buffer), vp, '\0'); if (is_truncated(len, sizeof(buffer))) { assert(0); return -1; } io_vector[0].iov_base = buffer; io_vector[0].iov_len = len; io_vector[1].iov_base = newline; io_vector[1].iov_len = 1; DEBUG2("said: %s", buffer); if (writev(fd, io_vector, sizeof(io_vector) / sizeof(*io_vector)) < 0) { fr_strerror_printf("Failed writing set response: %s", fr_syserror(errno)); return -1; } return 0; }
/** Prints attribute as string, escaped suitably for use as JSON string * * Returns < 0 if the buffer may be (or have been) too small to write the encoded * JSON value to. * * @param out Where to write the string. * @param outlen Lenth of output buffer. * @param vp to print. * @return * - Length of data written to out. * - value >= outlen on truncation. */ size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp) { char const *q; size_t len, freespace = outlen; if (!vp->da->flags.has_tag) { switch (vp->da->type) { case PW_TYPE_INTEGER: if (vp->da->flags.has_value) break; return snprintf(out, freespace, "%u", vp->vp_integer); case PW_TYPE_SHORT: if (vp->da->flags.has_value) break; return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short); case PW_TYPE_BYTE: if (vp->da->flags.has_value) break; return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte); case PW_TYPE_SIGNED: return snprintf(out, freespace, "%d", vp->vp_signed); default: break; } } /* Indicate truncation */ if (freespace < 2) return outlen + 1; *out++ = '"'; freespace--; switch (vp->da->type) { case PW_TYPE_STRING: for (q = vp->vp_strvalue; q < vp->vp_strvalue + vp->vp_length; q++) { /* Indicate truncation */ if (freespace < 3) return outlen + 1; if (*q == '"') { *out++ = '\\'; *out++ = '"'; freespace -= 2; } else if (*q == '\\') { *out++ = '\\'; *out++ = '\\'; freespace -= 2; } else if (*q == '/') { *out++ = '\\'; *out++ = '/'; freespace -= 2; } else if (*q >= ' ') { *out++ = *q; freespace--; } else { *out++ = '\\'; freespace--; switch (*q) { case '\b': *out++ = 'b'; freespace--; break; case '\f': *out++ = 'f'; freespace--; break; case '\n': *out++ = 'b'; freespace--; break; case '\r': *out++ = 'r'; freespace--; break; case '\t': *out++ = 't'; freespace--; break; default: len = snprintf(out, freespace, "u%04X", *q); if (is_truncated(len, freespace)) return (outlen - freespace) + len; out += len; freespace -= len; } } } break; default: len = vp_prints_value(out, freespace, vp, 0); if (is_truncated(len, freespace)) return (outlen - freespace) + len; out += len; freespace -= len; break; } /* Indicate truncation */ if (freespace < 2) return outlen + 1; *out++ = '"'; freespace--; *out = '\0'; // We don't increment out, because the nul byte should not be included in the length return outlen - freespace; }
/** Unpack data * * Example: %{unpack:&Class 0 integer} * * Expands Class, treating octet at offset 0 (bytes 0-3) as an "integer". */ static ssize_t unpack_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen) { char *data_name, *data_size, *data_type; char *p; size_t len, input_len; int offset; PW_TYPE type; DICT_ATTR const *da; VALUE_PAIR *vp, *cast; uint8_t const *input; char buffer[256]; uint8_t blob[256]; /* * FIXME: copy only the fields here, as we parse them. */ strlcpy(buffer, fmt, sizeof(buffer)); p = buffer; while (isspace((int) *p)) p++; /* skip leading spaces */ data_name = p; while (*p && !isspace((int) *p)) p++; if (!*p) { error: REDEBUG("Format string should be '<data> <offset> <type>' e.g. '&Class 1 integer'"); nothing: *out = '\0'; return -1; } while (isspace((int) *p)) *(p++) = '\0'; if (!*p) GOTO_ERROR; data_size = p; while (*p && !isspace((int) *p)) p++; if (!*p) GOTO_ERROR; while (isspace((int) *p)) *(p++) = '\0'; if (!*p) GOTO_ERROR; data_type = p; while (*p && !isspace((int) *p)) p++; if (*p) GOTO_ERROR; /* anything after the type is an error */ /* * Attribute reference */ if (*data_name == '&') { if (radius_get_vp(&vp, request, data_name) < 0) goto nothing; if ((vp->da->type != PW_TYPE_OCTETS) && (vp->da->type != PW_TYPE_STRING)) { REDEBUG("unpack requires the input attribute to be 'string' or 'octets'"); goto nothing; } input = vp->vp_octets; input_len = vp->vp_length; } else if ((data_name[0] == '0') && (data_name[1] == 'x')) { /* * Hex data. */ len = strlen(data_name + 2); if ((len & 0x01) != 0) { RDEBUG("Invalid hex string in '%s'", data_name); goto nothing; } input = blob; input_len = fr_hex2bin(blob, sizeof(blob), data_name + 2, len); } else { GOTO_ERROR; } offset = (int) strtoul(data_size, &p, 10); if (*p) { REDEBUG("unpack requires a decimal number, not '%s'", data_size); goto nothing; } type = fr_str2int(dict_attr_types, data_type, PW_TYPE_INVALID); if (type == PW_TYPE_INVALID) { REDEBUG("Invalid data type '%s'", data_type); goto nothing; } /* * Output must be a non-zero limited size. */ if ((dict_attr_sizes[type][0] == 0) || (dict_attr_sizes[type][0] != dict_attr_sizes[type][1])) { REDEBUG("unpack requires fixed-size output type, not '%s'", data_type); goto nothing; } if (input_len < (offset + dict_attr_sizes[type][0])) { REDEBUG("Insufficient data to unpack '%s' from '%s'", data_type, data_name); goto nothing; } da = dict_attrbyvalue(PW_CAST_BASE + type, 0); if (!da) { REDEBUG("Cannot decode type '%s'", data_type); goto nothing; } cast = pairalloc(request, da); if (!cast) goto nothing; memcpy(&(cast->data), input + offset, dict_attr_sizes[type][0]); cast->vp_length = dict_attr_sizes[type][0]; /* * Hacks */ switch (type) { case PW_TYPE_SIGNED: case PW_TYPE_INTEGER: case PW_TYPE_DATE: cast->vp_integer = ntohl(cast->vp_integer); break; case PW_TYPE_SHORT: cast->vp_short = ((input[offset] << 8) | input[offset + 1]); break; case PW_TYPE_INTEGER64: cast->vp_integer64 = ntohll(cast->vp_integer64); break; default: break; } len = vp_prints_value(out, outlen, cast, 0); talloc_free(cast); if (is_truncated(len, outlen)) { REDEBUG("Insufficient buffer space to unpack data"); goto nothing; } return len; }
/** Write the result of a get or getnext operation back to net-snmp * * Returns three lines of output per attribute: * - OID string * - type * - value * * Index attributes (num 0) must be in order of depth (shallowest first). * * If no attributes were written, will write "NONE\n" to inform net-snmp * that no value was available at the specified OID. * * @param fd to write to. * @param root of the SNMP portion of the main dictionary. * @param type attribute. * @param head of list of attributes to convert and write. * @return * - >=0 on success (the number of varbind responses written). * - -1 on failure. */ static int radsnmp_get_response(int fd, fr_dict_attr_t const *root, fr_dict_attr_t const *type, VALUE_PAIR *head) { fr_cursor_t cursor; VALUE_PAIR *vp, *type_vp; fr_dict_attr_t const *parent = root; unsigned int written = 0; ssize_t slen; size_t len; char type_buff[32]; /* type */ size_t type_len = 0; char oid_buff[256]; char value_buff[128]; char *p = oid_buff, *end = p + sizeof(oid_buff); struct iovec io_vector[6]; char newline[] = "\n"; type_buff[0] = '\0'; /* * Print first part of OID string. */ slen = snprintf(oid_buff, sizeof(oid_buff), "%u.", parent->attr); if (is_truncated((size_t)slen, sizeof(oid_buff))) { oob: fr_strerror_printf("OID Buffer too small"); return -1; } p += slen; /* * @fixme, this is very dependent on ordering * * This code should be reworked when we have proper * attribute grouping to coalesce all related index * attributes under a single request OID. */ for (vp = fr_cursor_init(&cursor, &head); vp; vp = fr_cursor_next(&cursor)) { fr_dict_attr_t const *common; /* * We only care about TLV attributes beneath our root */ if (!fr_dict_parent_common(root, vp->da, true)) continue; /* * Sanity checks to ensure we're processing attributes * in the right order. */ common = fr_dict_parent_common(parent, vp->da, true); if (!common) { fr_strerror_printf("Out of order index attributes. \"%s\" is not a child of \"%s\"", vp->da->name, parent->name); return -1; } /* * Index attribute */ if (vp->da->attr == 0) { /* * Print OID from last index/root up to the parent of * the index attribute. */ slen = fr_dict_print_attr_oid(p, end - p, parent, vp->da->parent); if (slen < 0) return -1; if (vp->vp_type != FR_TYPE_UINT32) { fr_strerror_printf("Index attribute \"%s\" is not of type \"integer\"", vp->da->name); return -1; } if (slen >= (end - p)) goto oob; p += slen; /* * Add the value of the index attribute as the next * OID component. */ len = snprintf(p, end - p, ".%i.", vp->vp_uint32); if (is_truncated(len, end - p)) goto oob; p += len; /* * Set the parent to be the attribute representing * the entry. */ parent = fr_dict_attr_child_by_num(vp->da->parent, 1); continue; } /* * Actual TLV attribute */ slen = fr_dict_print_attr_oid(p, end - p, parent, vp->da); if (slen < 0) return -1; /* * Next attribute should be the type */ type_vp = fr_cursor_next(&cursor); if (!type_vp || (type_vp->da != type)) { fr_strerror_printf("No %s found in response, or occurred out of order", type->name); return -1; } type_len = fr_pair_value_snprint(type_buff, sizeof(type_buff), type_vp, '\0'); /* * Build up the vector * * This represents output for a single varbind attribute */ io_vector[0].iov_base = oid_buff; io_vector[0].iov_len = strlen(oid_buff); io_vector[1].iov_base = newline; io_vector[1].iov_len = 1; io_vector[2].iov_base = type_buff; io_vector[2].iov_len = type_len; io_vector[3].iov_base = newline; io_vector[3].iov_len = 1; switch (vp->vp_type) { case FR_TYPE_OCTETS: memcpy(&io_vector[4].iov_base, &vp->vp_strvalue, sizeof(io_vector[4].iov_base)); io_vector[4].iov_len = vp->vp_length; break; case FR_TYPE_STRING: memcpy(&io_vector[4].iov_base, &vp->vp_strvalue, sizeof(io_vector[4].iov_base)); io_vector[4].iov_len = vp->vp_length; break; default: /* * We call fr_value_box_snprint with a NULL da pointer * because we always need return integer values not * value aliases. */ len = fr_value_box_snprint(value_buff, sizeof(value_buff), &vp->data, '\0'); if (is_truncated(len, sizeof(value_buff))) { fr_strerror_printf("Insufficient fixed value buffer"); return -1; } io_vector[4].iov_base = value_buff; io_vector[4].iov_len = len; break; } io_vector[5].iov_base = newline; io_vector[5].iov_len = 1; DEBUG2("said: %s", (char *)io_vector[0].iov_base); DEBUG2("said: %s", (char *)io_vector[2].iov_base); DEBUG2("said: %s", (char *)io_vector[4].iov_base); if (writev(fd, io_vector, sizeof(io_vector) / sizeof(*io_vector)) < 0) { fr_strerror_printf("Failed writing varbind result: %s", fr_syserror(errno)); return -1; } /* * Reset in case we're encoding multiple values */ parent = root; p = oid_buff; type_buff[0] = '\0'; written++; } if (!written && (write(fd, "NONE\n", 5)) < 0) { fr_strerror_printf("Failed writing get response: %s", fr_syserror(errno)); return -1; } return written; }
/** Write accounting data to Couchbase documents * * Handle accounting requests and store the associated data into JSON documents * in couchbase mapping attribute names to JSON element names per the module configuration. * * When an existing document already exists for the same accounting section the new attributes * will be merged with the currently existing data. When conflicts arrise the new attribute * value will replace or be added to the existing value. * * @param instance The module instance. * @param thread specific data. * @param request The accounting request object. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ VALUE_PAIR *vp; /* radius value pair linked list */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ char document[MAX_VALUE_SIZE]; /* our document body */ char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */ int status = 0; /* account status type */ int docfound = 0; /* document found toggle */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* sanity check */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY)) == NULL) { /* log debug */ RDEBUG2("could not find status type in packet"); /* return */ return RLM_MODULE_NOOP; } /* set status */ status = vp->vp_uint32; /* acknowledge the request but take no action */ if (status == FR_STATUS_ACCOUNTING_ON || status == FR_STATUS_ACCOUNTING_OFF) { /* log debug */ RDEBUG2("handling accounting on/off request without action"); /* return */ return RLM_MODULE_OK; } /* get handle */ handle = fr_pool_connection_get(inst->pool, request); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->acct_key, NULL, NULL); if (slen < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* attempt to fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute get request or parse returned json object"); /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* check cookie json object */ } else if (cookie->jobj) { /* set doc found */ docfound = 1; /* debugging */ RDEBUG3("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj)); } /* start json document if needed */ if (docfound != 1) { /* debugging */ RDEBUG2("no existing document found - creating new json document"); /* create new json object */ cookie->jobj = json_object_new_object(); /* set 'docType' element for new document */ json_object_object_add(cookie->jobj, "docType", json_object_new_string(inst->doctype)); /* default startTimestamp and stopTimestamp to null values */ json_object_object_add(cookie->jobj, "startTimestamp", NULL); json_object_object_add(cookie->jobj, "stopTimestamp", NULL); } /* status specific replacements for start/stop time */ switch (status) { case FR_STATUS_START: /* add start time */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "startTimestamp", mod_value_pair_to_json_object(request, vp)); } break; case FR_STATUS_STOP: /* add stop time */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_event_timestamp, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "stopTimestamp", mod_value_pair_to_json_object(request, vp)); } /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; case FR_STATUS_ALIVE: /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; default: /* don't doing anything */ rcode = RLM_MODULE_NOOP; /* return */ goto finish; } /* loop through pairs and add to json document */ for (vp = request->packet->vps; vp; vp = vp->next) { /* map attribute to element */ if (mod_attribute_to_element(vp->da->name, inst->map, &element) == 0) { /* debug */ RDEBUG3("mapped attribute %s => %s", vp->da->name, element); /* add to json object with mapped name */ json_object_object_add(cookie->jobj, element, mod_value_pair_to_json_object(request, vp)); } } /* copy json string to document and check size */ if (strlcpy(document, json_object_to_json_string(cookie->jobj), sizeof(document)) >= sizeof(document)) { /* this isn't good */ RERROR("could not write json document - insufficient buffer space"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("setting '%s' => '%s'", dockey, document); /* store document/key in couchbase */ cb_error = couchbase_set_key(cb_inst, dockey, document, inst->expire); /* check return */ if (cb_error != LCB_SUCCESS) { RERROR("failed to store document (%s): %s (0x%x)", dockey, lcb_strerror(NULL, cb_error), cb_error); } finish: /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release our connection handle */ if (handle) { fr_pool_connection_release(inst->pool, request, handle); } /* return */ return rcode; }
/** Handle authorization requests using Couchbase document data * * Attempt to fetch the document assocaited with the requested user by * using the deterministic key defined in the configuration. When a valid * document is found it will be parsed and the containing value pairs will be * injected into the request. * * @param instance The module instance. * @param thread specific data. * @param request The authorization request. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->user_key, NULL, NULL); if (slen < 0) return RLM_MODULE_FAIL; if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); return RLM_MODULE_FAIL; } /* get handle */ handle = fr_pool_connection_get(inst->pool, request); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || !cookie->jobj) { /* log error */ RERROR("failed to fetch document or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj)); { TALLOC_CTX *pool = talloc_pool(request, 1024); /* We need to do lots of allocs */ fr_cursor_t maps, vlms; vp_map_t *map_head = NULL, *map; vp_list_mod_t *vlm_head = NULL, *vlm; fr_cursor_init(&maps, &map_head); /* * Convert JSON data into maps */ if ((mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_CONTROL) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REPLY) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REQUEST) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_STATE) < 0)) { invalid: talloc_free(pool); rcode = RLM_MODULE_INVALID; goto finish; } fr_cursor_init(&vlms, &vlm_head); /* * Convert all the maps into list modifications, * which are guaranteed to succeed. */ for (map = fr_cursor_head(&maps); map; map = fr_cursor_next(&maps)) { if (map_to_list_mod(pool, &vlm, request, map, NULL, NULL) < 0) goto invalid; fr_cursor_insert(&vlms, vlm); } if (!vlm_head) { RDEBUG2("Nothing to update"); talloc_free(pool); rcode = RLM_MODULE_NOOP; goto finish; } /* * Apply the list of modifications */ for (vlm = fr_cursor_head(&vlms); vlm; vlm = fr_cursor_next(&vlms)) { int ret; ret = map_list_mod_apply(request, vlm); /* SHOULD NOT FAIL */ if (!fr_cond_assert(ret == 0)) { talloc_free(pool); rcode = RLM_MODULE_FAIL; goto finish; } } talloc_free(pool); } finish: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) fr_pool_connection_release(inst->pool, request, handle); /* return */ return rcode; }
/** Handle authorization requests using Couchbase document data * * Attempt to fetch the document assocaited with the requested user by * using the deterministic key defined in the configuration. When a valid * document is found it will be parsed and the containing value pairs will be * injected into the request. * * @param instance The module instance. * @param request The authorization request. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->user_key, NULL, NULL); if (slen < 0) return RLM_MODULE_FAIL; if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); return RLM_MODULE_FAIL; } /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || !cookie->jobj) { /* log error */ RERROR("failed to fetch document or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj)); /* inject config value pairs defined in this json oblect */ mod_json_object_to_value_pairs(cookie->jobj, "config", request); /* inject reply value pairs defined in this json oblect */ mod_json_object_to_value_pairs(cookie->jobj, "reply", request); finish: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return rcode; }
/** Allocate a new IP address from a pool * */ static ippool_rcode_t redis_ippool_allocate(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, uint8_t const *device_id, size_t device_id_len, uint8_t const *gateway_id, size_t gateway_id_len, uint32_t expires) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; rad_assert(key_prefix); rad_assert(device_id); gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!gateway_id) gateway_id = (uint8_t const *)""; status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_alloc_digest, lua_alloc_cmd, "EVALSHA %s 1 %b %u %u %b %b", lua_alloc_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, device_id, device_id_len, gateway_id, gateway_id_len); if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } rad_assert(reply); if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; /* * Process IP address */ if (reply->elements > 1) { vp_tmpl_t ip_rhs = { .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING }; vp_map_t ip_map = { .lhs = inst->allocated_address_attr, .op = T_OP_SET, .rhs = &ip_rhs }; switch (reply->element[1]->type) { /* * Destination attribute may not be IPv4, in which case * we want to pre-convert the integer value to an IPv4 * address before casting it once more to the type of * the destination attribute. */ case REDIS_REPLY_INTEGER: { if (ip_map.lhs->tmpl_da->type != FR_TYPE_IPV4_ADDR) { fr_value_box_t tmp; memset(&tmp, 0, sizeof(tmp)); tmp.vb_uint32 = ntohl((uint32_t)reply->element[1]->integer); tmp.type = FR_TYPE_UINT32; if (fr_value_box_cast(NULL, &ip_map.rhs->tmpl_value, FR_TYPE_IPV4_ADDR, NULL, &tmp)) { RPEDEBUG("Failed converting integer to IPv4 address"); ret = IPPOOL_RCODE_FAIL; goto finish; } } else { ip_map.rhs->tmpl_value.vb_uint32 = ntohl((uint32_t)reply->element[1]->integer); ip_map.rhs->tmpl_value_type = FR_TYPE_UINT32; } } goto do_ip_map; case REDIS_REPLY_STRING: ip_map.rhs->tmpl_value.vb_strvalue = reply->element[1]->str; ip_map.rhs->tmpl_value_length = reply->element[1]->len; ip_map.rhs->tmpl_value_type = FR_TYPE_STRING; do_ip_map: if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } break; default: REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])", fr_int2str(redis_reply_types, reply->element[1]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Process Range identifier */ if (reply->elements > 2) { switch (reply->element[2]->type) { /* * Add range ID to request */ case REDIS_REPLY_STRING: { vp_tmpl_t range_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t range_map = { .lhs = inst->range_attr, .op = T_OP_SET, .rhs = &range_rhs }; range_map.rhs->tmpl_value.vb_strvalue = reply->element[2]->str; range_map.rhs->tmpl_value_length = reply->element[2]->len; range_map.rhs->tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } break; case REDIS_REPLY_NIL: break; default: REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])", fr_int2str(redis_reply_types, reply->element[2]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Process Expiry time */ if (inst->expiry_attr && (reply->elements > 3)) { vp_tmpl_t expiry_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t expiry_map = { .lhs = inst->expiry_attr, .op = T_OP_SET, .rhs = &expiry_rhs }; if (reply->element[3]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])", fr_int2str(redis_reply_types, reply->element[3]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } expiry_map.rhs->tmpl_value.vb_uint32 = reply->element[3]->integer; expiry_map.rhs->tmpl_value_type = FR_TYPE_UINT32; if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } finish: fr_redis_reply_free(&reply); return ret; } /** Update an existing IP address in a pool * */ static ippool_rcode_t redis_ippool_update(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, fr_ipaddr_t *ip, uint8_t const *device_id, size_t device_id_len, uint8_t const *gateway_id, size_t gateway_id_len, uint32_t expires) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; vp_tmpl_t range_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t range_map = { .lhs = inst->range_attr, .op = T_OP_SET, .rhs = &range_rhs }; gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!device_id) device_id = (uint8_t const *)""; if (!gateway_id) gateway_id = (uint8_t const *)""; if ((ip->af == AF_INET) && inst->ipv4_integer) { status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_update_digest, lua_update_cmd, "EVALSHA %s 1 %b %u %u %u %b %b", lua_update_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, htonl(ip->addr.v4.s_addr), device_id, device_id_len, gateway_id, gateway_id_len); } else { char ip_buff[FR_IPADDR_PREFIX_STRLEN]; IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix); status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_update_digest, lua_update_cmd, "EVALSHA %s 1 %b %u %u %s %b %b", lua_update_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, ip_buff, device_id, device_id_len, gateway_id, gateway_id_len); } if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; /* * Process Range identifier */ if (reply->elements > 1) { switch (reply->element[1]->type) { /* * Add range ID to request */ case REDIS_REPLY_STRING: range_map.rhs->tmpl_value.vb_strvalue = reply->element[1]->str; range_map.rhs->tmpl_value_length = reply->element[1]->len; range_map.rhs->tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } break; case REDIS_REPLY_NIL: break; default: REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])", fr_int2str(redis_reply_types, reply->element[0]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Copy expiry time to expires attribute (if set) */ if (inst->expiry_attr) { vp_tmpl_t expiry_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t expiry_map = { .lhs = inst->expiry_attr, .op = T_OP_SET, .rhs = &expiry_rhs }; expiry_map.rhs->tmpl_value.vb_uint32 = expires; expiry_map.rhs->tmpl_value_type = FR_TYPE_UINT32; if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } finish: fr_redis_reply_free(&reply); return ret; } /** Release an existing IP address in a pool * */ static ippool_rcode_t redis_ippool_release(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, fr_ipaddr_t *ip, uint8_t const *device_id, size_t device_id_len) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!device_id) device_id = (uint8_t const *)""; if ((ip->af == AF_INET) && inst->ipv4_integer) { status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_release_digest, lua_release_cmd, "EVALSHA %s 1 %b %u %u %b", lua_release_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, htonl(ip->addr.v4.s_addr), device_id, device_id_len); } else { char ip_buff[FR_IPADDR_PREFIX_STRLEN]; IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix); status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_release_digest, lua_release_cmd, "EVALSHA %s 1 %b %u %s %b", lua_release_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, ip_buff, device_id, device_id_len); } if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; finish: fr_redis_reply_free(&reply); return ret; } /** Find the pool name we'll be allocating from * * @param[out] out Where to write the pool name. * @param[out] buff Where to write the pool name (in the case of an expansion). * @param[in] bufflen Size of the output buffer. * @param[in] inst This instance of the rlm_redis_ippool module. * @param[in] request The current request. * @return * - < 0 on error. * - 0 if no pool attribute exists, or the pool name is a zero length string. * - > 0 on success (length of data written to out). */ static inline ssize_t ippool_pool_name(uint8_t const **out, uint8_t buff[], size_t bufflen, rlm_redis_ippool_t const *inst, REQUEST *request) { ssize_t slen; slen = tmpl_expand(out, (char *)buff, bufflen, request, inst->pool_name, NULL, NULL); if (slen < 0) { if (inst->pool_name->type == TMPL_TYPE_ATTR) { RDEBUG2("Pool attribute not present in request. Doing nothing"); return 0; } REDEBUG("Failed expanding pool name"); return -1; } if (slen == 0) { RDEBUG2("Empty pool name. Doing nothing"); return 0; } if ((*out == buff) && is_truncated((size_t)slen, bufflen)) { REDEBUG("Pool name too long. Expected %zu bytes, got %zu bytes", bufflen, (size_t)slen); return -1; } return slen; } static rlm_rcode_t mod_action(rlm_redis_ippool_t const *inst, REQUEST *request, ippool_action_t action) { uint8_t key_prefix_buff[IPPOOL_MAX_KEY_PREFIX_SIZE], device_id_buff[256], gateway_id_buff[256]; uint8_t const *key_prefix, *device_id = NULL, *gateway_id = NULL; size_t key_prefix_len, device_id_len = 0, gateway_id_len = 0; ssize_t slen; fr_ipaddr_t ip; char expires_buff[20]; char const *expires_str; unsigned long expires = 0; char *q; slen = ippool_pool_name(&key_prefix, (uint8_t *)&key_prefix_buff, sizeof(key_prefix_len), inst, request); if (slen < 0) return RLM_MODULE_FAIL; if (slen == 0) return RLM_MODULE_NOOP; key_prefix_len = (size_t)slen; if (inst->device_id) { slen = tmpl_expand((char const **)&device_id, (char *)&device_id_buff, sizeof(device_id_buff), request, inst->device_id, NULL, NULL); if (slen < 0) { REDEBUG("Failed expanding device (%s)", inst->device_id->name); return RLM_MODULE_FAIL; } device_id_len = (size_t)slen; } if (inst->gateway_id) { slen = tmpl_expand((char const **)&gateway_id, (char *)&gateway_id_buff, sizeof(gateway_id_buff), request, inst->gateway_id, NULL, NULL); if (slen < 0) { REDEBUG("Failed expanding gateway (%s)", inst->gateway_id->name); return RLM_MODULE_FAIL; } gateway_id_len = (size_t)slen; } switch (action) { case POOL_ACTION_ALLOCATE: if (tmpl_expand(&expires_str, expires_buff, sizeof(expires_buff), request, inst->offer_time, NULL, NULL) < 0) { REDEBUG("Failed expanding offer_time (%s)", inst->offer_time->name); return RLM_MODULE_FAIL; } expires = strtoul(expires_str, &q, 10); if (q != (expires_str + strlen(expires_str))) { REDEBUG("Invalid offer_time. Must be an integer value"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, NULL, device_id, device_id_len, gateway_id, gateway_id_len, expires); switch (redis_ippool_allocate(inst, request, key_prefix, key_prefix_len, device_id, device_id_len, gateway_id, gateway_id_len, (uint32_t)expires)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("IP address lease allocated"); return RLM_MODULE_UPDATED; case IPPOOL_RCODE_POOL_EMPTY: RWDEBUG("Pool contains no free addresses"); return RLM_MODULE_NOTFOUND; default: return RLM_MODULE_FAIL; } case POOL_ACTION_UPDATE: { char ip_buff[INET6_ADDRSTRLEN + 4]; char const *ip_str; if (tmpl_expand(&expires_str, expires_buff, sizeof(expires_buff), request, inst->lease_time, NULL, NULL) < 0) { REDEBUG("Failed expanding lease_time (%s)", inst->lease_time->name); return RLM_MODULE_FAIL; } expires = strtoul(expires_str, &q, 10); if (q != (expires_str + strlen(expires_str))) { REDEBUG("Invalid expires. Must be an integer value"); return RLM_MODULE_FAIL; } if (tmpl_expand(&ip_str, ip_buff, sizeof(ip_buff), request, inst->requested_address, NULL, NULL) < 0) { REDEBUG("Failed expanding requested_address (%s)", inst->requested_address->name); return RLM_MODULE_FAIL; } if (fr_inet_pton(&ip, ip_str, -1, AF_UNSPEC, false, true) < 0) { RPEDEBUG("Failed parsing address"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, ip_str, device_id, device_id_len, gateway_id, gateway_id_len, expires); switch (redis_ippool_update(inst, request, key_prefix, key_prefix_len, &ip, device_id, device_id_len, gateway_id, gateway_id_len, (uint32_t)expires)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("Requested IP address' \"%s\" lease updated", ip_str); /* * Copy over the input IP address to the reply attribute */ if (inst->copy_on_update) { vp_tmpl_t ip_rhs = { .name = "", .type = TMPL_TYPE_DATA, .quote = T_BARE_WORD, }; vp_map_t ip_map = { .lhs = inst->allocated_address_attr, .op = T_OP_SET, .rhs = &ip_rhs }; ip_rhs.tmpl_value_length = strlen(ip_str); ip_rhs.tmpl_value.vb_strvalue = ip_str; ip_rhs.tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) return RLM_MODULE_FAIL; } return RLM_MODULE_UPDATED; /* * It's useful to be able to identify the 'not found' case * as we can relay to a server where the IP address might * be found. This extremely useful for migrations. */ case IPPOOL_RCODE_NOT_FOUND: REDEBUG("Requested IP address \"%s\" is not a member of the specified pool", ip_str); return RLM_MODULE_NOTFOUND; case IPPOOL_RCODE_EXPIRED: REDEBUG("Requested IP address' \"%s\" lease already expired at time of renewal", ip_str); return RLM_MODULE_INVALID; case IPPOOL_RCODE_DEVICE_MISMATCH: REDEBUG("Requested IP address' \"%s\" lease allocated to another device", ip_str); return RLM_MODULE_INVALID; default: return RLM_MODULE_FAIL; } } case POOL_ACTION_RELEASE: { char ip_buff[INET6_ADDRSTRLEN + 4]; char const *ip_str; if (tmpl_expand(&ip_str, ip_buff, sizeof(ip_buff), request, inst->requested_address, NULL, NULL) < 0) { REDEBUG("Failed expanding requested_address (%s)", inst->requested_address->name); return RLM_MODULE_FAIL; } if (fr_inet_pton(&ip, ip_str, -1, AF_UNSPEC, false, true) < 0) { RPEDEBUG("Failed parsing address"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, ip_str, device_id, device_id_len, gateway_id, gateway_id_len, 0); switch (redis_ippool_release(inst, request, key_prefix, key_prefix_len, &ip, device_id, device_id_len)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("IP address \"%s\" released", ip_str); return RLM_MODULE_UPDATED; /* * It's useful to be able to identify the 'not found' case * as we can relay to a server where the IP address might * be found. This extremely useful for migrations. */ case IPPOOL_RCODE_NOT_FOUND: REDEBUG("Requested IP address \"%s\" is not a member of the specified pool", ip_str); return RLM_MODULE_NOTFOUND; case IPPOOL_RCODE_DEVICE_MISMATCH: REDEBUG("Requested IP address' \"%s\" lease allocated to another device", ip_str); return RLM_MODULE_INVALID; default: return RLM_MODULE_FAIL; } } case POOL_ACTION_BULK_RELEASE: RDEBUG2("Bulk release not yet implemented"); return RLM_MODULE_NOOP; default: rad_assert(0); return RLM_MODULE_FAIL; } } static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; /* * Pool-Action override */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); if (vp) return mod_action(inst, request, vp->vp_uint32); /* * Otherwise, guess the action by Acct-Status-Type */ vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY); if (!vp) { RDEBUG2("Couldn't find &request:Acct-Status-Type or &control:Pool-Action, doing nothing..."); return RLM_MODULE_NOOP; } switch (vp->vp_uint32) { case FR_STATUS_START: case FR_STATUS_ALIVE: return mod_action(inst, request, POOL_ACTION_UPDATE); case FR_STATUS_STOP: return mod_action(inst, request, POOL_ACTION_RELEASE); case FR_STATUS_ACCOUNTING_OFF: case FR_STATUS_ACCOUNTING_ON: return mod_action(inst, request, POOL_ACTION_BULK_RELEASE); default: return RLM_MODULE_NOOP; } } static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; /* * Unless it's overridden the default action is to allocate * when called in Post-Auth. */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); return mod_action(inst, request, vp ? vp->vp_uint32 : POOL_ACTION_ALLOCATE); } static rlm_rcode_t mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; ippool_action_t action = POOL_ACTION_ALLOCATE; /* * Unless it's overridden the default action is to allocate * when called in Post-Auth. */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); if (vp) { if ((vp->vp_uint32 > 0) && (vp->vp_uint32 <= POOL_ACTION_BULK_RELEASE)) { action = vp->vp_uint32; } else { RWDEBUG("Ignoring invalid action %d", vp->vp_uint32); return RLM_MODULE_NOOP; } #ifdef WITH_DHCP } else if (request->dict == dict_dhcpv4) { vp = fr_pair_find_by_da(request->control, attr_message_type, TAG_ANY); if (!vp) goto run; if (vp->vp_uint8 == FR_DHCP_REQUEST) action = POOL_ACTION_UPDATE; #endif } run: return mod_action(inst, request, action); } static int mod_instantiate(void *instance, CONF_SECTION *conf) { static bool done_hash = false; CONF_SECTION *subcs = cf_section_find(conf, "redis", NULL); rlm_redis_ippool_t *inst = instance; rad_assert(inst->allocated_address_attr->type == TMPL_TYPE_ATTR); rad_assert(subcs); inst->cluster = fr_redis_cluster_alloc(inst, subcs, &inst->conf, true, NULL, NULL, NULL); if (!inst->cluster) return -1; if (!fr_redis_cluster_min_version(inst->cluster, "3.0.2")) { PERROR("Cluster error"); return -1; } /* * Pre-Compute the SHA1 hashes of the Lua scripts */ if (!done_hash) { fr_sha1_ctx sha1_ctx; uint8_t digest[SHA1_DIGEST_LENGTH]; fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_alloc_digest, digest, sizeof(digest)); fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_update_digest, digest, sizeof(digest)); fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_release_digest, digest, sizeof(digest)); } /* * If we don't have a separate time specifically for offers * just use the lease time. */ if (!inst->offer_time) inst->offer_time = inst->lease_time; return 0; } static int mod_load(void) { fr_redis_version_print(); return 0; } extern module_t rlm_redis_ippool; module_t rlm_redis_ippool = { .magic = RLM_MODULE_INIT, .name = "redis", .type = RLM_TYPE_THREAD_SAFE, .inst_size = sizeof(rlm_redis_ippool_t), .config = module_config, .onload = mod_load, .instantiate = mod_instantiate, .methods = { [MOD_ACCOUNTING] = mod_accounting, [MOD_AUTHORIZE] = mod_authorize, [MOD_POST_AUTH] = mod_post_auth, }, };
static ssize_t jsonquote_xlat(UNUSED void *instance, UNUSED REQUEST *request, char const *fmt, char *out, size_t outlen) { char const *p; size_t freespace = outlen; size_t len; for (p = fmt; *p != '\0'; p++) { /* Indicate truncation */ if (freespace < 3) { *out = '\0'; return outlen + 1; } if (*p == '"') { *out++ = '\\'; *out++ = '"'; freespace -= 2; } else if (*p == '\\') { *out++ = '\\'; *out++ = '\\'; freespace -= 2; } else if (*p == '/') { *out++ = '\\'; *out++ = '/'; freespace -= 2; } else if (*p >= ' ') { *out++ = *p; freespace--; /* * Unprintable chars */ } else { *out++ = '\\'; freespace--; switch (*p) { case '\b': *out++ = 'b'; freespace--; break; case '\f': *out++ = 'f'; freespace--; break; case '\n': *out++ = 'b'; freespace--; break; case '\r': *out++ = 'r'; freespace--; break; case '\t': *out++ = 't'; freespace--; break; default: len = snprintf(out, freespace, "u%04X", *p); if (is_truncated(len, freespace)) return (outlen - freespace) + len; out += len; freespace -= len; } } } *out = '\0'; return outlen - freespace; }
/** Check if a given user is already logged in. * * Process accounting data to determine if a user is already logged in. Sets request->simul_count * to the current session count for this user. * * Check twice. If on the first pass the user exceeds his maximum number of logins, do a second * pass and validate all logins by querying the terminal server. * * @param instance The module instance. * @param request The checksimul request object. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char vpath[256]; char buffer[MAX_KEY_SIZE]; char const *vkey; /* view path and query key */ char docid[MAX_KEY_SIZE]; /* document id returned from view */ char error[512]; /* view error return */ int idx = 0; /* row array index counter */ char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ json_object *json, *jval; /* json object holders */ json_object *jrows = NULL; /* json object to hold view rows */ VALUE_PAIR *vp; /* value pair */ uint32_t client_ip_addr = 0; /* current client ip address */ char const *client_cs_id = NULL; /* current client calling station id */ char *user_name = NULL; /* user name from accounting document */ char *session_id = NULL; /* session id from accounting document */ char *cs_id = NULL; /* calling station id from accounting document */ uint32_t nas_addr = 0; /* nas address from accounting document */ uint32_t nas_port = 0; /* nas port from accounting document */ uint32_t framed_ip_addr = 0; /* framed ip address from accounting document */ char framed_proto = 0; /* framed proto from accounting document */ int session_time = 0; /* session time from accounting document */ ssize_t slen; /* do nothing if this is not enabled */ if (inst->check_simul != true) { RDEBUG3("mod_checksimul returning noop - not enabled"); return RLM_MODULE_NOOP; } /* ensure valid username in request */ if ((!request->username) || (request->username->vp_length == '\0')) { RDEBUG3("mod_checksimul - invalid username"); return RLM_MODULE_INVALID; } slen = tmpl_expand(&vkey, buffer, sizeof(buffer), request, inst->simul_vkey, NULL, NULL); if (slen < 0) return RLM_MODULE_FAIL; if ((vkey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); return RLM_MODULE_FAIL; } /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* build view path */ snprintf(vpath, sizeof(vpath), "%s?key=\"%s\"&stale=update_after", inst->simul_view, vkey); /* query view for document */ cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute view request or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* check for error in json object */ if (json_object_object_get_ex(cookie->jobj, "error", &json)) { /* build initial error buffer */ strlcpy(error, json_object_get_string(json), sizeof(error)); /* get error reason */ if (json_object_object_get_ex(cookie->jobj, "reason", &json)) { /* append divider */ strlcat(error, " - ", sizeof(error)); /* append reason */ strlcat(error, json_object_get_string(json), sizeof(error)); } /* log error */ RERROR("view request failed with error: %s", error); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* check for document id in return */ if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) { /* log error */ RERROR("failed to fetch rows from view payload"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* get and hold rows */ jrows = json_object_get(json); /* free cookie object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* check for valid row value */ if (!jrows || !fr_json_object_is_type(jrows, json_type_array)) { /* log error */ RERROR("no valid rows returned from view: %s", vpath); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("jrows == %s", json_object_to_json_string(jrows)); /* set the count */ request->simul_count = json_object_array_length(jrows); /* debugging */ RDEBUG("found %d open sessions for %s", request->simul_count, request->username->vp_strvalue); /* check count */ if (request->simul_count < request->simul_max) { rcode = RLM_MODULE_OK; goto finish; } /* * Current session count exceeds configured maximum. * Continue on to verify the sessions if configured otherwise stop here. */ if (inst->verify_simul != true) { rcode = RLM_MODULE_OK; goto finish; } /* debugging */ RDEBUG("verifying session count"); /* reset the count */ request->simul_count = 0; /* get client ip address for MPP detection below */ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) { client_ip_addr = vp->vp_ipaddr; } /* get calling station id for MPP detection below */ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) { client_cs_id = vp->vp_strvalue; } /* loop across all row elements */ for (idx = 0; idx < json_object_array_length(jrows); idx++) { /* clear docid */ memset(docid, 0, sizeof(docid)); /* fetch current index */ json = json_object_array_get_idx(jrows, idx); /* get document id */ if (json_object_object_get_ex(json, "id", &jval)) { /* copy and check length */ if (strlcpy(docid, json_object_get_string(jval), sizeof(docid)) >= sizeof(docid)) { RERROR("document id from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE); continue; } } /* check for valid doc id */ if (docid[0] == 0) { RWARN("failed to fetch document id from row - skipping"); continue; } /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, docid); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute get request or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* get element name for User-Name attribute */ if (mod_attribute_to_element("User-Name", inst->map, &element) == 0) { /* get and check username element */ if (!json_object_object_get_ex(cookie->jobj, element, &jval)){ RDEBUG("cannot zap stale entry without username"); rcode = RLM_MODULE_FAIL; goto finish; } /* copy json string value to user_name */ user_name = talloc_typed_strdup(request, json_object_get_string(jval)); } else { RDEBUG("failed to find map entry for User-Name attribute"); rcode = RLM_MODULE_FAIL; goto finish; } /* get element name for Acct-Session-Id attribute */ if (mod_attribute_to_element("Acct-Session-Id", inst->map, &element) == 0) { /* get and check session id element */ if (!json_object_object_get_ex(cookie->jobj, element, &jval)){ RDEBUG("cannot zap stale entry without session id"); rcode = RLM_MODULE_FAIL; goto finish; } /* copy json string value to session_id */ session_id = talloc_typed_strdup(request, json_object_get_string(jval)); } else { RDEBUG("failed to find map entry for Acct-Session-Id attribute"); rcode = RLM_MODULE_FAIL; goto finish; } /* get element name for NAS-IP-Address attribute */ if (mod_attribute_to_element("NAS-IP-Address", inst->map, &element) == 0) { /* attempt to get and nas address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)){ nas_addr = inet_addr(json_object_get_string(jval)); } } /* get element name for NAS-Port attribute */ if (mod_attribute_to_element("NAS-Port", inst->map, &element) == 0) { /* attempt to get nas port element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { nas_port = (uint32_t) json_object_get_int(jval); } } /* check terminal server */ int check = rad_check_ts(nas_addr, nas_port, user_name, session_id); /* take action based on check return */ if (check == 0) { /* stale record - zap it if enabled */ if (inst->delete_stale_sessions) { /* get element name for Framed-IP-Address attribute */ if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { framed_ip_addr = inet_addr(json_object_get_string(jval)); } } /* get element name for Framed-Port attribute */ if (mod_attribute_to_element("Framed-Port", inst->map, &element) == 0) { /* attempt to get framed port element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { if (strcmp(json_object_get_string(jval), "PPP") == 0) { framed_proto = 'P'; } else if (strcmp(json_object_get_string(jval), "SLIP") == 0) { framed_proto = 'S'; } } } /* get element name for Acct-Session-Time attribute */ if (mod_attribute_to_element("Acct-Session-Time", inst->map, &element) == 0) { /* attempt to get session time element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { session_time = json_object_get_int(jval); } } /* zap session */ session_zap(request, nas_addr, nas_port, user_name, session_id, framed_ip_addr, framed_proto, session_time); } } else if (check == 1) { /* user is still logged in - increase count */ ++request->simul_count; /* get element name for Framed-IP-Address attribute */ if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { framed_ip_addr = inet_addr(json_object_get_string(jval)); } else { /* ensure 0 if not found */ framed_ip_addr = 0; } } /* get element name for Calling-Station-Id attribute */ if (mod_attribute_to_element("Calling-Station-Id", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { /* copy json string value to cs_id */ cs_id = talloc_typed_strdup(request, json_object_get_string(jval)); } else { /* ensure null if not found */ cs_id = NULL; } } /* Does it look like a MPP attempt? */ if (client_ip_addr && framed_ip_addr && framed_ip_addr == client_ip_addr) { request->simul_mpp = 2; } else if (client_cs_id && cs_id && !strncmp(cs_id, client_cs_id, 16)) { request->simul_mpp = 2; } } else { /* check failed - return error */ REDEBUG("failed to check the terminal server for user '%s'", user_name); rcode = RLM_MODULE_FAIL; goto finish; } /* free and reset document user name talloc */ if (user_name) TALLOC_FREE(user_name); /* free and reset document calling station id talloc */ if (cs_id) TALLOC_FREE(cs_id); /* free and reset document session id talloc */ if (session_id) TALLOC_FREE(session_id); /* free and reset json object before fetching next row */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } } /* debugging */ RDEBUG("Retained %d open sessions for %s after verification", request->simul_count, request->username->vp_strvalue); finish: if (user_name) talloc_free(user_name); if (cs_id) talloc_free(cs_id); if (session_id) talloc_free(session_id); /* free rows */ if (jrows) json_object_put(jrows); /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } if (handle) fr_connection_release(inst->pool, handle); /* * The Auth module apparently looks at request->simul_count, * not the return value of this module when deciding to deny * a call for too many sessions. */ return rcode; }
/** Create and insert a cache entry * * @return * - #RLM_MODULE_OK on success. * - #RLM_MODULE_UPDATED if we merged the cache entry. * - #RLM_MODULE_FAIL on failure. */ static rlm_rcode_t cache_insert(rlm_cache_t const *inst, REQUEST *request, rlm_cache_handle_t **handle, uint8_t const *key, size_t key_len, int ttl) { vp_map_t const *map; vp_map_t **last, *c_map; VALUE_PAIR *vp; bool merge = false; rlm_cache_entry_t *c; size_t len; TALLOC_CTX *pool; if ((inst->config.max_entries > 0) && inst->driver->count && (inst->driver->count(&inst->config, inst->driver_inst->data, request, handle) > inst->config.max_entries)) { RWDEBUG("Cache is full: %d entries", inst->config.max_entries); return RLM_MODULE_FAIL; } c = cache_alloc(inst, request); if (!c) return RLM_MODULE_FAIL; c->key = talloc_memdup(c, key, key_len); c->key_len = key_len; c->created = c->expires = request->packet->timestamp.tv_sec; c->expires += ttl; last = &c->maps; RDEBUG2("Creating new cache entry"); /* * Alloc a pool so we don't have excessive allocs when * gathering VALUE_PAIRs to cache. */ pool = talloc_pool(NULL, 2048); for (map = inst->maps; map != NULL; map = map->next) { VALUE_PAIR *to_cache = NULL; fr_cursor_t cursor; rad_assert(map->lhs && map->rhs); /* * Calling map_to_vp gives us exactly the same result, * as if this were an update section. */ if (map_to_vp(pool, &to_cache, request, map, NULL) < 0) { RDEBUG2("Skipping %s", map->rhs->name); continue; } for (vp = fr_cursor_init(&cursor, &to_cache); vp; vp = fr_cursor_next(&cursor)) { /* * Prevent people from accidentally caching * cache control attributes. */ if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) { case FR_CACHE_TTL: case FR_CACHE_STATUS_ONLY: case FR_CACHE_MERGE_NEW: case FR_CACHE_ENTRY_HITS: RDEBUG2("Skipping %s", vp->da->name); continue; default: break; } RINDENT(); if (RDEBUG_ENABLED2) map_debug_log(request, map, vp); REXDENT(); MEM(c_map = talloc_zero(c, vp_map_t)); c_map->op = map->op; /* * Now we turn the VALUE_PAIRs into maps. */ switch (map->lhs->type) { /* * Attributes are easy, reuse the LHS, and create a new * RHS with the fr_value_box_t from the VALUE_PAIR. */ case TMPL_TYPE_ATTR: c_map->lhs = map->lhs; /* lhs shouldn't be touched, so this is ok */ do_rhs: MEM(c_map->rhs = tmpl_init(talloc(c_map, vp_tmpl_t), TMPL_TYPE_DATA, map->rhs->name, map->rhs->len, T_BARE_WORD)); if (fr_value_box_copy(c_map->rhs, &c_map->rhs->tmpl_value, &vp->data) < 0) { REDEBUG("Failed copying attribute value"); error: talloc_free(pool); talloc_free(c); return RLM_MODULE_FAIL; } c_map->rhs->tmpl_value_type = vp->vp_type; if (vp->vp_type == FR_TYPE_STRING) { c_map->rhs->quote = is_printable(vp->vp_strvalue, vp->vp_length) ? T_SINGLE_QUOTED_STRING : T_DOUBLE_QUOTED_STRING; } break; /* * Lists are weird... We need to fudge a new LHS template, * which is a combination of the LHS list and the attribute. */ case TMPL_TYPE_LIST: { char attr[256]; MEM(c_map->lhs = tmpl_init(talloc(c_map, vp_tmpl_t), TMPL_TYPE_ATTR, map->lhs->name, map->lhs->len, T_BARE_WORD)); c_map->lhs->tmpl_da = vp->da; if (vp->da->flags.is_unknown) { /* for tmpl_verify() */ c_map->lhs->tmpl_unknown = fr_dict_unknown_acopy(c_map->lhs, vp->da); c_map->lhs->tmpl_da = c_map->lhs->tmpl_unknown; } c_map->lhs->tmpl_tag = vp->tag; c_map->lhs->tmpl_list = map->lhs->tmpl_list; c_map->lhs->tmpl_num = map->lhs->tmpl_num; c_map->lhs->tmpl_request = map->lhs->tmpl_request; /* * We need to rebuild the attribute name, to be the * one we copied from the source list. */ len = tmpl_snprint(attr, sizeof(attr), c_map->lhs); if (is_truncated(len, sizeof(attr))) { REDEBUG("Serialized attribute too long. Must be < " STRINGIFY(sizeof(attr)) " bytes, got %zu bytes", len); goto error; } c_map->lhs->len = len; c_map->lhs->name = talloc_typed_strdup(c_map->lhs, attr); } goto do_rhs; default: rad_assert(0); } *last = c_map; last = &(*last)->next; } talloc_free_children(pool); /* reset pool state */ } talloc_free(pool); /* * Check to see if we need to merge the entry into the request */ vp = fr_pair_find_by_da(request->control, attr_cache_merge_new, TAG_ANY); if (vp && vp->vp_bool) merge = true; if (merge) cache_merge(inst, request, c); for (;;) { cache_status_t ret; ret = inst->driver->insert(&inst->config, inst->driver_inst->data, request, *handle, c); switch (ret) { case CACHE_RECONNECT: if (cache_reconnect(handle, inst, request) == 0) continue; return RLM_MODULE_FAIL; case CACHE_OK: RDEBUG2("Committed entry, TTL %d seconds", ttl); cache_free(inst, &c); return merge ? RLM_MODULE_UPDATED : RLM_MODULE_OK; default: talloc_free(c); /* Failed insertion - use talloc_free not the driver free */ return RLM_MODULE_FAIL; } } }
/* * Allow single attribute values to be retrieved from the cache. */ static ssize_t cache_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace) { rlm_cache_entry_t *c; rlm_cache_t *inst = instance; rlm_cache_handle_t *handle; VALUE_PAIR *vp, *vps; pair_lists_t list; DICT_ATTR const *target; char const *p = fmt; size_t len; int ret = 0; list = radius_list_name(&p, PAIR_LIST_REQUEST); target = dict_attrbyname(p); if (!target) { REDEBUG("Unknown attribute \"%s\"", p); return -1; } if (cache_acquire(&handle, inst, request) < 0) return -1; switch (cache_find(&c, inst, request, handle, fmt)) { case RLM_MODULE_OK: /* found */ break; case RLM_MODULE_NOTFOUND: /* not found */ *out = '\0'; return 0; default: return -1; } switch (list) { case PAIR_LIST_REQUEST: vps = c->packet; break; case PAIR_LIST_REPLY: vps = c->reply; break; case PAIR_LIST_CONTROL: vps = c->control; break; case PAIR_LIST_UNKNOWN: REDEBUG("Unknown list qualifier in \"%s\"", fmt); ret = -1; goto finish; default: REDEBUG("Unsupported list \"%s\"", fr_int2str(pair_lists, list, "<UNKNOWN>")); ret = -1; goto finish; } vp = pairfind(vps, target->attr, target->vendor, TAG_ANY); if (!vp) { RDEBUG("No instance of this attribute has been cached"); *out = '\0'; goto finish; } len = vp_prints_value(out, freespace, vp, 0); if (is_truncated(len, freespace)) { REDEBUG("Insufficient buffer space to write cached value"); ret = -1; goto finish; } finish: cache_free(inst, &c); cache_release(inst, request, &handle); return ret; }
static ssize_t cache_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace) { rlm_cache_entry_t *c = NULL; rlm_cache_t *inst = instance; rlm_cache_handle_t *handle = NULL; size_t slen; ssize_t ret = 0; vp_tmpl_t target; vp_map_t *map = NULL; slen = tmpl_from_attr_substr(&target, fmt, REQUEST_CURRENT, PAIR_LIST_REQUEST, false, false); if (slen <= 0) { REDEBUG("%s", fr_strerror()); return -1; } if (cache_acquire(&handle, inst, request) < 0) return -1; switch (cache_find(&c, inst, request, handle, fmt)) { case RLM_MODULE_OK: /* found */ break; case RLM_MODULE_NOTFOUND: /* not found */ *out = '\0'; return 0; default: return -1; } for (map = c->maps; map; map = map->next) { if ((map->lhs->tmpl_da != target.tmpl_da) || (map->lhs->tmpl_tag != target.tmpl_tag) || (map->lhs->tmpl_list != target.tmpl_list)) continue; ret = value_data_prints(out, freespace, map->rhs->tmpl_data_type, map->lhs->tmpl_da, &map->rhs->tmpl_data_value, '\0'); if (is_truncated(slen, freespace)) { REDEBUG("Insufficient buffer space to write cached value"); ret = -1; goto finish; } break; } /* * Check if we found a matching map */ if (!map) { *out = '\0'; return 0; } finish: cache_free(inst, &c); cache_release(inst, request, &handle); return ret; }