1 /* $NetBSD: pam_radius.c,v 1.7 2006/11/03 18:55:40 christos Exp $ */ 2 3 /*- 4 * Copyright 1998 Juniper Networks, Inc. 5 * All rights reserved. 6 * Copyright (c) 2001-2003 Networks Associates Technology, Inc. 7 * All rights reserved. 8 * 9 * Portions of this software were developed for the FreeBSD Project by 10 * ThinkSec AS and NAI Labs, the Security Research Division of Network 11 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 12 * ("CBOSS"), as part of the DARPA CHATS research program. 13 * 14 * Redistribution and use in source and binary forms, with or without 15 * modification, are permitted provided that the following conditions 16 * are met: 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 2. Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * 3. The name of the author may not be used to endorse or promote 23 * products derived from this software without specific prior written 24 * permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 * SUCH DAMAGE. 37 */ 38 39 #include <sys/cdefs.h> 40 #ifdef __FreeBSD__ 41 __FBSDID("$FreeBSD: src/lib/libpam/modules/pam_radius/pam_radius.c,v 1.22 2004/06/25 12:32:45 kan Exp $"); 42 #else 43 __RCSID("$NetBSD: pam_radius.c,v 1.7 2006/11/03 18:55:40 christos Exp $"); 44 #endif 45 46 #include <sys/param.h> 47 #include <sys/types.h> 48 #include <sys/socket.h> 49 #include <netdb.h> 50 #include <pwd.h> 51 #include <radlib.h> 52 #include <stdlib.h> 53 #include <string.h> 54 #include <syslog.h> 55 #include <unistd.h> 56 #include <stdarg.h> 57 58 #define PAM_SM_AUTH 59 60 #include <security/pam_appl.h> 61 #include <security/pam_modules.h> 62 #include <security/pam_mod_misc.h> 63 64 #define PAM_OPT_CONF "conf" 65 #define PAM_OPT_TEMPLATE_USER "template_user" 66 #define PAM_OPT_NAS_ID "nas_id" 67 #define PAM_OPT_NAS_IPADDR "nas_ipaddr" 68 69 #define MAX_CHALLENGE_MSGS 10 70 #define PASSWORD_PROMPT "RADIUS Password:" 71 72 static int build_access_request(struct rad_handle *, const char *, 73 const char *, const char *, const char *, const void *, 74 size_t); 75 static int do_accept(pam_handle_t *, struct rad_handle *); 76 static int do_challenge(pam_handle_t *, struct rad_handle *, 77 const char *); 78 79 static void 80 logit(int level, const char *fmt, ...) 81 { 82 va_list ap; 83 struct syslog_data data = SYSLOG_DATA_INIT; 84 85 openlog_r("pam_radius", LOG_PID, LOG_AUTHPRIV, &data); 86 va_start(ap, fmt); 87 vsyslog_r(level, &data, fmt, ap); 88 va_end(ap); 89 closelog_r(&data); 90 } 91 92 /* 93 * Construct an access request, but don't send it. Returns 0 on success, 94 * -1 on failure. 95 */ 96 static int 97 build_access_request(struct rad_handle *radh, const char *user, 98 const char *pass, const char *nas_id, const char *nas_ipaddr, 99 const void *state, size_t state_len) 100 { 101 int error; 102 char host[MAXHOSTNAMELEN]; 103 struct sockaddr_in *haddr; 104 struct addrinfo hints; 105 struct addrinfo *res; 106 107 if (rad_create_request(radh, RAD_ACCESS_REQUEST) == -1) { 108 logit(LOG_CRIT, "rad_create_request: %s", rad_strerror(radh)); 109 return (-1); 110 } 111 if (nas_id == NULL || 112 (nas_ipaddr != NULL && strlen(nas_ipaddr) == 0)) { 113 if (gethostname(host, sizeof host) != -1) { 114 if (nas_id == NULL) 115 nas_id = host; 116 if (nas_ipaddr != NULL && strlen(nas_ipaddr) == 0) 117 nas_ipaddr = host; 118 } 119 } 120 if ((user != NULL && 121 rad_put_string(radh, RAD_USER_NAME, user) == -1) || 122 (pass != NULL && 123 rad_put_string(radh, RAD_USER_PASSWORD, pass) == -1) || 124 (nas_id != NULL && 125 rad_put_string(radh, RAD_NAS_IDENTIFIER, nas_id) == -1)) { 126 logit(LOG_CRIT, "rad_put_string: %s", rad_strerror(radh)); 127 return (-1); 128 } 129 if (nas_ipaddr != NULL) { 130 memset(&hints, 0, sizeof(hints)); 131 hints.ai_family = PF_INET; 132 if (getaddrinfo(nas_ipaddr, NULL, &hints, &res) == 0 && 133 res != NULL) { 134 haddr = (struct sockaddr_in *)res->ai_addr; 135 error = rad_put_addr(radh, RAD_NAS_IP_ADDRESS, 136 haddr->sin_addr); 137 freeaddrinfo(res); 138 if (error == -1) { 139 logit(LOG_CRIT, "rad_put_addr: %s", 140 rad_strerror(radh)); 141 return (-1); 142 } 143 } 144 } 145 if (state != NULL && rad_put_attr(radh, RAD_STATE, state, 146 state_len) == -1) { 147 logit(LOG_CRIT, "rad_put_attr: %s", rad_strerror(radh)); 148 return (-1); 149 } 150 if (rad_put_int(radh, RAD_SERVICE_TYPE, RAD_AUTHENTICATE_ONLY) == -1) { 151 logit(LOG_CRIT, "rad_put_int: %s", rad_strerror(radh)); 152 return (-1); 153 } 154 return (0); 155 } 156 157 static int 158 do_accept(pam_handle_t *pamh, struct rad_handle *radh) 159 { 160 int attrtype; 161 const void *attrval; 162 size_t attrlen; 163 char *s; 164 165 while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) { 166 if (attrtype == RAD_USER_NAME) { 167 s = rad_cvt_string(attrval, attrlen); 168 if (s == NULL) { 169 logit(LOG_CRIT, 170 "rad_cvt_string: out of memory"); 171 return (-1); 172 } 173 pam_set_item(pamh, PAM_USER, s); 174 free(s); 175 } 176 } 177 if (attrtype == -1) { 178 logit(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh)); 179 return (-1); 180 } 181 return (0); 182 } 183 184 static int 185 do_challenge(pam_handle_t *pamh, struct rad_handle *radh, const char *user) 186 { 187 int retval; 188 int attrtype; 189 const void *attrval; 190 size_t attrlen; 191 const void *state; 192 size_t statelen; 193 struct pam_message msgs[MAX_CHALLENGE_MSGS]; 194 const struct pam_message *msg_ptrs[MAX_CHALLENGE_MSGS]; 195 struct pam_response *resp; 196 int num_msgs; 197 const void *item; 198 const struct pam_conv *conv; 199 200 state = NULL; 201 statelen = 0; 202 num_msgs = 0; 203 while ((attrtype = rad_get_attr(radh, &attrval, &attrlen)) > 0) { 204 switch (attrtype) { 205 206 case RAD_STATE: 207 state = attrval; 208 statelen = attrlen; 209 break; 210 211 case RAD_REPLY_MESSAGE: 212 if (num_msgs >= MAX_CHALLENGE_MSGS) { 213 logit(LOG_CRIT, 214 "Too many RADIUS challenge messages"); 215 return (PAM_SERVICE_ERR); 216 } 217 msgs[num_msgs].msg = rad_cvt_string(attrval, attrlen); 218 if (msgs[num_msgs].msg == NULL) { 219 logit(LOG_CRIT, 220 "rad_cvt_string: out of memory"); 221 return (PAM_SERVICE_ERR); 222 } 223 msgs[num_msgs].msg_style = PAM_TEXT_INFO; 224 msg_ptrs[num_msgs] = &msgs[num_msgs]; 225 num_msgs++; 226 break; 227 } 228 } 229 if (attrtype == -1) { 230 logit(LOG_CRIT, "rad_get_attr: %s", rad_strerror(radh)); 231 return (PAM_SERVICE_ERR); 232 } 233 if (num_msgs == 0) { 234 msgs[num_msgs].msg = strdup("(null RADIUS challenge): "); 235 if (msgs[num_msgs].msg == NULL) { 236 logit(LOG_CRIT, "Out of memory"); 237 return (PAM_SERVICE_ERR); 238 } 239 msgs[num_msgs].msg_style = PAM_TEXT_INFO; 240 msg_ptrs[num_msgs] = &msgs[num_msgs]; 241 num_msgs++; 242 } 243 msgs[num_msgs-1].msg_style = PAM_PROMPT_ECHO_ON; 244 if ((retval = pam_get_item(pamh, PAM_CONV, &item)) != PAM_SUCCESS) { 245 logit(LOG_CRIT, "do_challenge: cannot get PAM_CONV"); 246 return (retval); 247 } 248 conv = (const struct pam_conv *)item; 249 if ((retval = conv->conv(num_msgs, msg_ptrs, &resp, 250 conv->appdata_ptr)) != PAM_SUCCESS) 251 return (retval); 252 if (build_access_request(radh, user, resp[num_msgs-1].resp, NULL, 253 NULL, state, statelen) == -1) 254 return (PAM_SERVICE_ERR); 255 memset(resp[num_msgs-1].resp, 0, strlen(resp[num_msgs-1].resp)); 256 free(resp[num_msgs-1].resp); 257 free(resp); 258 while (num_msgs > 0) 259 free(msgs[--num_msgs].msg); 260 return (PAM_SUCCESS); 261 } 262 263 PAM_EXTERN int 264 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 265 int argc __unused, const char *argv[] __unused) 266 { 267 struct rad_handle *radh; 268 const char *user, *pass; 269 const void *tmpuser; 270 struct passwd *pwd, pwres; 271 char pwbuf[1024]; 272 const char *conf_file, *template_user, *nas_id, *nas_ipaddr; 273 int retval; 274 int e; 275 276 conf_file = openpam_get_option(pamh, PAM_OPT_CONF); 277 template_user = openpam_get_option(pamh, PAM_OPT_TEMPLATE_USER); 278 nas_id = openpam_get_option(pamh, PAM_OPT_NAS_ID); 279 nas_ipaddr = openpam_get_option(pamh, PAM_OPT_NAS_IPADDR); 280 281 retval = pam_get_user(pamh, &user, NULL); 282 if (retval != PAM_SUCCESS) 283 return (retval); 284 285 PAM_LOG("Got user: %s", user); 286 287 retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT); 288 if (retval != PAM_SUCCESS) 289 return (retval); 290 291 PAM_LOG("Got password"); 292 293 radh = rad_open(); 294 if (radh == NULL) { 295 logit(LOG_CRIT, "rad_open failed"); 296 return (PAM_SERVICE_ERR); 297 } 298 299 PAM_LOG("Radius opened"); 300 301 if (rad_config(radh, conf_file) == -1) { 302 logit(LOG_ALERT, "rad_config: %s", rad_strerror(radh)); 303 rad_close(radh); 304 return (PAM_SERVICE_ERR); 305 } 306 307 PAM_LOG("Radius config file read"); 308 309 if (build_access_request(radh, user, pass, nas_id, nas_ipaddr, NULL, 310 0) == -1) { 311 rad_close(radh); 312 return (PAM_SERVICE_ERR); 313 } 314 315 PAM_LOG("Radius build access done"); 316 317 for (;;) { 318 switch (rad_send_request(radh)) { 319 320 case RAD_ACCESS_ACCEPT: 321 e = do_accept(pamh, radh); 322 rad_close(radh); 323 if (e == -1) 324 return (PAM_SERVICE_ERR); 325 if (template_user != NULL) { 326 327 PAM_LOG("Trying template user: %s", 328 template_user); 329 330 /* 331 * If the given user name doesn't exist in 332 * the local password database, change it 333 * to the value given in the "template_user" 334 * option. 335 */ 336 retval = pam_get_item(pamh, PAM_USER, &tmpuser); 337 if (retval != PAM_SUCCESS) 338 return (retval); 339 if (getpwnam_r(tmpuser, &pwres, pwbuf, 340 sizeof(pwbuf), &pwd) != 0 || 341 pwd == NULL) { 342 pam_set_item(pamh, PAM_USER, 343 template_user); 344 PAM_LOG("Using template user"); 345 } 346 347 } 348 return (PAM_SUCCESS); 349 350 case RAD_ACCESS_REJECT: 351 rad_close(radh); 352 PAM_VERBOSE_ERROR("Radius rejection"); 353 return (PAM_AUTH_ERR); 354 355 case RAD_ACCESS_CHALLENGE: 356 retval = do_challenge(pamh, radh, user); 357 if (retval != PAM_SUCCESS) { 358 rad_close(radh); 359 return (retval); 360 } 361 break; 362 363 case -1: 364 logit(LOG_CRIT, "rad_send_request: %s", 365 rad_strerror(radh)); 366 rad_close(radh); 367 PAM_VERBOSE_ERROR("Radius failure"); 368 return (PAM_AUTHINFO_UNAVAIL); 369 370 default: 371 logit(LOG_CRIT, 372 "rad_send_request: unexpected return value"); 373 rad_close(radh); 374 PAM_VERBOSE_ERROR("Radius error"); 375 return (PAM_SERVICE_ERR); 376 } 377 } 378 } 379 380 PAM_EXTERN int 381 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 382 int argc __unused, const char *argv[] __unused) 383 { 384 385 return (PAM_SUCCESS); 386 } 387 388 PAM_MODULE_ENTRY("pam_radius"); 389