1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association. 26 */ 27 28 #include <sys/types.h> 29 #include <sys/varargs.h> 30 #include <string.h> 31 #include <stdlib.h> 32 #include <syslog.h> 33 #include <unistd.h> 34 #include <crypt.h> 35 #include <pwd.h> 36 #include <libintl.h> 37 38 #include <security/pam_appl.h> 39 #include <security/pam_modules.h> 40 #include <security/pam_impl.h> 41 42 #include <passwdutil.h> 43 44 #include <sys/note.h> 45 46 /*PRINTFLIKE3*/ 47 void 48 error(int nowarn, pam_handle_t *pamh, char *fmt, ...) 49 { 50 va_list ap; 51 char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; 52 53 va_start(ap, fmt); 54 (void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap); 55 if (nowarn == 0) 56 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages, 57 NULL); 58 va_end(ap); 59 } 60 61 /* 62 * int pam_sm_authenticate(pamh, flags, argc, argv) 63 * 64 * Read authentication token from user. 65 */ 66 int 67 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 68 { 69 70 const char *user; 71 char *password; 72 const char *service; 73 int i; 74 int debug = 0; 75 int nowarn = 0; 76 int res; 77 char prompt[PAM_MAX_MSG_SIZE]; 78 char *auth_user = NULL; 79 int retval; 80 int privileged; 81 char *rep_passwd = NULL; 82 char *repository_name = NULL; 83 attrlist al[8]; 84 int min; 85 int max; 86 int lstchg; 87 int server_policy = 0; 88 89 const pam_repository_t *auth_rep = NULL; 90 pwu_repository_t *pwu_rep = NULL; 91 92 for (i = 0; i < argc; i++) { 93 if (strcmp(argv[i], "debug") == 0) 94 debug = 1; 95 if (strcmp(argv[i], "nowarn") == 0) 96 nowarn = 1; 97 if (strcmp(argv[i], "server_policy") == 0) 98 server_policy = 1; 99 } 100 101 if (flags & PAM_SILENT) 102 nowarn = 1; 103 104 if ((res = pam_get_user(pamh, (const char **)&user, NULL)) != 105 PAM_SUCCESS) { 106 if (debug) 107 syslog(LOG_DEBUG, "pam_passwd_auth: " 108 "get user failed: %s", pam_strerror(pamh, res)); 109 return (res); 110 } 111 112 if (user == NULL || *user == '\0') { 113 syslog(LOG_ERR, "pam_passwd_auth: pam_sm_authenticate: " 114 "PAM_USER NULL or empty"); 115 return (PAM_SYSTEM_ERR); 116 } 117 118 res = pam_get_item(pamh, PAM_AUTHTOK, (const void **)&password); 119 if (res != PAM_SUCCESS) 120 return (res); 121 122 if (password != NULL) 123 return (PAM_IGNORE); 124 125 res = pam_get_item(pamh, PAM_SERVICE, (const void **)&service); 126 if (res != PAM_SUCCESS) 127 return (res); 128 129 res = pam_get_item(pamh, PAM_REPOSITORY, (const void **)&auth_rep); 130 if (res != PAM_SUCCESS) { 131 syslog(LOG_ERR, "pam_passwd_auth: pam_sm_authenticate: " 132 "error getting repository"); 133 return (PAM_SYSTEM_ERR); 134 } 135 136 if (auth_rep == NULL) { 137 pwu_rep = PWU_DEFAULT_REP; 138 } else { 139 if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) 140 return (PAM_BUF_ERR); 141 pwu_rep->type = auth_rep->type; 142 pwu_rep->scope = auth_rep->scope; 143 pwu_rep->scope_len = auth_rep->scope_len; 144 } 145 146 res = __user_to_authenticate(user, pwu_rep, &auth_user, &privileged); 147 if (res != PWU_SUCCESS) { 148 if (res == PWU_NOT_FOUND) 149 retval = PAM_USER_UNKNOWN; 150 else if (res == PWU_DENIED) 151 retval = PAM_PERM_DENIED; 152 else if (res == PWU_REPOSITORY_ERROR) { 153 syslog(LOG_NOTICE, 154 "pam_passwd_auth: detected unsupported " 155 "configuration in /etc/nsswitch.conf."); 156 error(nowarn, pamh, dgettext(TEXT_DOMAIN, "%s: " 157 "Unsupported nsswitch entry for \"passwd:\"." 158 " Use \"-r repository \"."), service); 159 retval = PAM_SYSTEM_ERR; 160 } else 161 retval = PAM_SYSTEM_ERR; 162 if (debug) 163 syslog(LOG_DEBUG, "passwd_auth: __user_to_authenticate " 164 "returned %d", retval); 165 goto out; 166 } 167 168 if (auth_user == NULL) { /* No authentication needed */ 169 if (debug) 170 syslog(LOG_DEBUG, 171 "passwd_auth: no authentication needed."); 172 retval = PAM_SUCCESS; 173 goto out; 174 } 175 176 /* 177 * The password prompt differs between users updating their 178 * own password, and users updating other an user's password 179 */ 180 if (privileged) { 181 /* 182 * TRANSLATION_NOTE 183 * The following string has a single space at the end 184 */ 185 (void) snprintf(prompt, sizeof (prompt), 186 dgettext(TEXT_DOMAIN, "Enter %s's password: "), 187 auth_user); 188 } else { 189 /* 190 * TRANSLATION_NOTE 191 * The following string has a single space at the end 192 */ 193 (void) snprintf(prompt, sizeof (prompt), 194 dgettext(TEXT_DOMAIN, "Enter existing login password: ")); 195 } 196 197 retval = __pam_get_authtok(pamh, PAM_PROMPT, PAM_AUTHTOK, prompt, 198 &password); 199 if (retval != PAM_SUCCESS) 200 goto out; 201 202 if (password == NULL) { 203 syslog(LOG_ERR, "pam_passwd_auth: pam_sm_authenticate: " 204 "got NULL password from get_authtok()"); 205 retval = PAM_AUTH_ERR; 206 goto out; 207 } 208 209 /* Privileged users can skip the tests that follow */ 210 if (privileged) 211 goto setitem; 212 213 /* 214 * Non privileged user: so we need to check the old password 215 * and possible restrictions on password changes. 216 */ 217 218 /* Get password and it's age from the repository specified */ 219 al[0].type = ATTR_PASSWD; al[0].next = &al[1]; 220 al[1].type = ATTR_MIN; al[1].next = &al[2]; 221 al[2].type = ATTR_MAX; al[2].next = &al[3]; 222 al[3].type = ATTR_LSTCHG; al[3].next = &al[4]; 223 al[4].type = ATTR_WARN; al[4].next = &al[5]; 224 al[5].type = ATTR_INACT; al[5].next = &al[6]; 225 al[6].type = ATTR_EXPIRE; al[6].next = &al[7]; 226 al[7].type = ATTR_REP_NAME; al[7].next = NULL; 227 228 res = __get_authtoken_attr(auth_user, pwu_rep, al); 229 230 if (res != PWU_SUCCESS) { 231 retval = PAM_SYSTEM_ERR; 232 goto out; 233 } 234 235 repository_name = al[7].data.val_s; 236 237 /* 238 * if repository isn't files|nis, and user wants to follow server 239 * policy, return PAM_IGNORE 240 */ 241 if (server_policy && 242 strcmp(repository_name, "files") != 0 && 243 strcmp(repository_name, "nis") != 0) { 244 retval = PAM_IGNORE; 245 goto out; 246 } 247 248 rep_passwd = al[0].data.val_s; 249 250 /* 251 * Chop off old SunOS-style password aging information. 252 * 253 * Note: old style password aging is only defined for UNIX-style 254 * crypt strings, hence the comma will always be at position 14. 255 * Note: This code is here because some other vendors might still 256 * support this style of password aging. If we don't remove 257 * the age field, users won't be able to change their password. 258 * XXX yank this code when we're certain this "compatibility" 259 * isn't needed anymore. 260 */ 261 if (rep_passwd != NULL && rep_passwd[0] != '$' && 262 strlen(rep_passwd) > 13 && rep_passwd[13] == ',') 263 rep_passwd[13] = '\0'; 264 265 if (strcmp(crypt(password, rep_passwd), rep_passwd) != 0) { 266 retval = PAM_AUTH_ERR; 267 goto out; 268 } 269 270 /* 271 * Now check to see if the user is allowed to change 272 * the password. 273 */ 274 min = al[1].data.val_i; 275 max = al[2].data.val_i; 276 lstchg = al[3].data.val_i; 277 278 if (max != -1 && lstchg != 0) { 279 /* aging is turned on, and a change is not forced */ 280 time_t daynow = DAY_NOW_32; 281 if ((time_t)lstchg <= daynow) { 282 /* Aged enough? */ 283 if (daynow < (time_t)(lstchg + min)) { 284 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 285 "%s: Sorry: less than %d days " 286 "since the last change."), 287 service, min); 288 retval = PAM_PERM_DENIED; 289 goto out; 290 } 291 292 /* 293 * users with min>max are not allowed to 294 * change their password. 295 */ 296 if (min > max) { 297 error(nowarn, pamh, dgettext(TEXT_DOMAIN, 298 "%s: You may not change " 299 "this password."), service); 300 retval = PAM_PERM_DENIED; 301 goto out; 302 } 303 } 304 } 305 306 setitem: 307 308 retval = pam_set_item(pamh, PAM_AUTHTOK, (void *)password); 309 310 out: 311 if (password) { 312 (void) memset(password, 0, strlen(password)); 313 free(password); 314 } 315 if (rep_passwd) { 316 (void) memset(rep_passwd, 0, strlen(rep_passwd)); 317 free(rep_passwd); 318 } 319 if (pwu_rep) 320 free(pwu_rep); 321 if (auth_user) 322 free(auth_user); 323 if (repository_name) 324 free(repository_name); 325 326 return (retval); 327 } 328 329 /*ARGSUSED*/ 330 int 331 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 332 { 333 return (PAM_IGNORE); 334 } 335