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