141227Sjdp/*- 241227Sjdp * Copyright 1998 Juniper Networks, Inc. 341227Sjdp * All rights reserved. 4115470Sdes * Copyright (c) 2001-2003 Networks Associates Technology, Inc. 587398Sdes * All rights reserved. 641227Sjdp * 787398Sdes * Portions of this software were developed for the FreeBSD Project by 887398Sdes * ThinkSec AS and NAI Labs, the Security Research Division of Network 987398Sdes * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 1087398Sdes * ("CBOSS"), as part of the DARPA CHATS research program. 1187398Sdes * 1241227Sjdp * Redistribution and use in source and binary forms, with or without 1341227Sjdp * modification, are permitted provided that the following conditions 1441227Sjdp * are met: 1541227Sjdp * 1. Redistributions of source code must retain the above copyright 1641227Sjdp * notice, this list of conditions and the following disclaimer. 1741227Sjdp * 2. Redistributions in binary form must reproduce the above copyright 1841227Sjdp * notice, this list of conditions and the following disclaimer in the 1941227Sjdp * documentation and/or other materials provided with the distribution. 2087398Sdes * 3. The name of the author may not be used to endorse or promote 2187398Sdes * products derived from this software without specific prior written 2287398Sdes * permission. 2341227Sjdp * 2441227Sjdp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 2541227Sjdp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2641227Sjdp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2741227Sjdp * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2841227Sjdp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2941227Sjdp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 3041227Sjdp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 3141227Sjdp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 3241227Sjdp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3341227Sjdp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3441227Sjdp * SUCH DAMAGE. 3541227Sjdp */ 3641227Sjdp 3784218Sdillon#include <sys/cdefs.h> 3884218Sdillon__FBSDID("$FreeBSD$"); 3984218Sdillon 4041227Sjdp#include <sys/param.h> 41122571Ssobomax#include <sys/socket.h> 42122571Ssobomax#include <netdb.h> 4341227Sjdp#include <pwd.h> 4441227Sjdp#include <radlib.h> 4541227Sjdp#include <stdlib.h> 4641227Sjdp#include <string.h> 4741227Sjdp#include <syslog.h> 4841227Sjdp#include <unistd.h> 4941227Sjdp 5041227Sjdp#define PAM_SM_AUTH 5187398Sdes 5290229Sdes#include <security/pam_appl.h> 5341227Sjdp#include <security/pam_modules.h> 5490229Sdes#include <security/pam_mod_misc.h> 5541227Sjdp 56115465Sdes#define PAM_OPT_CONF "conf" 57115465Sdes#define PAM_OPT_TEMPLATE_USER "template_user" 58115465Sdes#define PAM_OPT_NAS_ID "nas_id" 59122571Ssobomax#define PAM_OPT_NAS_IPADDR "nas_ipaddr" 6041227Sjdp 6179476Smarkm#define MAX_CHALLENGE_MSGS 10 6293984Sdes#define PASSWORD_PROMPT "RADIUS Password:" 6379476Smarkm 6441227Sjdpstatic int build_access_request(struct rad_handle *, const char *, 65122571Ssobomax const char *, const char *, const char *, const void *, 66122571Ssobomax size_t); 6741227Sjdpstatic int do_accept(pam_handle_t *, struct rad_handle *); 6841227Sjdpstatic int do_challenge(pam_handle_t *, struct rad_handle *, 69166136Spjd const char *, const char *, const char *); 7041227Sjdp 7141227Sjdp/* 7241227Sjdp * Construct an access request, but don't send it. Returns 0 on success, 7341227Sjdp * -1 on failure. 7441227Sjdp */ 7541227Sjdpstatic int 7641227Sjdpbuild_access_request(struct rad_handle *radh, const char *user, 77122571Ssobomax const char *pass, const char *nas_id, const char *nas_ipaddr, 78122571Ssobomax const void *state, size_t state_len) 7941227Sjdp{ 80122571Ssobomax int error; 81122571Ssobomax char host[MAXHOSTNAMELEN]; 82122571Ssobomax struct sockaddr_in *haddr; 83122571Ssobomax struct addrinfo hints; 84122571Ssobomax struct addrinfo *res; 8541227Sjdp 8641227Sjdp if (rad_create_request(radh, RAD_ACCESS_REQUEST) == -1) { 8741227Sjdp syslog(LOG_CRIT, "rad_create_request: %s", rad_strerror(radh)); 8894564Sdes return (-1); 8941227Sjdp } 90122571Ssobomax if (nas_id == NULL || 91122571Ssobomax (nas_ipaddr != NULL && strlen(nas_ipaddr) == 0)) { 92122571Ssobomax if (gethostname(host, sizeof host) != -1) { 93122571Ssobomax if (nas_id == NULL) 94122571Ssobomax nas_id = host; 95122571Ssobomax if (nas_ipaddr != NULL && strlen(nas_ipaddr) == 0) 96122571Ssobomax nas_ipaddr = host; 97122571Ssobomax } 98122571Ssobomax } 9941227Sjdp if ((user != NULL && 10041227Sjdp rad_put_string(radh, RAD_USER_NAME, user) == -1) || 10141227Sjdp (pass != NULL && 10241227Sjdp rad_put_string(radh, RAD_USER_PASSWORD, pass) == -1) || 103106082Sdes (nas_id != NULL && 104106082Sdes rad_put_string(radh, RAD_NAS_IDENTIFIER, nas_id) == -1)) { 10541227Sjdp syslog(LOG_CRIT, "rad_put_string: %s", rad_strerror(radh)); 10694564Sdes return (-1); 10741227Sjdp } 108122571Ssobomax if (nas_ipaddr != NULL) { 109122571Ssobomax memset(&hints, 0, sizeof(hints)); 110147350Sdes hints.ai_family = AF_INET; 111122571Ssobomax if (getaddrinfo(nas_ipaddr, NULL, &hints, &res) == 0 && 112147350Sdes res != NULL && res->ai_family == AF_INET) { 113147350Sdes haddr = (struct sockaddr_in *)res->ai_addr; 114122571Ssobomax error = rad_put_addr(radh, RAD_NAS_IP_ADDRESS, 115122571Ssobomax haddr->sin_addr); 116122571Ssobomax freeaddrinfo(res); 117122571Ssobomax if (error == -1) { 118122571Ssobomax syslog(LOG_CRIT, "rad_put_addr: %s", 119122571Ssobomax rad_strerror(radh)); 120122571Ssobomax return (-1); 121122571Ssobomax } 122122571Ssobomax } 123122571Ssobomax } 12441227Sjdp if (state != NULL && rad_put_attr(radh, RAD_STATE, state, 12541227Sjdp state_len) == -1) { 12641227Sjdp syslog(LOG_CRIT, "rad_put_attr: %s", rad_strerror(radh)); 12794564Sdes return (-1); 12841227Sjdp } 12941227Sjdp if (rad_put_int(radh, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY) == -1) { 13041227Sjdp syslog(LOG_CRIT, "rad_put_int: %s", rad_strerror(radh)); 13194564Sdes return (-1); 13241227Sjdp } 13394564Sdes return (0); 13441227Sjdp} 13541227Sjdp 13641227Sjdpstatic int 13741227Sjdpdo_accept(pam_handle_t *pamh, struct rad_handle *radh) 13841227Sjdp{ 13941227Sjdp int attrtype; 14041227Sjdp const void *attrval; 14141227Sjdp size_t attrlen; 14241227Sjdp char *s; 14341227Sjdp 14441227Sjdp while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) { 14541227Sjdp if (attrtype == RAD_USER_NAME) { 14641227Sjdp s = rad_cvt_string(attrval, attrlen); 14741227Sjdp if (s == NULL) { 14841227Sjdp syslog(LOG_CRIT, 14941227Sjdp "rad_cvt_string: out of memory"); 15094564Sdes return (-1); 15141227Sjdp } 15241227Sjdp pam_set_item(pamh, PAM_USER, s); 15341227Sjdp free(s); 15441227Sjdp } 15541227Sjdp } 15641227Sjdp if (attrtype == -1) { 15741227Sjdp syslog(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh)); 15894564Sdes return (-1); 15941227Sjdp } 16094564Sdes return (0); 16141227Sjdp} 16241227Sjdp 16341227Sjdpstatic int 164166136Spjddo_challenge(pam_handle_t *pamh, struct rad_handle *radh, const char *user, 165166136Spjd const char *nas_id, const char *nas_ipaddr) 16641227Sjdp{ 16741227Sjdp int retval; 16841227Sjdp int attrtype; 16941227Sjdp const void *attrval; 17041227Sjdp size_t attrlen; 17141227Sjdp const void *state; 17241227Sjdp size_t statelen; 17341227Sjdp struct pam_message msgs[MAX_CHALLENGE_MSGS]; 17441227Sjdp const struct pam_message *msg_ptrs[MAX_CHALLENGE_MSGS]; 17541227Sjdp struct pam_response *resp; 17641227Sjdp int num_msgs; 17741227Sjdp const void *item; 17841227Sjdp const struct pam_conv *conv; 17941227Sjdp 18041227Sjdp state = NULL; 18141227Sjdp statelen = 0; 18241227Sjdp num_msgs = 0; 18341227Sjdp while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) { 18441227Sjdp switch (attrtype) { 18541227Sjdp 18641227Sjdp case RAD_STATE: 18741227Sjdp state = attrval; 18841227Sjdp statelen = attrlen; 18941227Sjdp break; 19041227Sjdp 19141227Sjdp case RAD_REPLY_MESSAGE: 19241227Sjdp if (num_msgs >= MAX_CHALLENGE_MSGS) { 19341227Sjdp syslog(LOG_CRIT, 19441227Sjdp "Too many RADIUS challenge messages"); 19594564Sdes return (PAM_SERVICE_ERR); 19641227Sjdp } 19741227Sjdp msgs[num_msgs].msg = rad_cvt_string(attrval, attrlen); 19841227Sjdp if (msgs[num_msgs].msg == NULL) { 19941227Sjdp syslog(LOG_CRIT, 20041227Sjdp "rad_cvt_string: out of memory"); 20194564Sdes return (PAM_SERVICE_ERR); 20241227Sjdp } 20341227Sjdp msgs[num_msgs].msg_style = PAM_TEXT_INFO; 20441227Sjdp msg_ptrs[num_msgs] = &msgs[num_msgs]; 20541227Sjdp num_msgs++; 20641227Sjdp break; 20741227Sjdp } 20841227Sjdp } 20941227Sjdp if (attrtype == -1) { 21041227Sjdp syslog(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh)); 21194564Sdes return (PAM_SERVICE_ERR); 21241227Sjdp } 21341227Sjdp if (num_msgs == 0) { 21441227Sjdp msgs[num_msgs].msg = strdup("(null RADIUS challenge): "); 21541227Sjdp if (msgs[num_msgs].msg == NULL) { 21641227Sjdp syslog(LOG_CRIT, "Out of memory"); 21794564Sdes return (PAM_SERVICE_ERR); 21841227Sjdp } 21941227Sjdp msgs[num_msgs].msg_style = PAM_TEXT_INFO; 22041227Sjdp msg_ptrs[num_msgs] = &msgs[num_msgs]; 22141227Sjdp num_msgs++; 22241227Sjdp } 22341227Sjdp msgs[num_msgs-1].msg_style = PAM_PROMPT_ECHO_ON; 22441227Sjdp if ((retval = pam_get_item(pamh, PAM_CONV, &item)) != PAM_SUCCESS) { 22541227Sjdp syslog(LOG_CRIT, "do_challenge: cannot get PAM_CONV"); 22694564Sdes return (retval); 22741227Sjdp } 22841227Sjdp conv = (const struct pam_conv *)item; 22941227Sjdp if ((retval = conv->conv(num_msgs, msg_ptrs, &resp, 23041227Sjdp conv->appdata_ptr)) != PAM_SUCCESS) 23194564Sdes return (retval); 232166136Spjd if (build_access_request(radh, user, resp[num_msgs-1].resp, nas_id, 233166136Spjd nas_ipaddr, state, statelen) == -1) 23494564Sdes return (PAM_SERVICE_ERR); 23541227Sjdp memset(resp[num_msgs-1].resp, 0, strlen(resp[num_msgs-1].resp)); 23641227Sjdp free(resp[num_msgs-1].resp); 23741227Sjdp free(resp); 23841227Sjdp while (num_msgs > 0) 239117841Sdes free(msgs[--num_msgs].msg); 24094564Sdes return (PAM_SUCCESS); 24141227Sjdp} 24241227Sjdp 24341227SjdpPAM_EXTERN int 24494564Sdespam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 245115465Sdes int argc __unused, const char *argv[] __unused) 24641227Sjdp{ 24741227Sjdp struct rad_handle *radh; 248123448Sdes const char *user, *pass; 249123448Sdes const void *tmpuser; 250122571Ssobomax const char *conf_file, *template_user, *nas_id, *nas_ipaddr; 25141227Sjdp int retval; 25241227Sjdp int e; 25341227Sjdp 254115465Sdes conf_file = openpam_get_option(pamh, PAM_OPT_CONF); 255115465Sdes template_user = openpam_get_option(pamh, PAM_OPT_TEMPLATE_USER); 256115465Sdes nas_id = openpam_get_option(pamh, PAM_OPT_NAS_ID); 257122571Ssobomax nas_ipaddr = openpam_get_option(pamh, PAM_OPT_NAS_IPADDR); 25841227Sjdp 25979476Smarkm retval = pam_get_user(pamh, &user, NULL); 26079476Smarkm if (retval != PAM_SUCCESS) 26194564Sdes return (retval); 26279476Smarkm 26379476Smarkm PAM_LOG("Got user: %s", user); 26479476Smarkm 26593984Sdes retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT); 26679476Smarkm if (retval != PAM_SUCCESS) 26794564Sdes return (retval); 26879476Smarkm 26979476Smarkm PAM_LOG("Got password"); 27079476Smarkm 27179476Smarkm radh = rad_open(); 27279476Smarkm if (radh == NULL) { 27341227Sjdp syslog(LOG_CRIT, "rad_open failed"); 27494564Sdes return (PAM_SERVICE_ERR); 27541227Sjdp } 27679476Smarkm 27779476Smarkm PAM_LOG("Radius opened"); 27879476Smarkm 27941227Sjdp if (rad_config(radh, conf_file) == -1) { 28041227Sjdp syslog(LOG_ALERT, "rad_config: %s", rad_strerror(radh)); 28141227Sjdp rad_close(radh); 28294564Sdes return (PAM_SERVICE_ERR); 28341227Sjdp } 28479476Smarkm 28579476Smarkm PAM_LOG("Radius config file read"); 28679476Smarkm 287122571Ssobomax if (build_access_request(radh, user, pass, nas_id, nas_ipaddr, NULL, 288122571Ssobomax 0) == -1) { 28941227Sjdp rad_close(radh); 29094564Sdes return (PAM_SERVICE_ERR); 29141227Sjdp } 29279476Smarkm 29379476Smarkm PAM_LOG("Radius build access done"); 29479476Smarkm 29587398Sdes for (;;) { 29641227Sjdp switch (rad_send_request(radh)) { 29741227Sjdp 29841227Sjdp case RAD_ACCESS_ACCEPT: 29941227Sjdp e = do_accept(pamh, radh); 30041227Sjdp rad_close(radh); 30141227Sjdp if (e == -1) 30294564Sdes return (PAM_SERVICE_ERR); 30341227Sjdp if (template_user != NULL) { 30441227Sjdp 30579476Smarkm PAM_LOG("Trying template user: %s", 30679476Smarkm template_user); 30779476Smarkm 30841227Sjdp /* 30941227Sjdp * If the given user name doesn't exist in 31041227Sjdp * the local password database, change it 31141227Sjdp * to the value given in the "template_user" 31241227Sjdp * option. 31341227Sjdp */ 314123448Sdes retval = pam_get_item(pamh, PAM_USER, &tmpuser); 31541227Sjdp if (retval != PAM_SUCCESS) 31694564Sdes return (retval); 31779476Smarkm if (getpwnam(tmpuser) == NULL) { 31841227Sjdp pam_set_item(pamh, PAM_USER, 31941227Sjdp template_user); 32079476Smarkm PAM_LOG("Using template user"); 32179476Smarkm } 32279476Smarkm 32341227Sjdp } 32494564Sdes return (PAM_SUCCESS); 32541227Sjdp 32641227Sjdp case RAD_ACCESS_REJECT: 32741227Sjdp rad_close(radh); 32881473Smarkm PAM_VERBOSE_ERROR("Radius rejection"); 32994564Sdes return (PAM_AUTH_ERR); 33041227Sjdp 33141227Sjdp case RAD_ACCESS_CHALLENGE: 332166136Spjd retval = do_challenge(pamh, radh, user, nas_id, 333166136Spjd nas_ipaddr); 33479476Smarkm if (retval != PAM_SUCCESS) { 33541227Sjdp rad_close(radh); 33694564Sdes return (retval); 33741227Sjdp } 33841227Sjdp break; 33941227Sjdp 34041227Sjdp case -1: 34141227Sjdp syslog(LOG_CRIT, "rad_send_request: %s", 34241227Sjdp rad_strerror(radh)); 34341227Sjdp rad_close(radh); 34481473Smarkm PAM_VERBOSE_ERROR("Radius failure"); 34594564Sdes return (PAM_AUTHINFO_UNAVAIL); 34641227Sjdp 34741227Sjdp default: 34841227Sjdp syslog(LOG_CRIT, 34941227Sjdp "rad_send_request: unexpected return value"); 35041227Sjdp rad_close(radh); 35181473Smarkm PAM_VERBOSE_ERROR("Radius error"); 35294564Sdes return (PAM_SERVICE_ERR); 35341227Sjdp } 35441227Sjdp } 35541227Sjdp} 35641227Sjdp 35741227SjdpPAM_EXTERN int 35894564Sdespam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 35994564Sdes int argc __unused, const char *argv[] __unused) 36041227Sjdp{ 36181473Smarkm 36294564Sdes return (PAM_SUCCESS); 36341227Sjdp} 36442917Sjdp 36542917SjdpPAM_MODULE_ENTRY("pam_radius"); 366