pam_unix.c revision 116394
155682Smarkm/*- 2233294Sstas * Copyright 1998 Juniper Networks, Inc. 3233294Sstas * All rights reserved. 4233294Sstas * Copyright (c) 2002-2003 Networks Associates Technology, Inc. 555682Smarkm * All rights reserved. 6233294Sstas * 7233294Sstas * Portions of this software was developed for the FreeBSD Project by 8233294Sstas * ThinkSec AS and NAI Labs, the Security Research Division of Network 955682Smarkm * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 10233294Sstas * ("CBOSS"), as part of the DARPA CHATS research program. 11233294Sstas * 1255682Smarkm * Redistribution and use in source and binary forms, with or without 13233294Sstas * modification, are permitted provided that the following conditions 14233294Sstas * are met: 15233294Sstas * 1. Redistributions of source code must retain the above copyright 1655682Smarkm * notice, this list of conditions and the following disclaimer. 17233294Sstas * 2. Redistributions in binary form must reproduce the above copyright 18233294Sstas * notice, this list of conditions and the following disclaimer in the 19233294Sstas * documentation and/or other materials provided with the distribution. 2055682Smarkm * 3. The name of the author may not be used to endorse or promote 21233294Sstas * products derived from this software without specific prior written 22233294Sstas * permission. 23233294Sstas * 24233294Sstas * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 25233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27233294Sstas * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 28233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 3255682Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3355682Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3455682Smarkm * SUCH DAMAGE. 3555682Smarkm */ 3655682Smarkm 3755682Smarkm#include <sys/cdefs.h> 3855682Smarkm__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_unix/pam_unix.c 116394 2003-06-15 10:37:22Z mbr $"); 3955682Smarkm 4055682Smarkm#include <sys/param.h> 4155682Smarkm#include <sys/socket.h> 4255682Smarkm#include <sys/time.h> 43233294Sstas#include <netinet/in.h> 44233294Sstas#include <arpa/inet.h> 45233294Sstas 46233294Sstas#include <login_cap.h> 47233294Sstas#include <netdb.h> 48233294Sstas#include <pwd.h> 49233294Sstas#include <stdlib.h> 5072445Sassar#include <string.h> 5172445Sassar#include <stdio.h> 52233294Sstas#include <syslog.h> 5372445Sassar#include <unistd.h> 5472445Sassar 55233294Sstas#include <libutil.h> 5655682Smarkm 5755682Smarkm#ifdef YP 5855682Smarkm#include <ypclnt.h> 5955682Smarkm#endif 6072445Sassar 6155682Smarkm#define PAM_SM_AUTH 6290926Snectar#define PAM_SM_ACCOUNT 63233294Sstas#define PAM_SM_PASSWORD 6455682Smarkm 6590926Snectar#include <security/pam_appl.h> 6655682Smarkm#include <security/pam_modules.h> 67233294Sstas#include <security/pam_mod_misc.h> 68233294Sstas 6955682Smarkm#define PASSWORD_HASH "md5" 7055682Smarkm#define DEFAULT_WARN (2L * 7L * 86400L) /* Two weeks */ 7155682Smarkm#define SALTSIZE 32 7255682Smarkm 7355682Smarkmstatic void makesalt(char []); 7455682Smarkm 75233294Sstasstatic char password_hash[] = PASSWORD_HASH; 7655682Smarkm 7755682Smarkm#define PAM_OPT_LOCAL_PASS "local_pass" 7855682Smarkm#define PAM_OPT_NIS_PASS "nis_pass" 7955682Smarkm 8055682Smarkmchar *tempname = NULL; 81233294Sstas 82233294Sstas/* 83233294Sstas * authentication management 84233294Sstas */ 85233294SstasPAM_EXTERN int 8655682Smarkmpam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 87233294Sstas int argc __unused, const char *argv[] __unused) 88233294Sstas{ 89233294Sstas login_cap_t *lc; 90233294Sstas struct passwd *pwd; 9155682Smarkm int retval; 9255682Smarkm const char *pass, *user, *realpw, *prompt; 9355682Smarkm 94233294Sstas if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) { 9555682Smarkm pwd = getpwnam(getlogin()); 9655682Smarkm } else { 9755682Smarkm retval = pam_get_user(pamh, &user, NULL); 9855682Smarkm if (retval != PAM_SUCCESS) 9955682Smarkm return (retval); 10055682Smarkm pwd = getpwnam(user); 10155682Smarkm } 102233294Sstas 10355682Smarkm PAM_LOG("Got user: %s", user); 10455682Smarkm 10555682Smarkm if (pwd != NULL) { 10672445Sassar PAM_LOG("Doing real authentication"); 10772445Sassar realpw = pwd->pw_passwd; 10872445Sassar if (realpw[0] == '\0') { 10955682Smarkm if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) && 11055682Smarkm openpam_get_option(pamh, PAM_OPT_NULLOK)) 11155682Smarkm return (PAM_SUCCESS); 11255682Smarkm realpw = "*"; 113233294Sstas } 114233294Sstas lc = login_getpwclass(pwd); 115233294Sstas } else { 116233294Sstas PAM_LOG("Doing dummy authentication"); 11755682Smarkm realpw = "*"; 11855682Smarkm lc = login_getclass(NULL); 11955682Smarkm } 12072445Sassar prompt = login_getcapstr(lc, "passwd_prompt", NULL, NULL); 121233294Sstas retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt); 12255682Smarkm login_close(lc); 12355682Smarkm if (retval != PAM_SUCCESS) 12455682Smarkm return (retval); 12555682Smarkm PAM_LOG("Got password"); 12655682Smarkm if (strcmp(crypt(pass, realpw), realpw) == 0) 12755682Smarkm return (PAM_SUCCESS); 12872445Sassar 12972445Sassar PAM_VERBOSE_ERROR("UNIX authentication refused"); 13072445Sassar return (PAM_AUTH_ERR); 13172445Sassar} 13272445Sassar 133233294SstasPAM_EXTERN int 13472445Sassarpam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 135233294Sstas int argc __unused, const char *argv[] __unused) 136233294Sstas{ 13772445Sassar 13872445Sassar return (PAM_SUCCESS); 139233294Sstas} 140233294Sstas 141233294Sstas/* 14272445Sassar * account management 14372445Sassar */ 14472445SassarPAM_EXTERN int 145233294Sstaspam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused, 146233294Sstas int argc __unused, const char *argv[] __unused) 147233294Sstas{ 148233294Sstas struct addrinfo hints, *res; 149233294Sstas struct passwd *pwd; 150233294Sstas struct timeval tp; 151233294Sstas login_cap_t *lc; 152233294Sstas time_t warntime; 153233294Sstas int retval; 154233294Sstas const char *rhost, *tty, *user; 155233294Sstas char rhostip[MAXHOSTNAMELEN] = ""; 156233294Sstas 157233294Sstas retval = pam_get_user(pamh, &user, NULL); 15872445Sassar if (retval != PAM_SUCCESS) 15972445Sassar return (retval); 16072445Sassar 161233294Sstas if (user == NULL || (pwd = getpwnam(user)) == NULL) 16272445Sassar return (PAM_SERVICE_ERR); 163233294Sstas 164233294Sstas PAM_LOG("Got user: %s", user); 16572445Sassar 16672445Sassar retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost); 16772445Sassar if (retval != PAM_SUCCESS) 16872445Sassar return (retval); 16972445Sassar 17072445Sassar retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty); 17172445Sassar if (retval != PAM_SUCCESS) 172233294Sstas return (retval); 17355682Smarkm 17455682Smarkm if (*pwd->pw_passwd == '\0' && 17555682Smarkm (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0) 17655682Smarkm return (PAM_NEW_AUTHTOK_REQD); 17755682Smarkm 17855682Smarkm lc = login_getpwclass(pwd); 17955682Smarkm if (lc == NULL) { 180178825Sdfr PAM_LOG("Unable to get login class for user %s", user); 18155682Smarkm return (PAM_SERVICE_ERR); 18255682Smarkm } 18372445Sassar 18472445Sassar PAM_LOG("Got login_cap"); 185233294Sstas 186233294Sstas if (pwd->pw_change || pwd->pw_expire) 187233294Sstas gettimeofday(&tp, NULL); 18855682Smarkm 189178825Sdfr /* 190178825Sdfr * Check pw_expire before pw_change - no point in letting the 191233294Sstas * user change the password on an expired account. 192233294Sstas */ 193233294Sstas 194233294Sstas if (pwd->pw_expire) { 195233294Sstas warntime = login_getcaptime(lc, "warnexpire", 196233294Sstas DEFAULT_WARN, DEFAULT_WARN); 197233294Sstas if (tp.tv_sec >= pwd->pw_expire) { 19872445Sassar login_close(lc); 19972445Sassar return (PAM_ACCT_EXPIRED); 20055682Smarkm } else if (pwd->pw_expire - tp.tv_sec < warntime && 201233294Sstas (flags & PAM_SILENT) == 0) { 20272445Sassar pam_error(pamh, "Warning: your account expires on %s", 20372445Sassar ctime(&pwd->pw_expire)); 204178825Sdfr } 205233294Sstas } 20672445Sassar 207233294Sstas retval = PAM_SUCCESS; 208178825Sdfr if (pwd->pw_change) { 20972445Sassar warntime = login_getcaptime(lc, "warnpassword", 210178825Sdfr DEFAULT_WARN, DEFAULT_WARN); 211233294Sstas if (tp.tv_sec >= pwd->pw_change) { 21272445Sassar retval = PAM_NEW_AUTHTOK_REQD; 21372445Sassar } else if (pwd->pw_change - tp.tv_sec < warntime && 214233294Sstas (flags & PAM_SILENT) == 0) { 215233294Sstas pam_error(pamh, "Warning: your password expires on %s", 216233294Sstas ctime(&pwd->pw_change)); 217233294Sstas } 218233294Sstas } 219178825Sdfr 220178825Sdfr /* 221178825Sdfr * From here on, we must leave retval untouched (unless we 222178825Sdfr * know we're going to fail), because we need to remember 223178825Sdfr * whether we're supposed to return PAM_SUCCESS or 224178825Sdfr * PAM_NEW_AUTHTOK_REQD. 225233294Sstas */ 22655682Smarkm 227178825Sdfr if (rhost && *rhost) { 228178825Sdfr memset(&hints, 0, sizeof(hints)); 22955682Smarkm hints.ai_family = AF_UNSPEC; 23055682Smarkm if (getaddrinfo(rhost, NULL, &hints, &res) == 0) { 231178825Sdfr getnameinfo(res->ai_addr, res->ai_addrlen, 232178825Sdfr rhostip, sizeof(rhostip), NULL, 0, 23355682Smarkm NI_NUMERICHOST|NI_WITHSCOPEID); 234178825Sdfr } 235178825Sdfr if (res != NULL) 23655682Smarkm freeaddrinfo(res); 237178825Sdfr } 238233294Sstas 239233294Sstas /* 24055682Smarkm * Check host / tty / time-of-day restrictions 24155682Smarkm */ 24255682Smarkm 24355682Smarkm if (!auth_hostok(lc, rhost, rhostip) || 24455682Smarkm !auth_ttyok(lc, tty) || 24555682Smarkm !auth_timeok(lc, time(NULL))) 246233294Sstas retval = PAM_AUTH_ERR; 247178825Sdfr 248178825Sdfr login_close(lc); 249233294Sstas 250233294Sstas return (retval); 251233294Sstas} 25255682Smarkm 25355682Smarkm/* 25455682Smarkm * password management 255233294Sstas * 256233294Sstas * standard Unix and NIS password changing 257233294Sstas */ 258233294SstasPAM_EXTERN int 259233294Sstaspam_sm_chauthtok(pam_handle_t *pamh, int flags, 260233294Sstas int argc __unused, const char *argv[] __unused) 261233294Sstas{ 262233294Sstas#ifdef YP 263233294Sstas struct ypclnt *ypclnt; 264233294Sstas const char *yp_domain, *yp_server; 265233294Sstas#endif 266233294Sstas char salt[SALTSIZE + 1]; 267233294Sstas login_cap_t * lc; 268233294Sstas struct passwd *pwd, *old_pwd; 269233294Sstas const char *user, *old_pass, *new_pass; 270233294Sstas char *encrypted; 271233294Sstas int pfd, tfd, retval; 272233294Sstas 273233294Sstas if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) 274233294Sstas pwd = getpwnam(getlogin()); 275233294Sstas else { 276233294Sstas retval = pam_get_user(pamh, &user, NULL); 277233294Sstas if (retval != PAM_SUCCESS) 278233294Sstas return (retval); 279233294Sstas pwd = getpwnam(user); 280233294Sstas } 281233294Sstas 282233294Sstas if (pwd == NULL) 283233294Sstas return (PAM_AUTHTOK_RECOVERY_ERR); 284233294Sstas 285233294Sstas PAM_LOG("Got user: %s", user); 286233294Sstas 287233294Sstas if (flags & PAM_PRELIM_CHECK) { 288233294Sstas 289233294Sstas PAM_LOG("PRELIM round"); 290233294Sstas 291233294Sstas if (getuid() == 0 && 292233294Sstas (pwd->pw_fields & _PWF_SOURCE) == _PWF_FILES) 293233294Sstas /* root doesn't need the old password */ 294233294Sstas return (pam_set_item(pamh, PAM_OLDAUTHTOK, "")); 295233294Sstas#ifdef YP 296233294Sstas if (getuid() == 0 && 297233294Sstas (pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) { 298233294Sstas 299233294Sstas yp_domain = yp_server = NULL; 300233294Sstas (void)pam_get_data(pamh, 301233294Sstas "yp_domain", (const void **)&yp_domain); 302233294Sstas (void)pam_get_data(pamh, 303233294Sstas "yp_server", (const void **)&yp_server); 304233294Sstas 305233294Sstas ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_server); 306233294Sstas if (ypclnt == NULL) 307233294Sstas return (PAM_BUF_ERR); 308233294Sstas 309233294Sstas if (ypclnt_connect(ypclnt) == -1) { 310233294Sstas ypclnt_free(ypclnt); 311233294Sstas return (PAM_SERVICE_ERR); 312233294Sstas } 313233294Sstas 314233294Sstas retval = ypclnt_havepasswdd(ypclnt); 315233294Sstas ypclnt_free(ypclnt); 316233294Sstas if (retval == 1) 317233294Sstas return (pam_set_item(pamh, PAM_OLDAUTHTOK, "")); 318233294Sstas else if (retval == -1) 319233294Sstas return (PAM_SERVICE_ERR); 320233294Sstas } 321233294Sstas#endif 322233294Sstas if (pwd->pw_passwd[0] == '\0' 323233294Sstas && openpam_get_option(pamh, PAM_OPT_NULLOK)) { 324233294Sstas /* 325233294Sstas * No password case. XXX Are we giving too much away 326233294Sstas * by not prompting for a password? 327233294Sstas * XXX check PAM_DISALLOW_NULL_AUTHTOK 328233294Sstas */ 329233294Sstas old_pass = ""; 330233294Sstas } else { 331233294Sstas retval = pam_get_authtok(pamh, 332233294Sstas PAM_OLDAUTHTOK, &old_pass, NULL); 333233294Sstas if (retval != PAM_SUCCESS) 334233294Sstas return (retval); 335233294Sstas } 336233294Sstas PAM_LOG("Got old password"); 337233294Sstas /* always encrypt first */ 338233294Sstas encrypted = crypt(old_pass, pwd->pw_passwd); 339233294Sstas if (old_pass[0] == '\0' && 340233294Sstas !openpam_get_option(pamh, PAM_OPT_NULLOK)) 341233294Sstas return (PAM_PERM_DENIED); 342233294Sstas if (strcmp(encrypted, pwd->pw_passwd) != 0) 343233294Sstas return (PAM_PERM_DENIED); 344233294Sstas } 345233294Sstas else if (flags & PAM_UPDATE_AUTHTOK) { 346233294Sstas PAM_LOG("UPDATE round"); 347233294Sstas 348233294Sstas retval = pam_get_authtok(pamh, 349233294Sstas PAM_OLDAUTHTOK, &old_pass, NULL); 350233294Sstas if (retval != PAM_SUCCESS) 351233294Sstas return (retval); 352233294Sstas PAM_LOG("Got old password"); 353233294Sstas 354233294Sstas /* get new password */ 355233294Sstas for (;;) { 356233294Sstas retval = pam_get_authtok(pamh, 357233294Sstas PAM_AUTHTOK, &new_pass, NULL); 358233294Sstas if (retval != PAM_TRY_AGAIN) 359233294Sstas break; 360233294Sstas pam_error(pamh, "Mismatch; try again, EOF to quit."); 361233294Sstas } 362233294Sstas PAM_LOG("Got new password"); 363233294Sstas if (retval != PAM_SUCCESS) { 364233294Sstas PAM_VERBOSE_ERROR("Unable to get new password"); 365233294Sstas return (retval); 366233294Sstas } 367233294Sstas 368233294Sstas if (getuid() != 0 && new_pass[0] == '\0' && 369233294Sstas !openpam_get_option(pamh, PAM_OPT_NULLOK)) 370233294Sstas return (PAM_PERM_DENIED); 371233294Sstas 372233294Sstas if ((old_pwd = pw_dup(pwd)) == NULL) 373233294Sstas return (PAM_BUF_ERR); 374233294Sstas 375233294Sstas pwd->pw_change = 0; 376233294Sstas lc = login_getclass(NULL); 377233294Sstas if (login_setcryptfmt(lc, password_hash, NULL) == NULL) 378233294Sstas openpam_log(PAM_LOG_ERROR, 379233294Sstas "can't set password cipher, relying on default"); 380233294Sstas login_close(lc); 381233294Sstas makesalt(salt); 382233294Sstas pwd->pw_passwd = crypt(new_pass, salt); 383233294Sstas#ifdef YP 384233294Sstas switch (old_pwd->pw_fields & _PWF_SOURCE) { 385233294Sstas case _PWF_FILES: 386233294Sstas#endif 387233294Sstas retval = PAM_SERVICE_ERR; 388233294Sstas if (pw_init(NULL, NULL)) 389233294Sstas openpam_log(PAM_LOG_ERROR, "pw_init() failed"); 390233294Sstas else if ((pfd = pw_lock()) == -1) 391233294Sstas openpam_log(PAM_LOG_ERROR, "pw_lock() failed"); 392233294Sstas else if ((tfd = pw_tmp(-1)) == -1) 393233294Sstas openpam_log(PAM_LOG_ERROR, "pw_tmp() failed"); 394233294Sstas else if (pw_copy(pfd, tfd, pwd, old_pwd) == -1) 395233294Sstas openpam_log(PAM_LOG_ERROR, "pw_copy() failed"); 396233294Sstas else if (pw_mkdb(pwd->pw_name) == -1) 397233294Sstas openpam_log(PAM_LOG_ERROR, "pw_mkdb() failed"); 398233294Sstas else 399233294Sstas retval = PAM_SUCCESS; 400233294Sstas pw_fini(); 401233294Sstas#ifdef YP 402233294Sstas break; 403233294Sstas case _PWF_NIS: 404233294Sstas yp_domain = yp_server = NULL; 405233294Sstas (void)pam_get_data(pamh, 406233294Sstas "yp_domain", (const void **)&yp_domain); 407233294Sstas (void)pam_get_data(pamh, 408233294Sstas "yp_server", (const void **)&yp_server); 409233294Sstas ypclnt = ypclnt_new(yp_domain, 410233294Sstas "passwd.byname", yp_server); 411233294Sstas if (ypclnt == NULL) { 41255682Smarkm retval = PAM_BUF_ERR; 41355682Smarkm } else if (ypclnt_connect(ypclnt) == -1 || 41455682Smarkm ypclnt_passwd(ypclnt, pwd, old_pass) == -1) { 41555682Smarkm openpam_log(PAM_LOG_ERROR, "%s", ypclnt->error); 41655682Smarkm retval = PAM_SERVICE_ERR; 417233294Sstas } else { 41855682Smarkm retval = PAM_SUCCESS; 419233294Sstas } 420233294Sstas ypclnt_free(ypclnt); 421233294Sstas break; 42255682Smarkm default: 42355682Smarkm openpam_log(PAM_LOG_ERROR, "unsupported source 0x%x", 42455682Smarkm pwd->pw_fields & _PWF_SOURCE); 425 retval = PAM_SERVICE_ERR; 426 } 427#endif 428 free(old_pwd); 429 } 430 else { 431 /* Very bad juju */ 432 retval = PAM_ABORT; 433 PAM_LOG("Illegal 'flags'"); 434 } 435 436 return (retval); 437} 438 439/* Mostly stolen from passwd(1)'s local_passwd.c - markm */ 440 441static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ 442 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 443 444static void 445to64(char *s, long v, int n) 446{ 447 while (--n >= 0) { 448 *s++ = itoa64[v&0x3f]; 449 v >>= 6; 450 } 451} 452 453/* Salt suitable for traditional DES and MD5 */ 454void 455makesalt(char salt[SALTSIZE]) 456{ 457 int i; 458 459 /* These are not really random numbers, they are just 460 * numbers that change to thwart construction of a 461 * dictionary. This is exposed to the public. 462 */ 463 for (i = 0; i < SALTSIZE; i += 4) 464 to64(&salt[i], arc4random(), 4); 465 salt[SALTSIZE] = '\0'; 466} 467 468PAM_MODULE_ENTRY("pam_unix"); 469