/* * Query the redis database */ int rlm_redis_query(REDISSOCK **dissocket_p, REDIS_INST *inst, const char *query, REQUEST *request) { REDISSOCK *dissocket; int argc; const char *argv[MAX_REDIS_ARGS]; char argv_buf[MAX_QUERY_LEN]; if (!query || !*query || !inst || !dissocket_p) { return -1; } argc = rad_expand_xlat(request, query, MAX_REDIS_ARGS, argv, 0, sizeof(argv_buf), argv_buf); if (argc <= 0) return -1; dissocket = *dissocket_p; DEBUG2("executing %s ...", argv[0]); dissocket->reply = redisCommandArgv(dissocket->conn, argc, argv, NULL); if (!dissocket->reply) { radlog(L_ERR, "rlm_redis: (%s) REDIS error: %s", inst->xlat_name, dissocket->conn->errstr); dissocket = fr_connection_reconnect(inst->pool, dissocket); if (!dissocket) { error: *dissocket_p = NULL; return -1; } dissocket->reply = redisCommand(dissocket->conn, query); if (!dissocket->reply) { radlog(L_ERR, "rlm_redis (%s): failed after re-connect", inst->xlat_name); fr_connection_del(inst->pool, dissocket); goto error; } *dissocket_p = dissocket; } if (dissocket->reply->type == REDIS_REPLY_ERROR) { radlog(L_ERR, "rlm_redis (%s): query failed, %s", inst->xlat_name, query); return -1; } return 0; }
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_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; }
/** Start a process * * @param cmd Command to execute. This is parsed into argv[] parts, * then each individual argv part is xlat'ed. * @param request Current reuqest * @param exec_wait set to 1 if you want to read from or write to child * @param[in,out] input_fd pointer to int, receives the stdin file. * descriptor. Set to NULL and the child will have /dev/null on stdin * @param[in,out] output_fd pinter to int, receives the stdout file * descriptor. Set to NULL and child will have /dev/null on stdout. * @param input_pairs list of value pairs - these will be put into * the environment variables of the child. * @param shell_escape values before passing them as arguments. * @return PID of the child process, -1 on error. */ pid_t radius_start_program(char const *cmd, REQUEST *request, int exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, int shell_escape) { #ifndef __MINGW32__ char *p; VALUE_PAIR *vp; int n; int to_child[2] = {-1, -1}; int from_child[2] = {-1, -1}; pid_t pid; #endif int argc; int i; char *argv[MAX_ARGV]; char argv_buf[4096]; #define MAX_ENVP 1024 char *envp[MAX_ENVP]; argc = rad_expand_xlat(request, cmd, MAX_ARGV, argv, true, sizeof(argv_buf), argv_buf); if (argc <= 0) { RDEBUG("invalid command line '%s'.", cmd); return -1; } #ifndef NDEBUG if (debug_flag > 2) { RDEBUG3("executing cmd %s", cmd); for (i = 0; i < argc; i++) { RDEBUG3("\t[%d] %s", i, argv[i]); } } #endif #ifndef __MINGW32__ /* * Open a pipe for child/parent communication, if necessary. */ if (exec_wait) { if (input_fd) { if (pipe(to_child) != 0) { RDEBUG("Couldn't open pipe to child: %s", strerror(errno)); return -1; } } if (output_fd) { if (pipe(from_child) != 0) { RDEBUG("Couldn't open pipe from child: %s", strerror(errno)); /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); return -1; } } } envp[0] = NULL; if (input_pairs) { vp_cursor_t cursor; int envlen; char buffer[1024]; /* * Set up the environment variables in the * parent, so we don't call libc functions that * hold mutexes. They might be locked when we fork, * and will remain locked in the child. */ envlen = 0; for (vp = paircursor(&cursor, &input_pairs); vp; vp = pairnext(&cursor)) { /* * Hmm... maybe we shouldn't pass the * user's password in an environment * variable... */ snprintf(buffer, sizeof(buffer), "%s=", vp->da->name); if (shell_escape) { for (p = buffer; *p != '='; p++) { if (*p == '-') { *p = '_'; } else if (isalpha((int) *p)) { *p = toupper(*p); } } } n = strlen(buffer); vp_prints_value(buffer+n, sizeof(buffer) - n, vp, shell_escape ? '"' : 0); envp[envlen++] = strdup(buffer); /* * Don't add too many attributes. */ if (envlen == (MAX_ENVP - 1)) break; } envp[envlen] = NULL; } if (exec_wait) { pid = rad_fork(); /* remember PID */ } else { pid = fork(); /* don't wait */ } if (pid == 0) { int devnull; /* * Child process. * * We try to be fail-safe here. So if ANYTHING * goes wrong, we exit with status 1. */ /* * Open STDIN to /dev/null */ devnull = open("/dev/null", O_RDWR); if (devnull < 0) { RDEBUG("Failed opening /dev/null: %s\n", strerror(errno)); /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Only massage the pipe handles if the parent * has created them. */ if (exec_wait) { if (input_fd) { close(to_child[1]); dup2(to_child[0], STDIN_FILENO); } else { dup2(devnull, STDIN_FILENO); } if (output_fd) { close(from_child[0]); dup2(from_child[1], STDOUT_FILENO); } else { dup2(devnull, STDOUT_FILENO); } } else { /* no pipe, STDOUT should be /dev/null */ dup2(devnull, STDIN_FILENO); dup2(devnull, STDOUT_FILENO); } /* * If we're not debugging, then we can't do * anything with the error messages, so we throw * them away. * * If we are debugging, then we want the error * messages to go to the STDERR of the server. */ if (debug_flag == 0) { dup2(devnull, STDERR_FILENO); } close(devnull); /* * The server may have MANY FD's open. We don't * want to leave dangling FD's for the child process * to play funky games with, so we close them. */ closefrom(3); /* * I swear the signature for execve is wrong and should take 'char const * const argv[]'. */ execve(argv[0], argv, envp); printf("Failed to execute \"%s\": %s", argv[0], strerror(errno)); /* fork output will be captured */ /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Free child environment variables */ for (i = 0; envp[i] != NULL; i++) { free(envp[i]); } /* * Parent process. */ if (pid < 0) { RDEBUG("Couldn't fork %s: %s", argv[0], strerror(errno)); if (exec_wait) { /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); close(from_child[0]); close(from_child[0]); } return -1; } /* * We're not waiting, exit, and ignore any child's status. */ if (exec_wait) { /* * Close the ends of the pipe(s) the child is using * return the ends of the pipe(s) our caller wants * */ if (input_fd) { *input_fd = to_child[1]; close(to_child[0]); } if (output_fd) { *output_fd = from_child[0]; close(from_child[1]); } } return pid; #else if (exec_wait) { RDEBUG("Wait is not supported"); return -1; } { /* * The _spawn and _exec families of functions are * found in Windows compiler libraries for * portability from UNIX. There is a variety of * functions, including the ability to pass * either a list or array of parameters, to * search in the PATH or otherwise, and whether * or not to pass an environment (a set of * environment variables). Using _spawn, you can * also specify whether you want the new process * to close your program (_P_OVERLAY), to wait * until the new process is finished (_P_WAIT) or * for the two to run concurrently (_P_NOWAIT). * _spawn and _exec are useful for instances in * which you have simple requirements for running * the program, don't want the overhead of the * Windows header file, or are interested * primarily in portability. */ /* * FIXME: check return code... what is it? */ _spawnve(_P_NOWAIT, argv[0], argv, envp); } return 0; #endif }
/** Start a process * * @param cmd Command to execute. This is parsed into argv[] parts, then each individual argv * part is xlat'ed. * @param request Current reuqest * @param exec_wait set to true to read from or write to child. * @param[in,out] input_fd pointer to int, receives the stdin file descriptor. Set to NULL * and the child will have /dev/null on stdin. * @param[in,out] output_fd pinter to int, receives the stdout file descriptor. Set to NULL * and child will have /dev/null on stdout. * @param input_pairs list of value pairs - these will be put into the environment variables * of the child. * @param shell_escape values before passing them as arguments. * @return * - PID of the child process. * - -1 on failure. */ pid_t radius_start_program(char const *cmd, REQUEST *request, bool exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, bool shell_escape) { #ifndef __MINGW32__ char *p; VALUE_PAIR *vp; int n; int to_child[2] = {-1, -1}; int from_child[2] = {-1, -1}; pid_t pid; #endif int argc; int i; char const **argv_p; char *argv[MAX_ARGV], **argv_start = argv; char argv_buf[4096]; #define MAX_ENVP 1024 char *envp[MAX_ENVP]; size_t envlen = 0; TALLOC_CTX *input_ctx = NULL; /* * Stupid array decomposition... * * If we do memcpy(&argv_p, &argv, sizeof(argv_p)) src ends up being a char ** * pointing to the value of the first element. */ memcpy(&argv_p, &argv_start, sizeof(argv_p)); argc = rad_expand_xlat(request, cmd, MAX_ARGV, argv_p, true, sizeof(argv_buf), argv_buf); if (argc <= 0) { ERROR("Invalid command '%s'", cmd); return -1; } if (DEBUG_ENABLED3) { for (i = 0; i < argc; i++) DEBUG3("arg[%d] %s", i, argv[i]); } #ifndef __MINGW32__ /* * Open a pipe for child/parent communication, if necessary. */ if (exec_wait) { if (input_fd) { if (pipe(to_child) != 0) { ERROR("Couldn't open pipe to child: %s", fr_syserror(errno)); return -1; } } if (output_fd) { if (pipe(from_child) != 0) { ERROR("Couldn't open pipe from child: %s", fr_syserror(errno)); /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); return -1; } } } envp[0] = NULL; if (input_pairs) { vp_cursor_t cursor; char buffer[1024]; input_ctx = talloc_new(request); /* * Set up the environment variables in the * parent, so we don't call libc functions that * hold mutexes. They might be locked when we fork, * and will remain locked in the child. */ for (vp = fr_cursor_init(&cursor, &input_pairs); vp && (envlen < ((sizeof(envp) / sizeof(*envp)) - 1)); vp = fr_cursor_next(&cursor)) { /* * Hmm... maybe we shouldn't pass the * user's password in an environment * variable... */ snprintf(buffer, sizeof(buffer), "%s=", vp->da->name); if (shell_escape) { for (p = buffer; *p != '='; p++) { if (*p == '-') { *p = '_'; } else if (isalpha((int) *p)) { *p = toupper(*p); } } } n = strlen(buffer); fr_pair_value_snprint(buffer + n, sizeof(buffer) - n, vp, shell_escape ? '"' : 0); DEBUG3("export %s", buffer); envp[envlen++] = talloc_strdup(input_ctx, buffer); } fr_cursor_init(&cursor, radius_list(request, PAIR_LIST_CONTROL)); while ((envlen < ((sizeof(envp) / sizeof(*envp)) - 1)) && (vp = fr_cursor_next_by_num(&cursor, 0, PW_EXEC_EXPORT, TAG_ANY))) { DEBUG3("export %s", vp->vp_strvalue); memcpy(&envp[envlen++], &vp->vp_strvalue, sizeof(*envp)); } /* * NULL terminate for execve */ envp[envlen] = NULL; } if (exec_wait) { pid = rad_fork(); /* remember PID */ } else { pid = fork(); /* don't wait */ } if (pid == 0) { int devnull; /* * Child process. * * We try to be fail-safe here. So if ANYTHING * goes wrong, we exit with status 1. */ /* * Open STDIN to /dev/null */ devnull = open("/dev/null", O_RDWR); if (devnull < 0) { ERROR("Failed opening /dev/null: %s\n", fr_syserror(errno)); /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Only massage the pipe handles if the parent * has created them. */ if (exec_wait) { if (input_fd) { close(to_child[1]); dup2(to_child[0], STDIN_FILENO); } else { dup2(devnull, STDIN_FILENO); } if (output_fd) { close(from_child[0]); dup2(from_child[1], STDOUT_FILENO); } else { dup2(devnull, STDOUT_FILENO); } } else { /* no pipe, STDOUT should be /dev/null */ dup2(devnull, STDIN_FILENO); dup2(devnull, STDOUT_FILENO); } /* * If we're not debugging, then we can't do * anything with the error messages, so we throw * them away. * * If we are debugging, then we want the error * messages to go to the STDERR of the server. */ if (rad_debug_lvl == 0) { dup2(devnull, STDERR_FILENO); } close(devnull); /* * The server may have MANY FD's open. We don't * want to leave dangling FD's for the child process * to play funky games with, so we close them. */ closefrom(3); /* * I swear the signature for execve is wrong and should * take 'char const * const argv[]'. * * Note: execve(), unlike system(), treats all the space * delimited arguments as literals, so there's no need * to perform additional escaping. */ execve(argv[0], argv, envp); printf("Failed to execute \"%s\": %s", argv[0], fr_syserror(errno)); /* fork output will be captured */ /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Free child environment variables */ talloc_free(input_ctx); /* * Parent process. */ if (pid < 0) { ERROR("Couldn't fork %s: %s", argv[0], fr_syserror(errno)); if (exec_wait) { /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); close(from_child[0]); close(from_child[1]); } return -1; } /* * We're not waiting, exit, and ignore any child's status. */ if (exec_wait) { /* * Close the ends of the pipe(s) the child is using * return the ends of the pipe(s) our caller wants * */ if (input_fd) { *input_fd = to_child[1]; close(to_child[0]); } if (output_fd) { *output_fd = from_child[0]; close(from_child[1]); } } return pid; #else if (exec_wait) { ERROR("Wait is not supported"); return -1; } { /* * The _spawn and _exec families of functions are * found in Windows compiler libraries for * portability from UNIX. There is a variety of * functions, including the ability to pass * either a list or array of parameters, to * search in the PATH or otherwise, and whether * or not to pass an environment (a set of * environment variables). Using _spawn, you can * also specify whether you want the new process * to close your program (_P_OVERLAY), to wait * until the new process is finished (_P_WAIT) or * for the two to run concurrently (_P_NOWAIT). * _spawn and _exec are useful for instances in * which you have simple requirements for running * the program, don't want the overhead of the * Windows header file, or are interested * primarily in portability. */ /* * FIXME: check return code... what is it? */ _spawnve(_P_NOWAIT, argv[0], argv, envp); } return 0; #endif }