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