/** Converts a serialized cache entry back into a structure * * @param c Cache entry to populate (should already be allocated) * @param in String representation of cache entry. * @param inlen Length of string. May be < 0 in which case strlen will be * used to calculate the length of the string. * @return 0 on success, -1 on error. */ int cache_deserialize(rlm_cache_entry_t *c, char *in, ssize_t inlen) { vp_cursor_t packet, control, reply; TALLOC_CTX *store = NULL; char *p, *q; store = talloc_pool(c, 1024); if (!store) return -1; if (inlen < 0) inlen = strlen(in); fr_cursor_init(&packet, &c->packet); fr_cursor_init(&control, &c->control); fr_cursor_init(&reply, &c->reply); p = in; while (((size_t)(p - in)) < (size_t)inlen) { value_pair_map_t *map = NULL; VALUE_PAIR *vp = NULL; ssize_t len; q = strchr(p, '\n'); if (!q) break; /* List should also be terminated with a \n */ *q = '\0'; if (map_afrom_attr_str(store, &map, p, REQUEST_CURRENT, PAIR_LIST_REQUEST, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { fr_strerror_printf("Failed parsing pair: %s", p); goto error; } if (map->lhs->type != TMPL_TYPE_ATTR) { fr_strerror_printf("Pair left hand side \"%s\" parsed as %s, needed attribute. " "Check local dictionaries", map->lhs->name, fr_int2str(tmpl_names, map->lhs->type, "<INVALID>")); goto error; } if (map->rhs->type != TMPL_TYPE_LITERAL) { fr_strerror_printf("Pair right hand side \"%s\" parsed as %s, needed literal. " "Check serialized data quoting", map->rhs->name, fr_int2str(tmpl_names, map->rhs->type, "<INVALID>")); goto error; } /* * Convert literal to a type appropriate for * the VP. */ if (tmpl_cast_in_place(map->rhs, map->lhs->tmpl_da->type, map->lhs->tmpl_da) < 0) goto error; vp = pairalloc(c, map->lhs->tmpl_da); len = value_data_copy(vp, &vp->data, map->rhs->tmpl_data_type, &map->rhs->tmpl_data_value, map->rhs->tmpl_data_length); if (len < 0) goto error; /* * Pull out the special attributes, and set the * relevant cache entry fields. */ if (vp->da->vendor == 0) switch (vp->da->attr) { case PW_CACHE_CREATED: c->created = vp->vp_date; talloc_free(vp); goto next; case PW_CACHE_EXPIRES: c->expires = vp->vp_date; talloc_free(vp); goto next; default: break; } switch (map->lhs->tmpl_list) { case PAIR_LIST_REQUEST: fr_cursor_insert(&packet, vp); break; case PAIR_LIST_CONTROL: fr_cursor_insert(&control, vp); break; case PAIR_LIST_REPLY: fr_cursor_insert(&reply, vp); break; default: fr_strerror_printf("Invalid cache list for pair: %s", p); error: talloc_free(vp); talloc_free(map); return -1; } next: p = q + 1; talloc_free(map); } return 0; }
/** 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 *inst, REQUEST *request, rlm_cache_handle_t **handle, char const *key, int ttl) { vp_map_t const *map; vp_map_t **last, *c_map; VALUE_PAIR *vp; bool merge = false; rlm_cache_entry_t *c; TALLOC_CTX *pool; if ((inst->max_entries > 0) && inst->module->count && (inst->module->count(inst, request, handle) > inst->max_entries)) { RWDEBUG("Cache is full: %d entries", inst->max_entries); return RLM_MODULE_FAIL; } c = cache_alloc(inst, request); if (!c) return RLM_MODULE_FAIL; c->key = talloc_typed_strdup(c, key); c->created = c->expires = request->timestamp; c->expires += ttl; last = &c->maps; RDEBUG("Creating new cache entry"); /* * Alloc a pool so we don't have excessive mallocs when * gathering VALUE_PAIRs to cache. */ pool = talloc_pool(NULL, 1024); for (map = inst->maps; map != NULL; map = map->next) { VALUE_PAIR *to_cache = NULL; vp_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) { RDEBUG("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 PW_CACHE_TTL: case PW_CACHE_STATUS_ONLY: case PW_CACHE_READ_ONLY: case PW_CACHE_MERGE: case PW_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 value_data_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)); if (value_data_copy(c_map->rhs, &c_map->rhs->tmpl_data_value, vp->da->type, &vp->data) < 0) { REDEBUG("Failed copying attribute value"); talloc_free(pool); talloc_free(c); return RLM_MODULE_FAIL; } c_map->rhs->tmpl_data_type = vp->da->type; 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)); c_map->lhs->tmpl_da = vp->da; 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. */ c_map->lhs->len = tmpl_prints(attr, sizeof(attr), c_map->lhs, NULL); c_map->lhs->name = talloc_strdup(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 = pairfind(request->config, PW_CACHE_MERGE, 0, TAG_ANY); if (vp && (vp->vp_integer > 0)) merge = true; if (merge) cache_merge(inst, request, c); for (;;) { cache_status_t ret; ret = inst->module->insert(inst, request, handle, c); switch (ret) { case CACHE_RECONNECT: if (cache_reconnect(inst, request, handle) == 0) continue; return RLM_MODULE_FAIL; case CACHE_OK: RDEBUG("Commited 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; } } }