]> CyberLeo.Net >> Repos - FreeBSD/releng/7.2.git/blob - lib/libpam/modules/pam_krb5/pam_krb5.c
Create releng/7.2 from stable/7 in preparation for 7.2-RELEASE.
[FreeBSD/releng/7.2.git] / lib / libpam / modules / pam_krb5 / pam_krb5.c
1 /*-
2  * This pam_krb5 module contains code that is:
3  *   Copyright (c) Derrick J. Brashear, 1996. All rights reserved.
4  *   Copyright (c) Frank Cusack, 1999-2001. All rights reserved.
5  *   Copyright (c) Jacques A. Vidrine, 2000-2001. All rights reserved.
6  *   Copyright (c) Nicolas Williams, 2001. All rights reserved.
7  *   Copyright (c) Perot Systems Corporation, 2001. All rights reserved.
8  *   Copyright (c) Mark R V Murray, 2001.  All rights reserved.
9  *   Copyright (c) Networks Associates Technology, Inc., 2002-2005.
10  *       All rights reserved.
11  *
12  * Portions of this software were developed for the FreeBSD Project by
13  * ThinkSec AS and NAI Labs, the Security Research Division of Network
14  * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
15  * ("CBOSS"), as part of the DARPA CHATS research program.
16  *
17  * Redistribution and use in source and binary forms, with or without
18  * modification, are permitted provided that the following conditions
19  * are met:
20  * 1. Redistributions of source code must retain the above copyright
21  *    notices, and the entire permission notice in its entirety,
22  *    including the disclaimer of warranties.
23  * 2. Redistributions in binary form must reproduce the above copyright
24  *    notice, this list of conditions and the following disclaimer in the
25  *    documentation and/or other materials provided with the distribution.
26  * 3. The name of the author may not be used to endorse or promote
27  *    products derived from this software without specific prior
28  *    written permission.
29  *
30  * ALTERNATIVELY, this product may be distributed under the terms of
31  * the GNU Public License, in which case the provisions of the GPL are
32  * required INSTEAD OF the above restrictions.  (This clause is
33  * necessary due to a potential bad interaction between the GPL and
34  * the restrictions contained in a BSD-style copyright.)
35  *
36  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
37  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
40  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
41  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
42  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46  * OF THE POSSIBILITY OF SUCH DAMAGE.
47  *
48  */
49
50 #include <sys/cdefs.h>
51 __FBSDID("$FreeBSD$");
52
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include <errno.h>
56 #include <limits.h>
57 #include <pwd.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <syslog.h>
62 #include <unistd.h>
63
64 #include <krb5.h>
65 #include <com_err.h>
66
67 #define PAM_SM_AUTH
68 #define PAM_SM_ACCOUNT
69 #define PAM_SM_PASSWORD
70
71 #include <security/pam_appl.h>
72 #include <security/pam_modules.h>
73 #include <security/pam_mod_misc.h>
74 #include <security/openpam.h>
75
76 #define COMPAT_HEIMDAL
77 /* #define      COMPAT_MIT */
78
79 static int      verify_krb_v5_tgt(krb5_context, krb5_ccache, char *, int);
80 static void     cleanup_cache(pam_handle_t *, void *, int);
81 static const    char *compat_princ_component(krb5_context, krb5_principal, int);
82 static void     compat_free_data_contents(krb5_context, krb5_data *);
83
84 #define USER_PROMPT             "Username: "
85 #define PASSWORD_PROMPT         "Password:"
86 #define NEW_PASSWORD_PROMPT     "New Password:"
87
88 #define PAM_OPT_CCACHE          "ccache"
89 #define PAM_OPT_DEBUG           "debug"
90 #define PAM_OPT_FORWARDABLE     "forwardable"
91 #define PAM_OPT_NO_CCACHE       "no_ccache"
92 #define PAM_OPT_REUSE_CCACHE    "reuse_ccache"
93
94 /*
95  * authentication management
96  */
97 PAM_EXTERN int
98 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
99     int argc __unused, const char *argv[] __unused)
100 {
101         krb5_error_code krbret;
102         krb5_context pam_context;
103         krb5_creds creds;
104         krb5_principal princ;
105         krb5_ccache ccache;
106         krb5_get_init_creds_opt opts;
107         struct passwd *pwd;
108         int retval;
109         const void *ccache_data;
110         const char *user, *pass;
111         const void *sourceuser, *service;
112         char *principal, *princ_name, *ccache_name, luser[32], *srvdup;
113
114         retval = pam_get_user(pamh, &user, USER_PROMPT);
115         if (retval != PAM_SUCCESS)
116                 return (retval);
117
118         PAM_LOG("Got user: %s", user);
119
120         retval = pam_get_item(pamh, PAM_RUSER, &sourceuser);
121         if (retval != PAM_SUCCESS)
122                 return (retval);
123
124         PAM_LOG("Got ruser: %s", (const char *)sourceuser);
125
126         service = NULL;
127         pam_get_item(pamh, PAM_SERVICE, &service);
128         if (service == NULL)
129                 service = "unknown";
130
131         PAM_LOG("Got service: %s", (const char *)service);
132
133         krbret = krb5_init_context(&pam_context);
134         if (krbret != 0) {
135                 PAM_VERBOSE_ERROR("Kerberos 5 error");
136                 return (PAM_SERVICE_ERR);
137         }
138
139         PAM_LOG("Context initialised");
140
141         krb5_get_init_creds_opt_init(&opts);
142
143         if (openpam_get_option(pamh, PAM_OPT_FORWARDABLE))
144                 krb5_get_init_creds_opt_set_forwardable(&opts, 1);
145
146         PAM_LOG("Credentials initialised");
147
148         krbret = krb5_cc_register(pam_context, &krb5_mcc_ops, FALSE);
149         if (krbret != 0 && krbret != KRB5_CC_TYPE_EXISTS) {
150                 PAM_VERBOSE_ERROR("Kerberos 5 error");
151                 retval = PAM_SERVICE_ERR;
152                 goto cleanup3;
153         }
154
155         PAM_LOG("Done krb5_cc_register()");
156
157         /* Get principal name */
158         if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF))
159                 asprintf(&principal, "%s/%s", (const char *)sourceuser, user);
160         else
161                 principal = strdup(user);
162
163         PAM_LOG("Created principal: %s", principal);
164
165         krbret = krb5_parse_name(pam_context, principal, &princ);
166         free(principal);
167         if (krbret != 0) {
168                 PAM_LOG("Error krb5_parse_name(): %s",
169                     krb5_get_err_text(pam_context, krbret));
170                 PAM_VERBOSE_ERROR("Kerberos 5 error");
171                 retval = PAM_SERVICE_ERR;
172                 goto cleanup3;
173         }
174
175         PAM_LOG("Done krb5_parse_name()");
176
177         /* Now convert the principal name into something human readable */
178         princ_name = NULL;
179         krbret = krb5_unparse_name(pam_context, princ, &princ_name);
180         if (krbret != 0) {
181                 PAM_LOG("Error krb5_unparse_name(): %s",
182                     krb5_get_err_text(pam_context, krbret));
183                 PAM_VERBOSE_ERROR("Kerberos 5 error");
184                 retval = PAM_SERVICE_ERR;
185                 goto cleanup2;
186         }
187
188         PAM_LOG("Got principal: %s", princ_name);
189
190         /* Get password */
191         retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, PASSWORD_PROMPT);
192         if (retval != PAM_SUCCESS)
193                 goto cleanup2;
194
195         PAM_LOG("Got password");
196
197         /* Verify the local user exists (AFTER getting the password) */
198         if (strchr(user, '@')) {
199                 /* get a local account name for this principal */
200                 krbret = krb5_aname_to_localname(pam_context, princ,
201                     sizeof(luser), luser);
202                 if (krbret != 0) {
203                         PAM_VERBOSE_ERROR("Kerberos 5 error");
204                         PAM_LOG("Error krb5_aname_to_localname(): %s",
205                             krb5_get_err_text(pam_context, krbret));
206                         retval = PAM_USER_UNKNOWN;
207                         goto cleanup2;
208                 }
209
210                 retval = pam_set_item(pamh, PAM_USER, luser);
211                 if (retval != PAM_SUCCESS)
212                         goto cleanup2;
213
214                 PAM_LOG("PAM_USER Redone");
215         }
216
217         pwd = getpwnam(user);
218         if (pwd == NULL) {
219                 retval = PAM_USER_UNKNOWN;
220                 goto cleanup2;
221         }
222
223         PAM_LOG("Done getpwnam()");
224
225         /* Get a TGT */
226         memset(&creds, 0, sizeof(krb5_creds));
227         krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
228             pass, NULL, pamh, 0, NULL, &opts);
229         if (krbret != 0) {
230                 PAM_VERBOSE_ERROR("Kerberos 5 error");
231                 PAM_LOG("Error krb5_get_init_creds_password(): %s",
232                     krb5_get_err_text(pam_context, krbret));
233                 retval = PAM_AUTH_ERR;
234                 goto cleanup2;
235         }
236
237         PAM_LOG("Got TGT");
238
239         /* Generate a temporary cache */
240         krbret = krb5_cc_gen_new(pam_context, &krb5_mcc_ops, &ccache);
241         if (krbret != 0) {
242                 PAM_VERBOSE_ERROR("Kerberos 5 error");
243                 PAM_LOG("Error krb5_cc_gen_new(): %s",
244                     krb5_get_err_text(pam_context, krbret));
245                 retval = PAM_SERVICE_ERR;
246                 goto cleanup;
247         }
248         krbret = krb5_cc_initialize(pam_context, ccache, princ);
249         if (krbret != 0) {
250                 PAM_VERBOSE_ERROR("Kerberos 5 error");
251                 PAM_LOG("Error krb5_cc_initialize(): %s",
252                     krb5_get_err_text(pam_context, krbret));
253                 retval = PAM_SERVICE_ERR;
254                 goto cleanup;
255         }
256         krbret = krb5_cc_store_cred(pam_context, ccache, &creds);
257         if (krbret != 0) {
258                 PAM_VERBOSE_ERROR("Kerberos 5 error");
259                 PAM_LOG("Error krb5_cc_store_cred(): %s",
260                     krb5_get_err_text(pam_context, krbret));
261                 krb5_cc_destroy(pam_context, ccache);
262                 retval = PAM_SERVICE_ERR;
263                 goto cleanup;
264         }
265
266         PAM_LOG("Credentials stashed");
267
268         /* Verify them */
269         if ((srvdup = strdup(service)) == NULL) {
270                 retval = PAM_BUF_ERR;
271                 goto cleanup;
272         }
273         krbret = verify_krb_v5_tgt(pam_context, ccache, srvdup,
274             openpam_get_option(pamh, PAM_OPT_DEBUG) ? 1 : 0);
275         free(srvdup);
276         if (krbret == -1) {
277                 PAM_VERBOSE_ERROR("Kerberos 5 error");
278                 krb5_cc_destroy(pam_context, ccache);
279                 retval = PAM_AUTH_ERR;
280                 goto cleanup;
281         }
282
283         PAM_LOG("Credentials stash verified");
284
285         retval = pam_get_data(pamh, "ccache", &ccache_data);
286         if (retval == PAM_SUCCESS) {
287                 krb5_cc_destroy(pam_context, ccache);
288                 PAM_VERBOSE_ERROR("Kerberos 5 error");
289                 retval = PAM_AUTH_ERR;
290                 goto cleanup;
291         }
292
293         PAM_LOG("Credentials stash not pre-existing");
294
295         asprintf(&ccache_name, "%s:%s", krb5_cc_get_type(pam_context,
296                 ccache), krb5_cc_get_name(pam_context, ccache));
297         if (ccache_name == NULL) {
298                 PAM_VERBOSE_ERROR("Kerberos 5 error");
299                 retval = PAM_BUF_ERR;
300                 goto cleanup;
301         }
302         retval = pam_set_data(pamh, "ccache", ccache_name, cleanup_cache);
303         if (retval != 0) {
304                 krb5_cc_destroy(pam_context, ccache);
305                 PAM_VERBOSE_ERROR("Kerberos 5 error");
306                 retval = PAM_SERVICE_ERR;
307                 goto cleanup;
308         }
309
310         PAM_LOG("Credentials stash saved");
311
312 cleanup:
313         krb5_free_cred_contents(pam_context, &creds);
314         PAM_LOG("Done cleanup");
315 cleanup2:
316         krb5_free_principal(pam_context, princ);
317         PAM_LOG("Done cleanup2");
318 cleanup3:
319         if (princ_name)
320                 free(princ_name);
321
322         krb5_free_context(pam_context);
323
324         PAM_LOG("Done cleanup3");
325
326         if (retval != PAM_SUCCESS)
327                 PAM_VERBOSE_ERROR("Kerberos 5 refuses you");
328
329         return (retval);
330 }
331
332 PAM_EXTERN int
333 pam_sm_setcred(pam_handle_t *pamh, int flags,
334     int argc __unused, const char *argv[] __unused)
335 {
336 #ifdef _FREEFALL_CONFIG
337         return (PAM_SUCCESS);
338 #else
339
340         krb5_error_code krbret;
341         krb5_context pam_context;
342         krb5_principal princ;
343         krb5_creds creds;
344         krb5_ccache ccache_temp, ccache_perm;
345         krb5_cc_cursor cursor;
346         struct passwd *pwd = NULL;
347         int retval;
348         const char *cache_name, *q;
349         const void *user;
350         const void *cache_data;
351         char *cache_name_buf = NULL, *p;
352
353         uid_t euid;
354         gid_t egid;
355
356         if (flags & PAM_DELETE_CRED)
357                 return (PAM_SUCCESS);
358
359         if (flags & PAM_REFRESH_CRED)
360                 return (PAM_SUCCESS);
361
362         if (flags & PAM_REINITIALIZE_CRED)
363                 return (PAM_SUCCESS);
364
365         if (!(flags & PAM_ESTABLISH_CRED))
366                 return (PAM_SERVICE_ERR);
367
368         /* If a persistent cache isn't desired, stop now. */
369         if (openpam_get_option(pamh, PAM_OPT_NO_CCACHE))
370                 return (PAM_SUCCESS);
371
372         PAM_LOG("Establishing credentials");
373
374         /* Get username */
375         retval = pam_get_item(pamh, PAM_USER, &user);
376         if (retval != PAM_SUCCESS)
377                 return (retval);
378
379         PAM_LOG("Got user: %s", (const char *)user);
380
381         krbret = krb5_init_context(&pam_context);
382         if (krbret != 0) {
383                 PAM_LOG("Error krb5_init_context() failed");
384                 return (PAM_SERVICE_ERR);
385         }
386
387         PAM_LOG("Context initialised");
388
389         euid = geteuid();       /* Usually 0 */
390         egid = getegid();
391
392         PAM_LOG("Got euid, egid: %d %d", euid, egid);
393
394         /* Retrieve the temporary cache */
395         retval = pam_get_data(pamh, "ccache", &cache_data);
396         if (retval != PAM_SUCCESS) {
397                 retval = PAM_CRED_UNAVAIL;
398                 goto cleanup3;
399         }
400         krbret = krb5_cc_resolve(pam_context, cache_data, &ccache_temp);
401         if (krbret != 0) {
402                 PAM_LOG("Error krb5_cc_resolve(\"%s\"): %s", (const char *)cache_data,
403                     krb5_get_err_text(pam_context, krbret));
404                 retval = PAM_SERVICE_ERR;
405                 goto cleanup3;
406         }
407
408         /* Get the uid. This should exist. */
409         pwd = getpwnam(user);
410         if (pwd == NULL) {
411                 retval = PAM_USER_UNKNOWN;
412                 goto cleanup3;
413         }
414
415         PAM_LOG("Done getpwnam()");
416
417         /* Avoid following a symlink as root */
418         if (setegid(pwd->pw_gid)) {
419                 retval = PAM_SERVICE_ERR;
420                 goto cleanup3;
421         }
422         if (seteuid(pwd->pw_uid)) {
423                 retval = PAM_SERVICE_ERR;
424                 goto cleanup3;
425         }
426
427         PAM_LOG("Done setegid() & seteuid()");
428
429         /* Get the cache name */
430         cache_name = openpam_get_option(pamh, PAM_OPT_CCACHE);
431         if (cache_name == NULL) {
432                 asprintf(&cache_name_buf, "FILE:/tmp/krb5cc_%d", pwd->pw_uid);
433                 cache_name = cache_name_buf;
434         }
435
436         p = calloc(PATH_MAX + 16, sizeof(char));
437         q = cache_name;
438
439         if (p == NULL) {
440                 PAM_LOG("Error malloc(): failure");
441                 retval = PAM_BUF_ERR;
442                 goto cleanup3;
443         }
444         cache_name = p;
445
446         /* convert %u and %p */
447         while (*q) {
448                 if (*q == '%') {
449                         q++;
450                         if (*q == 'u') {
451                                 sprintf(p, "%d", pwd->pw_uid);
452                                 p += strlen(p);
453                         }
454                         else if (*q == 'p') {
455                                 sprintf(p, "%d", getpid());
456                                 p += strlen(p);
457                         }
458                         else {
459                                 /* Not a special token */
460                                 *p++ = '%';
461                                 q--;
462                         }
463                         q++;
464                 }
465                 else {
466                         *p++ = *q++;
467                 }
468         }
469
470         PAM_LOG("Got cache_name: %s", cache_name);
471
472         /* Initialize the new ccache */
473         krbret = krb5_cc_get_principal(pam_context, ccache_temp, &princ);
474         if (krbret != 0) {
475                 PAM_LOG("Error krb5_cc_get_principal(): %s",
476                     krb5_get_err_text(pam_context, krbret));
477                 retval = PAM_SERVICE_ERR;
478                 goto cleanup3;
479         }
480         krbret = krb5_cc_resolve(pam_context, cache_name, &ccache_perm);
481         if (krbret != 0) {
482                 PAM_LOG("Error krb5_cc_resolve(): %s",
483                     krb5_get_err_text(pam_context, krbret));
484                 retval = PAM_SERVICE_ERR;
485                 goto cleanup2;
486         }
487         krbret = krb5_cc_initialize(pam_context, ccache_perm, princ);
488         if (krbret != 0) {
489                 PAM_LOG("Error krb5_cc_initialize(): %s",
490                     krb5_get_err_text(pam_context, krbret));
491                 retval = PAM_SERVICE_ERR;
492                 goto cleanup2;
493         }
494
495         PAM_LOG("Cache initialised");
496
497         /* Prepare for iteration over creds */
498         krbret = krb5_cc_start_seq_get(pam_context, ccache_temp, &cursor);
499         if (krbret != 0) {
500                 PAM_LOG("Error krb5_cc_start_seq_get(): %s",
501                     krb5_get_err_text(pam_context, krbret));
502                 krb5_cc_destroy(pam_context, ccache_perm);
503                 retval = PAM_SERVICE_ERR;
504                 goto cleanup2;
505         }
506
507         PAM_LOG("Prepared for iteration");
508
509         /* Copy the creds (should be two of them) */
510         while ((krbret = krb5_cc_next_cred(pam_context, ccache_temp,
511                                 &cursor, &creds) == 0)) {
512                 krbret = krb5_cc_store_cred(pam_context, ccache_perm, &creds);
513                 if (krbret != 0) {
514                         PAM_LOG("Error krb5_cc_store_cred(): %s",
515                             krb5_get_err_text(pam_context, krbret));
516                         krb5_cc_destroy(pam_context, ccache_perm);
517                         krb5_free_cred_contents(pam_context, &creds);
518                         retval = PAM_SERVICE_ERR;
519                         goto cleanup2;
520                 }
521                 krb5_free_cred_contents(pam_context, &creds);
522                 PAM_LOG("Iteration");
523         }
524         krb5_cc_end_seq_get(pam_context, ccache_temp, &cursor);
525
526         PAM_LOG("Done iterating");
527
528         if (strstr(cache_name, "FILE:") == cache_name) {
529                 if (chown(&cache_name[5], pwd->pw_uid, pwd->pw_gid) == -1) {
530                         PAM_LOG("Error chown(): %s", strerror(errno));
531                         krb5_cc_destroy(pam_context, ccache_perm);
532                         retval = PAM_SERVICE_ERR;
533                         goto cleanup2;
534                 }
535                 PAM_LOG("Done chown()");
536
537                 if (chmod(&cache_name[5], (S_IRUSR | S_IWUSR)) == -1) {
538                         PAM_LOG("Error chmod(): %s", strerror(errno));
539                         krb5_cc_destroy(pam_context, ccache_perm);
540                         retval = PAM_SERVICE_ERR;
541                         goto cleanup2;
542                 }
543                 PAM_LOG("Done chmod()");
544         }
545
546         krb5_cc_close(pam_context, ccache_perm);
547
548         PAM_LOG("Cache closed");
549
550         retval = pam_setenv(pamh, "KRB5CCNAME", cache_name, 1);
551         if (retval != PAM_SUCCESS) {
552                 PAM_LOG("Error pam_setenv(): %s", pam_strerror(pamh, retval));
553                 krb5_cc_destroy(pam_context, ccache_perm);
554                 retval = PAM_SERVICE_ERR;
555                 goto cleanup2;
556         }
557
558         PAM_LOG("Environment done: KRB5CCNAME=%s", cache_name);
559
560 cleanup2:
561         krb5_free_principal(pam_context, princ);
562         PAM_LOG("Done cleanup2");
563 cleanup3:
564         krb5_free_context(pam_context);
565         PAM_LOG("Done cleanup3");
566
567         seteuid(euid);
568         setegid(egid);
569
570         PAM_LOG("Done seteuid() & setegid()");
571
572         if (cache_name_buf != NULL)
573                 free(cache_name_buf);
574
575         return (retval);
576 #endif
577 }
578
579 /*
580  * account management
581  */
582 PAM_EXTERN int
583 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
584     int argc __unused, const char *argv[] __unused)
585 {
586         krb5_error_code krbret;
587         krb5_context pam_context;
588         krb5_ccache ccache;
589         krb5_principal princ;
590         int retval;
591         const void *user;
592         const void *ccache_name;
593
594         retval = pam_get_item(pamh, PAM_USER, &user);
595         if (retval != PAM_SUCCESS)
596                 return (retval);
597
598         PAM_LOG("Got user: %s", (const char *)user);
599
600         retval = pam_get_data(pamh, "ccache", &ccache_name);
601         if (retval != PAM_SUCCESS)
602                 return (PAM_SUCCESS);
603
604         PAM_LOG("Got credentials");
605
606         krbret = krb5_init_context(&pam_context);
607         if (krbret != 0) {
608                 PAM_LOG("Error krb5_init_context() failed");
609                 return (PAM_PERM_DENIED);
610         }
611
612         PAM_LOG("Context initialised");
613
614         krbret = krb5_cc_resolve(pam_context, (const char *)ccache_name, &ccache);
615         if (krbret != 0) {
616                 PAM_LOG("Error krb5_cc_resolve(\"%s\"): %s", (const char *)ccache_name,
617                     krb5_get_err_text(pam_context, krbret));
618                 krb5_free_context(pam_context);
619                 return (PAM_PERM_DENIED);
620         }
621
622         PAM_LOG("Got ccache %s", (const char *)ccache_name);
623
624
625         krbret = krb5_cc_get_principal(pam_context, ccache, &princ);
626         if (krbret != 0) {
627                 PAM_LOG("Error krb5_cc_get_principal(): %s",
628                     krb5_get_err_text(pam_context, krbret));
629                 retval = PAM_PERM_DENIED;;
630                 goto cleanup;
631         }
632
633         PAM_LOG("Got principal");
634
635         if (krb5_kuserok(pam_context, princ, (const char *)user))
636                 retval = PAM_SUCCESS;
637         else
638                 retval = PAM_PERM_DENIED;
639         krb5_free_principal(pam_context, princ);
640
641         PAM_LOG("Done kuserok()");
642
643 cleanup:
644         krb5_free_context(pam_context);
645         PAM_LOG("Done cleanup");
646
647         return (retval);
648
649 }
650
651 /*
652  * password management
653  */
654 PAM_EXTERN int
655 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
656     int argc __unused, const char *argv[] __unused)
657 {
658         krb5_error_code krbret;
659         krb5_context pam_context;
660         krb5_creds creds;
661         krb5_principal princ;
662         krb5_get_init_creds_opt opts;
663         krb5_data result_code_string, result_string;
664         int result_code, retval;
665         const char *pass;
666         const void *user;
667         char *princ_name, *passdup;
668
669         if (!(flags & PAM_UPDATE_AUTHTOK))
670                 return (PAM_AUTHTOK_ERR);
671
672         retval = pam_get_item(pamh, PAM_USER, &user);
673         if (retval != PAM_SUCCESS)
674                 return (retval);
675
676         PAM_LOG("Got user: %s", (const char *)user);
677
678         krbret = krb5_init_context(&pam_context);
679         if (krbret != 0) {
680                 PAM_LOG("Error krb5_init_context() failed");
681                 return (PAM_SERVICE_ERR);
682         }
683
684         PAM_LOG("Context initialised");
685
686         krb5_get_init_creds_opt_init(&opts);
687
688         PAM_LOG("Credentials options initialised");
689
690         /* Get principal name */
691         krbret = krb5_parse_name(pam_context, (const char *)user, &princ);
692         if (krbret != 0) {
693                 PAM_LOG("Error krb5_parse_name(): %s",
694                     krb5_get_err_text(pam_context, krbret));
695                 retval = PAM_USER_UNKNOWN;
696                 goto cleanup3;
697         }
698
699         /* Now convert the principal name into something human readable */
700         princ_name = NULL;
701         krbret = krb5_unparse_name(pam_context, princ, &princ_name);
702         if (krbret != 0) {
703                 PAM_LOG("Error krb5_unparse_name(): %s",
704                     krb5_get_err_text(pam_context, krbret));
705                 retval = PAM_SERVICE_ERR;
706                 goto cleanup2;
707         }
708
709         PAM_LOG("Got principal: %s", princ_name);
710
711         /* Get password */
712         retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass, PASSWORD_PROMPT);
713         if (retval != PAM_SUCCESS)
714                 goto cleanup2;
715
716         PAM_LOG("Got password");
717
718         memset(&creds, 0, sizeof(krb5_creds));
719         krbret = krb5_get_init_creds_password(pam_context, &creds, princ,
720             pass, NULL, pamh, 0, "kadmin/changepw", &opts);
721         if (krbret != 0) {
722                 PAM_LOG("Error krb5_get_init_creds_password(): %s",
723                     krb5_get_err_text(pam_context, krbret));
724                 retval = PAM_AUTH_ERR;
725                 goto cleanup2;
726         }
727
728         PAM_LOG("Credentials established");
729
730         /* Now get the new password */
731         for (;;) {
732                 retval = pam_get_authtok(pamh,
733                     PAM_AUTHTOK, &pass, NEW_PASSWORD_PROMPT);
734                 if (retval != PAM_TRY_AGAIN)
735                         break;
736                 pam_error(pamh, "Mismatch; try again, EOF to quit.");
737         }
738         if (retval != PAM_SUCCESS)
739                 goto cleanup;
740
741         PAM_LOG("Got new password");
742
743         /* Change it */
744         if ((passdup = strdup(pass)) == NULL) {
745                 retval = PAM_BUF_ERR;
746                 goto cleanup;
747         }
748         krbret = krb5_change_password(pam_context, &creds, passdup,
749             &result_code, &result_code_string, &result_string);
750         free(passdup);
751         if (krbret != 0) {
752                 PAM_LOG("Error krb5_change_password(): %s",
753                     krb5_get_err_text(pam_context, krbret));
754                 retval = PAM_AUTHTOK_ERR;
755                 goto cleanup;
756         }
757         if (result_code) {
758                 PAM_LOG("Error krb5_change_password(): (result_code)");
759                 retval = PAM_AUTHTOK_ERR;
760                 goto cleanup;
761         }
762
763         PAM_LOG("Password changed");
764
765         if (result_string.data)
766                 free(result_string.data);
767         if (result_code_string.data)
768                 free(result_code_string.data);
769
770 cleanup:
771         krb5_free_cred_contents(pam_context, &creds);
772         PAM_LOG("Done cleanup");
773 cleanup2:
774         krb5_free_principal(pam_context, princ);
775         PAM_LOG("Done cleanup2");
776 cleanup3:
777         if (princ_name)
778                 free(princ_name);
779
780         krb5_free_context(pam_context);
781
782         PAM_LOG("Done cleanup3");
783
784         return (retval);
785 }
786
787 PAM_MODULE_ENTRY("pam_krb5");
788
789 /*
790  * This routine with some modification is from the MIT V5B6 appl/bsd/login.c
791  * Modified by Sam Hartman <hartmans@mit.edu> to support PAM services
792  * for Debian.
793  *
794  * Verify the Kerberos ticket-granting ticket just retrieved for the
795  * user.  If the Kerberos server doesn't respond, assume the user is
796  * trying to fake us out (since we DID just get a TGT from what is
797  * supposedly our KDC).  If the host/<host> service is unknown (i.e.,
798  * the local keytab doesn't have it), and we cannot find another
799  * service we do have, let her in.
800  *
801  * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
802  */
803 /* ARGSUSED */
804 static int
805 verify_krb_v5_tgt(krb5_context context, krb5_ccache ccache,
806     char *pam_service, int debug)
807 {
808         krb5_error_code retval;
809         krb5_principal princ;
810         krb5_keyblock *keyblock;
811         krb5_data packet;
812         krb5_auth_context auth_context;
813         char phost[BUFSIZ];
814         const char *services[3], **service;
815
816         packet.data = 0;
817
818         /* If possible we want to try and verify the ticket we have
819          * received against a keytab.  We will try multiple service
820          * principals, including at least the host principal and the PAM
821          * service principal.  The host principal is preferred because access
822          * to that key is generally sufficient to compromise root, while the
823          * service key for this PAM service may be less carefully guarded.
824          * It is important to check the keytab first before the KDC so we do
825          * not get spoofed by a fake KDC.
826          */
827         services[0] = "host";
828         services[1] = pam_service;
829         services[2] = NULL;
830         keyblock = 0;
831         retval = -1;
832         for (service = &services[0]; *service != NULL; service++) {
833                 retval = krb5_sname_to_principal(context, NULL, *service,
834                     KRB5_NT_SRV_HST, &princ);
835                 if (retval != 0) {
836                         if (debug)
837                                 syslog(LOG_DEBUG,
838                                     "pam_krb5: verify_krb_v5_tgt(): %s: %s",
839                                     "krb5_sname_to_principal()",
840                                     krb5_get_err_text(context, retval));
841                         return -1;
842                 }
843
844                 /* Extract the name directly. */
845                 strncpy(phost, compat_princ_component(context, princ, 1),
846                     BUFSIZ);
847                 phost[BUFSIZ - 1] = '\0';
848
849                 /*
850                  * Do we have service/<host> keys?
851                  * (use default/configured keytab, kvno IGNORE_VNO to get the
852                  * first match, and ignore enctype.)
853                  */
854                 retval = krb5_kt_read_service_key(context, NULL, princ, 0, 0,
855                     &keyblock);
856                 if (retval != 0)
857                         continue;
858                 break;
859         }
860         if (retval != 0) {      /* failed to find key */
861                 /* Keytab or service key does not exist */
862                 if (debug)
863                         syslog(LOG_DEBUG,
864                             "pam_krb5: verify_krb_v5_tgt(): %s: %s",
865                             "krb5_kt_read_service_key()",
866                             krb5_get_err_text(context, retval));
867                 retval = 0;
868                 goto cleanup;
869         }
870         if (keyblock)
871                 krb5_free_keyblock(context, keyblock);
872
873         /* Talk to the kdc and construct the ticket. */
874         auth_context = NULL;
875         retval = krb5_mk_req(context, &auth_context, 0, *service, phost,
876                 NULL, ccache, &packet);
877         if (auth_context) {
878                 krb5_auth_con_free(context, auth_context);
879                 auth_context = NULL;    /* setup for rd_req */
880         }
881         if (retval) {
882                 if (debug)
883                         syslog(LOG_DEBUG,
884                             "pam_krb5: verify_krb_v5_tgt(): %s: %s",
885                             "krb5_mk_req()",
886                             krb5_get_err_text(context, retval));
887                 retval = -1;
888                 goto cleanup;
889         }
890
891         /* Try to use the ticket. */
892         retval = krb5_rd_req(context, &auth_context, &packet, princ, NULL,
893             NULL, NULL);
894         if (retval) {
895                 if (debug)
896                         syslog(LOG_DEBUG,
897                             "pam_krb5: verify_krb_v5_tgt(): %s: %s",
898                             "krb5_rd_req()",
899                             krb5_get_err_text(context, retval));
900                 retval = -1;
901         }
902         else
903                 retval = 1;
904
905 cleanup:
906         if (packet.data)
907                 compat_free_data_contents(context, &packet);
908         krb5_free_principal(context, princ);
909         return retval;
910 }
911
912 /* Free the memory for cache_name. Called by pam_end() */
913 /* ARGSUSED */
914 static void
915 cleanup_cache(pam_handle_t *pamh __unused, void *data, int pam_end_status __unused)
916 {
917         krb5_context pam_context;
918         krb5_ccache ccache;
919         krb5_error_code krbret;
920
921         if (krb5_init_context(&pam_context))
922                 return;
923
924         krbret = krb5_cc_resolve(pam_context, data, &ccache);
925         if (krbret == 0)
926                 krb5_cc_destroy(pam_context, ccache);
927         krb5_free_context(pam_context);
928         free(data);
929 }
930
931 #ifdef COMPAT_HEIMDAL
932 #ifdef COMPAT_MIT
933 #error This cannot be MIT and Heimdal compatible!
934 #endif
935 #endif
936
937 #ifndef COMPAT_HEIMDAL
938 #ifndef COMPAT_MIT
939 #error One of COMPAT_MIT and COMPAT_HEIMDAL must be specified!
940 #endif
941 #endif
942
943 #ifdef COMPAT_HEIMDAL
944 /* ARGSUSED */
945 static const char *
946 compat_princ_component(krb5_context context __unused, krb5_principal princ, int n)
947 {
948         return princ->name.name_string.val[n];
949 }
950
951 /* ARGSUSED */
952 static void
953 compat_free_data_contents(krb5_context context __unused, krb5_data * data)
954 {
955         krb5_xfree(data->data);
956 }
957 #endif
958
959 #ifdef COMPAT_MIT
960 static const char *
961 compat_princ_component(krb5_context context, krb5_principal princ, int n)
962 {
963         return krb5_princ_component(context, princ, n)->data;
964 }
965
966 static void
967 compat_free_data_contents(krb5_context context, krb5_data * data)
968 {
969         krb5_free_data_contents(context, data);
970 }
971 #endif