Make realm comparisons case insensitive
[freeradius.git] / src / main / files.c
1 /*
2  * files.c      Read config files into memory.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Copyright 2000  The FreeRADIUS server project
21  * Copyright 2000  Miquel van Smoorenburg <miquels@cistron.nl>
22  * Copyright 2000  Alan DeKok <aland@ox.org>
23  */
24
25 static const char rcsid[] = "$Id$";
26
27 #include "autoconf.h"
28 #include "libradius.h"
29
30 #include <sys/stat.h>
31
32 #if HAVE_NETINET_IN_H
33 #       include <netinet/in.h>
34 #endif
35
36 #include <stdlib.h>
37 #include <string.h>
38 #include <netdb.h>
39 #include <ctype.h>
40 #include <fcntl.h>
41
42 #include "radiusd.h"
43
44 extern int proxy_dead_time;
45
46 REALM *realms = NULL;
47
48 /*
49  *      Free a PAIR_LIST
50  */
51 void pairlist_free(PAIR_LIST **pl)
52 {
53         PAIR_LIST *p, *next;
54
55         for (p = *pl; p; p = next) {
56                 if (p->name) free(p->name);
57                 if (p->check) pairfree(&p->check);
58                 if (p->reply) pairfree(&p->reply);
59                 next = p->next;
60                 free(p);
61         }
62         *pl = NULL;
63 }
64
65
66 /*
67  *      Fixup a check line.
68  *      If User-Password or Crypt-Password is set, but there is no
69  *      Auth-Type, add one (kludge!).
70  */
71 static void auth_type_fixup(VALUE_PAIR **check)
72 {
73         VALUE_PAIR *vp;
74         VALUE_PAIR *c = NULL;
75         int n = 0;
76
77         /*
78          *      See if a password is present. Return right away
79          *      if we see Auth-Type.
80          */
81         for (vp = *check; vp; vp = vp->next) {
82                 if (vp->attribute == PW_AUTHTYPE)
83                         return;
84                 if (vp->attribute == PW_PASSWORD) {
85                         c = vp;
86                         n = PW_AUTHTYPE_LOCAL;
87                 }
88                 if (vp->attribute == PW_CRYPT_PASSWORD) {
89                         c = vp;
90                         n = PW_AUTHTYPE_CRYPT;
91                 }
92         }
93
94         if (c == NULL)
95                 return;
96
97         /*
98          *      Add an Auth-Type attribute.
99          *      
100          */
101         if ((vp = paircreate(PW_AUTHTYPE, PW_TYPE_INTEGER)) == NULL) {
102                 radlog(L_CONS|L_ERR, "no memory");
103                 exit(1);
104         }
105         vp->lvalue = n;
106         vp->operator = T_OP_ADD;
107         strcpy(vp->strvalue, "Local");
108
109         vp->next = *check;
110         *check = vp;
111
112         for(vp = *check; vp; vp = vp->next) {
113                 DEBUG2("  auth_type_fixup: %s [%d]", vp->name, vp->attribute);
114         }
115
116 }
117
118
119 #define FIND_MODE_NAME  0
120 #define FIND_MODE_REPLY 1
121
122 /*
123  *      Read the users, huntgroups or hints file.
124  *      Return a PAIR_LIST.
125  */
126 int pairlist_read(const char *file, PAIR_LIST **list, int complain)
127 {
128         FILE *fp;
129         int mode = FIND_MODE_NAME;
130         char entry[256];
131         char buffer[256];
132         char *ptr, *s;
133         VALUE_PAIR *check_tmp;
134         VALUE_PAIR *reply_tmp;
135         PAIR_LIST *pl = NULL, *last = NULL, *t;
136         int lineno = 0;
137         int old_lineno = 0;
138         LRAD_TOKEN parsecode;
139         char newfile[8192];
140
141         /*
142          *      Open the file.  The error message should be a little
143          *      more useful...
144          */
145         if ((fp = fopen(file, "r")) == NULL) {
146                 if (!complain) 
147                         return -1;
148                 radlog(L_CONS|L_ERR, "Couldn't open %s for reading: %s",
149                                 file, strerror(errno));
150                 return -1;
151         }
152
153         parsecode = T_EOL;
154         /*
155          *      Read the entire file into memory for speed.
156          */
157         while(fgets(buffer, sizeof(buffer), fp) != NULL) {
158                 lineno++;
159                 if (!feof(fp) && (strchr(buffer, '\n') == NULL)) {
160                         radlog(L_ERR, "%s[%d]: line too long", file, lineno);
161                         pairlist_free(&pl);
162                         return -1;
163                 }
164                 if (buffer[0] == '#' || buffer[0] == '\n') continue;
165 parse_again:
166                 if(mode == FIND_MODE_NAME) {
167                         /*
168                          *      Find the entry starting with the users name
169                          */
170                         if (isspace((int) buffer[0]))  {
171                                 if (parsecode != T_EOL) {
172                                         radlog(L_ERR|L_CONS,
173                                                         "%s[%d]: Unexpected trailing comma for entry %s",
174                                                         file, lineno, entry);
175                                         fclose(fp);
176                                         return -1;
177                                 }
178                                 continue;
179                         }
180
181                         ptr = buffer;
182                         getword(&ptr, entry, sizeof(entry));
183
184                         /*
185                          *      Include another file if we see
186                          *      $INCLUDE filename
187                          */
188                         if (strcasecmp(entry, "$include") == 0) {
189                                 while(isspace((int) *ptr))
190                                         ptr++;
191                                 s = ptr;
192                                 while (!isspace((int) *ptr))
193                                         ptr++;
194                                 *ptr = 0;
195
196                                 /*
197                                  *      If it's an absolute pathname,
198                                  *      then use it verbatim.
199                                  *
200                                  *      If not, then make the $include
201                                  *      files *relative* to the current
202                                  *      file.
203                                  */
204                                 if (*s != '/') {
205                                         strNcpy(newfile, file,
206                                                 sizeof(newfile));
207                                         ptr = strrchr(newfile, '/');
208                                         strcpy(ptr + 1, s);
209                                         s = newfile;
210                                 }
211
212                                 t = NULL;
213                                 if (pairlist_read(s, &t, 0) != 0) {
214                                         pairlist_free(&pl);
215                                         radlog(L_ERR|L_CONS,
216                                                         "%s[%d]: Could not open included file %s: %s",
217                                                         file, lineno, s, strerror(errno));
218                                         fclose(fp);
219                                 return -1;
220                                 }
221                                 if (last)
222                                         last->next = t;
223                                 else
224                                         pl = t;
225                                 last = t;
226                                 while (last && last->next)
227                                         last = last->next;
228                                 continue;
229                         }
230
231                         /*
232                          *      Parse the check values
233                          */
234                         check_tmp = NULL;
235                         reply_tmp = NULL;
236                         old_lineno = lineno;
237                         parsecode = userparse(ptr, &check_tmp);
238                         if (parsecode == T_INVALID) {
239                                 pairlist_free(&pl);
240                                 radlog(L_ERR|L_CONS,
241                                 "%s[%d]: Parse error (check) for entry %s: %s",
242                                         file, lineno, entry, librad_errstr);
243                                 fclose(fp);
244                                 return -1;
245                         } else if (parsecode == T_COMMA) {
246                                 radlog(L_ERR|L_CONS,
247                                                 "%s[%d]: Unexpected trailing comma in check item list for entry %s",
248                                                 file, lineno, entry);
249                                 fclose(fp);
250                                 return -1;
251                         }
252                         mode = FIND_MODE_REPLY;
253                         parsecode = T_COMMA;
254                 }
255                 else {
256                         if(*buffer == ' ' || *buffer == '\t') {
257                                 if (parsecode != T_COMMA) {
258                                         radlog(L_ERR|L_CONS,
259                                                         "%s[%d]: Syntax error: Previous line is missing a trailing comma for entry %s",
260                                                         file, lineno, entry);
261                                         fclose(fp);
262                                         return -1;
263                                 }
264
265                                 /*
266                                  *      Parse the reply values
267                                  */
268                                 parsecode = userparse(buffer, &reply_tmp);
269                                 /* valid tokens are 1 or greater */
270                                 if (parsecode < 1) {
271                                         pairlist_free(&pl);
272                                         radlog(L_ERR|L_CONS,
273                                                         "%s[%d]: Parse error (reply) for entry %s: %s",
274                                                         file, lineno, entry, librad_errstr);
275                                         fclose(fp);
276                                         return -1;
277                                 }
278                         }
279                         else {
280                                 /*
281                                  *      Done with this entry...
282                                  */
283                                 t = rad_malloc(sizeof(PAIR_LIST));
284
285                                 auth_type_fixup(&check_tmp);
286                                 memset(t, 0, sizeof(*t));
287                                 t->name = strdup(entry);
288                                 t->check = check_tmp;
289                                 t->reply = reply_tmp;
290                                 t->lineno = old_lineno;
291                                 check_tmp = NULL;
292                                 reply_tmp = NULL;
293                                 if (last)
294                                         last->next = t;
295                                 else
296                                         pl = t;
297                                 last = t;
298
299                                 mode = FIND_MODE_NAME;
300                                 if (buffer[0] != 0)
301                                         goto parse_again;
302                         }
303                 }
304         }
305         /*
306          *      Make sure that we also read the last line of the file!
307          */
308         if (mode == FIND_MODE_REPLY) {
309                 buffer[0] = 0;
310                 goto parse_again;
311         }
312         fclose(fp);
313
314         *list = pl;
315         return 0;
316 }
317
318
319 /*
320  *      Debug code.
321  */
322 #if 0
323 static void debug_pair_list(PAIR_LIST *pl)
324 {
325         VALUE_PAIR *vp;
326
327         while(pl) {
328                 printf("Pair list: %s\n", pl->name);
329                 printf("** Check:\n");
330                 for(vp = pl->check; vp; vp = vp->next) {
331                         printf("    ");
332                         fprint_attr_val(stdout, vp);
333                         printf("\n");
334                 }
335                 printf("** Reply:\n");
336                 for(vp = pl->reply; vp; vp = vp->next) {
337                         printf("    ");
338                         fprint_attr_val(stdout, vp);
339                         printf("\n");
340                 }
341                 pl = pl->next;
342         }
343 }
344 #endif
345
346 #ifndef BUILDDBM /* HACK HACK */
347
348 /*
349  *      Free a REALM list.
350  */
351 static void realm_free(REALM *cl)
352 {
353         REALM *next;
354
355         while(cl) {
356                 next = cl->next;
357                 free(cl);
358                 cl = next;
359         }
360 }
361
362 /*
363  *      Read the realms file.
364  */
365 int read_realms_file(const char *file)
366 {
367         FILE *fp;
368         char buffer[256];
369         char realm[256];
370         char hostnm[256];
371         char opts[256];
372         char *s, *p;
373         int lineno = 0;
374         REALM *c, **tail;
375
376         realm_free(realms);
377         realms = NULL;
378         tail = &realms;
379
380         if ((fp = fopen(file, "r")) == NULL) {
381                 /* The realms file is not mandatory.  If it exists it will
382                    be used, however, since the new style config files are
383                    more robust and flexible they are more likely to get used.
384                    So this is a non-fatal error.  */
385                 return 0;
386         }
387         while(fgets(buffer, 256, fp) != NULL) {
388                 lineno++;
389                 if (!feof(fp) && (strchr(buffer, '\n') == NULL)) {
390                         radlog(L_ERR, "%s[%d]: line too long", file, lineno);
391                         return -1;
392                 }
393                 if (buffer[0] == '#' || buffer[0] == '\n')
394                         continue;
395                 p = buffer;
396                 if (!getword(&p, realm, sizeof(realm)) ||
397                                 !getword(&p, hostnm, sizeof(hostnm))) {
398                         radlog(L_ERR, "%s[%d]: syntax error", file, lineno);
399                         continue;
400                 }
401
402                 c = rad_malloc(sizeof(REALM));
403                 memset(c, 0, sizeof(REALM));
404
405                 if ((s = strchr(hostnm, ':')) != NULL) {
406                         *s++ = 0;
407                         c->auth_port = atoi(s);
408                         c->acct_port = c->auth_port + 1;
409                 } else {
410                         c->auth_port = PW_AUTH_UDP_PORT;
411                         c->acct_port = PW_ACCT_UDP_PORT;
412                 }
413
414                 if (strcmp(hostnm, "LOCAL") == 0) {
415                         /*
416                          *      Local realms don't have an IP address,
417                          *      secret, or port.
418                          */
419                         c->acct_ipaddr = c->ipaddr = htonl(INADDR_NONE);
420                         c->secret[0] = '\0';
421                         c->auth_port = auth_port;
422                         c->acct_port = acct_port;
423
424                 } else {
425                         RADCLIENT *client;
426                         c->ipaddr = ip_getaddr(hostnm);
427                         c->acct_ipaddr = c->ipaddr;
428
429                         if (c->ipaddr == htonl(INADDR_NONE)) {
430                                 radlog(L_CONS|L_ERR, "%s[%d]: Failed to look up hostname %s",
431                                        file, lineno, hostnm);
432                                 return -1;
433                         }
434
435                         /*
436                          *      Find the remote server in the "clients" list.
437                          *      If we can't find it, there's a big problem...
438                          */
439                         client = client_find(c->ipaddr);
440                         if (client == NULL) {
441                           radlog(L_CONS|L_ERR, "%s[%d]: Cannot find 'clients' file entry of remote server %s for realm \"%s\"",
442                                  file, lineno, hostnm, realm);
443                           return -1;
444                         }
445                         memcpy(c->secret, client->secret, sizeof(c->secret));
446                 }
447
448                 /*
449                  *      Double-check lengths to be sure they're sane
450                  */
451                 if (strlen(hostnm) >= sizeof(c->server)) {
452                         radlog(L_ERR, "%s[%d]: server name of length %d is greater than the allowed maximum of %d.",
453                                         file, lineno,
454                                         strlen(hostnm), sizeof(c->server) - 1);
455                         return -1;
456                 }
457                 if (strlen(realm) > sizeof(c->realm)) {
458                         radlog(L_ERR, "%s[%d]: realm of length %d is greater than the allowed maximum of %d.",
459                                         file, lineno,
460                                         strlen(realm), sizeof(c->realm) - 1);
461                         return -1;
462                 }
463
464                 /*
465                  *      OK, they're sane, copy them over.
466                  */
467                 strcpy(c->realm, realm);
468                 strcpy(c->server, hostnm);
469                 c->striprealm = TRUE;
470                 c->active = TRUE;
471                 c->acct_active = TRUE;
472
473                 while (getword(&p, opts, sizeof(opts))) {
474                         if (strcmp(opts, "nostrip") == 0)
475                                 c->striprealm = FALSE;
476                         if (strstr(opts, "noacct") != NULL)
477                                 c->acct_port = 0;
478                         if (strstr(opts, "trusted") != NULL)
479                                 c->trusted = 1;
480                         if (strstr(opts, "notrealm") != NULL)
481                                 c->notrealm = 1;
482                         if (strstr(opts, "notsuffix") != NULL)
483                                 c->notrealm = 1;
484                 }
485
486                 c->next = NULL;
487                 *tail = c;
488                 tail = &c->next;
489         }
490         fclose(fp);
491
492         return 0;
493 }
494 #endif /* BUILDDBM */
495
496 /*
497  * Mark a host inactive
498  */
499 void realm_disable(uint32_t ipaddr, int port)
500 {
501         REALM *cl;
502         time_t now;
503
504         now = time(NULL);
505         for(cl = realms; cl; cl = cl->next)
506                 if ((ipaddr == cl->ipaddr) && (port == cl->auth_port)) {
507                         cl->active = FALSE;
508                         cl->wakeup = now + proxy_dead_time;
509                         radlog(L_PROXY, "marking authentication server %s:%d for realm %s dead",
510                                 cl->server, port, cl->realm);
511                 } else if ((ipaddr == cl->acct_ipaddr) && (port == cl->acct_port)) {
512                         cl->acct_active = FALSE;
513                         cl->acct_wakeup = now + proxy_dead_time;
514                         radlog(L_PROXY, "marking accounting server %s:%d for realm %s dead",
515                                 cl->server, port, cl->realm);
516                 }
517 }
518
519 /*
520  *      Find a realm in the REALM list.
521  */
522 REALM *realm_find(const char *realm, int acct)
523 {
524         REALM *cl;
525         REALM *default_realm = NULL;
526         time_t now;
527         int dead_match = 0;
528
529         now = time(NULL);
530
531         /*
532          *      If we're passed a NULL realm pointer,
533          *      then look for a "NULL" realm string.
534          */
535         if (realm == NULL) {
536                 realm = "NULL";
537         }
538
539         for (cl = realms; cl; cl = cl->next) {
540                 /*
541                  *      Wake up any sleeping realm.
542                  */
543                 if (cl->wakeup <= now) {
544                         cl->active = TRUE;
545                 }
546                 if (cl->acct_wakeup <= now) {
547                         cl->acct_active = TRUE;
548                 }
549
550                 /*
551                  *      Asked for auth/acct, and the auth/acct server
552                  *      is not active.  Skip it.
553                  */
554                 if ((!acct && !cl->active) ||
555                     (acct && !cl->acct_active)) {
556
557                         /*
558                          *      We've been asked to NOT fall through
559                          *      to the DEFAULT realm if there are
560                          *      exact matches for this realm which are
561                          *      dead.
562                          */
563                         if ((!proxy_fallback) &&
564                             (strcasecmp(cl->realm, realm) == 0)) {
565                                 dead_match = 1;
566                         }
567                         continue;
568                 }
569
570                 /*
571                  *      If it matches exactly, return it.
572                  */
573                 if (strcasecmp(cl->realm, realm) == 0) {
574                         return cl;
575                 }
576
577                 /*
578                  *      No default realm, try to set one.
579                  */
580                 if ((default_realm == NULL) &&
581                     (strcmp(cl->realm, "DEFAULT") == 0)) {
582                   default_realm = cl;
583                 }
584         } /* loop over all realms */
585
586         /*
587          *      There WAS one or more matches which were marked dead,
588          *      AND there were NO live matches, AND we've been asked
589          *      to NOT fall through to the DEFAULT realm.  Therefore,
590          *      we return NULL, which means "no match found".
591          */
592         if (!proxy_fallback && dead_match) {
593                 return NULL;
594         }
595
596         /*
597          *      Didn't find anything that matched exactly, return the
598          *      DEFAULT realm.  We also return the DEFAULT realm if
599          *      all matching realms were marked dead, and we were
600          *      asked to fall through to the DEFAULT realm in this
601          *      case.
602          */
603         return default_realm;
604 }
605
606 /*
607  *      Find a realm for a proxy reply by proxy's IP
608  */
609 REALM *realm_findbyaddr(uint32_t ipaddr, int port)
610 {
611         REALM *cl;
612
613         /*
614          *      Note that we do NOT check for inactive realms!
615          *
616          *      If we get a packet from an end server, then we mark it
617          *      as active, and return the realm.
618          */
619         for(cl = realms; cl != NULL; cl = cl->next)
620                 if ((ipaddr == cl->ipaddr) && (port == cl->auth_port)) {
621                         cl->active = TRUE;
622                         return cl;
623                 } else if ((ipaddr == cl->acct_ipaddr) && (port == cl->acct_port)) {
624                         cl->acct_active = TRUE;
625                         return cl;
626                 }
627
628         return NULL;
629 }