When finding a realm by IP address, if we find it, then
[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 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
108 #if 0
109         vp->next = c->next;
110         c->next = vp;
111 #endif
112         vp->next = check;
113         check = vp;
114
115         for(vp = check; vp; vp = vp->next) {
116                 DEBUG2("  auth_type_fixup: %s [%d]", vp->name, vp->attribute);
117         }
118
119 }
120
121
122 #define FIND_MODE_NAME  0
123 #define FIND_MODE_REPLY 1
124
125 /*
126  *      Read the users, huntgroups or hints file.
127  *      Return a PAIR_LIST.
128  */
129 int pairlist_read(const char *file, PAIR_LIST **list, int complain)
130 {
131         FILE *fp;
132         int mode = FIND_MODE_NAME;
133         char entry[256];
134         char buffer[256];
135         char *ptr, *s;
136         VALUE_PAIR *check_tmp;
137         VALUE_PAIR *reply_tmp;
138         PAIR_LIST *pl = NULL, *last = NULL, *t;
139         int lineno = 0;
140         int old_lineno = 0;
141         LRAD_TOKEN parsecode;
142         char newfile[8192];
143
144         /*
145          *      Open the file.  The error message should be a little
146          *      more useful...
147          */
148         if ((fp = fopen(file, "r")) == NULL) {
149                 if (!complain) 
150                         return -1;
151                 radlog(L_CONS|L_ERR, "Couldn't open %s for reading: %s",
152                                 file, strerror(errno));
153                 return -1;
154         }
155
156         parsecode = T_EOL;
157         /*
158          *      Read the entire file into memory for speed.
159          */
160         while(fgets(buffer, sizeof(buffer), fp) != NULL) {
161                 lineno++;
162                 if (strchr(buffer, '\n') == NULL) {
163                         radlog(L_ERR, "%s[%d]: line too long", file, lineno);
164                         pairlist_free(&pl);
165                         return -1;
166                 }
167                 if (buffer[0] == '#' || buffer[0] == '\n') continue;
168 parse_again:
169                 if(mode == FIND_MODE_NAME) {
170                         /*
171                          *      Find the entry starting with the users name
172                          */
173                         if (isspace(buffer[0]))  {
174                                 if (parsecode != T_EOL) {
175                                         radlog(L_ERR|L_CONS,
176                                                         "%s[%d]: Unexpected trailing comma for entry %s",
177                                                         file, lineno, entry);
178                                         fclose(fp);
179                                         return -1;
180                                 }
181                                 continue;
182                         }
183
184                         ptr = buffer;
185                         getword(&ptr, entry, sizeof(entry));
186
187                         /*
188                          *      Include another file if we see
189                          *      $INCLUDE filename
190                          */
191                         if (strcasecmp(entry, "$include") == 0) {
192                                 while(isspace(*ptr))
193                                         ptr++;
194                                 s = ptr;
195                                 while (!isspace(*ptr))
196                                         ptr++;
197                                 *ptr = 0;
198
199                                 /*
200                                  *      If it's an absolute pathname,
201                                  *      then use it verbatim.
202                                  *
203                                  *      If not, then make the $include
204                                  *      files *relative* to the current
205                                  *      file.
206                                  */
207                                 if (*s != '/') {
208                                         strNcpy(newfile, file,
209                                                 sizeof(newfile));
210                                         ptr = strrchr(newfile, '/');
211                                         strcpy(ptr + 1, s);
212                                         s = newfile;
213                                 }
214
215                                 t = NULL;
216                                 if (pairlist_read(s, &t, 0) != 0) {
217                                         pairlist_free(&pl);
218                                         radlog(L_ERR|L_CONS,
219                                                         "%s[%d]: Could not open included file %s: %s",
220                                                         file, lineno, s, strerror(errno));
221                                         fclose(fp);
222                                 return -1;
223                                 }
224                                 if (last)
225                                         last->next = t;
226                                 else
227                                         pl = t;
228                                 last = t;
229                                 while (last && last->next)
230                                         last = last->next;
231                                 continue;
232                         }
233
234                         /*
235                          *      Parse the check values
236                          */
237                         check_tmp = NULL;
238                         reply_tmp = NULL;
239                         old_lineno = lineno;
240                         parsecode = userparse(ptr, &check_tmp);
241                         if (parsecode == T_INVALID) {
242                                 pairlist_free(&pl);
243                                 radlog(L_ERR|L_CONS,
244                                 "%s[%d]: Parse error (check) for entry %s: %s",
245                                         file, lineno, entry, librad_errstr);
246                                 fclose(fp);
247                                 return -1;
248                         } else if (parsecode == T_COMMA) {
249                                 radlog(L_ERR|L_CONS,
250                                                 "%s[%d]: Unexpected trailing comma in check item list for entry %s",
251                                                 file, lineno, entry);
252                                 fclose(fp);
253                                 return -1;
254                         }
255                         mode = FIND_MODE_REPLY;
256                         parsecode = T_COMMA;
257                 }
258                 else {
259                         if(*buffer == ' ' || *buffer == '\t') {
260                                 if (parsecode != T_COMMA) {
261                                         radlog(L_ERR|L_CONS,
262                                                         "%s[%d]: Syntax error: Previous line is missing a trailing comma for entry %s",
263                                                         file, lineno, entry);
264                                         fclose(fp);
265                                         return -1;
266                                 }
267
268                                 /*
269                                  *      Parse the reply values
270                                  */
271                                 parsecode = userparse(buffer, &reply_tmp);
272                                 if (parsecode < 0) {
273                                         pairlist_free(&pl);
274                                         radlog(L_ERR|L_CONS,
275                                                         "%s[%d]: Parse error (reply) for entry %s: %s",
276                                                         file, lineno, entry, librad_errstr);
277                                         fclose(fp);
278                                         return -1;
279                                 }
280                         }
281                         else {
282                                 /*
283                                  *      Done with this entry...
284                                  */
285                                 t = rad_malloc(sizeof(PAIR_LIST));
286
287                                 auth_type_fixup(check_tmp);
288                                 memset(t, 0, sizeof(*t));
289                                 t->name = strdup(entry);
290                                 t->check = check_tmp;
291                                 t->reply = reply_tmp;
292                                 t->lineno = old_lineno;
293                                 check_tmp = NULL;
294                                 reply_tmp = NULL;
295                                 if (last)
296                                         last->next = t;
297                                 else
298                                         pl = t;
299                                 last = t;
300
301                                 mode = FIND_MODE_NAME;
302                                 if (buffer[0] != 0)
303                                         goto parse_again;
304                         }
305                 }
306         }
307         /*
308          *      Make sure that we also read the last line of the file!
309          */
310         if (mode == FIND_MODE_REPLY) {
311                 buffer[0] = 0;
312                 goto parse_again;
313         }
314         fclose(fp);
315
316         *list = pl;
317         return 0;
318 }
319
320
321 /*
322  *      Debug code.
323  */
324 #if 0
325 static void debug_pair_list(PAIR_LIST *pl)
326 {
327         VALUE_PAIR *vp;
328
329         while(pl) {
330                 printf("Pair list: %s\n", pl->name);
331                 printf("** Check:\n");
332                 for(vp = pl->check; vp; vp = vp->next) {
333                         printf("    ");
334                         fprint_attr_val(stdout, vp);
335                         printf("\n");
336                 }
337                 printf("** Reply:\n");
338                 for(vp = pl->reply; vp; vp = vp->next) {
339                         printf("    ");
340                         fprint_attr_val(stdout, vp);
341                         printf("\n");
342                 }
343                 pl = pl->next;
344         }
345 }
346 #endif
347
348 #ifndef BUILDDBM /* HACK HACK */
349
350 /*
351  *      Free a REALM list.
352  */
353 static void realm_free(REALM *cl)
354 {
355         REALM *next;
356
357         while(cl) {
358                 next = cl->next;
359                 free(cl);
360                 cl = next;
361         }
362 }
363
364 /*
365  *      Read the realms file.
366  */
367 int read_realms_file(const char *file)
368 {
369         FILE *fp;
370         char buffer[256];
371         char realm[256];
372         char hostnm[256];
373         char opts[256];
374         char *s, *p;
375         int lineno = 0;
376         REALM *c, **tail;
377         RADCLIENT *client;
378
379         realm_free(realms);
380         realms = NULL;
381         tail = &realms;
382
383         if ((fp = fopen(file, "r")) == NULL) {
384                 /* The realms file is not mandatory.  If it exists it will
385                    be used, however, since the new style config files are
386                    more robust and flexible they are more likely to get used.
387                    So this is a non-fatal error.  */
388                 return 0;
389         }
390         while(fgets(buffer, 256, fp) != NULL) {
391                 lineno++;
392                 if (strchr(buffer, '\n') == NULL) {
393                         radlog(L_ERR, "%s[%d]: line too long", file, lineno);
394                         return -1;
395                 }
396                 if (buffer[0] == '#' || buffer[0] == '\n')
397                         continue;
398                 p = buffer;
399                 if (!getword(&p, realm, sizeof(realm)) ||
400                                 !getword(&p, hostnm, sizeof(hostnm))) {
401                         radlog(L_ERR, "%s[%d]: syntax error", file, lineno);
402                         continue;
403                 }
404
405                 c = rad_malloc(sizeof(REALM));
406                 memset(c, 0, sizeof(REALM));
407
408                 if ((s = strchr(hostnm, ':')) != NULL) {
409                         *s++ = 0;
410                         c->auth_port = atoi(s);
411                         c->acct_port = c->auth_port + 1;
412                 } else {
413                         c->auth_port = auth_port;
414                         c->acct_port = acct_port;
415                 }
416
417                 if (strcmp(hostnm, "LOCAL") == 0) {
418                         c->ipaddr = htonl(INADDR_LOOPBACK);
419                 } else {
420                         c->ipaddr = ip_getaddr(hostnm);
421                 }
422
423                 if (c->ipaddr == 0) {
424                         radlog(L_CONS|L_ERR, "%s[%d]: Failed to look up hostname %s",
425                                         file, lineno, hostnm);
426                         return -1;
427                 }
428
429                 /*
430                  *      Find the remote server in the "clients" list.
431                  *      If we can't find it, there's a big problem...
432                  */
433                 client = client_find(c->ipaddr);
434                 if (client == NULL) {
435                         radlog(L_CONS|L_ERR, "%s[%d]: Cannot find 'clients' file entry of remote server %s for realm \"%s\"",
436                                         file, lineno, hostnm, realm);
437                         return -1;
438                 }
439                 memcpy(c->secret, client->secret, sizeof(c->secret));
440
441                 /*
442                  *      Double-check lengths to be sure they're sane
443                  */
444                 if (strlen(hostnm) >= sizeof(c->server)) {
445                         radlog(L_ERR, "%s[%d]: server name of length %d is greater than the allowed maximum of %d.",
446                                         file, lineno,
447                                         strlen(hostnm), sizeof(c->server) - 1);
448                         return -1;
449                 }
450                 if (strlen(realm) > sizeof(c->realm)) {
451                         radlog(L_ERR, "%s[%d]: realm of length %d is greater than the allowed maximum of %d.",
452                                         file, lineno,
453                                         strlen(realm), sizeof(c->realm) - 1);
454                         return -1;
455                 }
456
457                 /*
458                  *      OK, they're sane, copy them over.
459                  */
460                 strcpy(c->realm, realm);
461                 strcpy(c->server, hostnm);
462                 c->striprealm = TRUE;
463                 c->active = TRUE;
464
465                 while (getword(&p, opts, sizeof(opts))) {
466                         if (strcmp(opts, "nostrip") == 0)
467                                 c->striprealm = FALSE;
468                         if (strstr(opts, "noacct") != NULL)
469                                 c->acct_port = 0;
470                         if (strstr(opts, "trusted") != NULL)
471                                 c->trusted = 1;
472                         if (strstr(opts, "notrealm") != NULL)
473                                 c->notrealm = 1;
474                         if (strstr(opts, "notsuffix") != NULL)
475                                 c->notrealm = 1;
476                 }
477
478                 c->next = NULL;
479                 *tail = c;
480                 tail = &c->next;
481         }
482         fclose(fp);
483
484         return 0;
485 }
486 #endif /* BUILDDBM */
487
488 /*
489  * Mark a host inactive
490  */
491 void realm_disable(uint32_t ipaddr)
492 {
493         REALM *cl;
494         time_t now;
495
496         now = time(NULL);
497         for(cl = realms; cl; cl = cl->next)
498                 if (ipaddr == cl->ipaddr) {
499                         cl->active = FALSE;
500                         cl->wakeup = now + proxy_dead_time;
501                 }
502 }
503
504 /*
505  *      Find a realm in the REALM list.
506  */
507 REALM *realm_find(const char *realm)
508 {
509         REALM *cl;
510         REALM *default_realm = NULL;
511         time_t now;
512
513         now = time(NULL);
514
515         /*
516          *      If we're passed a NULL realm pointer,
517          *      then look for a "NULL" realm string.
518          */
519         if (realm == NULL) {
520                 realm = "NULL";
521         }
522
523         for (cl = realms; cl; cl = cl->next) {
524                 /*
525                  *      Wake up any sleeping realm.
526                  */
527                 if (cl->wakeup <= now) {
528                         cl->active = TRUE;
529                 }
530
531                 /*
532                  *      It's not alive, skip it.
533                  */
534                 if (!cl->active) {
535                         continue;
536                 }
537
538                 /*
539                  *      If it matches exactly, return it.
540                  */
541                 if (strcmp(cl->realm, realm) == 0) {
542                         return cl;
543                 }
544
545                 /*
546                  *      No default realm, try to set one.
547                  */
548                 if ((default_realm == NULL) &&
549                     (strcmp(cl->realm, "DEFAULT") == 0)) {
550                   default_realm = cl;
551                 }
552         } /* loop over all realms */
553
554         /*
555          *      Didn't find anything that matched exactly, return
556          *      the default.
557          */
558         return default_realm;
559 }
560
561
562 /*
563  *      Find a realm for a proxy reply by proxy's IP
564  */
565 REALM *realm_findbyaddr(uint32_t ipaddr)
566 {
567         REALM *cl;
568
569         /*
570          *      Note that we do NOT check for inactive realms!
571          *
572          *      If we get a packet from an end server, then we mark it
573          *      as active, and return the realm.
574          */
575         for(cl = realms; cl != NULL; cl = cl->next)
576                 if (ipaddr == cl->ipaddr) {
577                         cl->active = TRUE;
578                         return cl;
579                 }
580
581         return NULL;
582 }