2 * auth.c User authentication.
8 static const char rcsid[] = "$Id$";
12 #include <sys/types.h>
30 # include <sys/security.h>
42 static const char *auth_username(VALUE_PAIR *namepair)
45 return (char *)namepair->strvalue;
51 * Return a short string showing the terminal server, port
52 * and calling station ID.
54 char *auth_name(char *buf, size_t buflen, REQUEST *request, int do_cli)
60 if ((cli = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) == NULL)
62 if ((pair = pairfind(request->packet->vps, PW_NAS_PORT_ID)) != NULL)
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 : ""));
74 * Check if account has expired, and if user may login now.
76 static int check_expiration(VALUE_PAIR *check_item, char *umsg, const char **user_msg)
79 umsg = umsg; /* -Wunused */
82 while (result == 0 && check_item != (VALUE_PAIR *)NULL) {
85 * Check expiration date if we are doing password aging.
87 if (check_item->attribute == PW_EXPIRATION) {
89 * Has this user's password expired
91 if (check_item->lvalue < (unsigned) time(NULL)) {
93 *user_msg = "Password Has Expired\r\n";
97 check_item = check_item->next;
108 * -2 Rejected (Auth-Type = Reject, send Port-Message back)
109 * 1 End check & return, don't reply
111 * NOTE: NOT the same as the RLM_ values !
113 static int rad_check_password(REQUEST *request,
114 VALUE_PAIR *check_item,
115 const char **user_msg)
117 VALUE_PAIR *auth_type_pair;
118 VALUE_PAIR *password_pair;
119 VALUE_PAIR *auth_item;
120 char string[MAX_STRING_LEN];
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.
130 if ((auth_type_pair = pairfind(check_item, PW_AUTHTYPE)) != NULL)
131 auth_type = auth_type_pair->lvalue;
133 if (auth_type == PW_AUTHTYPE_ACCEPT) {
134 DEBUG2(" auth: Auth-Type = Accept, accepting the user");
138 if (auth_type == PW_AUTHTYPE_REJECT) {
139 DEBUG2(" auth: Auth-Type = Reject, rejecting the user");
145 * Find the password sent by the user. It SHOULD be there,
146 * if it's not authentication fails.
148 * FIXME: add MS-CHAP support ?
150 auth_item = request->password;
151 if (auth_item == NULL) {
152 DEBUG2(" auth: No password in the request");
157 * Find the password from the users file.
159 if ((password_pair = pairfind(check_item, PW_CRYPT_PASSWORD)) != NULL)
160 auth_type = PW_AUTHTYPE_CRYPT;
162 password_pair = pairfind(check_item, PW_PASSWORD);
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.
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;
175 auth_type = PW_AUTHTYPE_LOCAL;
179 case PW_AUTHTYPE_CRYPT:
180 DEBUG2(" auth: Crypt");
181 if (password_pair == NULL) {
182 result = auth_item->strvalue ? -1 : 0;
185 if (strcmp(password_pair->strvalue,
186 crypt(auth_item->strvalue,
187 password_pair->strvalue)) != 0)
190 case PW_AUTHTYPE_LOCAL:
191 DEBUG2(" auth: Local");
193 * Local password is just plain text.
195 if (auth_item->attribute != PW_CHAP_PASSWORD) {
197 * Plain text password.
199 if (password_pair == NULL ||
200 strcmp(password_pair->strvalue,
201 auth_item->strvalue)!=0)
207 * CHAP - calculate MD5 sum over CHAP-ID,
208 * plain-text password and the Chap-Challenge.
209 * Compare to Chap-Response (strvalue + 1).
211 if (password_pair == NULL) {
215 rad_chap_encode(request->packet, string,
216 *auth_item->strvalue, password_pair);
221 if (memcmp(string + 1, auth_item->strvalue + 1,
222 CHAP_VALUE_LENGTH) != 0)
227 dict_valbyattr(PW_AUTHTYPE, auth_type)->name);
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.
234 result = module_authenticate(auth_type, request);
237 * An authentication module FAIL
238 * return code is the same as
239 * an explicit REJECT!
241 case RLM_MODULE_FAIL:
242 case RLM_MODULE_REJECT:
248 case RLM_MODULE_HANDLED:
262 * Process and reply to an authentication request
264 int rad_authenticate(REQUEST *request)
266 VALUE_PAIR *namepair;
267 VALUE_PAIR *check_item;
268 VALUE_PAIR *reply_item;
269 VALUE_PAIR *auth_item;
270 VALUE_PAIR *user_reply;
273 char umsg[MAX_STRING_LEN + 1];
274 const char *user_msg;
275 const char *password;
278 int seen_callback_id;
283 if (request->config_items) {
284 pairfree(request->config_items);
285 request->config_items = NULL;
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.
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");
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;
308 if (request->proxy_reply->vps) {
309 user_reply = request->proxy_reply->vps;
310 request->proxy_reply->vps = NULL;
315 * Get the username from the request.
317 * Note that namepair MAY be NULL, in which case there
318 * is no User-Name attribute in the request.
320 namepair = request->username;
323 * Decrypt the password, and remove trailing NULL's.
325 auth_item = pairfind(request->packet->vps, PW_PASSWORD);
326 if (auth_item != NULL) {
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);
335 for (i = auth_item->length; i >=0; i--) {
336 if (auth_item->strvalue[i]) {
339 auth_item->length = i;
342 password = (char *)auth_item->strvalue;
346 * Maybe there's a CHAP-Password?
348 if (auth_item == NULL) {
349 auth_item = pairfind(request->packet->vps, PW_CHAP_PASSWORD);
353 * Update the password with OUR preference for the
356 request->password = auth_item;
359 * Get the user's authorization information from the database
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);
372 pairfree(user_reply);
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.
382 if ((request->proxy == NULL) &&
383 (pairfind(request->config_items, PW_PROXY_TO_REALM) != NULL)) {
384 pairfree(user_reply);
389 * Perhaps there is a Stripped-User-Name now.
391 tmp=pairfind(request->packet->vps, PW_STRIPPED_USER_NAME);
400 if ((result = check_expiration(request->config_items, umsg, &user_msg))<0)
402 result = rad_check_password(request, request->config_items,
406 pairfree(user_reply);
410 reply_item = pairfind(user_reply, PW_REPLY_MESSAGE);
411 if (reply_item != NULL)
412 user_msg = (char *)reply_item->strvalue;
418 * Failed to validate the user.
420 DEBUG2(" auth: Failed to validate the user.");
421 request->reply = build_reply(PW_AUTHENTICATION_REJECT, request,
423 if (auth_item != NULL && log_auth) {
424 char clean_buffer[1024];
427 if (auth_item->attribute == PW_CHAP_PASSWORD) {
428 strcpy(clean_buffer, "CHAP-Password");
430 librad_safeprint((char *)auth_item->strvalue,
432 clean_buffer, sizeof(clean_buffer));
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;
446 log_debug(" WARNING: Unprintable characters in the password.\n Double-check the shared secret on the server and the NAS!");
456 (check_item = pairfind(request->config_items, PW_SIMULTANEOUS_USE)) != NULL) {
458 * User authenticated O.K. Now we have to check
459 * for the Simultaneous-Use parameter.
462 (r = radutmp_checksimul((char *)namepair->strvalue,
463 request->packet->vps, check_item->lvalue)) != 0) {
465 if (check_item->lvalue > 1) {
467 "\r\nYou are already logged in %d times - access denied\r\n\n",
468 (int)check_item->lvalue);
472 "\r\nYou are already logged in - access denied\r\n\n";
474 request->reply = build_reply(PW_AUTHENTICATION_REJECT,
475 request, NULL, user_msg);
476 log(L_ERR, "Multiple logins: [%s] (%s) max. %d%s",
478 auth_name(buf, sizeof(buf), request, 1),
480 r == 2 ? " [MPP attempt]" : "");
486 (check_item = pairfind(request->config_items, PW_LOGIN_TIME)) != NULL) {
489 * Authentication is OK. Now see if this
490 * user may login at this time of the day.
492 r = timestr_match((char *)check_item->strvalue,
495 * Session-Timeout needs to be at least
496 * 60 seconds, some terminal servers
497 * ignore smaller values.
501 * User called outside allowed time interval.
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);
516 * User is allowed, but set Session-Timeout.
518 if ((reply_item = pairfind(user_reply,
519 PW_SESSION_TIMEOUT)) != NULL) {
520 if (reply_item->lvalue > (unsigned) r)
521 reply_item->lvalue = r;
523 if ((reply_item = paircreate(
525 PW_TYPE_INTEGER)) == NULL) {
526 log(L_ERR|L_CONS, "no memory");
529 reply_item->lvalue = r;
530 pairadd(&user_reply, reply_item);
536 * Result should be >= 0 here - if not, we return.
539 pairfree(user_reply);
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...
548 if (auth_item->attribute == PW_CHAP_PASSWORD) {
549 password = "CHAP-Password";
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.
560 if ((auth_item = pairfind(user_reply, PW_EXEC_PROGRAM)) != NULL) {
562 exec_program = strdup(auth_item->strvalue);
563 pairdelete(&user_reply, PW_EXEC_PROGRAM);
565 if ((auth_item = pairfind(user_reply, PW_EXEC_PROGRAM_WAIT)) != NULL) {
567 exec_program = strdup(auth_item->strvalue);
568 pairdelete(&user_reply, PW_EXEC_PROGRAM_WAIT);
572 * Hack - allow % expansion in certain value strings.
573 * This is nice for certain Exec-Program programs.
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);
588 * If we want to exec a program, but wait for it,
589 * do it first before sending the reply.
591 if (exec_program && exec_wait) {
592 if (radius_exec_program(exec_program,
593 request->packet->vps, &user_reply, exec_wait, &user_msg) != 0) {
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.
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);
605 "Login incorrect: [%s] (%s) "
606 "(external check failed)",
607 auth_username(namepair),
608 auth_name(buf, sizeof(buf), request, 1));
610 pairfree(user_reply);
616 * Delete "normal" A/V pairs when using callback.
618 * FIXME: This is stupid. The portmaster should accept
619 * these settings instead of insisting on using a
622 * FIXME2: Move this into the above exec thingy?
623 * (if you knew how I use the exec_wait, you'd understand).
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);
638 * Filter (possibly multiple) Reply-Message attributes
639 * through radius_xlate, modifying them in place.
641 if (user_msg == NULL) {
642 reply_item = pairfind(user_reply, PW_REPLY_MESSAGE);
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);
651 reply_item = pairfind(reply_item->next, PW_REPLY_MESSAGE);
655 request->reply = build_reply(PW_AUTHENTICATION_ACK, request,
656 user_reply, user_msg);
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));
666 if (exec_program && !exec_wait) {
668 * No need to check the exit status here.
670 radius_exec_program(exec_program,
671 request->packet->vps, &user_reply, exec_wait, NULL);
674 if (exec_program) free(exec_program);
675 pairfree(user_reply);