pam_radius.c revision 81473
1226586Sdim/*-
2226586Sdim * Copyright 1998 Juniper Networks, Inc.
3226586Sdim * All rights reserved.
4226586Sdim *
5226586Sdim * Redistribution and use in source and binary forms, with or without
6226586Sdim * modification, are permitted provided that the following conditions
7226586Sdim * are met:
8226586Sdim * 1. Redistributions of source code must retain the above copyright
9226586Sdim *    notice, this list of conditions and the following disclaimer.
10226586Sdim * 2. Redistributions in binary form must reproduce the above copyright
11226586Sdim *    notice, this list of conditions and the following disclaimer in the
12226586Sdim *    documentation and/or other materials provided with the distribution.
13226586Sdim *
14239462Sdim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15226586Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16226586Sdim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17226586Sdim * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18226586Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19226586Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20226586Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21226586Sdim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22226586Sdim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23239462Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24226586Sdim * SUCH DAMAGE.
25226586Sdim *
26226586Sdim *	$FreeBSD: head/lib/libpam/modules/pam_radius/pam_radius.c 81473 2001-08-10 19:12:59Z markm $
27226586Sdim */
28226586Sdim
29226586Sdim#include <sys/param.h>
30226586Sdim#include <pwd.h>
31226586Sdim#include <radlib.h>
32249423Sdim#include <stdlib.h>
33226586Sdim#include <string.h>
34234353Sdim#include <syslog.h>
35234353Sdim#include <unistd.h>
36234353Sdim
37249423Sdim#define PAM_SM_AUTH
38226586Sdim#include <security/pam_modules.h>
39226586Sdim
40226586Sdim#include "pam_mod_misc.h"
41239462Sdim
42226586Sdimenum { PAM_OPT_CONF=PAM_OPT_STD_MAX, PAM_OPT_TEMPLATE_USER };
43226586Sdim
44226586Sdimstatic struct opttab other_options[] = {
45243830Sdim	{ "conf",		PAM_OPT_CONF },
46243830Sdim	{ "template_user",	PAM_OPT_TEMPLATE_USER },
47249423Sdim	{ NULL, 0 }
48243830Sdim};
49249423Sdim
50226586Sdim#define	MAX_CHALLENGE_MSGS	10
51226586Sdim#define	PASSWORD_PROMPT		"RADIUS password:"
52226586Sdim
53226586Sdimstatic int	 build_access_request(struct rad_handle *, const char *,
54226586Sdim		    const char *, const void *, size_t);
55226586Sdimstatic int	 do_accept(pam_handle_t *, struct rad_handle *);
56226586Sdimstatic int	 do_challenge(pam_handle_t *, struct rad_handle *,
57226586Sdim		    const char *);
58226586Sdim
59226586Sdim/*
60226586Sdim * Construct an access request, but don't send it.  Returns 0 on success,
61226586Sdim * -1 on failure.
62226586Sdim */
63226586Sdimstatic int
64226586Sdimbuild_access_request(struct rad_handle *radh, const char *user,
65226586Sdim    const char *pass, const void *state, size_t state_len)
66226586Sdim{
67226586Sdim	char	 host[MAXHOSTNAMELEN];
68226586Sdim
69226586Sdim	if (rad_create_request(radh, RAD_ACCESS_REQUEST) == -1) {
70226586Sdim		syslog(LOG_CRIT, "rad_create_request: %s", rad_strerror(radh));
71226586Sdim		return -1;
72226586Sdim	}
73226586Sdim	if ((user != NULL &&
74226586Sdim	    rad_put_string(radh, RAD_USER_NAME, user) == -1) ||
75226586Sdim	    (pass != NULL &&
76249423Sdim	    rad_put_string(radh, RAD_USER_PASSWORD, pass) == -1) ||
77249423Sdim	    (gethostname(host, sizeof host) != -1 &&
78249423Sdim	    rad_put_string(radh, RAD_NAS_IDENTIFIER, host) == -1)) {
79226586Sdim		syslog(LOG_CRIT, "rad_put_string: %s", rad_strerror(radh));
80226586Sdim		return -1;
81226586Sdim	}
82234353Sdim	if (state != NULL && rad_put_attr(radh, RAD_STATE, state,
83234353Sdim	    state_len) == -1) {
84234353Sdim		syslog(LOG_CRIT, "rad_put_attr: %s", rad_strerror(radh));
85234353Sdim		return -1;
86234353Sdim	}
87234353Sdim	if (rad_put_int(radh, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY) == -1) {
88234353Sdim		syslog(LOG_CRIT, "rad_put_int: %s", rad_strerror(radh));
89234353Sdim		return -1;
90234353Sdim	}
91249423Sdim	return 0;
92249423Sdim}
93226586Sdim
94226586Sdimstatic int
95226586Sdimdo_accept(pam_handle_t *pamh, struct rad_handle *radh)
96226586Sdim{
97226586Sdim	int attrtype;
98226586Sdim	const void *attrval;
99226586Sdim	size_t attrlen;
100239462Sdim	char *s;
101239462Sdim
102226586Sdim	while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) {
103226586Sdim		if (attrtype == RAD_USER_NAME) {
104226586Sdim			s = rad_cvt_string(attrval, attrlen);
105226586Sdim			if (s == NULL) {
106226586Sdim				syslog(LOG_CRIT,
107226586Sdim				    "rad_cvt_string: out of memory");
108243830Sdim				return -1;
109243830Sdim			}
110243830Sdim			pam_set_item(pamh, PAM_USER, s);
111243830Sdim			free(s);
112243830Sdim		}
113243830Sdim	}
114243830Sdim	if (attrtype == -1) {
115243830Sdim		syslog(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh));
116249423Sdim		return -1;
117249423Sdim	}
118249423Sdim	return 0;
119249423Sdim}
120243830Sdim
121243830Sdimstatic int
122249423Sdimdo_challenge(pam_handle_t *pamh, struct rad_handle *radh, const char *user)
123249423Sdim{
124249423Sdim	int retval;
125249423Sdim	int attrtype;
126243830Sdim	const void *attrval;
127243830Sdim	size_t attrlen;
128226586Sdim	const void *state;
129226586Sdim	size_t statelen;
130226586Sdim	struct pam_message msgs[MAX_CHALLENGE_MSGS];
131226586Sdim	const struct pam_message *msg_ptrs[MAX_CHALLENGE_MSGS];
132226586Sdim	struct pam_response *resp;
133226586Sdim	int num_msgs;
134226586Sdim	const void *item;
135226586Sdim	const struct pam_conv *conv;
136226586Sdim
137226586Sdim	state = NULL;
138226586Sdim	statelen = 0;
139226586Sdim	num_msgs = 0;
140226586Sdim	while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) {
141243830Sdim		switch (attrtype) {
142243830Sdim
143243830Sdim		case RAD_STATE:
144243830Sdim			state = attrval;
145243830Sdim			statelen = attrlen;
146243830Sdim			break;
147243830Sdim
148243830Sdim		case RAD_REPLY_MESSAGE:
149243830Sdim			if (num_msgs >= MAX_CHALLENGE_MSGS) {
150243830Sdim				syslog(LOG_CRIT,
151243830Sdim				    "Too many RADIUS challenge messages");
152243830Sdim				return PAM_SERVICE_ERR;
153243830Sdim			}
154243830Sdim			msgs[num_msgs].msg = rad_cvt_string(attrval, attrlen);
155243830Sdim			if (msgs[num_msgs].msg == NULL) {
156243830Sdim				syslog(LOG_CRIT,
157243830Sdim				    "rad_cvt_string: out of memory");
158243830Sdim				return PAM_SERVICE_ERR;
159243830Sdim			}
160243830Sdim			msgs[num_msgs].msg_style = PAM_TEXT_INFO;
161249423Sdim			msg_ptrs[num_msgs] = &msgs[num_msgs];
162249423Sdim			num_msgs++;
163249423Sdim			break;
164243830Sdim		}
165243830Sdim	}
166243830Sdim	if (attrtype == -1) {
167243830Sdim		syslog(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh));
168243830Sdim		return PAM_SERVICE_ERR;
169243830Sdim	}
170243830Sdim	if (num_msgs == 0) {
171243830Sdim		msgs[num_msgs].msg = strdup("(null RADIUS challenge): ");
172243830Sdim		if (msgs[num_msgs].msg == NULL) {
173243830Sdim			syslog(LOG_CRIT, "Out of memory");
174243830Sdim			return PAM_SERVICE_ERR;
175243830Sdim		}
176249423Sdim		msgs[num_msgs].msg_style = PAM_TEXT_INFO;
177249423Sdim		msg_ptrs[num_msgs] = &msgs[num_msgs];
178249423Sdim		num_msgs++;
179243830Sdim	}
180243830Sdim	msgs[num_msgs-1].msg_style = PAM_PROMPT_ECHO_ON;
181243830Sdim	if ((retval = pam_get_item(pamh, PAM_CONV, &item)) != PAM_SUCCESS) {
182243830Sdim		syslog(LOG_CRIT, "do_challenge: cannot get PAM_CONV");
183243830Sdim		return retval;
184243830Sdim	}
185243830Sdim	conv = (const struct pam_conv *)item;
186243830Sdim	if ((retval = conv->conv(num_msgs, msg_ptrs, &resp,
187243830Sdim	    conv->appdata_ptr)) != PAM_SUCCESS)
188243830Sdim		return retval;
189243830Sdim	if (build_access_request(radh, user, resp[num_msgs-1].resp, state,
190243830Sdim	    statelen) == -1)
191243830Sdim		return PAM_SERVICE_ERR;
192243830Sdim	memset(resp[num_msgs-1].resp, 0, strlen(resp[num_msgs-1].resp));
193243830Sdim	free(resp[num_msgs-1].resp);
194243830Sdim	free(resp);
195243830Sdim	while (num_msgs > 0)
196243830Sdim		free((void *)msgs[--num_msgs].msg);
197243830Sdim	return PAM_SUCCESS;
198243830Sdim}
199243830Sdim
200243830SdimPAM_EXTERN int
201243830Sdimpam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
202243830Sdim{
203243830Sdim	struct options options;
204243830Sdim	struct rad_handle *radh;
205243830Sdim	const char *user, *tmpuser, *pass;
206243830Sdim	char *conf_file, *template_user;
207249423Sdim	int retval;
208249423Sdim	int e;
209249423Sdim
210243830Sdim	pam_std_option(&options, other_options, argc, argv);
211243830Sdim
212243830Sdim	PAM_LOG("Options processed");
213249423Sdim
214249423Sdim	conf_file = NULL;
215249423Sdim	pam_test_option(&options, PAM_OPT_CONF, &conf_file);
216243830Sdim	template_user = NULL;
217243830Sdim	pam_test_option(&options, PAM_OPT_TEMPLATE_USER, &template_user);
218243830Sdim
219243830Sdim	retval = pam_get_user(pamh, &user, NULL);
220243830Sdim	if (retval != PAM_SUCCESS)
221243830Sdim		PAM_RETURN(retval);
222243830Sdim
223243830Sdim	PAM_LOG("Got user: %s", user);
224243830Sdim
225243830Sdim	retval = pam_get_pass(pamh, &pass, PASSWORD_PROMPT, &options);
226243830Sdim	if (retval != PAM_SUCCESS)
227243830Sdim		PAM_RETURN(retval);
228243830Sdim
229243830Sdim	PAM_LOG("Got password");
230243830Sdim
231243830Sdim	radh = rad_open();
232226586Sdim	if (radh == NULL) {
233243830Sdim		syslog(LOG_CRIT, "rad_open failed");
234234353Sdim		PAM_RETURN(PAM_SERVICE_ERR);
235243830Sdim	}
236226586Sdim
237226586Sdim	PAM_LOG("Radius opened");
238226586Sdim
239226586Sdim	if (rad_config(radh, conf_file) == -1) {
240226586Sdim		syslog(LOG_ALERT, "rad_config: %s", rad_strerror(radh));
241226586Sdim		rad_close(radh);
242243830Sdim		PAM_RETURN(PAM_SERVICE_ERR);
243226586Sdim	}
244
245	PAM_LOG("Radius config file read");
246
247	if (build_access_request(radh, user, pass, NULL, 0) == -1) {
248		rad_close(radh);
249		PAM_RETURN(PAM_SERVICE_ERR);
250	}
251
252	PAM_LOG("Radius build access done");
253
254	for ( ; ; ) {
255		switch (rad_send_request(radh)) {
256
257		case RAD_ACCESS_ACCEPT:
258			e = do_accept(pamh, radh);
259			rad_close(radh);
260			if (e == -1)
261				PAM_RETURN(PAM_SERVICE_ERR);
262			if (template_user != NULL) {
263
264				PAM_LOG("Trying template user: %s",
265				    template_user);
266
267				/*
268				 * If the given user name doesn't exist in
269				 * the local password database, change it
270				 * to the value given in the "template_user"
271				 * option.
272				 */
273				retval = pam_get_item(pamh, PAM_USER,
274				    (void *)&tmpuser);
275				if (retval != PAM_SUCCESS)
276					PAM_RETURN(retval);
277				if (getpwnam(tmpuser) == NULL) {
278					pam_set_item(pamh, PAM_USER,
279					    template_user);
280					PAM_LOG("Using template user");
281				}
282
283			}
284			PAM_RETURN(PAM_SUCCESS);
285
286		case RAD_ACCESS_REJECT:
287			rad_close(radh);
288			PAM_VERBOSE_ERROR("Radius rejection");
289			PAM_RETURN(PAM_AUTH_ERR);
290
291		case RAD_ACCESS_CHALLENGE:
292			retval = do_challenge(pamh, radh, user);
293			if (retval != PAM_SUCCESS) {
294				rad_close(radh);
295				PAM_RETURN(retval);
296			}
297			break;
298
299		case -1:
300			syslog(LOG_CRIT, "rad_send_request: %s",
301			    rad_strerror(radh));
302			rad_close(radh);
303			PAM_VERBOSE_ERROR("Radius failure");
304			PAM_RETURN(PAM_AUTHINFO_UNAVAIL);
305
306		default:
307			syslog(LOG_CRIT,
308			    "rad_send_request: unexpected return value");
309			rad_close(radh);
310			PAM_VERBOSE_ERROR("Radius error");
311			PAM_RETURN(PAM_SERVICE_ERR);
312		}
313	}
314}
315
316PAM_EXTERN int
317pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
318{
319	struct options options;
320
321	pam_std_option(&options, NULL, argc, argv);
322
323	PAM_LOG("Options processed");
324
325	PAM_RETURN(PAM_SUCCESS);
326}
327
328PAM_MODULE_ENTRY("pam_radius");
329