/** Load client entries from Couchbase client documents on startup * * This function executes the view defined in the module configuration and loops * through all returned rows. The view is called with "stale=false" to ensure the * most accurate data available when the view is called. This will force an index * rebuild on this design document in Couchbase. However, since this function is only * run once at sever startup this should not be a concern. * * @param inst The module instance. * @param tmpl Default values for new clients. * @param map The client attribute configuration section. * @return Returns 0 on success, -1 on error. */ int mod_load_client_documents(rlm_couchbase_t *inst, CONF_SECTION *tmpl, CONF_SECTION *map) { rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char vpath[256], vid[MAX_KEY_SIZE], vkey[MAX_KEY_SIZE]; /* view path and fields */ char error[512]; /* view error return */ int idx = 0; /* row array index counter */ int retval = 0; /* return value */ 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 */ CONF_SECTION *client; /* freeradius config section */ RADCLIENT *c; /* freeradius client */ /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return -1; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* build view path */ snprintf(vpath, sizeof(vpath), "%s?stale=false", inst->client_view); /* 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 */ ERROR("rlm_couchbase: failed to execute view request or parse return"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* debugging */ DEBUG3("rlm_couchbase: 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 */ ERROR("rlm_couchbase: view request failed with error: %s", error); /* set return */ retval = -1; /* return */ goto free_and_return; } /* check for document id in return */ if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) { /* log error */ ERROR("rlm_couchbase: failed to fetch rows from view payload"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* get and hold rows */ jrows = json_object_get(json); /* free cookie object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* debugging */ DEBUG3("rlm_couchbase: jrows == %s", json_object_to_json_string(jrows)); /* check for valid row value */ if (!json_object_is_type(jrows, json_type_array) || json_object_array_length(jrows) < 1) { /* log error */ ERROR("rlm_couchbase: no valid rows returned from view: %s", vpath); /* set return */ retval = -1; /* return */ goto free_and_return; } /* loop across all row elements */ for (idx = 0; idx < json_object_array_length(jrows); idx++) { /* fetch current index */ json = json_object_array_get_idx(jrows, idx); /* get view id */ if (json_object_object_get_ex(json, "id", &jval)) { /* clear view id */ memset(vid, 0, sizeof(vid)); /* copy and check length */ if (strlcpy(vid, json_object_get_string(jval), sizeof(vid)) >= sizeof(vid)) { ERROR("rlm_couchbase: id from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE); continue; } } else { WARN("rlm_couchbase: failed to fetch id from row - skipping"); continue; } /* get view key */ if (json_object_object_get_ex(json, "key", &jval)) { /* clear view key */ memset(vkey, 0, sizeof(vkey)); /* copy and check length */ if (strlcpy(vkey, json_object_get_string(jval), sizeof(vkey)) >= sizeof(vkey)) { ERROR("rlm_couchbase: key from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE); continue; } } else { WARN("rlm_couchbase: failed to fetch key from row - skipping"); continue; } /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, vid); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ ERROR("rlm_couchbase: failed to execute get request or parse return"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* debugging */ DEBUG3("rlm_couchbase: cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* allocate conf section */ client = tmpl ? cf_section_dup(NULL, tmpl, "client", vkey, true) : cf_section_alloc(NULL, "client", vkey); if (client_map_section(client, map, _get_client_value, cookie->jobj) < 0) { /* free config setion */ talloc_free(client); /* set return */ retval = -1; /* return */ goto free_and_return; } /* * @todo These should be parented from something. */ c = client_afrom_cs(NULL, client, false, false); if (!c) { ERROR("rlm_couchbase: failed to allocate client"); /* free config setion */ talloc_free(client); /* set return */ retval = -1; /* return */ goto free_and_return; } /* * Client parents the CONF_SECTION which defined it. */ talloc_steal(c, client); /* attempt to add client */ if (!client_add(NULL, c)) { ERROR("rlm_couchbase: failed to add client '%s' from '%s', possible duplicate?", vkey, vid); /* free client */ client_free(c); /* set return */ retval = -1; /* return */ goto free_and_return; } /* debugging */ DEBUG("rlm_couchbase: client '%s' added", c->longname); /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } } free_and_return: /* free rows */ if (jrows) { json_object_put(jrows); } /* 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 retval; }
/* * Expand the variables in an input string. */ char const *cf_expand_variables(char const *cf, int *lineno, CONF_SECTION *outer_cs, char *output, size_t outsize, char const *input, bool *soft_fail) { char *p; char const *end, *ptr; CONF_SECTION const *parent_cs; char name[8192]; if (soft_fail) *soft_fail = false; /* * Find the master parent conf section. * We can't use main_config->root_cs, because we're in the * process of re-building it, and it isn't set up yet... */ parent_cs = cf_root(outer_cs); p = output; ptr = input; while (*ptr) { /* * Ignore anything other than "${" */ if ((*ptr == '$') && (ptr[1] == '{')) { CONF_ITEM *ci; CONF_PAIR *cp; char *q; /* * FIXME: Add support for ${foo:-bar}, * like in xlat.c */ /* * Look for trailing '}', and log a * warning for anything that doesn't match, * and exit with a fatal error. */ end = strchr(ptr, '}'); if (end == NULL) { *p = '\0'; INFO("%s[%d]: Variable expansion missing }", cf, *lineno); return NULL; } ptr += 2; /* * Can't really happen because input lines are * capped at 8k, which is sizeof(name) */ if ((size_t) (end - ptr) >= sizeof(name)) { ERROR("%s[%d]: Reference string is too large", cf, *lineno); return NULL; } memcpy(name, ptr, end - ptr); name[end - ptr] = '\0'; q = strchr(name, ':'); if (q) { *(q++) = '\0'; } ci = cf_reference_item(parent_cs, outer_cs, name); if (!ci) { if (soft_fail) *soft_fail = true; ERROR("%s[%d]: Reference \"${%s}\" not found", cf, *lineno, name); return NULL; } /* * The expansion doesn't refer to another item or section * it's the property of a section. */ if (q) { CONF_SECTION *find = cf_item_to_section(ci); if (ci->type != CONF_ITEM_SECTION) { ERROR("%s[%d]: Can only reference properties of sections", cf, *lineno); return NULL; } switch (fr_str2int(conf_property_name, q, CONF_PROPERTY_INVALID)) { case CONF_PROPERTY_NAME: strcpy(p, find->name1); break; case CONF_PROPERTY_INSTANCE: strcpy(p, find->name2 ? find->name2 : find->name1); break; default: ERROR("%s[%d]: Invalid property '%s'", cf, *lineno, q); return NULL; } p += strlen(p); ptr = end + 1; } else if (ci->type == CONF_ITEM_PAIR) { /* * Substitute the value of the variable. */ cp = cf_item_to_pair(ci); /* * If the thing we reference is * marked up as being expanded in * pass2, don't expand it now. * Let it be expanded in pass2. */ if (cp->pass2) { if (soft_fail) *soft_fail = true; ERROR("%s[%d]: Reference \"%s\" points to a variable which has not been expanded.", cf, *lineno, input); return NULL; } if (!cp->value) { ERROR("%s[%d]: Reference \"%s\" has no value", cf, *lineno, input); return NULL; } if (p + strlen(cp->value) >= output + outsize) { ERROR("%s[%d]: Reference \"%s\" is too long", cf, *lineno, input); return NULL; } strcpy(p, cp->value); p += strlen(p); ptr = end + 1; } else if (ci->type == CONF_ITEM_SECTION) { CONF_SECTION *subcs; /* * Adding an entry again to a * section is wrong. We don't * want an infinite loop. */ if (cf_item_to_section(ci->parent) == outer_cs) { ERROR("%s[%d]: Cannot reference different item in same section", cf, *lineno); return NULL; } /* * Copy the section instead of * referencing it. */ subcs = cf_item_to_section(ci); subcs = cf_section_dup(outer_cs, outer_cs, subcs, cf_section_name1(subcs), cf_section_name2(subcs), false); if (!subcs) { ERROR("%s[%d]: Failed copying reference %s", cf, *lineno, name); return NULL; } subcs->item.filename = ci->filename; subcs->item.lineno = ci->lineno; cf_item_add(outer_cs, &(subcs->item)); ptr = end + 1; } else { ERROR("%s[%d]: Reference \"%s\" type is invalid", cf, *lineno, input); return NULL; } } else if (strncmp(ptr, "$ENV{", 5) == 0) { char *env; ptr += 5; /* * Look for trailing '}', and log a * warning for anything that doesn't match, * and exit with a fatal error. */ end = strchr(ptr, '}'); if (end == NULL) { *p = '\0'; INFO("%s[%d]: Environment variable expansion missing }", cf, *lineno); return NULL; } /* * Can't really happen because input lines are * capped at 8k, which is sizeof(name) */ if ((size_t) (end - ptr) >= sizeof(name)) { ERROR("%s[%d]: Environment variable name is too large", cf, *lineno); return NULL; } memcpy(name, ptr, end - ptr); name[end - ptr] = '\0'; /* * Get the environment variable. * If none exists, then make it an empty string. */ env = getenv(name); if (env == NULL) { *name = '\0'; env = name; } if (p + strlen(env) >= output + outsize) { ERROR("%s[%d]: Reference \"%s\" is too long", cf, *lineno, input); return NULL; } strcpy(p, env); p += strlen(p); ptr = end + 1; } else { /* * Copy it over verbatim. */ *(p++) = *(ptr++); } if (p >= (output + outsize)) { ERROR("%s[%d]: Reference \"%s\" is too long", cf, *lineno, input); return NULL; } } /* loop over all of the input string. */ *p = '\0'; return output; }