/** 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; }
/** 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 request The accounting request object. * @return Returns operation status (@p rlm_rcode_t). */ CC_HINT(nonnull) static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ void *handle = NULL; /* connection pool handle */ VALUE_PAIR *vp; /* radius value pair linked list */ char dockey[MAX_KEY_SIZE]; /* 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 */ /* assert packet as not null */ rad_assert(request->packet != NULL); /* sanity check */ if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) { /* log debug */ RDEBUG("could not find status type in packet"); /* return */ return RLM_MODULE_NOOP; } /* set status */ status = vp->vp_integer; /* acknowledge the request but take no action */ if (status == PW_STATUS_ACCOUNTING_ON || status == PW_STATUS_ACCOUNTING_OFF) { /* log debug */ RDEBUG("handling accounting on/off request without action"); /* return */ return RLM_MODULE_OK; } /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set handle pointer */ rlm_couchbase_handle_t *handle_t = handle; /* set couchbase instance */ lcb_t cb_inst = handle_t->handle; /* set cookie */ cookie_t *cookie = handle_t->cookie; /* check cookie */ if (cookie) { /* clear cookie */ memset(cookie, 0, sizeof(cookie_t)); } else { /* log error */ RERROR("cookie not usable - possibly not allocated"); /* free connection */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_FAIL; } /* attempt to build document key */ if (radius_xlat(dockey, sizeof(dockey), request, inst->acct_key, NULL, NULL) < 0) { /* log error */ RERROR("could not find accounting key attribute (%s) in packet", inst->acct_key); /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_NOOP; } /* init cookie error status */ cookie->jerr = json_tokener_success; /* attempt to fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success) { /* log error */ RERROR("failed to execute get request or parse returned json object"); /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); } } else { /* check cookie json object */ if (cookie->jobj != NULL) { /* set doc found */ docfound = 1; /* debugging */ RDEBUG("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj)); } } /* start json document if needed */ if (docfound != 1) { /* debugging */ RDEBUG("document not 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)); /* set start and stop times ... ensure we always have these elements */ json_object_object_add(cookie->jobj, "startTimestamp", json_object_new_string("null")); json_object_object_add(cookie->jobj, "stopTimestamp", json_object_new_string("null")); } /* status specific replacements for start/stop time */ switch (status) { case PW_STATUS_START: /* add start time */ if ((vp = pairfind(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "startTimestamp", mod_value_pair_to_json_object(request, vp)); } break; case PW_STATUS_STOP: /* add stop time */ if ((vp = pairfind(request->packet->vps, PW_EVENT_TIMESTAMP, 0, 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 PW_STATUS_ALIVE: /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; default: /* we shouldn't get here - free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* release our connection handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return without doing anything */ return RLM_MODULE_NOOP; } /* 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 */ RDEBUG("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"); /* free json output */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_FAIL; } /* free json output */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* debugging */ RDEBUG("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); } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_OK; }