0b41a613d892d50716ef3e98aed76ab3f3212e47
[freeradius.git] / src / modules / rlm_rediswho / rlm_rediswho.c
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$
19  * @file rlm_rediswho.c
20  * @brief Session tracking using redis.
21  *
22  * @copyright 2000,2006  The FreeRADIUS server project
23  * @copyright 2011  TekSavvy Solutions <gabe@teksavvy.com>
24  */
25
26 RCSID("$Id$")
27
28 #include <freeradius-devel/radiusd.h>
29
30 #include <ctype.h>
31
32 #include <rlm_redis.h>
33
34 typedef struct rlm_rediswho_t {
35         char const *xlat_name;
36         CONF_SECTION *cs;
37
38         char const *redis_instance_name;
39         REDIS_INST *redis_inst;
40
41         /*
42          *      expiry time in seconds if no updates are received for a user
43          */
44         int expiry_time;
45
46         /*
47          *      How many session updates to keep track of per user
48          */
49         int trim_count;
50         char const *insert;
51         char const *trim;
52         char const *expire;
53 } rlm_rediswho_t;
54
55 static CONF_PARSER module_config[] = {
56         { "redis-instance-name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_rediswho_t, redis_instance_name), NULL },
57         { "redis_module_instance", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rediswho_t, redis_instance_name), "redis" },
58
59         { "trim-count", FR_CONF_OFFSET(PW_TYPE_SIGNED | PW_TYPE_DEPRECATED, rlm_rediswho_t, trim_count), NULL },
60         { "trim_count", FR_CONF_OFFSET(PW_TYPE_SIGNED, rlm_rediswho_t, trim_count), "-1" },
61
62         { "insert", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_XLAT, rlm_rediswho_t, insert), NULL },
63         { "trim", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rediswho_t, trim), NULL }, /* required only if trim_count > 0 */
64         { "expire", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED | PW_TYPE_XLAT, rlm_rediswho_t, expire), NULL },
65
66         { NULL, -1, 0, NULL, NULL}
67 };
68
69 /*
70  *      Query the database executing a command with no result rows
71  */
72 static int rediswho_command(char const *fmt, REDISSOCK **dissocket_p,
73                             rlm_rediswho_t *inst, REQUEST *request)
74 {
75         REDISSOCK *dissocket;
76         int result = 0;
77
78         if (!fmt) {
79                 return 0;
80         }
81
82         if (inst->redis_inst->redis_query(dissocket_p, inst->redis_inst,
83                                           fmt, request) < 0) {
84
85                 ERROR("rediswho_command: database query error in: '%s'", fmt);
86                 return -1;
87
88         }
89         dissocket = *dissocket_p;
90
91         switch (dissocket->reply->type) {
92         case REDIS_REPLY_INTEGER:
93                 DEBUG("rediswho_command: query response %lld\n",
94                       dissocket->reply->integer);
95                 if (dissocket->reply->integer > 0)
96                         result = dissocket->reply->integer;
97                 break;
98
99         case REDIS_REPLY_STATUS:
100         case REDIS_REPLY_STRING:
101                 DEBUG("rediswho_command: query response %s\n",
102                       dissocket->reply->str);
103                 break;
104
105         default:
106                 break;
107         }
108
109         (inst->redis_inst->redis_finish_query)(dissocket);
110
111         return result;
112 }
113
114 static int mod_instantiate(CONF_SECTION *conf, void *instance)
115 {
116         module_instance_t *modinst;
117         rlm_rediswho_t *inst = instance;
118
119         inst->xlat_name = cf_section_name2(conf);
120
121         if (!inst->xlat_name)
122                 inst->xlat_name = cf_section_name1(conf);
123
124         inst->cs = conf;
125
126         modinst = module_instantiate(cf_section_find("modules"),
127                                        inst->redis_instance_name);
128         if (!modinst) {
129                 ERROR("rediswho: failed to find module instance \"%s\"",
130                        inst->redis_instance_name);
131                 return -1;
132         }
133
134         if (strcmp(modinst->entry->name, "rlm_redis") != 0) {
135                 ERROR("rediswho: Module \"%s\""
136                        " is not an instance of the redis module",
137                        inst->redis_instance_name);
138                 return -1;
139         }
140
141         inst->redis_inst = (REDIS_INST *) modinst->insthandle;
142
143         return 0;
144 }
145
146 static int mod_accounting_all(REDISSOCK **dissocket_p,
147                               rlm_rediswho_t *inst, REQUEST *request)
148 {
149         int result;
150
151         result = rediswho_command(inst->insert, dissocket_p, inst, request);
152         if (result < 0) {
153                 return RLM_MODULE_FAIL;
154         }
155
156         /* Only trim if necessary */
157         if (inst->trim_count >= 0 && result > inst->trim_count) {
158                 if (rediswho_command(inst->trim, dissocket_p,
159                                      inst, request) < 0) {
160                         return RLM_MODULE_FAIL;
161                 }
162         }
163
164         if (rediswho_command(inst->expire, dissocket_p, inst, request) < 0) {
165                 return RLM_MODULE_FAIL;
166         }
167
168         return RLM_MODULE_OK;
169 }
170
171 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void * instance, REQUEST * request)
172 {
173         rlm_rcode_t rcode;
174         VALUE_PAIR * vp;
175         DICT_VALUE *dv;
176         CONF_SECTION *cs;
177         rlm_rediswho_t *inst = (rlm_rediswho_t *) instance;
178         REDISSOCK *dissocket;
179
180         vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY);
181         if (!vp) {
182                 RDEBUG("Could not find account status type in packet");
183                 return RLM_MODULE_NOOP;
184         }
185
186         dv = dict_valbyattr(vp->da->attr, vp->da->vendor, vp->vp_integer);
187         if (!dv) {
188                 RDEBUG("Unknown Acct-Status-Type %u", vp->vp_integer);
189                 return RLM_MODULE_NOOP;
190         }
191
192         cs = cf_section_sub_find(inst->cs, dv->name);
193         if (!cs) {
194                 RDEBUG("No subsection %s", dv->name);
195                 return RLM_MODULE_NOOP;
196         }
197
198         dissocket = fr_connection_get(inst->redis_inst->pool);
199         if (!dissocket) return RLM_MODULE_FAIL;
200
201         rcode = mod_accounting_all(&dissocket, inst, request);
202
203         if (dissocket) fr_connection_release(inst->redis_inst->pool, dissocket);
204
205         return rcode;
206 }
207
208 extern module_t rlm_rediswho;
209 module_t rlm_rediswho = {
210         .magic          = RLM_MODULE_INIT,
211         .name           = "rediswho",
212         .type           = RLM_TYPE_THREAD_SAFE,
213         .inst_size      = sizeof(rlm_rediswho_t),
214         .config         = module_config,
215         .instantiate    = mod_instantiate,
216         .methods = {
217                 [MOD_ACCOUNTING]        = mod_accounting
218         },
219 };