349809521e16dcfd9abac5cb94bbe2ceafd82b66
[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 #endif
177
178         if (!realm) {
179                 RDEBUG2("No such realm \"%s\"", (!realmname) ? "NULL" : realmname);
180                 talloc_free(namebuf);
181                 return RLM_MODULE_NOOP;
182         }
183
184         if (inst->ignore_default && (strcmp(realm->name, "DEFAULT")) == 0) {
185                 RDEBUG2("Found DEFAULT, but skipping due to config");
186                 talloc_free(namebuf);
187                 return RLM_MODULE_NOOP;
188         }
189
190         RDEBUG2("Found realm \"%s\"", realm->name);
191
192         /*
193          *      If we've been told to strip the realm off, then do so.
194          */
195         if (realm->strip_realm) {
196                 /*
197                  *      Create the Stripped-User-Name attribute, if it
198                  *      doesn't exist.
199                  *
200                  */
201                 if (request->username->da->attr != PW_STRIPPED_USER_NAME) {
202                         vp = radius_pair_create(request->packet, &request->packet->vps,
203                                                PW_STRIPPED_USER_NAME, 0);
204                         RDEBUG2("Adding Stripped-User-Name = \"%s\"", username);
205                 } else {
206                         vp = request->username;
207                         RDEBUG2("Setting Stripped-User-Name = \"%s\"", username);
208                 }
209
210                 fr_pair_value_strcpy(vp, username);
211                 request->username = vp;
212         }
213
214         /*
215          *      Add the realm name to the request.
216          *      If the realm is a regex, the use the realm as entered
217          *      by the user.  Otherwise, use the configured realm name,
218          *      as realm name comparison is case insensitive.  We want
219          *      to use the configured name, rather than what the user
220          *      entered.
221          */
222         if (realm->name[0] != '~') realmname = realm->name;
223         pair_make_request("Realm", realmname, T_OP_EQ);
224         RDEBUG2("Adding Realm = \"%s\"", realmname);
225
226         talloc_free(namebuf);
227         username = NULL;
228
229         /*
230          *      Figure out what to do with the request.
231          */
232         switch (request->packet->code) {
233         default:
234                 RDEBUG2("Unknown packet code %d\n",
235                        request->packet->code);
236                 return RLM_MODULE_NOOP;
237
238                 /*
239                  *      Perhaps accounting proxying was turned off.
240                  */
241         case PW_CODE_ACCOUNTING_REQUEST:
242                 if (!realm->acct_pool) {
243                         RDEBUG2("Accounting realm is LOCAL");
244                         return RLM_MODULE_OK;
245                 }
246                 break;
247
248                 /*
249                  *      Perhaps authentication proxying was turned off.
250                  */
251         case PW_CODE_ACCESS_REQUEST:
252                 if (!realm->auth_pool) {
253                         RDEBUG2("Authentication realm is LOCAL");
254                         return RLM_MODULE_OK;
255                 }
256                 break;
257         }
258
259 #ifdef WITH_PROXY
260         RDEBUG2("Proxying request from user %s to realm %s",
261                request->username->vp_strvalue, realm->name);
262
263         /*
264          *      Skip additional checks if it's not an accounting
265          *      request.
266          */
267         if (request->packet->code != PW_CODE_ACCOUNTING_REQUEST) {
268                 *returnrealm = realm;
269                 return RLM_MODULE_UPDATED;
270         }
271
272         /*
273          *      FIXME: Each server should have a unique server key,
274          *      and put it in the accounting packet.  Every server
275          *      should know about the keys, and NOT proxy requests to
276          *      a server with key X if the packet already contains key
277          *      X.
278          */
279
280         /*
281          *      If this request has arrived from another freeradius server
282          *      that has already proxied the request, we don't need to do
283          *      it again.
284          */
285         vp = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_PROXIED_TO, 0, TAG_ANY);
286         if (vp && (request->packet->src_ipaddr.af == AF_INET)) {
287                 int i;
288                 fr_ipaddr_t my_ipaddr;
289
290                 my_ipaddr.af = AF_INET;
291                 my_ipaddr.prefix = 32;
292                 my_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
293
294                 /*
295                  *      Loop over the home accounting servers for this
296                  *      realm.  If one of them has the same IP as the
297                  *      FreeRADIUS-Proxied-To attribute, then the
298                  *      packet has already been sent there.  Don't
299                  *      send it there again.
300                  */
301                 for (i = 0; i < realm->acct_pool->num_home_servers; i++) {
302                         if (fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr, &my_ipaddr) == 0) {
303                                 RDEBUG2("Suppressing proxy due to FreeRADIUS-Proxied-To");
304                                 return RLM_MODULE_OK;
305                         }
306                 }
307
308                 /*
309                  *      See detail_recv() in src/main/listen.c for the
310                  *      additional checks.
311                  */
312 #ifdef WITH_DETAIL
313         } else if ((request->listener->type == RAD_LISTEN_DETAIL) &&
314                    !fr_inaddr_any(&request->packet->src_ipaddr)) {
315                 int i;
316
317                 /*
318                  *      Loop over the home accounting servers for this
319                  *      realm.  If one of them has the same IP as the
320                  *      FreeRADIUS-Proxied-To attribute, then the
321                  *      packet has already been sent there.  Don't
322                  *      send it there again.
323                  */
324                 for (i = 0; i < realm->acct_pool->num_home_servers; i++) {
325                         if ((fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr,
326                                              &request->packet->src_ipaddr) == 0) &&
327                             (realm->acct_pool->servers[i]->port == request->packet->src_port)) {
328                                 RDEBUG2("Suppressing proxy because packet was already sent to a server in that realm");
329                                 return RLM_MODULE_OK;
330                         }
331                 }
332 #endif  /* WITH_DETAIL */
333         }
334 #endif  /* WITH_PROXY */
335
336         /*
337          *      We got this far, which means we have a realm, set returnrealm
338          */
339         *returnrealm = realm;
340
341         return RLM_MODULE_UPDATED;
342 }
343
344 /*
345  *  Perform the realm module instantiation.  Configuration info is
346  *  stored in *instance for later use.
347  */
348
349 static int mod_instantiate(CONF_SECTION *conf, void *instance)
350 {
351         struct rlm_realm_t *inst = instance;
352
353         if (strcasecmp(inst->format_string, "suffix") == 0) {
354              inst->format = REALM_FORMAT_SUFFIX;
355
356         } else if (strcasecmp(inst->format_string, "prefix") == 0) {
357              inst->format = REALM_FORMAT_PREFIX;
358
359         } else {
360                 cf_log_err_cs(conf, "Invalid value \"%s\" for format",
361                               inst->format_string);
362              return -1;
363         }
364
365         if (cf_new_escape && (strcmp(inst->delim, "\\\\") == 0)) {
366                 /* it's OK */
367         } else
368
369         if (strlen(inst->delim) != 1) {
370                 cf_log_err_cs(conf, "Invalid value \"%s\" for delimiter",
371                               inst->delim);
372              return -1;
373         }
374
375 #ifdef HAVE_TRUST_ROUTER_TR_DH_H
376         /* initialize the trust router integration code */
377         if (strcmp(inst->trust_router, "none") != 0) {
378                 if (!tr_init()) return -1;
379         } else {
380                 rad_const_free(&inst->trust_router);
381         }
382 #endif
383
384         return 0;
385 }
386
387
388 /*
389  *  Examine a request for a username with an realm, and if it
390  *  corresponds to something in the realms file, set that realm as
391  *  Proxy-To.
392  *
393  *  This should very nearly duplicate the old proxy_send() code
394  */
395 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
396 {
397         rlm_rcode_t rcode;
398         REALM *realm;
399
400         /*
401          *      Check if we've got to proxy the request.
402          *      If not, return without adding a Proxy-To-Realm
403          *      attribute.
404          */
405         rcode = check_for_realm(instance, request, &realm);
406         if (rcode != RLM_MODULE_UPDATED) return rcode;
407         if (!realm) return RLM_MODULE_NOOP;
408
409         /*
410          *      Maybe add a Proxy-To-Realm attribute to the request.
411          */
412         RDEBUG2("Preparing to proxy authentication request to realm \"%s\"\n",
413                realm->name);
414         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
415
416         return RLM_MODULE_UPDATED; /* try the next module */
417 }
418
419 /*
420  * This does the exact same thing as the mod_authorize, it's just called
421  * differently.
422  */
423 static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request)
424 {
425         int rcode;
426         REALM *realm;
427
428         if (!request->username) {
429                 return RLM_MODULE_NOOP;
430         }
431
432         /*
433          *      Check if we've got to proxy the request.
434          *      If not, return without adding a Proxy-To-Realm
435          *      attribute.
436          */
437         rcode = check_for_realm(instance, request, &realm);
438         if (rcode != RLM_MODULE_UPDATED) return rcode;
439         if (!realm) return RLM_MODULE_NOOP;
440
441         /*
442          *      Maybe add a Proxy-To-Realm attribute to the request.
443          */
444         RDEBUG2("Preparing to proxy accounting request to realm \"%s\"\n",
445                realm->name);
446         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
447
448         return RLM_MODULE_UPDATED; /* try the next module */
449 }
450
451 #ifdef WITH_COA
452 /*
453  *      CoA realms via Operator-Name.  Because the realm isn't in a
454  *      User-Name, concepts like "prefix" and "suffix' don't matter.
455  */
456 static rlm_rcode_t mod_realm_recv_coa(UNUSED void *instance, REQUEST *request)
457 {
458         VALUE_PAIR *vp;
459         REALM *realm;
460
461         if (fr_pair_find_by_num(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL) {
462                 RDEBUG2("Request already has destination realm set.  Ignoring");
463                 return RLM_MODULE_NOOP;
464         }
465
466         vp = fr_pair_find_by_num(request->packet->vps, PW_OPERATOR_NAME, 0, TAG_ANY);
467         if (!vp) return RLM_MODULE_NOOP;
468
469         /*
470          *      Catch the case of broken dictionaries.
471          */
472         if (vp->da->type != PW_TYPE_STRING) return RLM_MODULE_NOOP;
473
474         /*
475          *      The string is too short.
476          */
477         if (vp->vp_length == 1) return RLM_MODULE_NOOP;
478
479         /*
480          *      '1' means "the rest of the string is a realm"
481          */
482         if (vp->vp_strvalue[0] != '1') return RLM_MODULE_NOOP;
483
484         realm = realm_find(vp->vp_strvalue + 1);
485         if (!realm) return RLM_MODULE_NOTFOUND;
486
487         if (!realm->coa_pool) {
488                 RDEBUG2("CoA realm is LOCAL");
489                 return RLM_MODULE_OK;
490         }
491
492         /*
493          *      Maybe add a Proxy-To-Realm attribute to the request.
494          */
495         RDEBUG2("Preparing to proxy authentication request to realm \"%s\"\n",
496                realm->name);
497         pair_make_config("Proxy-To-Realm", realm->name, T_OP_EQ);
498
499         return RLM_MODULE_UPDATED; /* try the next module */
500 }
501 #endif
502
503 /* globally exported name */
504 extern module_t rlm_realm;
505 module_t rlm_realm = {
506         .magic          = RLM_MODULE_INIT,
507         .name           = "realm",
508         .type           = RLM_TYPE_HUP_SAFE,
509         .inst_size      = sizeof(struct rlm_realm_t),
510         .config         = module_config,
511         .instantiate    = mod_instantiate,
512         .methods = {
513                 [MOD_AUTHORIZE]         = mod_authorize,
514                 [MOD_PREACCT]           = mod_preacct,
515 #ifdef WITH_COA
516                 [MOD_RECV_COA]          = mod_realm_recv_coa
517 #endif
518         },
519 };
520