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