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