pam_radius.c revision 122571
169408Sache/*-
259243Sobrien * Copyright 1998 Juniper Networks, Inc.
359243Sobrien * All rights reserved.
459243Sobrien * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
559243Sobrien * All rights reserved.
659243Sobrien *
759243Sobrien * Portions of this software were developed for the FreeBSD Project by
859243Sobrien * ThinkSec AS and NAI Labs, the Security Research Division of Network
959243Sobrien * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
1059243Sobrien * ("CBOSS"), as part of the DARPA CHATS research program.
1159243Sobrien *
1259243Sobrien * Redistribution and use in source and binary forms, with or without
1359243Sobrien * modification, are permitted provided that the following conditions
1459243Sobrien * are met:
1559243Sobrien * 1. Redistributions of source code must retain the above copyright
1659243Sobrien *    notice, this list of conditions and the following disclaimer.
1759243Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1859243Sobrien *    notice, this list of conditions and the following disclaimer in the
1959243Sobrien *    documentation and/or other materials provided with the distribution.
2059243Sobrien * 3. The name of the author may not be used to endorse or promote
2159243Sobrien *    products derived from this software without specific prior written
2259243Sobrien *    permission.
2359243Sobrien *
2459243Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2559243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2659243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2759243Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2859243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2959243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
3059243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3159243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3259243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3359243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3459243Sobrien * SUCH DAMAGE.
3559243Sobrien */
3659243Sobrien
3759243Sobrien#include <sys/cdefs.h>
3859243Sobrien__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_radius/pam_radius.c 122571 2003-11-12 17:47:23Z sobomax $");
3959243Sobrien
4059243Sobrien#include <sys/param.h>
4159243Sobrien#include <sys/types.h>
4259243Sobrien#include <sys/socket.h>
4359243Sobrien#include <netdb.h>
4459243Sobrien#include <pwd.h>
4559243Sobrien#include <radlib.h>
4659243Sobrien#include <stdlib.h>
4759243Sobrien#include <string.h>
4859243Sobrien#include <syslog.h>
4959243Sobrien#include <unistd.h>
5059243Sobrien
5159243Sobrien#define PAM_SM_AUTH
5259243Sobrien
5359243Sobrien#include <security/pam_appl.h>
5459243Sobrien#include <security/pam_modules.h>
5559243Sobrien#include <security/pam_mod_misc.h>
5659243Sobrien
5759243Sobrien#define PAM_OPT_CONF		"conf"
5859243Sobrien#define PAM_OPT_TEMPLATE_USER	"template_user"
5959243Sobrien#define PAM_OPT_NAS_ID		"nas_id"
6059243Sobrien#define PAM_OPT_NAS_IPADDR	"nas_ipaddr"
6159243Sobrien
6259243Sobrien#define	MAX_CHALLENGE_MSGS	10
6359243Sobrien#define	PASSWORD_PROMPT		"RADIUS Password:"
6459243Sobrien
6559243Sobrienstatic int	 build_access_request(struct rad_handle *, const char *,
6659243Sobrien		    const char *, const char *, const char *, const void *,
6759243Sobrien		    size_t);
6859243Sobrienstatic int	 do_accept(pam_handle_t *, struct rad_handle *);
6959243Sobrienstatic int	 do_challenge(pam_handle_t *, struct rad_handle *,
7059243Sobrien		    const char *);
7159243Sobrien
7259243Sobrien/*
7359243Sobrien * Construct an access request, but don't send it.  Returns 0 on success,
7459243Sobrien * -1 on failure.
7559243Sobrien */
7659243Sobrienstatic int
7759243Sobrienbuild_access_request(struct rad_handle *radh, const char *user,
7859243Sobrien    const char *pass, const char *nas_id, const char *nas_ipaddr,
7959243Sobrien    const void *state, size_t state_len)
8059243Sobrien{
8159243Sobrien	int error;
8259243Sobrien	char host[MAXHOSTNAMELEN];
8359243Sobrien	struct sockaddr_in *haddr;
8459243Sobrien	struct addrinfo hints;
8559243Sobrien	struct addrinfo *res;
8659243Sobrien
8759243Sobrien	if (rad_create_request(radh, RAD_ACCESS_REQUEST) == -1) {
8859243Sobrien		syslog(LOG_CRIT, "rad_create_request: %s", rad_strerror(radh));
8959243Sobrien		return (-1);
9059243Sobrien	}
9159243Sobrien	if (nas_id == NULL ||
9259243Sobrien	    (nas_ipaddr != NULL && strlen(nas_ipaddr) == 0)) {
9359243Sobrien		if (gethostname(host, sizeof host) != -1) {
9459243Sobrien			if (nas_id == NULL)
9569408Sache				nas_id = host;
9659243Sobrien			if (nas_ipaddr != NULL && strlen(nas_ipaddr) == 0)
9759243Sobrien				nas_ipaddr = host;
9859243Sobrien		}
9959243Sobrien	}
10059243Sobrien	if ((user != NULL &&
10159243Sobrien	    rad_put_string(radh, RAD_USER_NAME, user) == -1) ||
10259243Sobrien	    (pass != NULL &&
10359243Sobrien	    rad_put_string(radh, RAD_USER_PASSWORD, pass) == -1) ||
10459243Sobrien	    (nas_id != NULL &&
10559243Sobrien	    rad_put_string(radh, RAD_NAS_IDENTIFIER, nas_id) == -1)) {
10659243Sobrien		syslog(LOG_CRIT, "rad_put_string: %s", rad_strerror(radh));
10759243Sobrien		return (-1);
10859243Sobrien	}
10959243Sobrien	if (nas_ipaddr != NULL) {
11059243Sobrien		memset(&hints, 0, sizeof(hints));
11159243Sobrien		hints.ai_family = PF_INET;
11259243Sobrien		if (getaddrinfo(nas_ipaddr, NULL, &hints, &res) == 0 &&
11359243Sobrien		    res != NULL) {
11459243Sobrien			haddr = (struct sockaddr_in *)res->ai_addr;
11559243Sobrien			error = rad_put_addr(radh, RAD_NAS_IP_ADDRESS,
11659243Sobrien			    haddr->sin_addr);
11759243Sobrien			freeaddrinfo(res);
11859243Sobrien			if (error == -1) {
11959243Sobrien				syslog(LOG_CRIT, "rad_put_addr: %s",
12059243Sobrien				    rad_strerror(radh));
12159243Sobrien				return (-1);
12259243Sobrien			}
12359243Sobrien		}
12459243Sobrien	}
12559243Sobrien	if (state != NULL && rad_put_attr(radh, RAD_STATE, state,
12659243Sobrien	    state_len) == -1) {
12759243Sobrien		syslog(LOG_CRIT, "rad_put_attr: %s", rad_strerror(radh));
12859243Sobrien		return (-1);
12959243Sobrien	}
13059243Sobrien	if (rad_put_int(radh, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY) == -1) {
13159243Sobrien		syslog(LOG_CRIT, "rad_put_int: %s", rad_strerror(radh));
13259243Sobrien		return (-1);
13359243Sobrien	}
13459243Sobrien	return (0);
13559243Sobrien}
13659243Sobrien
13759243Sobrienstatic int
13859243Sobriendo_accept(pam_handle_t *pamh, struct rad_handle *radh)
13959243Sobrien{
14059243Sobrien	int attrtype;
14159243Sobrien	const void *attrval;
14259243Sobrien	size_t attrlen;
14359243Sobrien	char *s;
14459243Sobrien
14559243Sobrien	while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) {
14659243Sobrien		if (attrtype == RAD_USER_NAME) {
14759243Sobrien			s = rad_cvt_string(attrval, attrlen);
14859243Sobrien			if (s == NULL) {
14959243Sobrien				syslog(LOG_CRIT,
15059243Sobrien				    "rad_cvt_string: out of memory");
15159243Sobrien				return (-1);
15259243Sobrien			}
15359243Sobrien			pam_set_item(pamh, PAM_USER, s);
15459243Sobrien			free(s);
15559243Sobrien		}
15659243Sobrien	}
15759243Sobrien	if (attrtype == -1) {
15859243Sobrien		syslog(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh));
15959243Sobrien		return (-1);
16059243Sobrien	}
16159243Sobrien	return (0);
16259243Sobrien}
16359243Sobrien
16459243Sobrienstatic int
16559243Sobriendo_challenge(pam_handle_t *pamh, struct rad_handle *radh, const char *user)
16659243Sobrien{
16759243Sobrien	int retval;
16859243Sobrien	int attrtype;
16959243Sobrien	const void *attrval;
17059243Sobrien	size_t attrlen;
17159243Sobrien	const void *state;
17259243Sobrien	size_t statelen;
17359243Sobrien	struct pam_message msgs[MAX_CHALLENGE_MSGS];
17459243Sobrien	const struct pam_message *msg_ptrs[MAX_CHALLENGE_MSGS];
17559243Sobrien	struct pam_response *resp;
17659243Sobrien	int num_msgs;
17759243Sobrien	const void *item;
17859243Sobrien	const struct pam_conv *conv;
17959243Sobrien
18059243Sobrien	state = NULL;
18159243Sobrien	statelen = 0;
18259243Sobrien	num_msgs = 0;
18359243Sobrien	while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) {
18459243Sobrien		switch (attrtype) {
18559243Sobrien
18659243Sobrien		case RAD_STATE:
18759243Sobrien			state = attrval;
18859243Sobrien			statelen = attrlen;
18959243Sobrien			break;
19059243Sobrien
19159243Sobrien		case RAD_REPLY_MESSAGE:
19259243Sobrien			if (num_msgs >= MAX_CHALLENGE_MSGS) {
19359243Sobrien				syslog(LOG_CRIT,
19459243Sobrien				    "Too many RADIUS challenge messages");
19559243Sobrien				return (PAM_SERVICE_ERR);
19659243Sobrien			}
19759243Sobrien			msgs[num_msgs].msg = rad_cvt_string(attrval, attrlen);
19859243Sobrien			if (msgs[num_msgs].msg == NULL) {
19959243Sobrien				syslog(LOG_CRIT,
20059243Sobrien				    "rad_cvt_string: out of memory");
20159243Sobrien				return (PAM_SERVICE_ERR);
20259243Sobrien			}
20359243Sobrien			msgs[num_msgs].msg_style = PAM_TEXT_INFO;
20459243Sobrien			msg_ptrs[num_msgs] = &msgs[num_msgs];
20559243Sobrien			num_msgs++;
20659243Sobrien			break;
20759243Sobrien		}
20859243Sobrien	}
20959243Sobrien	if (attrtype == -1) {
21059243Sobrien		syslog(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh));
21159243Sobrien		return (PAM_SERVICE_ERR);
21259243Sobrien	}
21359243Sobrien	if (num_msgs == 0) {
21459243Sobrien		msgs[num_msgs].msg = strdup("(null RADIUS challenge): ");
21559243Sobrien		if (msgs[num_msgs].msg == NULL) {
21659243Sobrien			syslog(LOG_CRIT, "Out of memory");
21759243Sobrien			return (PAM_SERVICE_ERR);
21859243Sobrien		}
21959243Sobrien		msgs[num_msgs].msg_style = PAM_TEXT_INFO;
22059243Sobrien		msg_ptrs[num_msgs] = &msgs[num_msgs];
22159243Sobrien		num_msgs++;
22259243Sobrien	}
22359243Sobrien	msgs[num_msgs-1].msg_style = PAM_PROMPT_ECHO_ON;
22459243Sobrien	if ((retval = pam_get_item(pamh, PAM_CONV, &item)) != PAM_SUCCESS) {
22559243Sobrien		syslog(LOG_CRIT, "do_challenge: cannot get PAM_CONV");
22659243Sobrien		return (retval);
22759243Sobrien	}
22859243Sobrien	conv = (const struct pam_conv *)item;
22959243Sobrien	if ((retval = conv->conv(num_msgs, msg_ptrs, &resp,
23059243Sobrien	    conv->appdata_ptr)) != PAM_SUCCESS)
23159243Sobrien		return (retval);
23259243Sobrien	if (build_access_request(radh, user, resp[num_msgs-1].resp, NULL,
23359243Sobrien	    NULL, state, statelen) == -1)
23459243Sobrien		return (PAM_SERVICE_ERR);
23559243Sobrien	memset(resp[num_msgs-1].resp, 0, strlen(resp[num_msgs-1].resp));
23659243Sobrien	free(resp[num_msgs-1].resp);
23759243Sobrien	free(resp);
23859243Sobrien	while (num_msgs > 0)
23959243Sobrien		free(msgs[--num_msgs].msg);
24059243Sobrien	return (PAM_SUCCESS);
24159243Sobrien}
24259243Sobrien
24359243SobrienPAM_EXTERN int
24459243Sobrienpam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
24559243Sobrien    int argc __unused, const char *argv[] __unused)
24659243Sobrien{
24759243Sobrien	struct rad_handle *radh;
24859243Sobrien	const char *user, *tmpuser, *pass;
24959243Sobrien	const char *conf_file, *template_user, *nas_id, *nas_ipaddr;
25059243Sobrien	int retval;
25159243Sobrien	int e;
25259243Sobrien
25359243Sobrien	conf_file = openpam_get_option(pamh, PAM_OPT_CONF);
25459243Sobrien	template_user = openpam_get_option(pamh, PAM_OPT_TEMPLATE_USER);
25559243Sobrien	nas_id = openpam_get_option(pamh, PAM_OPT_NAS_ID);
25659243Sobrien	nas_ipaddr = openpam_get_option(pamh, PAM_OPT_NAS_IPADDR);
25759243Sobrien
25859243Sobrien	retval = pam_get_user(pamh, &user, NULL);
25959243Sobrien	if (retval != PAM_SUCCESS)
26059243Sobrien		return (retval);
26159243Sobrien
26259243Sobrien	PAM_LOG("Got user: %s", user);
26359243Sobrien
26459243Sobrien	retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT);
26559243Sobrien	if (retval != PAM_SUCCESS)
26659243Sobrien		return (retval);
26759243Sobrien
26859243Sobrien	PAM_LOG("Got password");
26959243Sobrien
27059243Sobrien	radh = rad_open();
27159243Sobrien	if (radh == NULL) {
27259243Sobrien		syslog(LOG_CRIT, "rad_open failed");
27359243Sobrien		return (PAM_SERVICE_ERR);
27459243Sobrien	}
27559243Sobrien
27659243Sobrien	PAM_LOG("Radius opened");
27759243Sobrien
27859243Sobrien	if (rad_config(radh, conf_file) == -1) {
27959243Sobrien		syslog(LOG_ALERT, "rad_config: %s", rad_strerror(radh));
28059243Sobrien		rad_close(radh);
28159243Sobrien		return (PAM_SERVICE_ERR);
28259243Sobrien	}
28359243Sobrien
28459243Sobrien	PAM_LOG("Radius config file read");
28559243Sobrien
28659243Sobrien	if (build_access_request(radh, user, pass, nas_id, nas_ipaddr, NULL,
28759243Sobrien	    0) == -1) {
28859243Sobrien		rad_close(radh);
28959243Sobrien		return (PAM_SERVICE_ERR);
29059243Sobrien	}
29159243Sobrien
29259243Sobrien	PAM_LOG("Radius build access done");
29359243Sobrien
29459243Sobrien	for (;;) {
29559243Sobrien		switch (rad_send_request(radh)) {
29659243Sobrien
29759243Sobrien		case RAD_ACCESS_ACCEPT:
29859243Sobrien			e = do_accept(pamh, radh);
29959243Sobrien			rad_close(radh);
30059243Sobrien			if (e == -1)
30159243Sobrien				return (PAM_SERVICE_ERR);
30259243Sobrien			if (template_user != NULL) {
30359243Sobrien
30459243Sobrien				PAM_LOG("Trying template user: %s",
30559243Sobrien				    template_user);
30659243Sobrien
30759243Sobrien				/*
30859243Sobrien				 * If the given user name doesn't exist in
30959243Sobrien				 * the local password database, change it
31059243Sobrien				 * to the value given in the "template_user"
31159243Sobrien				 * option.
31259243Sobrien				 */
31359243Sobrien				retval = pam_get_item(pamh, PAM_USER,
31459243Sobrien				    (const void **)&tmpuser);
31559243Sobrien				if (retval != PAM_SUCCESS)
31659243Sobrien					return (retval);
31759243Sobrien				if (getpwnam(tmpuser) == NULL) {
31859243Sobrien					pam_set_item(pamh, PAM_USER,
31959243Sobrien					    template_user);
32059243Sobrien					PAM_LOG("Using template user");
32159243Sobrien				}
32259243Sobrien
32359243Sobrien			}
32459243Sobrien			return (PAM_SUCCESS);
32559243Sobrien
32659243Sobrien		case RAD_ACCESS_REJECT:
32759243Sobrien			rad_close(radh);
32859243Sobrien			PAM_VERBOSE_ERROR("Radius rejection");
32959243Sobrien			return (PAM_AUTH_ERR);
33059243Sobrien
33159243Sobrien		case RAD_ACCESS_CHALLENGE:
33259243Sobrien			retval = do_challenge(pamh, radh, user);
33359243Sobrien			if (retval != PAM_SUCCESS) {
33459243Sobrien				rad_close(radh);
33559243Sobrien				return (retval);
33659243Sobrien			}
33759243Sobrien			break;
33859243Sobrien
33959243Sobrien		case -1:
34059243Sobrien			syslog(LOG_CRIT, "rad_send_request: %s",
34159243Sobrien			    rad_strerror(radh));
34259243Sobrien			rad_close(radh);
34359243Sobrien			PAM_VERBOSE_ERROR("Radius failure");
34459243Sobrien			return (PAM_AUTHINFO_UNAVAIL);
34559243Sobrien
34659243Sobrien		default:
34759243Sobrien			syslog(LOG_CRIT,
34859243Sobrien			    "rad_send_request: unexpected return value");
34959243Sobrien			rad_close(radh);
35059243Sobrien			PAM_VERBOSE_ERROR("Radius error");
35159243Sobrien			return (PAM_SERVICE_ERR);
35259243Sobrien		}
35359243Sobrien	}
35459243Sobrien}
35559243Sobrien
35659243SobrienPAM_EXTERN int
35759243Sobrienpam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
35859243Sobrien    int argc __unused, const char *argv[] __unused)
35959243Sobrien{
36059243Sobrien
36159243Sobrien	return (PAM_SUCCESS);
36259243Sobrien}
36359243Sobrien
36459243SobrienPAM_MODULE_ENTRY("pam_radius");
36559243Sobrien