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 2010 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 <kadm5/admin.h>
29 #include <krb5.h>
30
31 #include <security/pam_appl.h>
32 #include <security/pam_modules.h>
33 #include <security/pam_impl.h>
34 #include <syslog.h>
35 #include <string.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <sys/types.h>
39 #include <pwd.h>
40 #include <libintl.h>
41 #include <netdb.h>
42 #include "utils.h"
43 #include "krb5_repository.h"
44
45 extern int attempt_krb5_auth(pam_handle_t *, krb5_module_data_t *, const char *,
46 char **, boolean_t);
47 extern int krb5_verifypw(const char *, char *, int);
48
49 static void display_msg(pam_handle_t *, int, char *);
50 static void display_msgs(pam_handle_t *, int, int,
51 char msgs[][PAM_MAX_MSG_SIZE]);
52 static int krb5_changepw(pam_handle_t *, const char *, char *, char *, int);
53
54 /*
55 * set_ccname()
56 *
57 * set KRB5CCNAME shell var
58 */
59 static void
set_ccname(pam_handle_t * pamh,krb5_module_data_t * kmd,int login_result,int debug)60 set_ccname(
61 pam_handle_t *pamh,
62 krb5_module_data_t *kmd,
63 int login_result,
64 int debug)
65 {
66 int result;
67
68 if (debug)
69 __pam_log(LOG_AUTH | LOG_DEBUG,
70 "PAM-KRB5 (password): password: finalize"
71 " ccname env, login_result =%d, env ='%s'",
72 login_result, kmd->env ? kmd->env : "<null>");
73
74 if (kmd->env) {
75
76 if (login_result == PAM_SUCCESS) {
77 /*
78 * Put ccname into the pamh so that login
79 * apps can pick this up when they run
80 * pam_getenvlist().
81 */
82 if ((result = pam_putenv(pamh, kmd->env))
83 != PAM_SUCCESS) {
84 /* should not happen but... */
85 __pam_log(LOG_AUTH | LOG_ERR,
86 "PAM-KRB5 (password):"
87 " pam_putenv failed: result: %d",
88 result);
89 goto cleanupccname;
90 }
91 } else {
92 cleanupccname:
93 /* for lack of a Solaris unputenv() */
94 krb5_unsetenv(KRB5_ENV_CCNAME);
95 free(kmd->env);
96 kmd->env = NULL;
97 }
98 }
99 }
100
101 /*
102 * get_set_creds()
103 *
104 * do a krb5 login to get and set krb5 creds (needed after a pw change
105 * on pw expire on login)
106 */
107 static void
get_set_creds(pam_handle_t * pamh,krb5_module_data_t * kmd,const char * user,char * newpass,int debug)108 get_set_creds(
109 pam_handle_t *pamh,
110 krb5_module_data_t *kmd,
111 const char *user,
112 char *newpass,
113 int debug)
114 {
115 int login_result;
116
117 if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
118 return;
119
120 /*
121 * if pw has expired, get/set krb5 creds ala auth mod
122 *
123 * pwchange verified user sufficiently, so don't request strict
124 * tgt verification (will cause rcache perm issues possibly anyways)
125 */
126 login_result = attempt_krb5_auth(pamh, kmd, user, &newpass, 0);
127 if (debug)
128 __pam_log(LOG_AUTH | LOG_DEBUG,
129 "PAM-KRB5 (password): get_set_creds: login_result= %d",
130 login_result);
131 /*
132 * the krb5 login should not fail, but if so,
133 * warn the user they have to kinit(1)
134 */
135 if (login_result != PAM_SUCCESS) {
136 display_msg(pamh, PAM_TEXT_INFO,
137 dgettext(TEXT_DOMAIN,
138 "Warning: "
139 "Could not cache Kerberos"
140 " credentials, please run "
141 "kinit(1) or re-login\n"));
142 }
143 set_ccname(pamh, kmd, login_result, debug);
144 }
145 /*
146 * This is the PAM Kerberos Password Change module
147 *
148 */
149
150 int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)151 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
152 {
153
154 const char *user;
155 int err, result = PAM_AUTHTOK_ERR;
156 char *newpass = NULL;
157 char *oldpass = NULL;
158 int i;
159 int debug = 0;
160 uid_t pw_uid;
161 krb5_module_data_t *kmd = NULL;
162 const pam_repository_t *rep_data = NULL;
163
164 for (i = 0; i < argc; i++) {
165 if (strcmp(argv[i], "debug") == 0)
166 debug = 1;
167 else
168 __pam_log(LOG_AUTH | LOG_ERR,
169 "PAM-KRB5 (password): illegal option %s",
170 argv[i]);
171 }
172
173 if (debug)
174 __pam_log(LOG_AUTH | LOG_DEBUG,
175 "PAM-KRB5 (password): start: flags = %x",
176 flags);
177
178 (void) pam_get_item(pamh, PAM_REPOSITORY, (const void **)&rep_data);
179
180 if (rep_data != NULL) {
181 if (strcmp(rep_data->type, KRB5_REPOSITORY_NAME) != 0) {
182 if (debug)
183 __pam_log(LOG_AUTH | LOG_DEBUG,
184 "PAM-KRB5 (auth): wrong"
185 "repository found (%s), returning "
186 "PAM_IGNORE", rep_data->type);
187 return (PAM_IGNORE);
188 }
189 }
190
191 if (flags & PAM_PRELIM_CHECK) {
192 /* Nothing to do here */
193 if (debug)
194 __pam_log(LOG_AUTH | LOG_DEBUG,
195 "PAM-KRB5 (password): prelim check");
196 return (PAM_IGNORE);
197 }
198
199 /* make sure PAM framework is telling us to update passwords */
200 if (!(flags & PAM_UPDATE_AUTHTOK)) {
201 __pam_log(LOG_AUTH | LOG_ERR,
202 "PAM-KRB5 (password): bad flags: %d",
203 flags);
204 return (PAM_SYSTEM_ERR);
205 }
206
207
208 if ((err = pam_get_data(pamh, KRB5_DATA, (const void **)&kmd))
209 != PAM_SUCCESS) {
210 if (debug)
211 __pam_log(LOG_AUTH | LOG_DEBUG,
212 "PAM-KRB5 (password): get mod data failed %d",
213 err);
214 kmd = NULL;
215 }
216
217 if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) {
218 /* let's make sure we know the krb5 pw has expired */
219
220 if (debug)
221 __pam_log(LOG_AUTH | LOG_DEBUG,
222 "PAM-KRB5 (password): kmd age status %d",
223 kmd ? kmd->age_status : -99);
224
225 if (!kmd || kmd->age_status != PAM_NEW_AUTHTOK_REQD)
226 return (PAM_IGNORE);
227 }
228
229 (void) pam_get_item(pamh, PAM_USER, (const void **)&user);
230
231 if (user == NULL || *user == '\0') {
232 __pam_log(LOG_AUTH | LOG_ERR,
233 "PAM-KRB5 (password): username is empty");
234 return (PAM_USER_UNKNOWN);
235 }
236
237 if (!get_pw_uid(user, &pw_uid)) {
238 __pam_log(LOG_AUTH | LOG_ERR,
239 "PAM-KRB5 (password): can't get uid for %s", user);
240 return (PAM_USER_UNKNOWN);
241 }
242
243 /*
244 * if root key exists in the keytab, it's a random key so no
245 * need to prompt for pw and we just return IGNORE
246 */
247 if ((strcmp(user, ROOT_UNAME) == 0) &&
248 key_in_keytab(user, debug)) {
249 if (debug)
250 __pam_log(LOG_AUTH | LOG_DEBUG,
251 "PAM-KRB5 (password): "
252 "key for '%s' in keytab, returning IGNORE", user);
253 result = PAM_IGNORE;
254 goto out;
255 }
256
257 (void) pam_get_item(pamh, PAM_AUTHTOK, (const void **)&newpass);
258
259 /*
260 * If the preauth type done didn't use a passwd just ignore the error.
261 */
262 if (newpass == NULL)
263 if (kmd && kmd->preauth_type == KRB_PKINIT)
264 return (PAM_IGNORE);
265 else
266 return (PAM_SYSTEM_ERR);
267
268 (void) pam_get_item(pamh, PAM_OLDAUTHTOK, (const void **)&oldpass);
269
270 if (oldpass == NULL)
271 if (kmd && kmd->preauth_type == KRB_PKINIT)
272 return (PAM_IGNORE);
273 else
274 return (PAM_SYSTEM_ERR);
275
276 result = krb5_verifypw(user, oldpass, debug);
277 if (debug)
278 __pam_log(LOG_AUTH | LOG_DEBUG,
279 "PAM-KRB5 (password): verifypw %d", result);
280
281 /*
282 * If it's a bad password or general failure, we are done.
283 */
284 if (result != 0) {
285 /*
286 * if the preauth type done didn't use a passwd just ignore the
287 * error.
288 */
289 if (kmd && kmd->preauth_type == KRB_PKINIT)
290 return (PAM_IGNORE);
291
292 if (result == 2)
293 display_msg(pamh, PAM_ERROR_MSG, dgettext(TEXT_DOMAIN,
294 "Old Kerberos password incorrect\n"));
295 return (PAM_AUTHTOK_ERR);
296 }
297
298 /*
299 * If the old password verifies try to change it regardless of the
300 * preauth type and do not ignore the error.
301 */
302 result = krb5_changepw(pamh, user, oldpass, newpass, debug);
303 if (result == PAM_SUCCESS) {
304 display_msg(pamh, PAM_TEXT_INFO, dgettext(TEXT_DOMAIN,
305 "Kerberos password successfully changed\n"));
306
307 get_set_creds(pamh, kmd, user, newpass, debug);
308 }
309
310 out:
311 if (debug)
312 __pam_log(LOG_AUTH | LOG_DEBUG,
313 "PAM-KRB5 (password): out: returns %d",
314 result);
315
316 return (result);
317 }
318
319 int
krb5_verifypw(const char * princ_str,char * old_password,int debug)320 krb5_verifypw(
321 const char *princ_str,
322 char *old_password,
323 int debug)
324 {
325 kadm5_ret_t code;
326 krb5_principal princ = 0;
327 char admin_realm[1024];
328 char kprinc[2*MAXHOSTNAMELEN];
329 char *cpw_service;
330 void *server_handle;
331 krb5_context context;
332 kadm5_config_params params;
333
334 (void) memset((char *)¶ms, 0, sizeof (params));
335
336 if (code = krb5_init_secure_context(&context)) {
337 return (6);
338 }
339
340 if ((code = get_kmd_kuser(context, princ_str, kprinc,
341 2*MAXHOSTNAMELEN)) != 0) {
342 return (code);
343 }
344
345 /* Need to get a krb5_principal struct */
346
347 code = krb5_parse_name(context, kprinc, &princ);
348
349 if (code != 0)
350 return (6);
351
352 if (strlen(old_password) == 0) {
353 krb5_free_principal(context, princ);
354 return (5);
355 }
356
357 (void) strlcpy(admin_realm,
358 krb5_princ_realm(context, princ)->data,
359 sizeof (admin_realm));
360
361 params.mask |= KADM5_CONFIG_REALM;
362 params.realm = admin_realm;
363
364
365 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
366 __pam_log(LOG_AUTH | LOG_ERR,
367 "PAM-KRB5 (password): unable to get host based "
368 "service name for realm %s\n",
369 admin_realm);
370 krb5_free_principal(context, princ);
371 return (3);
372 }
373
374 code = kadm5_init_with_password(kprinc, old_password, cpw_service,
375 ¶ms, KADM5_STRUCT_VERSION,
376 KADM5_API_VERSION_2, NULL,
377 &server_handle);
378 if (code != 0) {
379 if (debug)
380 __pam_log(LOG_AUTH | LOG_DEBUG,
381 "PAM-KRB5: krb5_verifypw: init_with_pw"
382 " failed: (%s)", error_message(code));
383 krb5_free_principal(context, princ);
384 return ((code == KADM5_BAD_PASSWORD) ? 2 : 3);
385 }
386
387 krb5_free_principal(context, princ);
388
389 (void) kadm5_destroy(server_handle);
390
391 return (0);
392 }
393
394 /*
395 * Function: krb5_changepw
396 *
397 * Purpose: Initialize and call lower level routines to change a password
398 *
399 * Arguments:
400 *
401 * princ_str principal name to use, optional
402 * old_password old password
403 * new_password new password
404 *
405 * Returns:
406 * exit status of PAM_SUCCESS for success
407 * else returns PAM failure
408 *
409 * Requires:
410 * Passwords cannot be more than 255 characters long.
411 *
412 * Modifies:
413 *
414 * Changes the principal's password.
415 *
416 */
417 static int
krb5_changepw(pam_handle_t * pamh,const char * princ_str,char * old_password,char * new_password,int debug)418 krb5_changepw(
419 pam_handle_t *pamh,
420 const char *princ_str,
421 char *old_password,
422 char *new_password,
423 int debug)
424 {
425 kadm5_ret_t code;
426 krb5_principal princ = 0;
427 char msg_ret[1024], admin_realm[1024];
428 char kprinc[2*MAXHOSTNAMELEN];
429 char *cpw_service;
430 void *server_handle;
431 krb5_context context;
432 kadm5_config_params params;
433
434 (void) memset((char *)¶ms, 0, sizeof (params));
435
436 if (krb5_init_secure_context(&context) != 0)
437 return (PAM_SYSTEM_ERR);
438
439 if ((code = get_kmd_kuser(context, princ_str, kprinc,
440 2*MAXHOSTNAMELEN)) != 0) {
441 return (code);
442 }
443
444 /* Need to get a krb5_principal struct */
445
446 code = krb5_parse_name(context, kprinc, &princ);
447 if (code != 0)
448 return (PAM_SYSTEM_ERR);
449
450 if (strlen(old_password) == 0) {
451 krb5_free_principal(context, princ);
452 return (PAM_AUTHTOK_ERR);
453 }
454
455 (void) snprintf(admin_realm, sizeof (admin_realm), "%s",
456 krb5_princ_realm(context, princ)->data);
457 params.mask |= KADM5_CONFIG_REALM;
458 params.realm = admin_realm;
459
460
461 if (kadm5_get_cpw_host_srv_name(context, admin_realm, &cpw_service)) {
462 __pam_log(LOG_AUTH | LOG_ERR,
463 "PAM-KRB5 (password):unable to get host based "
464 "service name for realm %s\n",
465 admin_realm);
466 return (PAM_SYSTEM_ERR);
467 }
468
469 code = kadm5_init_with_password(kprinc, old_password, cpw_service,
470 ¶ms, KADM5_STRUCT_VERSION,
471 KADM5_API_VERSION_2, NULL,
472 &server_handle);
473 free(cpw_service);
474 if (code != 0) {
475 if (debug)
476 __pam_log(LOG_AUTH | LOG_DEBUG,
477 "PAM-KRB5 (password): changepw: "
478 "init_with_pw failed: (%s)", error_message(code));
479 krb5_free_principal(context, princ);
480 return ((code == KADM5_BAD_PASSWORD) ?
481 PAM_AUTHTOK_ERR : PAM_SYSTEM_ERR);
482 }
483
484 code = kadm5_chpass_principal_util(server_handle, princ,
485 new_password,
486 NULL /* don't need pw back */,
487 msg_ret,
488 sizeof (msg_ret));
489
490 if (code) {
491 char msgs[2][PAM_MAX_MSG_SIZE];
492
493 (void) snprintf(msgs[0], PAM_MAX_MSG_SIZE, "%s",
494 dgettext(TEXT_DOMAIN,
495 "Kerberos password not changed: "));
496 (void) snprintf(msgs[1], PAM_MAX_MSG_SIZE, "%s", msg_ret);
497
498 display_msgs(pamh, PAM_ERROR_MSG, 2, msgs);
499 }
500
501 krb5_free_principal(context, princ);
502
503 (void) kadm5_destroy(server_handle);
504
505 if (debug)
506 __pam_log(LOG_AUTH | LOG_DEBUG,
507 "PAM-KRB5 (password): changepw: end %d", code);
508
509 if (code != 0)
510 return (PAM_AUTHTOK_ERR);
511
512 return (PAM_SUCCESS);
513 }
514
515 static void
display_msgs(pam_handle_t * pamh,int msg_style,int nmsg,char msgs[][PAM_MAX_MSG_SIZE])516 display_msgs(pam_handle_t *pamh,
517 int msg_style, int nmsg, char msgs[][PAM_MAX_MSG_SIZE])
518 {
519 (void) __pam_display_msg(pamh, msg_style, nmsg, msgs, NULL);
520 }
521
522
523 static void
display_msg(pam_handle_t * pamh,int msg_style,char * msg)524 display_msg(pam_handle_t *pamh, int msg_style, char *msg)
525 {
526 char pam_msg[1][PAM_MAX_MSG_SIZE];
527
528 (void) snprintf(pam_msg[0], PAM_MAX_MSG_SIZE, "%s", msg);
529 display_msgs(pamh, msg_style, 1, pam_msg);
530 }
531