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