09f9f0fbdb98be8b06767381626de624833df0f0
[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 (fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &my_ipaddr) == 0) {
312                                 RDEBUG2("Suppressing proxy due to FreeRADIUS-Proxied-To");
313                                 return RLM_MODULE_OK;
314                         }
315                 }
316
317                 /*
318                  *      See detail_recv() in src/main/listen.c for the
319                  *      additional checks.
320                  */
321 #ifdef WITH_DETAIL
322         } else if ((request->listener->type == RAD_LISTEN_DETAIL) &&
323                    !fr_inaddr_any(&request->packet->src_ipaddr)) {
324                 int i;
325
326                 /*
327                  *      Loop over the home accounting servers for this
328                  *      realm.  If one of them has the same IP as the
329                  *      FreeRADIUS-Proxied-To attribute, then the
330                  *      packet has already been sent there.  Don't
331                  *      send it there again.
332                  */
333                 for (i = 0; i < realm->acct_pool->num_home_servers; i++) {
334                         if ((fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr,
335                                              &request->packet->src_ipaddr) == 0) &&
336                             (realm->acct_pool->servers[i]->port == request->packet->src_port)) {
337                                 RDEBUG2("Suppressing proxy because packet was already sent to a server in that realm");
338                                 return RLM_MODULE_OK;
339                         }
340                 }
341 #endif  /* WITH_DETAIL */
342         }
343 #endif  /* WITH_PROXY */
344
345         /*
346          *      We got this far, which means we have a realm, set returnrealm
347          */
348         *returnrealm = realm;
349
350         return RLM_MODULE_UPDATED;
351 }
352
353 /*
354  *  Perform the realm module instantiation.  Configuration info is
355  *  stored in *instance for later use.
356  */
357
358 static int mod_instantiate(CONF_SECTION *conf, void *instance)
359 {
360         struct rlm_realm_t *inst = instance;
361
362         if (strcasecmp(inst->format_string, "suffix") == 0) {
363              inst->format = REALM_FORMAT_SUFFIX;
364
365         } else if (strcasecmp(inst->format_string, "prefix") == 0) {
366              inst->format = REALM_FORMAT_PREFIX;
367
368         } else {
369                 cf_log_err_cs(conf, "Invalid value \"%s\" for format",
370                               inst->format_string);
371              return -1;
372         }
373
374         if (cf_new_escape && (strcmp(inst->delim, "\\\\") == 0)) {
375                 /* it's OK */
376         } else
377
378         if (strlen(inst->delim) != 1) {
379                 cf_log_err_cs(conf, "Invalid value \"%s\" for delimiter",
380                               inst->delim);
381              return -1;
382         }
383
384 #ifdef HAVE_TRUST_ROUTER_TR_DH_H
385         /* initialize the trust router integration code */
386         if (strcmp(inst->trust_router, "none") != 0) {
387                 if (!tr_init()) return -1;
388         } else {
389                 rad_const_free(inst->trust_router);
390                 inst->trust_router = NULL;
391         }
392 #endif
393
394         return 0;
395 }
396
397
398 /*
399  *  Examine a request for a username with an realm, and if it
400  *  corresponds to something in the realms file, set that realm as
401  *  Proxy-To.
402  *
403  *  This should very nearly duplicate the old proxy_send() code
404  */
405 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
406 {
407         rlm_rcode_t rcode;
408         REALM *realm;
409
410         /*
411          *      Check if we've got to proxy the request.
412          *      If not, return without adding a Proxy-To-Realm
413          *      attribute.
414          */
415         rcode = check_for_realm(instance, request, &realm);
416         if (rcode != RLM_MODULE_UPDATED) return rcode;
417         if (!realm) return RLM_MODULE_NOOP;
418
419         /*
420          *      Maybe add a Proxy-To-Realm attribute to the request.
421          */
422         RDEBUG2("Preparing to proxy authentication request to realm \"%s\"\n",
423                realm->name);
424         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
425
426         return RLM_MODULE_UPDATED; /* try the next module */
427 }
428
429 /*
430  * This does the exact same thing as the mod_authorize, it's just called
431  * differently.
432  */
433 static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request)
434 {
435         int rcode;
436         REALM *realm;
437
438         if (!request->username) {
439                 return RLM_MODULE_NOOP;
440         }
441
442         /*
443          *      Check if we've got to proxy the request.
444          *      If not, return without adding a Proxy-To-Realm
445          *      attribute.
446          */
447         rcode = check_for_realm(instance, request, &realm);
448         if (rcode != RLM_MODULE_UPDATED) return rcode;
449         if (!realm) return RLM_MODULE_NOOP;
450
451         /*
452          *      Maybe add a Proxy-To-Realm attribute to the request.
453          */
454         RDEBUG2("Preparing to proxy accounting request to realm \"%s\"\n",
455                realm->name);
456         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
457
458         return RLM_MODULE_UPDATED; /* try the next module */
459 }
460
461 #ifdef WITH_COA
462 /*
463  *      CoA realms via Operator-Name.  Because the realm isn't in a
464  *      User-Name, concepts like "prefix" and "suffix' don't matter.
465  */
466 static rlm_rcode_t mod_realm_recv_coa(UNUSED void *instance, REQUEST *request)
467 {
468         VALUE_PAIR *vp;
469         REALM *realm;
470
471         if (fr_pair_find_by_num(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL) {
472                 RDEBUG2("Request already has destination realm set.  Ignoring");
473                 return RLM_MODULE_NOOP;
474         }
475
476         vp = fr_pair_find_by_num(request->packet->vps, PW_OPERATOR_NAME, 0, TAG_ANY);
477         if (!vp) return RLM_MODULE_NOOP;
478
479         /*
480          *      Catch the case of broken dictionaries.
481          */
482         if (vp->da->type != PW_TYPE_STRING) return RLM_MODULE_NOOP;
483
484         /*
485          *      The string is too short.
486          */
487         if (vp->vp_length == 1) return RLM_MODULE_NOOP;
488
489         /*
490          *      '1' means "the rest of the string is a realm"
491          */
492         if (vp->vp_strvalue[0] != '1') return RLM_MODULE_NOOP;
493
494         realm = realm_find(vp->vp_strvalue + 1);
495         if (!realm) return RLM_MODULE_NOTFOUND;
496
497         if (!realm->coa_pool) {
498                 RDEBUG2("CoA realm is LOCAL");
499                 return RLM_MODULE_OK;
500         }
501
502         /*
503          *      Maybe add a Proxy-To-Realm attribute to the request.
504          */
505         RDEBUG2("Preparing to proxy authentication request to realm \"%s\"\n",
506                realm->name);
507         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
508
509         return RLM_MODULE_UPDATED; /* try the next module */
510 }
511 #endif
512
513 /* globally exported name */
514 extern module_t rlm_realm;
515 module_t rlm_realm = {
516         .magic          = RLM_MODULE_INIT,
517         .name           = "realm",
518         .type           = RLM_TYPE_HUP_SAFE,
519         .inst_size      = sizeof(struct rlm_realm_t),
520         .config         = module_config,
521         .instantiate    = mod_instantiate,
522         .methods = {
523                 [MOD_AUTHORIZE]         = mod_authorize,
524                 [MOD_PREACCT]           = mod_preacct,
525 #ifdef WITH_COA
526                 [MOD_RECV_COA]          = mod_realm_recv_coa
527 #endif
528         },
529 };
530