pam_radius.c revision 122589
1193326Sed/*-
2193326Sed * Copyright 1998 Juniper Networks, Inc.
3193326Sed * All rights reserved.
4193326Sed * Copyright (c) 2001-2003 Networks Associates Technology, Inc.
5193326Sed * All rights reserved.
6193326Sed *
7193326Sed * Portions of this software were developed for the FreeBSD Project by
8193326Sed * ThinkSec AS and NAI Labs, the Security Research Division of Network
9193326Sed * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
10193326Sed * ("CBOSS"), as part of the DARPA CHATS research program.
11193326Sed *
12193326Sed * Redistribution and use in source and binary forms, with or without
13193326Sed * modification, are permitted provided that the following conditions
14193326Sed * are met:
15193326Sed * 1. Redistributions of source code must retain the above copyright
16198092Srdivacky *    notice, this list of conditions and the following disclaimer.
17193326Sed * 2. Redistributions in binary form must reproduce the above copyright
18193326Sed *    notice, this list of conditions and the following disclaimer in the
19198092Srdivacky *    documentation and/or other materials provided with the distribution.
20198398Srdivacky * 3. The name of the author may not be used to endorse or promote
21193326Sed *    products derived from this software without specific prior written
22198092Srdivacky *    permission.
23193326Sed *
24195341Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25193326Sed * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26193326Sed * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27193326Sed * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28193326Sed * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29193326Sed * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30198092Srdivacky * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31198092Srdivacky * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32193326Sed * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33193326Sed * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34198893Srdivacky * SUCH DAMAGE.
35198893Srdivacky */
36198893Srdivacky
37198893Srdivacky#include <sys/cdefs.h>
38198893Srdivacky__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_radius/pam_radius.c 122589 2003-11-12 23:36:17Z sobomax $");
39193326Sed
40198893Srdivacky#include <sys/param.h>
41198893Srdivacky#include <sys/types.h>
42198893Srdivacky#include <sys/socket.h>
43198893Srdivacky#include <netdb.h>
44198893Srdivacky#include <pwd.h>
45198893Srdivacky#include <radlib.h>
46193326Sed#include <stdlib.h>
47193326Sed#include <string.h>
48193326Sed#include <syslog.h>
49193326Sed#include <unistd.h>
50193326Sed
51198893Srdivacky#define PAM_SM_AUTH
52198893Srdivacky
53198893Srdivacky#include <security/pam_appl.h>
54198893Srdivacky#include <security/pam_modules.h>
55198893Srdivacky#include <security/pam_mod_misc.h>
56198893Srdivacky
57198893Srdivacky#define PAM_OPT_CONF		"conf"
58198893Srdivacky#define PAM_OPT_TEMPLATE_USER	"template_user"
59198893Srdivacky#define PAM_OPT_NAS_ID		"nas_id"
60198893Srdivacky#define PAM_OPT_NAS_IPADDR	"nas_ipaddr"
61198893Srdivacky
62198893Srdivacky#define	MAX_CHALLENGE_MSGS	10
63198893Srdivacky#define	PASSWORD_PROMPT		"RADIUS Password:"
64198893Srdivacky
65198893Srdivackystatic int	 build_access_request(struct rad_handle *, const char *,
66198893Srdivacky		    const char *, const char *, const char *, const void *,
67198893Srdivacky		    size_t);
68198893Srdivackystatic int	 do_accept(pam_handle_t *, struct rad_handle *);
69198893Srdivackystatic int	 do_challenge(pam_handle_t *, struct rad_handle *,
70193326Sed		    const char *);
71193326Sed
72198893Srdivacky/*
73193326Sed * Construct an access request, but don't send it.  Returns 0 on success,
74193326Sed * -1 on failure.
75198893Srdivacky */
76193326Sedstatic int
77193326Sedbuild_access_request(struct rad_handle *radh, const char *user,
78198893Srdivacky    const char *pass, const char *nas_id, const char *nas_ipaddr,
79198893Srdivacky    const void *state, size_t state_len)
80198893Srdivacky{
81198893Srdivacky	int error;
82198893Srdivacky	char host[MAXHOSTNAMELEN];
83198893Srdivacky	struct sockaddr_in *haddr;
84198893Srdivacky	struct addrinfo hints;
85193326Sed	struct addrinfo *res;
86193326Sed
87193326Sed	if (rad_create_request(radh, RAD_ACCESS_REQUEST) == -1) {
88193326Sed		syslog(LOG_CRIT, "rad_create_request: %s", rad_strerror(radh));
89193326Sed		return (-1);
90193326Sed	}
91193326Sed	if (nas_id == NULL ||
92193326Sed	    (nas_ipaddr != NULL && strlen(nas_ipaddr) == 0)) {
93193326Sed		if (gethostname(host, sizeof host) != -1) {
94193326Sed			if (nas_id == NULL)
95193326Sed				nas_id = host;
96193326Sed			if (nas_ipaddr != NULL && strlen(nas_ipaddr) == 0)
97193326Sed				nas_ipaddr = host;
98193326Sed		}
99193326Sed	}
100193326Sed	if ((user != NULL &&
101193326Sed	    rad_put_string(radh, RAD_USER_NAME, user) == -1) ||
102193326Sed	    (pass != NULL &&
103193326Sed	    rad_put_string(radh, RAD_USER_PASSWORD, pass) == -1) ||
104193326Sed	    (nas_id != NULL &&
105198893Srdivacky	    rad_put_string(radh, RAD_NAS_IDENTIFIER, nas_id) == -1)) {
106193326Sed		syslog(LOG_CRIT, "rad_put_string: %s", rad_strerror(radh));
107193326Sed		return (-1);
108193326Sed	}
109193326Sed	if (nas_ipaddr != NULL) {
110193326Sed		memset(&hints, 0, sizeof(hints));
111198893Srdivacky		hints.ai_family = PF_INET;
112193326Sed		if (getaddrinfo(nas_ipaddr, NULL, &hints, &res) == 0 &&
113193326Sed		    res != NULL) {
114193326Sed			(struct sockaddr *)haddr = res->ai_addr;
115193326Sed			error = rad_put_addr(radh, RAD_NAS_IP_ADDRESS,
116198092Srdivacky			    haddr->sin_addr);
117198092Srdivacky			freeaddrinfo(res);
118198092Srdivacky			if (error == -1) {
119198092Srdivacky				syslog(LOG_CRIT, "rad_put_addr: %s",
120198092Srdivacky				    rad_strerror(radh));
121198092Srdivacky				return (-1);
122198092Srdivacky			}
123198092Srdivacky		}
124198092Srdivacky	}
125198092Srdivacky	if (state != NULL && rad_put_attr(radh, RAD_STATE, state,
126193326Sed	    state_len) == -1) {
127193326Sed		syslog(LOG_CRIT, "rad_put_attr: %s", rad_strerror(radh));
128193326Sed		return (-1);
129198092Srdivacky	}
130198092Srdivacky	if (rad_put_int(radh, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY) == -1) {
131195341Sed		syslog(LOG_CRIT, "rad_put_int: %s", rad_strerror(radh));
132193326Sed		return (-1);
133193326Sed	}
134198893Srdivacky	return (0);
135198893Srdivacky}
136198893Srdivacky
137198893Srdivackystatic int
138198893Srdivackydo_accept(pam_handle_t *pamh, struct rad_handle *radh)
139198893Srdivacky{
140198893Srdivacky	int attrtype;
141198092Srdivacky	const void *attrval;
142193326Sed	size_t attrlen;
143193326Sed	char *s;
144193326Sed
145193326Sed	while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) {
146193326Sed		if (attrtype == RAD_USER_NAME) {
147193326Sed			s = rad_cvt_string(attrval, attrlen);
148193326Sed			if (s == NULL) {
149198893Srdivacky				syslog(LOG_CRIT,
150193326Sed				    "rad_cvt_string: out of memory");
151193326Sed				return (-1);
152193326Sed			}
153198893Srdivacky			pam_set_item(pamh, PAM_USER, s);
154193326Sed			free(s);
155193326Sed		}
156193326Sed	}
157193326Sed	if (attrtype == -1) {
158193326Sed		syslog(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh));
159193326Sed		return (-1);
160193326Sed	}
161193326Sed	return (0);
162193326Sed}
163193326Sed
164198893Srdivackystatic int
165198893Srdivackydo_challenge(pam_handle_t *pamh, struct rad_handle *radh, const char *user)
166198893Srdivacky{
167193326Sed	int retval;
168198092Srdivacky	int attrtype;
169195099Sed	const void *attrval;
170195099Sed	size_t attrlen;
171195099Sed	const void *state;
172198893Srdivacky	size_t statelen;
173195099Sed	struct pam_message msgs[MAX_CHALLENGE_MSGS];
174198893Srdivacky	const struct pam_message *msg_ptrs[MAX_CHALLENGE_MSGS];
175193326Sed	struct pam_response *resp;
176195099Sed	int num_msgs;
177193326Sed	const void *item;
178198092Srdivacky	const struct pam_conv *conv;
179198092Srdivacky
180193326Sed	state = NULL;
181193326Sed	statelen = 0;
182193326Sed	num_msgs = 0;
183193326Sed	while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) {
184193326Sed		switch (attrtype) {
185193326Sed
186198893Srdivacky		case RAD_STATE:
187198893Srdivacky			state = attrval;
188198893Srdivacky			statelen = attrlen;
189198893Srdivacky			break;
190198893Srdivacky
191198893Srdivacky		case RAD_REPLY_MESSAGE:
192198893Srdivacky			if (num_msgs >= MAX_CHALLENGE_MSGS) {
193198893Srdivacky				syslog(LOG_CRIT,
194193326Sed				    "Too many RADIUS challenge messages");
195193326Sed				return (PAM_SERVICE_ERR);
196193326Sed			}
197193326Sed			msgs[num_msgs].msg = rad_cvt_string(attrval, attrlen);
198193326Sed			if (msgs[num_msgs].msg == NULL) {
199193326Sed				syslog(LOG_CRIT,
200198893Srdivacky				    "rad_cvt_string: out of memory");
201198893Srdivacky				return (PAM_SERVICE_ERR);
202198893Srdivacky			}
203198893Srdivacky			msgs[num_msgs].msg_style = PAM_TEXT_INFO;
204198893Srdivacky			msg_ptrs[num_msgs] = &msgs[num_msgs];
205198893Srdivacky			num_msgs++;
206198893Srdivacky			break;
207198893Srdivacky		}
208193326Sed	}
209193326Sed	if (attrtype == -1) {
210193326Sed		syslog(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh));
211193326Sed		return (PAM_SERVICE_ERR);
212193326Sed	}
213193326Sed	if (num_msgs == 0) {
214193326Sed		msgs[num_msgs].msg = strdup("(null RADIUS challenge): ");
215193326Sed		if (msgs[num_msgs].msg == NULL) {
216193326Sed			syslog(LOG_CRIT, "Out of memory");
217193326Sed			return (PAM_SERVICE_ERR);
218193326Sed		}
219193326Sed		msgs[num_msgs].msg_style = PAM_TEXT_INFO;
220193326Sed		msg_ptrs[num_msgs] = &msgs[num_msgs];
221193326Sed		num_msgs++;
222193326Sed	}
223198893Srdivacky	msgs[num_msgs-1].msg_style = PAM_PROMPT_ECHO_ON;
224193326Sed	if ((retval = pam_get_item(pamh, PAM_CONV, &item)) != PAM_SUCCESS) {
225198893Srdivacky		syslog(LOG_CRIT, "do_challenge: cannot get PAM_CONV");
226193326Sed		return (retval);
227193326Sed	}
228193326Sed	conv = (const struct pam_conv *)item;
229193326Sed	if ((retval = conv->conv(num_msgs, msg_ptrs, &resp,
230193326Sed	    conv->appdata_ptr)) != PAM_SUCCESS)
231198893Srdivacky		return (retval);
232198092Srdivacky	if (build_access_request(radh, user, resp[num_msgs-1].resp, NULL,
233198092Srdivacky	    NULL, state, statelen) == -1)
234198092Srdivacky		return (PAM_SERVICE_ERR);
235198893Srdivacky	memset(resp[num_msgs-1].resp, 0, strlen(resp[num_msgs-1].resp));
236198092Srdivacky	free(resp[num_msgs-1].resp);
237198092Srdivacky	free(resp);
238198092Srdivacky	while (num_msgs > 0)
239198893Srdivacky		free(msgs[--num_msgs].msg);
240198893Srdivacky	return (PAM_SUCCESS);
241198893Srdivacky}
242193326Sed
243198893SrdivackyPAM_EXTERN int
244198893Srdivackypam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
245193326Sed    int argc __unused, const char *argv[] __unused)
246198893Srdivacky{
247198092Srdivacky	struct rad_handle *radh;
248198092Srdivacky	const char *user, *tmpuser, *pass;
249198893Srdivacky	const char *conf_file, *template_user, *nas_id, *nas_ipaddr;
250198092Srdivacky	int retval;
251198092Srdivacky	int e;
252198092Srdivacky
253198092Srdivacky	conf_file = openpam_get_option(pamh, PAM_OPT_CONF);
254198092Srdivacky	template_user = openpam_get_option(pamh, PAM_OPT_TEMPLATE_USER);
255193326Sed	nas_id = openpam_get_option(pamh, PAM_OPT_NAS_ID);
256198893Srdivacky	nas_ipaddr = openpam_get_option(pamh, PAM_OPT_NAS_IPADDR);
257193326Sed
258198092Srdivacky	retval = pam_get_user(pamh, &user, NULL);
259193326Sed	if (retval != PAM_SUCCESS)
260193326Sed		return (retval);
261193326Sed
262193326Sed	PAM_LOG("Got user: %s", user);
263198893Srdivacky
264193326Sed	retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT);
265193326Sed	if (retval != PAM_SUCCESS)
266198092Srdivacky		return (retval);
267198092Srdivacky
268198092Srdivacky	PAM_LOG("Got password");
269198092Srdivacky
270198092Srdivacky	radh = rad_open();
271198092Srdivacky	if (radh == NULL) {
272198092Srdivacky		syslog(LOG_CRIT, "rad_open failed");
273198092Srdivacky		return (PAM_SERVICE_ERR);
274198092Srdivacky	}
275198092Srdivacky
276198092Srdivacky	PAM_LOG("Radius opened");
277198092Srdivacky
278198092Srdivacky	if (rad_config(radh, conf_file) == -1) {
279198092Srdivacky		syslog(LOG_ALERT, "rad_config: %s", rad_strerror(radh));
280198092Srdivacky		rad_close(radh);
281193326Sed		return (PAM_SERVICE_ERR);
282198092Srdivacky	}
283198092Srdivacky
284198092Srdivacky	PAM_LOG("Radius config file read");
285193326Sed
286198092Srdivacky	if (build_access_request(radh, user, pass, nas_id, nas_ipaddr, NULL,
287198092Srdivacky	    0) == -1) {
288193326Sed		rad_close(radh);
289198893Srdivacky		return (PAM_SERVICE_ERR);
290193326Sed	}
291198893Srdivacky
292193326Sed	PAM_LOG("Radius build access done");
293193326Sed
294198092Srdivacky	for (;;) {
295193326Sed		switch (rad_send_request(radh)) {
296193326Sed
297193326Sed		case RAD_ACCESS_ACCEPT:
298193326Sed			e = do_accept(pamh, radh);
299198092Srdivacky			rad_close(radh);
300198893Srdivacky			if (e == -1)
301193326Sed				return (PAM_SERVICE_ERR);
302193326Sed			if (template_user != NULL) {
303193326Sed
304193326Sed				PAM_LOG("Trying template user: %s",
305193326Sed				    template_user);
306193326Sed
307193326Sed				/*
308193326Sed				 * If the given user name doesn't exist in
309193326Sed				 * the local password database, change it
310193326Sed				 * to the value given in the "template_user"
311193326Sed				 * option.
312195099Sed				 */
313195099Sed				retval = pam_get_item(pamh, PAM_USER,
314195099Sed				    (const void **)&tmpuser);
315195099Sed				if (retval != PAM_SUCCESS)
316198893Srdivacky					return (retval);
317195341Sed				if (getpwnam(tmpuser) == NULL) {
318195341Sed					pam_set_item(pamh, PAM_USER,
319198893Srdivacky					    template_user);
320195341Sed					PAM_LOG("Using template user");
321195099Sed				}
322195099Sed
323195099Sed			}
324195099Sed			return (PAM_SUCCESS);
325195099Sed
326195099Sed		case RAD_ACCESS_REJECT:
327195099Sed			rad_close(radh);
328198092Srdivacky			PAM_VERBOSE_ERROR("Radius rejection");
329193326Sed			return (PAM_AUTH_ERR);
330193326Sed
331198893Srdivacky		case RAD_ACCESS_CHALLENGE:
332193326Sed			retval = do_challenge(pamh, radh, user);
333193326Sed			if (retval != PAM_SUCCESS) {
334198092Srdivacky				rad_close(radh);
335193326Sed				return (retval);
336193326Sed			}
337198893Srdivacky			break;
338198893Srdivacky
339193326Sed		case -1:
340193326Sed			syslog(LOG_CRIT, "rad_send_request: %s",
341198092Srdivacky			    rad_strerror(radh));
342193326Sed			rad_close(radh);
343193326Sed			PAM_VERBOSE_ERROR("Radius failure");
344198092Srdivacky			return (PAM_AUTHINFO_UNAVAIL);
345193326Sed
346193326Sed		default:
347193326Sed			syslog(LOG_CRIT,
348198893Srdivacky			    "rad_send_request: unexpected return value");
349198092Srdivacky			rad_close(radh);
350193326Sed			PAM_VERBOSE_ERROR("Radius error");
351193326Sed			return (PAM_SERVICE_ERR);
352193326Sed		}
353193326Sed	}
354193326Sed}
355193326Sed
356198092SrdivackyPAM_EXTERN int
357193326Sedpam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
358198092Srdivacky    int argc __unused, const char *argv[] __unused)
359198092Srdivacky{
360198092Srdivacky
361198092Srdivacky	return (PAM_SUCCESS);
362193326Sed}
363193326Sed
364193326SedPAM_MODULE_ENTRY("pam_radius");
365198893Srdivacky