static ssize_t redis_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt) { rlm_redis_t const *inst = mod_inst; fr_redis_conn_t *conn; bool read_only = false; uint8_t const *key = NULL; size_t key_len = 0; fr_redis_cluster_state_t state; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; size_t len; int ret; char const *p = fmt, *q; int argc; char const *argv[MAX_REDIS_ARGS]; char argv_buf[MAX_REDIS_COMMAND_LEN]; if (p[0] == '-') { p++; read_only = true; } /* * Hack to allow querying against a specific node for testing */ if (p[0] == '@') { fr_socket_addr_t node_addr; fr_pool_t *pool; RDEBUG3("Overriding node selection"); p++; q = strchr(p, ' '); if (!q) { REDEBUG("Found node specifier but no command, format is [-][@<host>[:port]] <redis command>"); return -1; } if (fr_inet_pton_port(&node_addr.ipaddr, &node_addr.port, p, q - p, AF_UNSPEC, true, true) < 0) { RPEDEBUG("Failed parsing node address"); return -1; } p = q + 1; if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &node_addr, true) < 0) { RPEDEBUG("Failed locating cluster node"); return -1; } conn = fr_pool_connection_get(pool, request); if (!conn) { REDEBUG("No connections available for cluster node"); return -1; } argc = rad_expand_xlat(request, p, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf); if (argc <= 0) { RPEDEBUG("Invalid command: %s", p); arg_error: fr_pool_connection_release(pool, request, conn); return -1; } if (argc >= (MAX_REDIS_ARGS - 1)) { RPEDEBUG("Too many parameters; increase MAX_REDIS_ARGS and recompile: %s", p); goto arg_error; } RDEBUG2("Executing command: %s", argv[0]); if (argc > 1) { RDEBUG2("With argments"); RINDENT(); for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]); REXDENT(); } if (!read_only) { reply = redisCommandArgv(conn->handle, argc, argv, NULL); status = fr_redis_command_status(conn, reply); } else if (redis_command_read_only(&status, &reply, request, conn, argc, argv) == -2) { goto close_conn; } if (!reply) goto fail; switch (status) { case REDIS_RCODE_MOVE: { fr_value_box_t vb; if (fr_redis_reply_to_value_box(NULL, &vb, reply, FR_TYPE_STRING, NULL) == 0) { REDEBUG("Key served by a different node: %pV", &vb); fr_value_box_clear(&vb); } goto fail; } case REDIS_RCODE_SUCCESS: goto reply_parse; case REDIS_RCODE_RECONNECT: close_conn: fr_pool_connection_close(pool, request, conn); ret = -1; goto finish; default: fail: fr_pool_connection_release(pool, request, conn); ret = -1; goto finish; } } /* * Normal node selection and execution based on key */ argc = rad_expand_xlat(request, p, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf); if (argc <= 0) { RPEDEBUG("Invalid command: %s", p); ret = -1; goto finish; } if (argc >= (MAX_REDIS_ARGS - 1)) { RPEDEBUG("Too many parameters; increase MAX_REDIS_ARGS and recompile: %s", p); ret = -1; goto finish; } /* * If we've got multiple arguments, the second one is usually the key. * The Redis docs say commands should be analysed first to get key * positions, but this involves sending them to the server, which is * just as expensive as sending them to the wrong server and receiving * a redirect. */ if (argc > 1) { key = (uint8_t const *)argv[1]; key_len = strlen((char const *)key); } for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, request, key, key_len, read_only); s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */ s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, request, status, &reply)) { RDEBUG2("Executing command: %s", argv[0]); if (argc > 1) { RDEBUG2("With arguments"); RINDENT(); for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]); REXDENT(); } if (!read_only) { reply = redisCommandArgv(conn->handle, argc, argv, NULL); status = fr_redis_command_status(conn, reply); } else if (redis_command_read_only(&status, &reply, request, conn, argc, argv) == -2) { state.close_conn = true; } } if (s_ret != REDIS_RCODE_SUCCESS) { ret = -1; goto finish; } if (!fr_cond_assert(reply)) { ret = -1; goto finish; } reply_parse: switch (reply->type) { case REDIS_REPLY_INTEGER: ret = snprintf(*out, outlen, "%lld", reply->integer); break; case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: len = (((size_t)reply->len) >= outlen) ? outlen - 1: (size_t) reply->len; memcpy(*out, reply->str, len); (*out)[len] = '\0'; ret = reply->len; break; default: REDEBUG("Server returned non-value type \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = -1; break; } finish: fr_redis_reply_free(&reply); return ret; }
/** Logging callback to write log messages to a destination * * This allows the logging destination to be customised on a per request basis. * * @note Function does not write log output immediately * * @param[in] type What type of message this is (error, warn, info, debug). * @param[in] lvl At what logging level this message should be output. * @param[in] request The current request. * @param[in] file src file the log message was generated in. * @param[in] line number the log message was generated on. * @param[in] fmt sprintf style fmt string. * @param[in] ap Arguments for the fmt string. * @param[in] uctx Context data for the log function. */ static void logtee_it(fr_log_type_t type, fr_log_lvl_t lvl, REQUEST *request, UNUSED char const *file, UNUSED int line, char const *fmt, va_list ap, void *uctx) { rlm_logtee_thread_t *t = talloc_get_type_abort(uctx, rlm_logtee_thread_t); rlm_logtee_t const *inst = t->inst; char *msg, *exp; fr_cursor_t cursor; VALUE_PAIR *vp; log_dst_t *dst; rad_assert(t->msg->vp_length == 0); /* Should have been cleared before returning */ /* * None of this should involve mallocs unless msg > 1k */ msg = talloc_typed_vasprintf(t->msg, fmt, ap); fr_value_box_strdup_buffer_shallow(NULL, &t->msg->data, attr_log_message, msg, true); t->type->vp_uint32 = (uint32_t) type; t->lvl->vp_uint32 = (uint32_t) lvl; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_prepend(&cursor, t->msg); fr_cursor_prepend(&cursor, t->type); fr_cursor_prepend(&cursor, t->lvl); fr_cursor_head(&cursor); /* * Now expand our fmt string to encapsulate the * message and any metadata * * Fixme: Would be better to call tmpl_expand * into a variable length ring buffer. */ dst = request->log.dst; request->log.dst = NULL; if (tmpl_aexpand(t, &exp, request, inst->log_fmt, NULL, NULL) < 0) goto finish; request->log.dst = dst; fr_fring_overwrite(t->fring, exp); /* Insert it into the buffer */ if (!t->pending) { t->pending = true; logtee_fd_active(t); /* Listen for when the fd is writable */ } finish: /* * Don't free, we re-use the VALUE_PAIRs for the next message */ vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->lvl)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->type)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->msg)) fr_cursor_append(&cursor, vp); fr_value_box_clear(&t->msg->data); /* Clear message data */ }