The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
rlm_rediswho.c
Go to the documentation of this file.
1/*
2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 1c8a9200957305b9d3be94ddf8a30077c6fad248 $
19 * @file rlm_rediswho.c
20 * @brief Session tracking using redis.
21 *
22 * @author Gabriel Blanchard
23 *
24 * @copyright 2015 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
25 * @copyright 2011 TekSavvy Solutions (gabe@teksavvy.com)
26 * @copyright 2000,2006 The FreeRADIUS server project
27 */
28
29RCSID("$Id: 1c8a9200957305b9d3be94ddf8a30077c6fad248 $")
30
31#include <freeradius-devel/server/base.h>
32#include <freeradius-devel/server/module_rlm.h>
33#include <freeradius-devel/server/modpriv.h>
34#include <freeradius-devel/util/debug.h>
35
36#include <freeradius-devel/redis/base.h>
37#include <freeradius-devel/redis/cluster.h>
38
39typedef struct {
40 fr_redis_conf_t conf; //!< Connection parameters for the Redis server.
41 //!< Must be first field in this struct.
42
43 fr_redis_cluster_t *cluster; //!< Pool O pools
44
45 int expiry_time; //!< Expiry time in seconds if no updates are received for a user
46
47 int trim_count; //!< How many session updates to keep track of per user.
48
49 char const *insert; //!< Command for inserting session data
50 char const *trim; //!< Command for trimming the session list.
51 char const *expire; //!< Command for expiring entries.
53
56 { FR_CONF_OFFSET_FLAGS("trim", CONF_FLAG_XLAT, rlm_rediswho_t, trim) }, /* required only if trim_count > 0 */
59};
60
63
64 { FR_CONF_OFFSET("trim_count", rlm_rediswho_t, trim_count), .dflt = "-1" },
65
66 /*
67 * These all smash the same variables, because we don't care about them right now.
68 * In 3.1, we should have a way of saying "parse a set of sub-sections according to a template"
69 */
70 { FR_CONF_POINTER("Start", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = section_config },
71 { FR_CONF_POINTER("Interim-Update", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = section_config },
72 { FR_CONF_POINTER("Stop", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = section_config },
73 { FR_CONF_POINTER("Accounting-On", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = section_config },
74 { FR_CONF_POINTER("Accounting-Off", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = section_config },
75 { FR_CONF_POINTER("Failed", 0, CONF_FLAG_SUBSECTION, NULL), .subcs = section_config },
76
78};
79
80static fr_dict_t const *dict_radius;
81
84 { .out = &dict_radius, .proto = "radius" },
85 { NULL }
86};
87
89
92 { .out = &attr_acct_status_type, .name = "Acct-Status-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius },
93 { NULL }
94};
95
96/*
97 * Query the database executing a command with no result rows
98 */
99static int rediswho_command(rlm_rediswho_t const *inst, request_t *request, char const *fmt)
100{
101 fr_redis_conn_t *conn;
102
103 int ret = -1;
104
106 fr_redis_rcode_t status;
107 redisReply *reply = NULL;
108 int s_ret;
109
110 uint8_t const *key = NULL;
111 size_t key_len = 0;
112
113 int argc;
114 char const *argv[MAX_REDIS_ARGS];
115 char argv_buf[MAX_REDIS_COMMAND_LEN];
116
117 if (!fmt || !*fmt) return 0;
118
119 argc = rad_expand_xlat(request, fmt, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf);
120 if (argc < 0) {
121 RPEDEBUG("Invalid command: %s", fmt);
122 return -1;
123 }
124
125 /*
126 * If we've got multiple arguments, the second one is usually the key.
127 * The Redis docs say commands should be analysed first to get key
128 * positions, but this involves sending them to the server, which is
129 * just as expensive as sending them to the wrong server and receiving
130 * a redirect.
131 */
132 if (argc > 1) {
133 key = (uint8_t const *)argv[1];
134 key_len = strlen((char const *)key);
135 }
136
137 for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, request, key, key_len, false);
138 s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */
139 s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, request, status, &reply)) {
140 reply = redisCommandArgv(conn->handle, argc, argv, NULL);
141 status = fr_redis_command_status(conn, reply);
142 }
143 if (s_ret != REDIS_RCODE_SUCCESS) {
144 RERROR("Failed inserting accounting data");
145 error:
146 fr_redis_reply_free(&reply);
147 return -1;
148 }
149 if (!fr_cond_assert(reply)) goto error;
150
151 /*
152 * Write the response to the debug log
153 */
154 fr_redis_reply_print(L_DBG_LVL_2, reply, request, 0);
155
156 switch (reply->type) {
157 case REDIS_REPLY_ERROR:
158 break;
159
160 case REDIS_REPLY_INTEGER:
161 if (reply->integer > 0) ret = reply->integer;
162 break;
163
164 /*
165 * We don't know to interpret this, the user has probably messed
166 * up the queries, so print an error message and fail.
167 */
168 default:
169 REDEBUG("Expected type \"integer\" got type \"%s\"",
170 fr_table_str_by_value(redis_reply_types, reply->type, "<UNKNOWN>"));
171 break;
172 }
173 fr_redis_reply_free(&reply);
174
175 return ret;
176}
177
179 char const *insert,
180 char const *trim,
181 char const *expire)
182{
183 int ret;
184
185 ret = rediswho_command(inst, request, insert);
186 if (ret < 0) RETURN_MODULE_FAIL;
187
188 /* Only trim if necessary */
189 if ((inst->trim_count >= 0) && (ret > inst->trim_count)) {
190 if (rediswho_command(inst, request, trim) < 0) RETURN_MODULE_FAIL;
191 }
192
193 if (rediswho_command(inst, request, expire) < 0) RETURN_MODULE_FAIL;
195}
196
197static unlang_action_t CC_HINT(nonnull) mod_accounting(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
198{
200 CONF_SECTION *conf = mctx->mi->conf;
201 rlm_rcode_t rcode;
202 fr_pair_t *vp;
204 CONF_SECTION *cs;
205 char const *insert, *trim, *expire;
206
207 vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_acct_status_type);
208 if (!vp) {
209 RDEBUG2("Could not find account status type in packet");
211 }
212
213 dv = fr_dict_enum_by_value(vp->da, &vp->data);
214 if (!dv) {
215 RDEBUG2("Unknown Acct-Status-Type %u", vp->vp_uint32);
217 }
218
219 cs = cf_section_find(conf, dv->name, NULL);
220 if (!cs) {
221 RDEBUG2("No subsection %s", dv->name);
223 }
224
225 insert = cf_pair_value(cf_pair_find(cs, "insert"));
226 trim = cf_pair_value(cf_pair_find(cs, "trim"));
227 expire = cf_pair_value(cf_pair_find(cs, "expire"));
228
229 return mod_accounting_all(&rcode, inst, request, insert, trim, expire);
230}
231
232static int mod_instantiate(module_inst_ctx_t const *mctx)
233{
234 rlm_rediswho_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_rediswho_t);
235 CONF_SECTION *conf = mctx->mi->conf;
236
237 inst->cluster = fr_redis_cluster_alloc(inst, conf, &inst->conf, true, NULL, NULL, NULL);
238 if (!inst->cluster) return -1;
239
240 return 0;
241}
242
243static int mod_load(void)
244{
246
247 return 0;
248}
249
252 .common = {
253 .magic = MODULE_MAGIC_INIT,
254 .name = "rediswho",
255 .inst_size = sizeof(rlm_rediswho_t),
257 .onload = mod_load,
259 },
260 .method_group = {
261 .bindings = (module_method_binding_t[]){
262 { .section = SECTION_NAME("accounting", CF_IDENT_ANY), .method = mod_accounting },
264 }
265 }
266};
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
static int const char * fmt
Definition acutest.h:573
#define RCSID(id)
Definition build.h:483
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:642
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:268
#define FR_CONF_POINTER(_name, _type, _flags, _res_p)
conf_parser_t which parses a single CONF_PAIR producing a single global result
Definition cf_parse.h:323
#define FR_CONF_OFFSET_FLAGS(_name, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:256
@ CONF_FLAG_REQUIRED
Error out if no matching CONF_PAIR is found, and no dflt value is set.
Definition cf_parse.h:418
@ CONF_FLAG_XLAT
string will be dynamically expanded.
Definition cf_parse.h:429
@ CONF_FLAG_SUBSECTION
Instead of putting the information into a configuration structure, the configuration file routines MA...
Definition cf_parse.h:412
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:579
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:101
CONF_SECTION * cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2)
Find a CONF_SECTION with name1 and optionally name2.
Definition cf_util.c:1028
CONF_PAIR * cf_pair_find(CONF_SECTION const *cs, char const *attr)
Search for a CONF_PAIR with a specific name.
Definition cf_util.c:1439
char const * cf_pair_value(CONF_PAIR const *pair)
Return the value of a CONF_PAIR.
Definition cf_util.c:1594
#define CF_IDENT_ANY
Definition cf_util.h:78
fr_redis_cluster_t * fr_redis_cluster_alloc(TALLOC_CTX *ctx, CONF_SECTION *module, fr_redis_conf_t *conf, bool triggers_enabled, char const *log_prefix, char const *trigger_prefix, fr_pair_list_t *trigger_args)
Allocate and initialise a new cluster structure.
Definition cluster.c:2261
fr_redis_rcode_t fr_redis_cluster_state_next(fr_redis_cluster_state_t *state, fr_redis_conn_t **conn, fr_redis_cluster_t *cluster, request_t *request, fr_redis_rcode_t status, redisReply **reply)
Get the next connection to attempt a command against.
Definition cluster.c:1863
fr_redis_rcode_t fr_redis_cluster_state_init(fr_redis_cluster_state_t *state, fr_redis_conn_t **conn, fr_redis_cluster_t *cluster, request_t *request, uint8_t const *key, size_t key_len, bool read_only)
Resolve a key to a pool, and reserve a connection in that pool.
Definition cluster.c:1741
A redis cluster.
Definition cluster.c:251
Redis connection sequence state.
Definition cluster.h:49
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:139
fr_dict_enum_value_t * fr_dict_enum_by_value(fr_dict_attr_t const *da, fr_value_box_t const *value)
Lookup the structure representing an enum value in a fr_dict_attr_t.
Definition dict_util.c:3349
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:268
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:281
char const * name
Enum name.
Definition dict.h:228
Specifies an attribute which must be present for the module to function.
Definition dict.h:267
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:280
Value of an enumerated attribute.
Definition dict.h:227
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
#define RERROR(fmt,...)
Definition log.h:298
#define RPEDEBUG(fmt,...)
Definition log.h:376
int rad_expand_xlat(request_t *request, char const *cmd, int max_argc, char const *argv[], bool can_fail, size_t argv_buflen, char *argv_buf)
Split string into words and expand each one.
Definition util.c:599
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:71
@ FR_TYPE_UINT32
32 Bit unsigned integer.
unsigned char uint8_t
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
module_t common
Common fields presented by all modules.
Definition module_rlm.h:39
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:693
static const conf_parser_t config[]
Definition base.c:183
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RDEBUG2(fmt,...)
Definition radclient.h:54
static rs_t * conf
Definition radsniff.c:53
#define RETURN_MODULE_NOOP
Definition rcode.h:62
#define RETURN_MODULE_OK
Definition rcode.h:57
#define RETURN_MODULE_FAIL
Definition rcode.h:56
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
#define MAX_REDIS_COMMAND_LEN
Definition base.h:43
redisContext * handle
Hiredis context used when issuing commands.
Definition base.h:101
#define REDIS_COMMON_CONFIG
Definition base.h:133
#define MAX_REDIS_ARGS
Definition base.h:44
void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, request_t *request, int idx)
Print the response data in a useful treelike form.
Definition redis.c:141
static void fr_redis_reply_free(redisReply **reply)
Wrap freeReplyObject so we consistently check for NULL pointers.
Definition base.h:64
void fr_redis_version_print(void)
Print the version of libhiredis the server was built against.
Definition redis.c:53
fr_redis_rcode_t fr_redis_command_status(fr_redis_conn_t *conn, redisReply *reply)
Check the reply for errors.
Definition redis.c:71
fr_table_num_sorted_t const redis_reply_types[]
Definition redis.c:30
fr_redis_rcode_t
Codes are ordered inversely by priority.
Definition base.h:87
@ REDIS_RCODE_SUCCESS
Operation was successful.
Definition base.h:88
@ REDIS_RCODE_TRY_AGAIN
Try the operation again.
Definition base.h:90
Configuration parameters for a redis connection.
Definition base.h:109
Connection handle, holding a redis context.
Definition base.h:100
static int mod_load(void)
fr_dict_autoload_t rlm_rediswho_dict[]
fr_dict_attr_autoload_t rlm_rediswho_dict_attr[]
int trim_count
How many session updates to keep track of per user.
int expiry_time
Expiry time in seconds if no updates are received for a user.
fr_redis_conf_t conf
Connection parameters for the Redis server.
char const * insert
Command for inserting session data.
static fr_dict_t const * dict_radius
fr_redis_cluster_t * cluster
Pool O pools.
module_rlm_t rlm_rediswho
static unlang_action_t mod_accounting(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
static conf_parser_t section_config[]
static fr_dict_attr_t const * attr_acct_status_type
static int rediswho_command(rlm_rediswho_t const *inst, request_t *request, char const *fmt)
char const * expire
Command for expiring entries.
static int mod_instantiate(module_inst_ctx_t const *mctx)
char const * trim
Command for trimming the session list.
static conf_parser_t module_config[]
static unlang_action_t mod_accounting_all(rlm_rcode_t *p_result, rlm_rediswho_t const *inst, request_t *request, char const *insert, char const *trim, char const *expire)
static int instantiate(module_inst_ctx_t const *mctx)
Definition rlm_rest.c:1310
#define SECTION_NAME(_name1, _name2)
Define a section name consisting of a verb and a noun.
Definition section.h:40
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:329
size_t inst_size
Size of the module's instance data.
Definition module.h:203
void * data
Module's instance data.
Definition module.h:271
#define MODULE_BINDING_TERMINATOR
Terminate a module binding list.
Definition module.h:151
Named methods exported by a module.
Definition module.h:173
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition pair.h:69
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:772
#define talloc_get_type_abort_const
Definition talloc.h:282
int nonnull(2, 5))