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