2892e7f5905355dd5e84d7d239029f33626e4625
[freeradius.git] / src / modules / rlm_realm / rlm_realm.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_realm.c
20  * @brief Parses NAIs and assigns requests to realms.
21  *
22  * @copyright 2000-2013  The FreeRADIUS server project
23  */
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28
29 #include "trustrouter.h"
30
31 #define  REALM_FORMAT_PREFIX   0
32 #define  REALM_FORMAT_SUFFIX   1
33
34 typedef struct rlm_realm_t {
35         int             format;
36         char const      *format_string;
37         char const      *delim;
38         bool            ignore_default;
39         bool            ignore_null;
40
41 #ifdef HAVE_TRUST_ROUTER_TR_DH_H
42         char const      *default_community;
43         char const      *rp_realm;
44         char const      *trust_router;
45         uint32_t        tr_port;
46 #endif
47 } rlm_realm_t;
48
49 static CONF_PARSER module_config[] = {
50         { "format", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t, format_string), "suffix" },
51         { "delimiter", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t, delim), "@" },
52         { "ignore_default", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_realm_t, ignore_default), "no" },
53         { "ignore_null", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_realm_t, ignore_null), "no" },
54 #ifdef HAVE_TRUST_ROUTER_TR_DH_H
55         { "default_community", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t,default_community),  "none" },
56         { "rp_realm", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t,rp_realm),  "none" },
57         { "trust_router", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_realm_t,trust_router),  "none" },
58         { "tr_port", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_realm_t,tr_port),  "0" },
59 #endif
60         CONF_PARSER_TERMINATOR
61 };
62
63 /*
64  *      Internal function to cut down on duplicated code.
65  *
66  *      Returns -1 on failure, 0 on no failure.  returnrealm
67  *      is NULL on don't proxy, realm otherwise.
68  */
69 static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm)
70 {
71         char *namebuf;
72         char *username;
73         char const *realmname = NULL;
74         char *ptr;
75         VALUE_PAIR *vp;
76         REALM *realm;
77
78         struct rlm_realm_t *inst = instance;
79
80         /* initiate returnrealm */
81         *returnrealm = NULL;
82
83         /*
84          *      If the request has a proxy entry, then it's a proxy
85          *      reply, and we're walking through the module list again.
86          *
87          *      In that case, don't bother trying to proxy the request
88          *      again.
89          *
90          *      Also, if there's no User-Name attribute, we can't
91          *      proxy it, either.
92          */
93         if ((!request->username)
94 #ifdef WITH_PROXY
95             || (request->proxy != NULL)
96 #endif
97             ) {
98
99                 RDEBUG2("Proxy reply, or no User-Name.  Ignoring");
100                 return RLM_MODULE_NOOP;
101         }
102
103         /*
104          *      Check for 'Realm' attribute.  If it exists, then we've proxied
105          *      it already ( via another rlm_realm instance ) and should return.
106          */
107
108         if (fr_pair_find_by_num(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL ) {
109                 RDEBUG2("Request already has destination realm set.  Ignoring");
110                 return RLM_MODULE_NOOP;
111         }
112
113         /*
114          *      We will be modifing this later, so we want our own copy
115          *      of it.
116          */
117         namebuf = talloc_typed_strdup(request,  request->username->vp_strvalue);
118         username = namebuf;
119
120         switch (inst->format) {
121         case REALM_FORMAT_SUFFIX:
122                 RDEBUG2("Checking for suffix after \"%c\"", inst->delim[0]);
123                 ptr = strrchr(username, inst->delim[0]);
124                 if (ptr) {
125                         *ptr = '\0';
126                         realmname = ptr + 1;
127                 }
128                 break;
129
130         case REALM_FORMAT_PREFIX:
131                 RDEBUG2("Checking for prefix before \"%c\"", inst->delim[0]);
132                 ptr = strchr(username, inst->delim[0]);
133                 if (ptr) {
134                         *ptr = '\0';
135                         ptr++;
136                         realmname = username;
137                         username = ptr;
138                 }
139                 break;
140
141         default:
142                 realmname = NULL;
143                 break;
144         }
145
146         /*
147          *      Print out excruciatingly descriptive debugging messages
148          *      for the people who find it too difficult to think about
149          *      what's going on.
150          */
151         if (realmname) {
152                 RDEBUG2("Looking up realm \"%s\" for User-Name = \"%s\"",
153                        realmname, request->username->vp_strvalue);
154         } else {
155                 if (inst->ignore_null ) {
156                         RDEBUG2("No '%c' in User-Name = \"%s\", skipping NULL due to config.",
157                         inst->delim[0], request->username->vp_strvalue);
158                         talloc_free(namebuf);
159                         return RLM_MODULE_NOOP;
160                 }
161                 RDEBUG2("No '%c' in User-Name = \"%s\", looking up realm NULL",
162                         inst->delim[0], request->username->vp_strvalue);
163         }
164
165         /*
166          *      Allow DEFAULT realms unless told not to.
167          */
168         realm = realm_find(realmname);
169
170 #ifdef HAVE_TRUST_ROUTER_TR_DH_H
171         /*
172          *      Try querying for the dynamic realm.
173          */
174         if (!realm && inst->trust_router) {
175                 realm = tr_query_realm(request, realmname, inst->default_community, inst->rp_realm, inst->trust_router, inst->tr_port);
176         } else {
177                 RDEBUG2("No trust router configured, skipping dynamic realm lookup");
178         }
179 #endif
180
181         if (!realm) {
182                 RDEBUG2("No such realm \"%s\"", (!realmname) ? "NULL" : realmname);
183                 talloc_free(namebuf);
184                 return RLM_MODULE_NOOP;
185         }
186
187         if (inst->ignore_default && (strcmp(realm->name, "DEFAULT")) == 0) {
188                 RDEBUG2("Found DEFAULT, but skipping due to config");
189                 talloc_free(namebuf);
190                 return RLM_MODULE_NOOP;
191         }
192
193         RDEBUG2("Found realm \"%s\"", realm->name);
194
195         /*
196          *      If we've been told to strip the realm off, then do so.
197          */
198         if (realm->strip_realm) {
199                 /*
200                  *      Create the Stripped-User-Name attribute, if it
201                  *      doesn't exist.
202                  *
203                  */
204                 if (request->username->da->attr != PW_STRIPPED_USER_NAME) {
205                         vp = radius_pair_create(request->packet, &request->packet->vps,
206                                                PW_STRIPPED_USER_NAME, 0);
207                         RDEBUG2("Adding Stripped-User-Name = \"%s\"", username);
208                 } else {
209                         vp = request->username;
210                         RDEBUG2("Setting Stripped-User-Name = \"%s\"", username);
211                 }
212
213                 fr_pair_value_strcpy(vp, username);
214                 request->username = vp;
215         }
216
217         /*
218          *      Add the realm name to the request.
219          *      If the realm is a regex, the use the realm as entered
220          *      by the user.  Otherwise, use the configured realm name,
221          *      as realm name comparison is case insensitive.  We want
222          *      to use the configured name, rather than what the user
223          *      entered.
224          */
225         if (realm->name[0] != '~') realmname = realm->name;
226
227         /*
228          *      A NULL realmname is allowed.
229          */
230         if (realmname) {
231                 pair_make_request("Realm", realmname, T_OP_EQ);
232                 RDEBUG2("Adding Realm = \"%s\"", realmname);
233         }
234
235         talloc_free(namebuf);
236         username = NULL;
237
238         /*
239          *      Figure out what to do with the request.
240          */
241         switch (request->packet->code) {
242         default:
243                 RDEBUG2("Unknown packet code %d\n",
244                        request->packet->code);
245                 return RLM_MODULE_NOOP;
246
247                 /*
248                  *      Perhaps accounting proxying was turned off.
249                  */
250         case PW_CODE_ACCOUNTING_REQUEST:
251                 if (!realm->acct_pool) {
252                         RDEBUG2("Accounting realm is LOCAL");
253                         return RLM_MODULE_OK;
254                 }
255                 break;
256
257                 /*
258                  *      Perhaps authentication proxying was turned off.
259                  */
260         case PW_CODE_ACCESS_REQUEST:
261                 if (!realm->auth_pool) {
262                         RDEBUG2("Authentication realm is LOCAL");
263                         return RLM_MODULE_OK;
264                 }
265                 break;
266         }
267
268 #ifdef WITH_PROXY
269         RDEBUG2("Proxying request from user %s to realm %s",
270                request->username->vp_strvalue, realm->name);
271
272         /*
273          *      Skip additional checks if it's not an accounting
274          *      request.
275          */
276         if (request->packet->code != PW_CODE_ACCOUNTING_REQUEST) {
277                 *returnrealm = realm;
278                 return RLM_MODULE_UPDATED;
279         }
280
281         /*
282          *      FIXME: Each server should have a unique server key,
283          *      and put it in the accounting packet.  Every server
284          *      should know about the keys, and NOT proxy requests to
285          *      a server with key X if the packet already contains key
286          *      X.
287          */
288
289         /*
290          *      If this request has arrived from another freeradius server
291          *      that has already proxied the request, we don't need to do
292          *      it again.
293          */
294         vp = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_PROXIED_TO, 0, TAG_ANY);
295         if (vp && (request->packet->src_ipaddr.af == AF_INET)) {
296                 int i;
297                 fr_ipaddr_t my_ipaddr;
298
299                 my_ipaddr.af = AF_INET;
300                 my_ipaddr.prefix = 32;
301                 my_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
302
303                 /*
304                  *      Loop over the home accounting servers for this
305                  *      realm.  If one of them has the same IP as the
306                  *      FreeRADIUS-Proxied-To attribute, then the
307                  *      packet has already been sent there.  Don't
308                  *      send it there again.
309                  */
310                 for (i = 0; i < realm->acct_pool->num_home_servers; i++) {
311                         if (realm->acct_pool->servers[i]->ipaddr.af == AF_UNSPEC) continue;
312
313                         if (fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &my_ipaddr) == 0) {
314                                 RDEBUG2("Suppressing proxy due to FreeRADIUS-Proxied-To");
315                                 return RLM_MODULE_OK;
316                         }
317                 }
318
319                 /*
320                  *      See detail_recv() in src/main/listen.c for the
321                  *      additional checks.
322                  */
323 #ifdef WITH_DETAIL
324         } else if ((request->listener->type == RAD_LISTEN_DETAIL) &&
325                    !fr_inaddr_any(&request->packet->src_ipaddr)) {
326                 int i;
327
328                 /*
329                  *      Loop over the home accounting servers for this
330                  *      realm.  If one of them has the same IP as the
331                  *      FreeRADIUS-Proxied-To attribute, then the
332                  *      packet has already been sent there.  Don't
333                  *      send it there again.
334                  */
335                 for (i = 0; i < realm->acct_pool->num_home_servers; i++) {
336                         if ((fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr,
337                                              &request->packet->src_ipaddr) == 0) &&
338                             (realm->acct_pool->servers[i]->port == request->packet->src_port)) {
339                                 RDEBUG2("Suppressing proxy because packet was already sent to a server in that realm");
340                                 return RLM_MODULE_OK;
341                         }
342                 }
343 #endif  /* WITH_DETAIL */
344         }
345 #endif  /* WITH_PROXY */
346
347         /*
348          *      We got this far, which means we have a realm, set returnrealm
349          */
350         *returnrealm = realm;
351
352         return RLM_MODULE_UPDATED;
353 }
354
355 /*
356  *  Perform the realm module instantiation.  Configuration info is
357  *  stored in *instance for later use.
358  */
359
360 static int mod_instantiate(CONF_SECTION *conf, void *instance)
361 {
362         struct rlm_realm_t *inst = instance;
363
364         if (strcasecmp(inst->format_string, "suffix") == 0) {
365              inst->format = REALM_FORMAT_SUFFIX;
366
367         } else if (strcasecmp(inst->format_string, "prefix") == 0) {
368              inst->format = REALM_FORMAT_PREFIX;
369
370         } else {
371                 cf_log_err_cs(conf, "Invalid value \"%s\" for format",
372                               inst->format_string);
373              return -1;
374         }
375
376         if (cf_new_escape && (strcmp(inst->delim, "\\\\") == 0)) {
377                 /* it's OK */
378         } else
379
380         if (strlen(inst->delim) != 1) {
381                 cf_log_err_cs(conf, "Invalid value \"%s\" for delimiter",
382                               inst->delim);
383              return -1;
384         }
385
386 #ifdef HAVE_TRUST_ROUTER_TR_DH_H
387         /* initialize the trust router integration code */
388         if (strcmp(inst->trust_router, "none") != 0) {
389                 if (!tr_init()) return -1;
390         } else {
391                 rad_const_free(inst->trust_router);
392                 inst->trust_router = NULL;
393         }
394 #endif
395
396         return 0;
397 }
398
399
400 /*
401  *  Examine a request for a username with an realm, and if it
402  *  corresponds to something in the realms file, set that realm as
403  *  Proxy-To.
404  *
405  *  This should very nearly duplicate the old proxy_send() code
406  */
407 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
408 {
409         rlm_rcode_t rcode;
410         REALM *realm;
411
412         /*
413          *      Check if we've got to proxy the request.
414          *      If not, return without adding a Proxy-To-Realm
415          *      attribute.
416          */
417         rcode = check_for_realm(instance, request, &realm);
418         if (rcode != RLM_MODULE_UPDATED) return rcode;
419         if (!realm) return RLM_MODULE_NOOP;
420
421         /*
422          *      Maybe add a Proxy-To-Realm attribute to the request.
423          */
424         RDEBUG2("Preparing to proxy authentication request to realm \"%s\"\n",
425                realm->name);
426         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
427
428         return RLM_MODULE_UPDATED; /* try the next module */
429 }
430
431 /*
432  * This does the exact same thing as the mod_authorize, it's just called
433  * differently.
434  */
435 static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request)
436 {
437         int rcode;
438         REALM *realm;
439
440         if (!request->username) {
441                 return RLM_MODULE_NOOP;
442         }
443
444         /*
445          *      Check if we've got to proxy the request.
446          *      If not, return without adding a Proxy-To-Realm
447          *      attribute.
448          */
449         rcode = check_for_realm(instance, request, &realm);
450         if (rcode != RLM_MODULE_UPDATED) return rcode;
451         if (!realm) return RLM_MODULE_NOOP;
452
453         /*
454          *      Maybe add a Proxy-To-Realm attribute to the request.
455          */
456         RDEBUG2("Preparing to proxy accounting request to realm \"%s\"\n",
457                realm->name);
458         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
459
460         return RLM_MODULE_UPDATED; /* try the next module */
461 }
462
463 #ifdef WITH_COA
464 /*
465  *      CoA realms via Operator-Name.  Because the realm isn't in a
466  *      User-Name, concepts like "prefix" and "suffix' don't matter.
467  */
468 static rlm_rcode_t mod_realm_recv_coa(UNUSED void *instance, REQUEST *request)
469 {
470         VALUE_PAIR *vp;
471         REALM *realm;
472
473         if (fr_pair_find_by_num(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL) {
474                 RDEBUG2("Request already has destination realm set.  Ignoring");
475                 return RLM_MODULE_NOOP;
476         }
477
478         vp = fr_pair_find_by_num(request->packet->vps, PW_OPERATOR_NAME, 0, TAG_ANY);
479         if (!vp) return RLM_MODULE_NOOP;
480
481         /*
482          *      Catch the case of broken dictionaries.
483          */
484         if (vp->da->type != PW_TYPE_STRING) return RLM_MODULE_NOOP;
485
486         /*
487          *      The string is too short.
488          */
489         if (vp->vp_length == 1) return RLM_MODULE_NOOP;
490
491         /*
492          *      '1' means "the rest of the string is a realm"
493          */
494         if (vp->vp_strvalue[0] != '1') return RLM_MODULE_NOOP;
495
496         realm = realm_find(vp->vp_strvalue + 1);
497         if (!realm) return RLM_MODULE_NOTFOUND;
498
499         if (!realm->coa_pool) {
500                 RDEBUG2("CoA realm is LOCAL");
501                 return RLM_MODULE_OK;
502         }
503
504         /*
505          *      Maybe add a Proxy-To-Realm attribute to the request.
506          */
507         RDEBUG2("Preparing to proxy authentication request to realm \"%s\"\n",
508                realm->name);
509         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
510
511         return RLM_MODULE_UPDATED; /* try the next module */
512 }
513 #endif
514
515 /* globally exported name */
516 extern module_t rlm_realm;
517 module_t rlm_realm = {
518         .magic          = RLM_MODULE_INIT,
519         .name           = "realm",
520         .type           = RLM_TYPE_HUP_SAFE,
521         .inst_size      = sizeof(struct rlm_realm_t),
522         .config         = module_config,
523         .instantiate    = mod_instantiate,
524         .methods = {
525                 [MOD_AUTHORIZE]         = mod_authorize,
526                 [MOD_PREACCT]           = mod_preacct,
527 #ifdef WITH_COA
528                 [MOD_RECV_COA]          = mod_realm_recv_coa
529 #endif
530         },
531 };
532