Patches from "Alan Curry" <pacman-radius@cqc.com>
[freeradius.git] / src / main / auth.c
1 /*
2  * auth.c       User authentication.
3  *
4  *
5  * Version:     $Id$
6  *
7  */
8 static const char rcsid[] = "$Id$";
9
10 #include        "autoconf.h"
11
12 #include        <sys/types.h>
13
14 #include        <stdio.h>
15 #include        <stdlib.h>
16 #include        <string.h>
17 #include        <unistd.h>
18 #include        <errno.h>
19 #include        <ctype.h>
20
21 #if HAVE_MALLOC_H
22 #  include <malloc.h>
23 #endif
24
25 #if HAVE_CRYPT_H
26 #  include <crypt.h>
27 #endif
28
29 #ifdef OSFC2
30 #  include      <sys/security.h>
31 #  include      <prot.h>
32 #endif
33
34 #include        "radiusd.h"
35 #include        "modules.h"
36
37 #if !HAVE_CRYPT_H
38   extern char *crypt();
39 #endif
40
41
42 static const char *auth_username(VALUE_PAIR *namepair)
43 {
44         if (namepair) {
45                 return (char *)namepair->strvalue;
46         }
47         return "<NONE>";
48 }
49
50 /*
51  *      Return a short string showing the terminal server, port
52  *      and calling station ID.
53  */
54 char *auth_name(char *buf, size_t buflen, REQUEST *request, int do_cli)
55 {
56         VALUE_PAIR      *cli;
57         VALUE_PAIR      *pair;
58         int             port = 0;
59
60         if ((cli = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) == NULL)
61                 do_cli = 0;
62         if ((pair = pairfind(request->packet->vps, PW_NAS_PORT_ID)) != NULL)
63                 port = pair->lvalue;
64
65         snprintf(buf, buflen, "from nas %.128s port %d%s%.128s",
66                  nas_name2(request->packet), port,
67                  (do_cli ? " cli " : ""), (do_cli ? (char *)cli->strvalue : ""));
68
69         return buf;
70 }
71
72
73 /*
74  *      Check if account has expired, and if user may login now.
75  */
76 static int check_expiration(VALUE_PAIR *check_item, char *umsg, const char **user_msg)
77 {
78         int result;
79         umsg = umsg; /* -Wunused */
80
81         result = 0;
82         while (result == 0 && check_item != (VALUE_PAIR *)NULL) {
83
84                 /*
85                  *      Check expiration date if we are doing password aging.
86                  */
87                 if (check_item->attribute == PW_EXPIRATION) {
88                         /*
89                          *      Has this user's password expired
90                          */
91                         if (check_item->lvalue < (unsigned) time(NULL)) {
92                                 result = -1;
93                                 *user_msg = "Password Has Expired\r\n";
94                                 break;
95                         }
96                 }
97                 check_item = check_item->next;
98         }
99         return result;
100 }
101
102
103 /*
104  *      Check password.
105  *
106  *      Returns:        0  OK
107  *                      -1 Password fail
108  *                      -2 Rejected (Auth-Type = Reject, send Port-Message back)
109  *                      1  End check & return, don't reply
110  *
111  *      NOTE: NOT the same as the RLM_ values !
112  */
113 static int rad_check_password(REQUEST *request,
114         VALUE_PAIR *check_item,
115         const char **user_msg)
116 {
117         VALUE_PAIR      *auth_type_pair;
118         VALUE_PAIR      *password_pair;
119         VALUE_PAIR      *auth_item;
120         char            string[MAX_STRING_LEN];
121         int             auth_type = -1;
122         int             result;
123         result = 0;
124
125         /*
126          *      Look for matching check items. We skip the whole lot
127          *      if the authentication type is PW_AUTHTYPE_ACCEPT or
128          *      PW_AUTHTYPE_REJECT.
129          */
130         if ((auth_type_pair = pairfind(check_item, PW_AUTHTYPE)) != NULL)
131                 auth_type = auth_type_pair->lvalue;
132
133         if (auth_type == PW_AUTHTYPE_ACCEPT) {
134                 DEBUG2("  auth: Auth-Type = Accept, accepting the user");
135                 return 0;
136         }
137
138         if (auth_type == PW_AUTHTYPE_REJECT) {
139                 DEBUG2("  auth: Auth-Type = Reject, rejecting the user");
140                 *user_msg = NULL;
141                 return -2;
142         }
143
144         /*
145          *      Find the password sent by the user. It SHOULD be there,
146          *      if it's not authentication fails.
147          *
148          *      FIXME: add MS-CHAP support ?
149          */
150         auth_item = request->password;
151         if (auth_item == NULL) {
152                 DEBUG2("  auth: No password in the request");
153                 return -1;
154         }
155
156         /*
157          *      Find the password from the users file.
158          */
159         if ((password_pair = pairfind(check_item, PW_CRYPT_PASSWORD)) != NULL)
160                 auth_type = PW_AUTHTYPE_CRYPT;
161         else
162                 password_pair = pairfind(check_item, PW_PASSWORD);
163
164         /*
165          *      For backward compatibility, we check the
166          *      password to see if it is the magic value
167          *      UNIX if auth_type was not set.
168          */
169         if (auth_type < 0) {
170                 if (password_pair && !strcmp(password_pair->strvalue, "UNIX"))
171                         auth_type = PW_AUTHTYPE_SYSTEM;
172                 else if(password_pair && !strcmp(password_pair->strvalue,"PAM"))
173                         auth_type = PW_AUTHTYPE_PAM;
174                 else
175                         auth_type = PW_AUTHTYPE_LOCAL;
176         }
177
178         switch(auth_type) {
179                 case PW_AUTHTYPE_CRYPT:
180                         DEBUG2("  auth: Crypt");
181                         if (password_pair == NULL) {
182                                 result = auth_item->strvalue ? -1 : 0;
183                                 break;
184                         }
185                         if (strcmp(password_pair->strvalue,
186                             crypt(auth_item->strvalue,
187                                   password_pair->strvalue)) != 0)
188                                         result = -1;
189                         break;
190                 case PW_AUTHTYPE_LOCAL:
191                         DEBUG2("  auth: Local");
192                         /*
193                          *      Local password is just plain text.
194                          */
195                         if (auth_item->attribute != PW_CHAP_PASSWORD) {
196                                 /*
197                                  *      Plain text password.
198                                  */
199                                 if (password_pair == NULL ||
200                                     strcmp(password_pair->strvalue,
201                                            auth_item->strvalue)!=0)
202                                         result = -1;
203                                 break;
204                         }
205
206                         /*
207                          *      CHAP - calculate MD5 sum over CHAP-ID,
208                          *      plain-text password and the Chap-Challenge.
209                          *      Compare to Chap-Response (strvalue + 1).
210                          */
211                         if (password_pair == NULL) {
212                                 result= -1;
213                                 break;
214                         }
215                         rad_chap_encode(request->packet, string,
216                                         *auth_item->strvalue, password_pair);
217
218                         /*
219                          *      Compare them
220                          */
221                         if (memcmp(string + 1, auth_item->strvalue + 1,
222                                         CHAP_VALUE_LENGTH) != 0)
223                                 result = -1;
224                         break;
225                 default:
226                         DEBUG2("  auth: %s",
227                                dict_valbyattr(PW_AUTHTYPE, auth_type)->name);
228                         /*
229                          *      See if there is a module that handles
230                          *      this type, and turn the RLM_ return
231                          *      status into the values as defined at
232                          *      the top of this function.
233                          */
234                         result = module_authenticate(auth_type, request);
235                         switch (result) {
236                                 /*
237                                  *      An authentication module FAIL
238                                  *      return code is the same as
239                                  *      an explicit REJECT!
240                                  */
241                                 case RLM_MODULE_FAIL:
242                                 case RLM_MODULE_REJECT:
243                                         result = -1;
244                                         break;
245                                 case RLM_MODULE_OK:
246                                         result = 0;
247                                         break;
248                                 case RLM_MODULE_HANDLED:
249                                         result = 1;
250                                         break;
251                         }
252                         break;
253         }
254
255         if (result < 0)
256                 *user_msg = NULL;
257
258         return result;
259 }
260
261 /*
262  *      Process and reply to an authentication request
263  */
264 int rad_authenticate(REQUEST *request)
265 {
266         VALUE_PAIR      *namepair;
267         VALUE_PAIR      *check_item;
268         VALUE_PAIR      *reply_item;
269         VALUE_PAIR      *auth_item;
270         VALUE_PAIR      *user_reply;
271         VALUE_PAIR      *tmp;
272         int             result, r;
273         char            umsg[MAX_STRING_LEN + 1];
274         const char      *user_msg;
275         const char      *password;
276         char            *exec_program;
277         int             exec_wait;
278         int             seen_callback_id;
279         char            buf[1024];
280
281         user_reply = NULL;
282         password = "";
283         if (request->config_items) {
284           pairfree(request->config_items);
285           request->config_items = NULL;
286         }
287
288         /*
289          *      If this request got proxied to another server, we need
290          *      to add an initial Auth-Type: Auth-Accept for success,
291          *      Auth-Reject for fail. We also need to add the reply
292          *      pairs from the server to the initial reply.
293          */
294         if (request->proxy_reply) {
295                 if (request->proxy_reply->code == PW_AUTHENTICATION_REJECT ||
296                     request->proxy_reply->code == PW_AUTHENTICATION_ACK) {
297                         request->config_items = paircreate(PW_AUTHTYPE, PW_TYPE_INTEGER);
298                         if (request->config_items == NULL) {
299                                 log(L_ERR|L_CONS, "no memory");
300                                 exit(1);
301                         }
302                 }
303                 if (request->proxy_reply->code == PW_AUTHENTICATION_REJECT)
304                         request->config_items->lvalue = PW_AUTHTYPE_REJECT;
305                 if (request->proxy_reply->code == PW_AUTHENTICATION_ACK)
306                         request->config_items->lvalue = PW_AUTHTYPE_ACCEPT;
307
308                 if (request->proxy_reply->vps) {
309                         user_reply = request->proxy_reply->vps;
310                         request->proxy_reply->vps = NULL;
311                 }
312         }
313
314         /*
315          *      Get the username from the request.
316          *
317          *      Note that namepair MAY be NULL, in which case there
318          *      is no User-Name attribute in the request.
319          */
320         namepair = request->username;
321
322         /*
323          *      Decrypt the password, and remove trailing NULL's.
324          */
325         auth_item = pairfind(request->packet->vps, PW_PASSWORD);
326         if (auth_item != NULL) {
327                 int i;
328
329                 /* If we proxied this, we already did pwdecode */
330                 if (request->proxy == NULL) {
331                         rad_pwdecode((char *)auth_item->strvalue,
332                                      auth_item->length, request->secret,
333                                      (char *)request->packet->vector);
334                 }
335                 for (i = auth_item->length; i >=0; i--) {
336                         if (auth_item->strvalue[i]) {
337                                 break;
338                         } else {
339                                 auth_item->length = i;
340                         }
341                 }
342                 password = (char *)auth_item->strvalue;
343         }
344
345         /*
346          *      Maybe there's a CHAP-Password?
347          */
348         if (auth_item == NULL) {
349                 auth_item = pairfind(request->packet->vps, PW_CHAP_PASSWORD);
350         }
351
352         /*
353          *      Update the password with OUR preference for the
354          *      password.
355          */
356         request->password = auth_item;
357
358         /*
359          *      Get the user's authorization information from the database
360          */
361         r = module_authorize(request, &request->config_items, &user_reply);
362         if (r != RLM_MODULE_OK) {
363                 if (r != RLM_MODULE_FAIL && r != RLM_MODULE_HANDLED) {
364                         log(L_AUTH, "Invalid user: [%s%s%s] (%s)",
365                             auth_username(namepair),
366                             log_auth_pass ? "/" : "",
367                             log_auth_pass ? password : "",
368                             auth_name(buf, sizeof(buf), request, 1));
369                         request->reply = build_reply(PW_AUTHENTICATION_REJECT,
370                                                      request, NULL, NULL);
371                 }
372                 pairfree(user_reply);
373                 return r;
374         }
375
376         /*
377          *      If we haven't already proxied the packet, then check
378          *      to see if we should.  Maybe one of the authorize
379          *      modules has decided that a proxy should be used. If
380          *      so, get out of here and send the packet.
381          */
382         if ((request->proxy == NULL) &&
383             (pairfind(request->config_items, PW_PROXY_TO_REALM) != NULL)) {
384                 pairfree(user_reply);
385                 return 0;
386         }
387
388         /*
389          *      Perhaps there is a Stripped-User-Name now.
390          */
391         tmp=pairfind(request->packet->vps, PW_STRIPPED_USER_NAME);
392         if (tmp != NULL)
393                 namepair = tmp;
394
395         /*
396          *      Validate the user
397          */
398         user_msg = NULL;
399         do {
400                 if ((result = check_expiration(request->config_items, umsg, &user_msg))<0)
401                                 break;
402                 result = rad_check_password(request, request->config_items,
403                         &user_msg);
404                 if (result > 0) {
405                         /* don't reply! */
406                         pairfree(user_reply);
407                         return -1;
408                 }
409                 if (result == -2) {
410                         reply_item = pairfind(user_reply, PW_REPLY_MESSAGE);
411                         if (reply_item != NULL)
412                                 user_msg = (char *)reply_item->strvalue;
413                 }
414         } while(0);
415
416         if (result < 0) {
417                 /*
418                  *      Failed to validate the user.
419                  */
420                 DEBUG2("  auth: Failed to validate the user.");
421                 request->reply = build_reply(PW_AUTHENTICATION_REJECT, request,
422                                              NULL, user_msg);
423                 if (auth_item != NULL && log_auth) {
424                         char clean_buffer[1024];
425                         u_char *p;
426
427                         if (auth_item->attribute == PW_CHAP_PASSWORD) {
428                           strcpy(clean_buffer, "CHAP-Password");
429                         } else {
430                           librad_safeprint((char *)auth_item->strvalue,
431                                            auth_item->length,
432                                            clean_buffer, sizeof(clean_buffer));
433                         }
434                         log(L_AUTH,
435                                 "Login incorrect: [%s%s%s] (%s)%s",
436                                 auth_username(namepair),
437                                 log_auth_pass?"/":"",
438                                 log_auth_pass?clean_buffer:"",
439                                 auth_name(buf, sizeof(buf), request, 1),
440                                 ((result == -2) ? " reject" : ""));
441                         /* double check: maybe the secret is wrong? */
442                         if (debug_flag > 1) {
443                           p = auth_item->strvalue;
444                           while (*p) {
445                             if (!isprint(*p)) {
446                               log_debug("  WARNING: Unprintable characters in the password.\n           Double-check the shared secret on the server and the NAS!");
447                               break;
448                             }
449                             p++;
450                           }
451                         }
452                 }
453         }
454
455         if (result >= 0 &&
456            (check_item = pairfind(request->config_items, PW_SIMULTANEOUS_USE)) != NULL) {
457                 /*
458                  *      User authenticated O.K. Now we have to check
459                  *      for the Simultaneous-Use parameter.
460                  */
461                 if (namepair &&
462                     (r = radutmp_checksimul((char *)namepair->strvalue,
463                     request->packet->vps, check_item->lvalue)) != 0) {
464
465                         if (check_item->lvalue > 1) {
466                                 sprintf(umsg,
467                 "\r\nYou are already logged in %d times  - access denied\r\n\n",
468                                         (int)check_item->lvalue);
469                                 user_msg = umsg;
470                         } else {
471                                 user_msg =
472                 "\r\nYou are already logged in - access denied\r\n\n";
473                         }
474                         request->reply = build_reply(PW_AUTHENTICATION_REJECT,
475                                                      request, NULL, user_msg);
476                 log(L_ERR, "Multiple logins: [%s] (%s) max. %d%s",
477                                 namepair->strvalue,
478                                 auth_name(buf, sizeof(buf), request, 1),
479                                 check_item->lvalue,
480                                 r == 2 ? " [MPP attempt]" : "");
481                         result = -1;
482                 }
483         }
484
485         if (result >= 0 &&
486            (check_item = pairfind(request->config_items, PW_LOGIN_TIME)) != NULL) {
487
488                 /*
489                  *      Authentication is OK. Now see if this
490                  *      user may login at this time of the day.
491                  */
492                 r = timestr_match((char *)check_item->strvalue,
493                                   request->timestamp);
494                 /*
495                  *      Session-Timeout needs to be at least
496                  *      60 seconds, some terminal servers
497                  *      ignore smaller values.
498                  */
499                 if (r < 60) {
500                         /*
501                          *      User called outside allowed time interval.
502                          */
503                         result = -1;
504                         user_msg =
505                         "You are calling outside your allowed timespan\r\n";
506                         request->reply = build_reply(PW_AUTHENTICATION_REJECT,
507                                                      request, NULL, user_msg);
508                         log(L_ERR, "Outside allowed timespan: [%s]"
509                                    " (%s) time allowed: %s",
510                                         auth_username(namepair),
511                                         auth_name(buf, sizeof(buf), request, 1),
512                                         check_item->strvalue);
513                 } else if (r > 0) {
514
515                         /*
516                          *      User is allowed, but set Session-Timeout.
517                          */
518                         if ((reply_item = pairfind(user_reply,
519                             PW_SESSION_TIMEOUT)) != NULL) {
520                                 if (reply_item->lvalue > (unsigned) r)
521                                         reply_item->lvalue = r;
522                         } else {
523                                 if ((reply_item = paircreate(
524                                     PW_SESSION_TIMEOUT,
525                                     PW_TYPE_INTEGER)) == NULL) {
526                                         log(L_ERR|L_CONS, "no memory");
527                                         exit(1);
528                                 }
529                                 reply_item->lvalue = r;
530                                 pairadd(&user_reply, reply_item);
531                         }
532                 }
533         }
534
535         /*
536          *      Result should be >= 0 here - if not, we return.
537          */
538         if (result < 0) {
539                 pairfree(user_reply);
540                 return 0;
541         }
542
543         /*
544          *      We might need this later.  The 'password' string
545          *      is NOT used anywhere below here, except for logging,
546          *      so it should be safe...
547          */
548         if (auth_item->attribute == PW_CHAP_PASSWORD) {
549                 password = "CHAP-Password";
550         }
551
552         /*
553          *      See if we need to execute a program.
554          *      FIXME: somehow cache this info, and only execute the
555          *      program when we receive an Accounting-START packet.
556          *      Only at that time we know dynamic IP etc.
557          */
558         exec_program = NULL;
559         exec_wait = 0;
560         if ((auth_item = pairfind(user_reply, PW_EXEC_PROGRAM)) != NULL) {
561                 exec_wait = 0;
562                 exec_program = strdup(auth_item->strvalue);
563                 pairdelete(&user_reply, PW_EXEC_PROGRAM);
564         }
565         if ((auth_item = pairfind(user_reply, PW_EXEC_PROGRAM_WAIT)) != NULL) {
566                 exec_wait = 1;
567                 exec_program = strdup(auth_item->strvalue);
568                 pairdelete(&user_reply, PW_EXEC_PROGRAM_WAIT);
569         }
570
571         /*
572          *      Hack - allow % expansion in certain value strings.
573          *      This is nice for certain Exec-Program programs.
574          */
575         seen_callback_id = 0;
576         if ((auth_item = pairfind(user_reply, PW_CALLBACK_ID)) != NULL) {
577                 seen_callback_id = 1;
578                 radius_xlate(buf, sizeof(auth_item->strvalue),
579                              (char *)auth_item->strvalue,
580                              request->packet->vps, user_reply);
581                 strNcpy((char *)auth_item->strvalue, buf,
582                         sizeof(auth_item->strvalue));
583                 auth_item->length = strlen((char *)auth_item->strvalue);
584         }
585
586
587         /*
588          *      If we want to exec a program, but wait for it,
589          *      do it first before sending the reply.
590          */
591         if (exec_program && exec_wait) {
592                 if (radius_exec_program(exec_program,
593                     request->packet->vps, &user_reply, exec_wait, &user_msg) != 0) {
594                         /*
595                          *      Error. radius_exec_program() returns -1 on
596                          *      fork/exec errors, or >0 if the exec'ed program
597                          *      had a non-zero exit status.
598                          */
599                         if (user_msg == NULL)
600                 user_msg = "\r\nAccess denied (external check failed).";
601                         request->reply = build_reply(PW_AUTHENTICATION_REJECT,
602                                                      request, NULL, user_msg);
603                         if (log_auth) {
604                                 log(L_AUTH,
605                                         "Login incorrect: [%s] (%s) "
606                                         "(external check failed)",
607                                         auth_username(namepair),
608                                         auth_name(buf, sizeof(buf), request, 1));
609                         }
610                         pairfree(user_reply);
611                         return 0;
612                 }
613         }
614
615         /*
616          *      Delete "normal" A/V pairs when using callback.
617          *
618          *      FIXME: This is stupid. The portmaster should accept
619          *      these settings instead of insisting on using a
620          *      dialout location.
621          *
622          *      FIXME2: Move this into the above exec thingy?
623          *      (if you knew how I use the exec_wait, you'd understand).
624          */
625         if (seen_callback_id) {
626                 pairdelete(&user_reply, PW_FRAMED_PROTOCOL);
627                 pairdelete(&user_reply, PW_FRAMED_IP_ADDRESS);
628                 pairdelete(&user_reply, PW_FRAMED_IP_NETMASK);
629                 pairdelete(&user_reply, PW_FRAMED_ROUTE);
630                 pairdelete(&user_reply, PW_FRAMED_MTU);
631                 pairdelete(&user_reply, PW_FRAMED_COMPRESSION);
632                 pairdelete(&user_reply, PW_FILTER_ID);
633                 pairdelete(&user_reply, PW_PORT_LIMIT);
634                 pairdelete(&user_reply, PW_CALLBACK_NUMBER);
635         }
636
637         /*
638          *      Filter (possibly multiple) Reply-Message attributes
639          *      through radius_xlate, modifying them in place.
640          */
641         if (user_msg == NULL) {
642           reply_item = pairfind(user_reply, PW_REPLY_MESSAGE);
643           while (reply_item) {
644                 radius_xlate(buf, sizeof(reply_item->strvalue),
645                              (char *)reply_item->strvalue,
646                              request->packet->vps, user_reply);
647                 strNcpy((char *)reply_item->strvalue, buf,
648                         sizeof(reply_item->strvalue));
649                 reply_item->length = strlen((char *)reply_item->strvalue);
650                 user_msg = NULL;
651                 reply_item = pairfind(reply_item->next, PW_REPLY_MESSAGE);
652           }
653         }
654
655         request->reply = build_reply(PW_AUTHENTICATION_ACK, request,
656                                      user_reply, user_msg);
657
658         if (log_auth) {
659                 log(L_AUTH,
660                         "Login OK: [%s%s%s] (%s)",
661                         auth_username(namepair),
662                         log_auth_pass ? "/" : "",
663                         log_auth_pass ? password : "",
664                         auth_name(buf, sizeof(buf), request, 0));
665         }
666         if (exec_program && !exec_wait) {
667                 /*
668                  *      No need to check the exit status here.
669                  */
670                 radius_exec_program(exec_program,
671                         request->packet->vps, &user_reply, exec_wait, NULL);
672         }
673
674         if (exec_program) free(exec_program);
675         pairfree(user_reply);
676         return 0;
677 }
678