2 * auth.c User authentication.
8 static const char rcsid[] = "$Id$";
11 #include "libradius.h"
22 # include <sys/security.h>
27 # include <netinet/in.h>
34 * Return a short string showing the terminal server, port
35 * and calling station ID.
37 char *auth_name(char *buf, size_t buflen, REQUEST *request, int do_cli)
43 if ((cli = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) == NULL)
45 if ((pair = pairfind(request->packet->vps, PW_NAS_PORT_ID)) != NULL)
48 snprintf(buf, buflen, "from nas %.128s port %d%s%.128s",
49 nas_name2(request->packet), port,
50 (do_cli ? " cli " : ""), (do_cli ? (char *)cli->strvalue : ""));
57 * Check if account has expired, and if user may login now.
59 static int check_expiration(REQUEST *request)
62 VALUE_PAIR *check_item = request->config_items;
65 while (result == 0 && check_item != NULL) {
68 * Check expiration date if we are doing password aging.
70 if (check_item->attribute == PW_EXPIRATION) {
72 * Has this user's password expired?
74 * If so, remove ALL reply attributes,
75 * and add our own Reply-Message, saying
76 * why they're being rejected.
78 if (check_item->lvalue < (unsigned) time(NULL)) {
82 vp = pairmake("Reply-Message",
83 "Password Has Expired\r\n",
85 pairfree(&request->reply->vps);
86 request->reply->vps = vp;
90 check_item = check_item->next;
101 * -2 Rejected (Auth-Type = Reject, send Port-Message back)
102 * 1 End check & return, don't reply
104 * NOTE: NOT the same as the RLM_ values !
106 int rad_check_password(REQUEST *request)
108 VALUE_PAIR *auth_type_pair;
109 VALUE_PAIR *cur_config_item;
110 VALUE_PAIR *password_pair;
111 VALUE_PAIR *auth_item;
112 char string[MAX_STRING_LEN];
115 int auth_type_count = 0;
119 * Look for matching check items. We skip the whole lot
120 * if the authentication type is PW_AUTHTYPE_ACCEPT or
121 * PW_AUTHTYPE_REJECT.
123 cur_config_item = request->config_items;
124 while(((auth_type_pair = pairfind(cur_config_item, PW_AUTHTYPE))) != NULL) {
125 auth_type = auth_type_pair->lvalue;
128 DEBUG2(" rad_check_password: Found auth-type %d", auth_type);
129 cur_config_item = auth_type_pair->next;
131 if (auth_type == PW_AUTHTYPE_REJECT) {
132 DEBUG2(" rad_check_password: Auth-Type = Reject, rejecting user");
137 if(auth_type_count>1) {
138 radlog(L_ERR, "Warning: Found %d auth-types on request for user '%s'",
139 auth_type_count, request->username->strvalue);
143 * This means we have a proxy reply or an accept
144 * and it wasn't rejected in the above loop. So
145 * that means it is accepted and we do no further
148 if ((auth_type == PW_AUTHTYPE_ACCEPT) || (request->proxy)) {
149 DEBUG2(" rad_check_password: Auth-Type = Accept, accepting the user");
154 * Find the password sent by the user. It SHOULD be there,
155 * if it's not authentication fails.
157 * FIXME: add MS-CHAP support ?
159 auth_item = request->password;
160 if (auth_item == NULL) {
161 DEBUG2(" auth: No password in the request");
166 * Find the password from the users file.
168 if ((password_pair = pairfind(request->config_items, PW_CRYPT_PASSWORD)) != NULL)
169 auth_type = PW_AUTHTYPE_CRYPT;
171 password_pair = pairfind(request->config_items, PW_PASSWORD);
174 * For backward compatibility, we check the
175 * password to see if it is the magic value
176 * UNIX if auth_type was not set.
180 !strcmp((char *)password_pair->strvalue, "UNIX"))
181 auth_type = PW_AUTHTYPE_SYSTEM;
182 else if(password_pair &&
183 !strcmp((char *)password_pair->strvalue,"PAM"))
184 auth_type = PW_AUTHTYPE_PAM;
186 auth_type = PW_AUTHTYPE_LOCAL;
190 case PW_AUTHTYPE_CRYPT:
191 DEBUG2(" auth: Crypt");
192 if (password_pair == NULL) {
193 result = auth_item->strvalue ? -1 : 0;
196 if (strcmp((char *)password_pair->strvalue,
197 crypt((char *)auth_item->strvalue,
198 (char *)password_pair->strvalue)) != 0)
201 case PW_AUTHTYPE_LOCAL:
202 DEBUG2(" auth: Local");
204 * Local password is just plain text.
206 if (auth_item->attribute != PW_CHAP_PASSWORD) {
208 * Plain text password.
210 if (password_pair == NULL ||
211 strcmp((char *)password_pair->strvalue,
212 (char *)auth_item->strvalue)!=0)
218 * CHAP - calculate MD5 sum over CHAP-ID,
219 * plain-text password and the Chap-Challenge.
220 * Compare to Chap-Response (strvalue + 1).
222 if (password_pair == NULL) {
226 rad_chap_encode(request->packet, string,
227 *auth_item->strvalue, password_pair);
232 if (memcmp(string + 1, auth_item->strvalue + 1,
233 CHAP_VALUE_LENGTH) != 0)
238 dict_valbyattr(PW_AUTHTYPE, auth_type)->name);
240 * See if there is a module that handles
241 * this type, and turn the RLM_ return
242 * status into the values as defined at
243 * the top of this function.
245 result = module_authenticate(auth_type, request);
248 * An authentication module FAIL
249 * return code, or any return code that
250 * is not expected from authentication,
251 * is the same as an explicit REJECT!
253 case RLM_MODULE_FAIL:
254 case RLM_MODULE_REJECT:
255 case RLM_MODULE_USERLOCK:
256 case RLM_MODULE_INVALID:
257 case RLM_MODULE_NOTFOUND:
258 case RLM_MODULE_NOOP:
259 case RLM_MODULE_UPDATED:
265 case RLM_MODULE_HANDLED:
276 * Process and reply to an authentication request
278 * The return value of this function isn't actually used right now, so
279 * it's not entirely clear if it is returning the right things. --Pac.
281 int rad_authenticate(REQUEST *request)
283 VALUE_PAIR *namepair;
284 VALUE_PAIR *check_item;
285 VALUE_PAIR *reply_item;
286 VALUE_PAIR *auth_item;
289 char umsg[MAX_STRING_LEN + 1];
290 const char *user_msg = NULL;
291 const char *password;
294 int seen_callback_id;
296 char buf[1024], logstr[1024];
301 * Free any pre-existing configuration items.
303 * This should ONLY be happening for proxy replies.
305 if ((request->proxy_reply) && (request->config_items)) {
306 pairfree(&request->config_items);
307 request->config_items = NULL;
311 * If this request got proxied to another server,
312 * AND it was an authentication request, then we need
313 * to add an initial Auth-Type: Auth-Accept for success,
314 * Auth-Reject for fail. We also need to add the reply
315 * pairs from the server to the initial reply.
317 if ((request->proxy_reply) &&
318 (request->packet->code == PW_AUTHENTICATION_REQUEST)) {
319 tmp = paircreate(PW_AUTHTYPE, PW_TYPE_INTEGER);
321 radlog(L_ERR|L_CONS, "no memory");
326 * Reply of ACCEPT means accept, ALL other
327 * replies mean reject. This is fail-safe.
329 if (request->proxy_reply->code == PW_AUTHENTICATION_ACK)
330 tmp->lvalue = PW_AUTHTYPE_ACCEPT;
332 tmp->lvalue = PW_AUTHTYPE_REJECT;
333 pairadd(&request->config_items, tmp);
336 * Initialize our reply to the user, by taking
337 * the reply attributes from the proxy.
339 if (request->proxy_reply->vps) {
340 request->reply->vps = request->proxy_reply->vps;
341 request->proxy_reply->vps = NULL;
345 * If it's an Access-Reject, then do NOT do any
346 * authorization or authentication. They're being
347 * rejected, so we minimize the amount of work
348 * done by the server, by rejecting them here.
350 if (request->proxy_reply->code != PW_AUTHENTICATION_ACK) {
351 request->reply->code = PW_AUTHENTICATION_REJECT;
352 return RLM_MODULE_REJECT;
357 * Get the username from the request.
359 * Note that namepair MAY be NULL, in which case there
360 * is no User-Name attribute in the request.
362 namepair = request->username;
365 * Discover which password we want to use.
367 if ((auth_item = rad_getpass(request)) != NULL) {
368 password = (const char *)auth_item->strvalue;
372 * Maybe there's a CHAP-Password?
374 if (auth_item == NULL) {
375 if ((auth_item = pairfind(request->packet->vps,
376 PW_CHAP_PASSWORD)) != NULL) {
377 password = "<CHAP-PASSWORD>";
381 * No password we recognize.
383 password = "<NO-PASSWORD>";
386 request->password = auth_item;
389 * Get the user's authorization information from the database
391 r = module_authorize(request);
392 if (r != RLM_MODULE_NOTFOUND &&
393 r != RLM_MODULE_NOOP &&
394 r != RLM_MODULE_OK &&
395 r != RLM_MODULE_UPDATED) {
396 if (r != RLM_MODULE_FAIL && r != RLM_MODULE_HANDLED) {
397 rad_authlog("Invalid user", request, 0);
398 request->reply->code = PW_AUTHENTICATION_REJECT;
400 pairfree(&request->reply->vps);
401 request->reply->vps = NULL;
406 * If we haven't already proxied the packet, then check
407 * to see if we should. Maybe one of the authorize
408 * modules has decided that a proxy should be used. If
409 * so, get out of here and send the packet.
411 if ((request->proxy == NULL) &&
412 (pairfind(request->config_items, PW_PROXY_TO_REALM) != NULL)) {
413 return RLM_MODULE_OK;
417 * Perhaps there is a Stripped-User-Name now.
419 namepair = request->username;
425 if ((result = check_expiration(request)) < 0)
427 result = rad_check_password(request);
430 return RLM_MODULE_HANDLED;
435 * Failed to validate the user.
437 * We PRESUME that the code which failed will clean up
438 * request->reply->vps, to be ONLY the reply items it
439 * wants to send back.
443 DEBUG2(" auth: Failed to validate the user.");
444 request->reply->code = PW_AUTHENTICATION_REJECT;
446 rad_authlog("Login incorrect", request, 0);
448 /* double check: maybe the secret is wrong? */
449 if (debug_flag > 1) {
452 p = auth_item->strvalue;
455 log_debug(" WARNING: Unprintable characters in the password.\n Double-check the shared secret on the server and the NAS!");
464 (check_item = pairfind(request->config_items, PW_SIMULTANEOUS_USE)) != NULL) {
466 * User authenticated O.K. Now we have to check
467 * for the Simultaneous-Use parameter.
470 (r = radutmp_checksimul((char *)namepair->strvalue,
471 request->packet->vps, check_item->lvalue)) != 0) {
473 if (check_item->lvalue > 1) {
475 "\r\nYou are already logged in %d times - access denied\r\n\n",
476 (int)check_item->lvalue);
480 "\r\nYou are already logged in - access denied\r\n\n";
483 request->reply->code = PW_AUTHENTICATION_REJECT;
486 * They're trying to log in too many times.
487 * Remove ALL reply attributes.
489 pairfree(&request->reply->vps);
490 tmp = pairmake("Reply-Message", user_msg, T_OP_SET);
491 request->reply->vps = tmp;
493 strcpy(logstr, "Multiple logins");
494 sprintf(logstr, "%s (max %d) %s", logstr, check_item->lvalue,
495 r == 2 ? "[MPP attempt]" : "");
496 rad_authlog(logstr, request, 1);
503 (check_item = pairfind(request->config_items, PW_LOGIN_TIME)) != NULL) {
506 * Authentication is OK. Now see if this
507 * user may login at this time of the day.
509 r = timestr_match((char *)check_item->strvalue,
512 * Session-Timeout needs to be at least
513 * 60 seconds, some terminal servers
514 * ignore smaller values.
518 * User called outside allowed time interval.
522 "You are calling outside your allowed timespan\r\n";
524 request->reply->code = PW_AUTHENTICATION_REJECT;
525 pairfree(&request->reply->vps);
527 tmp = pairmake("Reply-Message", user_msg, T_OP_SET);
528 request->reply->vps = tmp;
530 strcpy(logstr, "Outside allowed timespan");
531 sprintf(logstr, "%s (time allowed %s)", logstr,
532 check_item->strvalue);
533 rad_authlog(logstr, request, 1);
538 * User is allowed, but set Session-Timeout.
540 if ((reply_item = pairfind(request->reply->vps,
541 PW_SESSION_TIMEOUT)) != NULL) {
542 if (reply_item->lvalue > (unsigned) r)
543 reply_item->lvalue = r;
545 if ((reply_item = paircreate(
547 PW_TYPE_INTEGER)) == NULL) {
548 radlog(L_ERR|L_CONS, "no memory");
551 reply_item->lvalue = r;
552 pairadd(&request->reply->vps, reply_item);
558 * Result should be >= 0 here - if not, we return.
561 return RLM_MODULE_OK;
565 * We might need this later. The 'password' string
566 * is NOT used anywhere below here, except for logging,
567 * so it should be safe...
569 if (auth_item->attribute == PW_CHAP_PASSWORD) {
570 password = "CHAP-Password";
574 * Add the port number to the Framed-IP-Address if
575 * vp->addport is set, or if the Add-Port-To-IP-Address
578 * FIXME: This doesn't work because PW_ADD_PORT_TO_IP_ADDRESS
579 * is never added to the request pairs!
581 if ((tmp = pairfind(request->reply->vps,
582 PW_FRAMED_IP_ADDRESS)) != NULL) {
586 * Find the NAS port ID.
588 if ((tmp = pairfind(request->packet->vps,
589 PW_NAS_PORT_ID)) != NULL)
590 nas_port = tmp->lvalue;
592 if((tmp2 = pairfind(request->reply->vps,
593 PW_ADD_PORT_TO_IP_ADDRESS)) != NULL) {
594 if (tmp->addport || (tmp2 && tmp2->lvalue)) {
595 tmp->lvalue = htonl(ntohl(tmp->lvalue) + nas_port);
598 pairdelete(&request->reply->vps,
599 PW_ADD_PORT_TO_IP_ADDRESS);
604 * See if we need to execute a program.
605 * FIXME: somehow cache this info, and only execute the
606 * program when we receive an Accounting-START packet.
607 * Only at that time we know dynamic IP etc.
611 if ((auth_item = pairfind(request->reply->vps, PW_EXEC_PROGRAM)) != NULL) {
613 exec_program = strdup((char *)auth_item->strvalue);
614 pairdelete(&request->reply->vps, PW_EXEC_PROGRAM);
616 if ((auth_item = pairfind(request->reply->vps, PW_EXEC_PROGRAM_WAIT)) != NULL) {
618 exec_program = strdup((char *)auth_item->strvalue);
619 pairdelete(&request->reply->vps, PW_EXEC_PROGRAM_WAIT);
623 * Hack - allow % expansion in certain value strings.
624 * This is nice for certain Exec-Program programs.
626 seen_callback_id = 0;
627 if ((auth_item = pairfind(request->reply->vps, PW_CALLBACK_ID)) != NULL) {
628 seen_callback_id = 1;
629 radius_xlat2(buf, sizeof(auth_item->strvalue),
630 (char *)auth_item->strvalue, request);
631 strNcpy((char *)auth_item->strvalue, buf,
632 sizeof(auth_item->strvalue));
633 auth_item->length = strlen((char *)auth_item->strvalue);
638 * If we want to exec a program, but wait for it,
639 * do it first before sending the reply.
641 if (exec_program && exec_wait) {
642 if (radius_exec_program(exec_program, request,
643 exec_wait, &user_msg) != 0) {
645 * Error. radius_exec_program() returns -1 on
646 * fork/exec errors, or >0 if the exec'ed program
647 * had a non-zero exit status.
649 if (user_msg == NULL)
650 user_msg = "\r\nAccess denied (external check failed).";
652 request->reply->code = PW_AUTHENTICATION_REJECT;
653 pairfree(&request->reply->vps);
654 tmp = pairmake("Reply-Message", user_msg, T_OP_SET);
655 request->reply->vps = tmp;
657 rad_authlog("Login incorrect (external check failed)",
660 return RLM_MODULE_OK;
665 * Delete "normal" A/V pairs when using callback.
667 * FIXME: This is stupid. The portmaster should accept
668 * these settings instead of insisting on using a
671 * FIXME2: Move this into the above exec thingy?
672 * (if you knew how I use the exec_wait, you'd understand).
674 if (seen_callback_id) {
675 pairdelete(&request->reply->vps, PW_FRAMED_PROTOCOL);
676 pairdelete(&request->reply->vps, PW_FRAMED_IP_ADDRESS);
677 pairdelete(&request->reply->vps, PW_FRAMED_IP_NETMASK);
678 pairdelete(&request->reply->vps, PW_FRAMED_ROUTE);
679 pairdelete(&request->reply->vps, PW_FRAMED_MTU);
680 pairdelete(&request->reply->vps, PW_FRAMED_COMPRESSION);
681 pairdelete(&request->reply->vps, PW_FILTER_ID);
682 pairdelete(&request->reply->vps, PW_PORT_LIMIT);
683 pairdelete(&request->reply->vps, PW_CALLBACK_NUMBER);
687 * Filter (possibly multiple) Reply-Message attributes
688 * through radius_xlat2, modifying them in place.
690 if (user_msg == NULL) {
691 reply_item = pairfind(request->reply->vps, PW_REPLY_MESSAGE);
693 radius_xlat2(buf, sizeof(reply_item->strvalue),
694 (char *)reply_item->strvalue, request);
695 strNcpy((char *)reply_item->strvalue, buf,
696 sizeof(reply_item->strvalue));
697 reply_item->length = strlen((char *)reply_item->strvalue);
699 reply_item = pairfind(reply_item->next, PW_REPLY_MESSAGE);
703 request->reply->code = PW_AUTHENTICATION_ACK;
705 rad_authlog("Login OK", request, 1);
706 if (exec_program && !exec_wait) {
708 * No need to check the exit status here.
710 radius_exec_program(exec_program, request, exec_wait, NULL);
713 if (exec_program) free(exec_program);
714 return RLM_MODULE_OK;
719 * These definitions are local, and shouldn't be used by anyone else.
725 * Find the password pair, decode pass if
726 * needed, and return the value pair. If
727 * not found, return NULL
729 VALUE_PAIR *rad_getpass(REQUEST *request) {
730 VALUE_PAIR *auth_item;
733 * First, look up the password in the request header.
735 auth_item = request->password;
738 * It's there, but it's not a clear-text password.
741 if (auth_item->attribute != PW_PASSWORD) {
746 * Go find the request password.
748 auth_item = pairfind(request->packet->vps, PW_PASSWORD);
754 * Save the found password for later.
756 request->password = auth_item;
761 * If we proxied already, it's been decoded
762 * Or if the decoded flag is set...just return
764 if ((request->proxy != NULL) ||
765 (auth_item->lvalue == PW_DECODED)) {
770 * If we get here, we have to decode the password.
772 rad_pwdecode((char *)auth_item->strvalue,
773 auth_item->length, request->secret,
774 (char *)request->packet->vector);
777 * Set lvalue to PW_DECODED so we know not to
778 * decode next time we get here
780 auth_item->lvalue = PW_DECODED;
782 /* ignore more than one trailing '\0' */
783 auth_item->length = strlen(auth_item->strvalue);
789 * Make sure user/pass are clean
792 int rad_authlog(char *msg, REQUEST *request, int goodpass) {
794 char clean_password[1024];
795 char clean_username[1024];
798 if(!mainconfig.log_auth)
802 * Clean up the username
804 if(!request->username) {
805 DEBUG2("rad_authlog: no username found");
808 if(request->username->strvalue) {
809 librad_safeprint((char *)request->username->strvalue,
810 request->username->length,
811 clean_username, sizeof(clean_username));
813 strcpy(clean_username, "<No Username>");
817 * Clean up the password
819 if(mainconfig.log_auth_badpass || mainconfig.log_auth_goodpass) {
820 if(!request->password) {
821 DEBUG2("rad_authlog: no password found");
825 if (request->password->attribute == PW_CHAP_PASSWORD) {
826 strcpy(clean_password, "<CHAP-Password>");
828 if(request->username->strvalue) {
829 librad_safeprint((char *)request->password->strvalue,
830 request->password->length,
831 clean_password, sizeof(clean_password));
833 strcpy(clean_password, "<No Password>");
839 radlog(L_AUTH, "%s: [%s%s%s] (%s)",
842 mainconfig.log_auth_goodpass ? "/" : "",
843 mainconfig.log_auth_goodpass ? clean_password : "",
844 auth_name(buf, sizeof(buf), request, 1));
846 radlog(L_AUTH, "%s: [%s%s%s] (%s)",
849 mainconfig.log_auth_badpass ? "/" : "",
850 mainconfig.log_auth_badpass ? clean_password : "",
851 auth_name(buf, sizeof(buf), request, 1));