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