Re: How to replace pam_krb5 on RHEL 8 systems

Stephan Wonczak <[email protected]> Mon, 11 Jul 2022 10:30:30 +0200 (CEST)
Newsgroups gmane.comp.file-systems.openafs.general
Message-ID <[email protected]>
   Hi Jeffrey,
   Thanks for having a look at the problem.
   However, I obviously did not do a very good job detailing exactly what 
we did ... so here's my next try. Warning: It is going to be lengthy :-)

   First off: We do not use SSSD. And we would like to keep it that way, 
since it caused various massive problems in the past.

   On RHEL-7, everything works perfectly. We are using the RedHat-supplied 
RPM of pam_krb5: pam_krb5-2.4.8-6.el7.x86_64
  Looking at the debug-output of the module, this is what the relevant part 
looks like:

Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: 
pam_unix(sshd:session): session opened for user XXXX by (uid=0)
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
default/local realm 'RRZ.UNI-KOELN.DE'
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
configured realm 'RRZ.UNI-KOELN.DE'
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
debug
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
don't always_allow_localname
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
no ignore_afs
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
no null_afs
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
no cred_session
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
no ignore_k5login
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
user_check
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: will 
try previously set password first
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: will 
ask for a password if that fails
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: will 
let libkrb5 ask questions
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
use_shmem
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
external
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
no multiple_ccaches
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
validate
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: flag: 
warn
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
banner: Kerberos 5
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: ccache 
dir: /tmp
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: ccname 
template: FILE:%d/krb5cc_%U_XXXXXX
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
keytab: FILE:/etc/krb5.keytab
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: token 
strategy: 2b
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
removing shared memory segment 3 creator pid 3197
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
cleanup function removing shared memory segment 3 belonging to process 
3197
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
obtaining afs tokens
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
creating new PAG
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
obtaining tokens for local cell 'rrz.uni-koeln.de'
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: trying 
with ticket (2b)
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
attempting to determine realm for "rrz.uni-koeln.de"
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: file 
server for "/afs/rrz.uni-koeln.de" is 134.95.67.97
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: file 
server for "/afs/rrz.uni-koeln.de" is 134.95.109.81
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: file 
server for "/afs/rrz.uni-koeln.de" is 134.95.109.75
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: file 
server for "/afs/rrz.uni-koeln.de" is 134.95.112.8
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: file 
server 134.95.67.97 has name afs.thp.uni-koeln.de
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
afs.thp.uni-koeln.de is in realm "RRZ.UNI-KOELN.DE"
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: 
attempting to obtain tokens for "rrz.uni-koeln.de" 
("afs/[email protected]")
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: got 
tokens for cell "rrz.uni-koeln.de"
Jul  8 10:26:51 cftest.rrz.uni-koeln.de sshd[3197]: pam_krb5[3197]: no 
additional afs cells configured


   We then took the source PRM: pam_krb5-2.4.8-6.el7.src.rpm and did a 
rebuild on a RHEL-8-Machine. This worked without any errors.
   However, when we try to use this to get a token, this happens:

Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_unix(sshd:session): session opened for user a0537 by (uid=0)
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: default/local realm 'RRZ.UNI-KOELN.DE'
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: configured realm 'RRZ.UNI-KOELN.DE'
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: debug
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: don't always_allow_localname
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: no ignore_afs
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: no null_afs
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: no cred_session
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: no ignore_k5login
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: user_check
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: will try previously set password first
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: will ask for a password if that fails
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: will let libkrb5 ask questions
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: use_shmem
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: external
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: no multiple_ccaches
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: validate
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: flag: warn
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: banner: Kerberos 5
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: ccache dir: /tmp
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: ccname template: FILE:%d/krb5cc_%U_XXXXXX
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: keytab: FILE:/etc/krb5.keytab
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: token strategy: 2b
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: removing shared memory segment 29 creator pid 2204130
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: cleanup function removing shared memory segment 29 
belonging to process 2204130
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: obtaining afs tokens
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: creating new PAG
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: obtaining tokens for local cell 'rrz.uni-koeln.de'
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: trying with ticket (2b)
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: attempting to determine realm for "rrz.uni-koeln.de"
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: file server for "/afs/rrz.uni-koeln.de" is 134.95.67.97
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: file server for "/afs/rrz.uni-koeln.de" is 134.95.112.8
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: file server for "/afs/rrz.uni-koeln.de" is 
134.95.109.81
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: file server for "/afs/rrz.uni-koeln.de" is 
134.95.109.75
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: file server 134.95.67.97 has name afs.thp.uni-koeln.de
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: afs.thp.uni-koeln.de is in realm "RRZ.UNI-KOELN.DE"
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: attempting to obtain tokens for "rrz.uni-koeln.de" 
("afs/[email protected]")
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for 
'afs/[email protected]' (enctype=1) on behalf of 
'[email protected]': No credentia
ls found with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for 
'afs/[email protected]' (enctype=2) on behalf of 
'[email protected]': No credentia
ls found with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for 
'afs/[email protected]' (enctype=3) on behalf of 
'[email protected]': No credentia
ls found with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: attempting to obtain tokens for "rrz.uni-koeln.de" 
("[email protected]")
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for '[email protected]' 
(enctype=1) on behalf of '[email protected]': No credentials found 
with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for '[email protected]' 
(enctype=2) on behalf of '[email protected]': No credentials found 
with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for '[email protected]' 
(enctype=3) on behalf of '[email protected]': No credentials found 
with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: attempting to obtain tokens for "rrz.uni-koeln.de" 
("afsx/[email protected]")
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for 
'afsx/[email protected]' (enctype=1) on behalf of 
'[email protected]': No credentials found with supported encryption 
types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for 
'afsx/[email protected]' (enctype=2) on behalf of 
'[email protected]': No credentials found with supported encryption 
types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for 
'afsx/[email protected]' (enctype=3) on behalf of 
'[email protected]': No credentials found with supported encryption 
types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: attempting to obtain tokens for "rrz.uni-koeln.de" 
("[email protected]")
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for '[email protected]' 
(enctype=1) on behalf of '[email protected]': No credentials found 
with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for '[email protected]' 
(enctype=2) on behalf of '[email protected]': No credentials found 
with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: error obtaining credentials for '[email protected]' 
(enctype=3) on behalf of '[email protected]': No credentials found 
with supported encryption types
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: afslog (2b) failed to "rrz.uni-koeln.de"
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: got error -1 (Unknown code ____ 255) while obtaining 
tokens for rrz.uni-koeln.de
Jul  8 15:14:57 kicktest.rrz.uni-koeln.de sshd[2204130]: 
pam_krb5[2204130]: no additional afs cells configured

   To reiterate: We get both kerberos ticket and AFS-Token on RHEL-7. On 
RHEL-8, we still get a valid kerberos ticket, but getting the AFS-Token 
fails. It -is- possible, however, to get a valid AFS-Token by klog.krb5. 
So -in principle- everything is in place to have this done by pam_afs.
   The problem is: I have no way to determine why it is complaining about 
"no supported encryption types" when other tools have no problems at all!

   Additional infO. Yes, we did rekey our AFS-cell quite a while ago, and 
our afs-Principal has two keys:

kadmin.local:  getprinc afs/rrz.uni-koeln.de
Principal: afs/[email protected]
<snip>
Anzahl der Schlüssel: 2
Key: vno 5, aes256-cts-hmac-sha1-96
Key: vno 4, des-cbc-crc
MKey: vno 1
Attribute: REQUIRES_PRE_AUTH
Richtlinie: [keins]

   Our users have three:

kadmin.local:  getprinc XXXX
Principal: [email protected]
<snip>
Anzahl der Schlüssel: 3
Key: vno 2, aes256-cts-hmac-sha1-96
Key: vno 2, des-cbc-crc
Key: vno 2, des-cbc-md5:afs3
MKey: vno 1
Attribute: REQUIRES_PRE_AUTH
Richtlinie: [keins]

   Like I said before, I looked at the sources of our version of pam_krb5, 
and the part where it is failing starts at line 775 inside the function 
"minikafs_5log_with_principal" (I'll attach the minikafs.c to this mail 
for reference)

 	/* Try to obtain a suitable credential. */
         for (i = 0; i < n_etypes; i++) {
                 memset(&mcreds, 0, sizeof(mcreds));
                 mcreds.client = client;
                 mcreds.server = server;
                 if (etypes != NULL) {
                         v5_creds_set_etype(ctx, &mcreds, etypes[i]);
                 }
                 new_creds = NULL;
                 tmp = krb5_get_credentials(ctx, 0, ccache,
                                            &mcreds, &new_creds);
                 if (tmp == 0) {
                         if (use_rxk5 &&
                             (minikafs_5settoken2(cell, new_creds, uid) == 
0)) {
                                	krb5_free_creds(ctx, new_creds);
                                 v5_free_unparsed_name(ctx, 
unparsed_client);
                                 krb5_free_principal(ctx, client);
                                 krb5_free_principal(ctx, server);
                                 return 0;
                         } else
                         if (use_v5_2b &&
                             (minikafs_5settoken(cell, new_creds, uid) == 
0)) {
                                 krb5_free_creds(ctx, new_creds);
                                 v5_free_unparsed_name(ctx, 
unparsed_client);
                                 krb5_free_principal(ctx, client);
                                 krb5_free_principal(ctx, server);
                                	return 0;
                         }
                         krb5_free_creds(ctx, new_creds);
                 } else {
                         if (options->debug) {
                                 if (etypes != NULL) {
                                         debug("error obtaining credentials 
for "
                                               "'%s' (enctype=%d) on behalf 
of "
                                               "'%s': %s",
                                               principal, etypes[i],
                                               unparsed_client,
                                               v5_error_message(tmp));
                                	} else {
                                        	debug("error obtaining credentials 
for "
                                               "'%s' on behalf of "
                                               "'%s': %s",
                                               principal,
                                               unparsed_client,
                                               v5_error_message(tmp));
                                 }
                         }
                 }
         }

 	v5_free_unparsed_name(ctx, unparsed_client);
         krb5_free_principal(ctx, client);
         krb5_free_principal(ctx, server);

   If you or anyone else has any ideas how to tackle the problem, any help 
would be greatly appreciated.

   Cheers from Cologne,
   Stephan Wonczak


On Fri, 8 Jul 2022, Jeffrey E Altman wrote:

> 
> Sounds like the version of pam_krb5 you are attempting to build does not
> include support for rxkad-kdf.
> 
>  https://lists.openafs.org/pipermail/afs3-standardization/2013-July/002738.h
> tml
> 
> The version of pam_krb5 that supports rxkad-kdf contains a
> minikafs_kd_derive() function at minikafs.c line 775.
> 
> See https://github.com/frozencemetery/pam_krb5.
> 
> As mentioned in my prior reply pam_krb5 should not be used in conjunction
> with sssd.
> 
> Jeffrey Altman
> 
> On 7/8/2022 8:35 AM, Stephan Wonczak ([email protected]) wrote:
>         Hi everyone!
>         (Berthold's colleague here)
>
>         We dug a little deeper and found the part in the
>       pam_krb5-sources where it fails. It is in the file "minikafs.c"
>       starting in line 775. It looks like the call to
>       krb5_get_credentials() gets a non-zero return value, thus making
>       it bail out.
>         The problem is that we (well, at least me!) have no idea which
>       enctype is expected, and which enctypes are actually tried.
>       Debug output is not too helpful here. Any ideas on how to get
>       useful information?
>         (I should mention I am waaay out of depth here with my
>       knowledge of Kerberos, and my C-fu is severely lacking, too ;-)
>       )
>
>         To be absolutley clear: We can ssh-login to the machine
>       running this pam_krb.so-module, and get a valid krb5-ticket. No
>       AFS-token after login, thus no access to AFS. If I do
>       "klog.krb5", I -do- get an AFS-Token without any issues, and
>       AFS-access starts working as it should.
>         It's maddening that only pam_krb5 complains, while other tools
>       work out of the box.
>
>         Any advice would be greatly appreciated!
>
>         Stephan
>
>       On Fri, 8 Jul 2022, Berthold Cogel wrote:
>
>             Am 07.07.22 um 19:04 schrieb Dirk Heinrichs:
>                    Benjamin Kaduk:
>
>                          Are you aware of
>                         pam_afs_session
>                          (https://github.com/rra/pam-afs-session)?
>                         Without knowing more about
>                          what you're using pam_krb5
>                         for it's hard to make
>                         specific suggestions
>                          about what alternatives
>                         might exist.
> 
>
>                    BTW: pam_krb5 != pam_krb5. There are
>                   two different modules with the same
>                    name out there. The one shipped with
>                   RedHat family distributions comes
>                    with integrated AFS support, while the
>                   one shipped with Debian family
>                    distributions doesn't. That's the
>                   reason why Debian also ships
>                    pam_afs_session and RH does not.
>
>                    Bye...
>
>                         Dirk
> 
>
>             We're using the pam_krb5 shipped with Red Hat.
>
>             I've rebuild the module from the RHEL 7 source rpm
>             on RHEL 8. And it seems to work.... for some value
>             of working....
>
>             Supported enctypes in our kdc:
>             aes256-cts-hmac-sha1-96:normal des-cbc-crc:normal
>             des:afs3
>
>             We 'rekeyed' our AFS environment with
>             aes256-cts-hmac-sha1-96:normal to get connections
>             from newer Ubuntu/Debian and Fedora 35 working.
>
>             We get a krb5 ticket and a login, but getting the
>             AFS token gives errors:
>
>             "error obtaining credentials for
>             'afs/[email protected]' (enctype=1)
>             on behalf of ....: No credentials found with
>             supported encryption types"
>
>             Same for two other enctypes.
>
>             So something else changed in RHEL 8, which we
>             haven't found yet.
> 
>
>             Regards
>             Berthold
>             _______________________________________________
>             OpenAFS-info mailing list
>             [email protected]
>             https://lists.openafs.org/mailman/listinfo/openafs-info
> 
>
>           Dipl. Chem. Dr. Stephan Wonczak
>
>               Regionales Rechenzentrum der Universitaet zu Koeln
>       (RRZK)
>               Universitaet zu Koeln, Weyertal 121, 50931 Koeln
>               Tel: +49/(0)221/470-89583, Fax: +49/(0)221/470-89625
> 
> 
>

 	Dipl. Chem. Dr. Stephan Wonczak

         Regionales Rechenzentrum der Universitaet zu Koeln (RRZK)
         Universitaet zu Koeln, Weyertal 121, 50931 Koeln
         Tel: +49/(0)221/470-89583, Fax: +49/(0)221/470-89625
minikafs.c (text/plain, 36.7 KB)
/*
 * Copyright 2004,2005,2006,2007,2008,2009,2010 Red Hat, Inc.
 * Copyright 2004 Kungliga Tekniska Högskolan
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * ALTERNATIVELY, this product may be distributed under the terms of the
 * GNU Lesser General Public License, in which case the provisions of the
 * LGPL are required INSTEAD OF the above restrictions.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

 /*
  * A miniature afslog implementation.  Requires cells served by OpenAFS 1.2.8
  * or later in combination with MIT Kerberos 1.2.6 or later.
  *
  * References:
  *   http://grand.central.org/numbers/pioctls.html
  *   http://www.afsig.se/afsig/space/rxgk-client-integration
  *   auth/afs_token.xg
  */

#include "../config.h"

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#ifdef HAVE_SYS_IOCCOM_H
#include <sys/ioccom.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif
#include <limits.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef HAVE_SECURITY_PAM_APPL_H
#include <security/pam_appl.h>
#endif

#ifdef HAVE_SECURITY_PAM_MODULES_H
#include <security/pam_modules.h>
#endif
 
#include KRB5_H

#include "init.h"
#include "log.h"
#include "minikafs.h"
#include "v5.h"
#include "xstr.h"

#ifndef KRB_TICKET_GRANTING_TICKET
#ifdef KRB5_TGS_NAME
#define KRB_TICKET_GRANTING_TICKET KRB5_TGS_NAME
#else
#define KRB_TICKET_GRANTING_TICKET "krbtgt"
#endif
#endif

#define HOSTNAME_SIZE NI_MAXHOST

#define OPENAFS_AFS_IOCTL_FILE  "/proc/fs/openafs/afs_ioctl"
#define ARLA_AFS_IOCTL_FILE     "/proc/fs/nnpfs/afs_ioctl"

#ifdef sun
#ifndef __NR_afs_syscall
#define __NR_afs_syscall 65
#endif
#endif

/* Global(!) containing the path to the file/device/whatever in /proc which we
 * can use to get the effect of the AFS syscall.  If we ever need to be
 * thread-safe, we'll have to lock around accesses to this. */
static const char *minikafs_procpath = NULL;

#define VIOCTL_SYSCALL ((unsigned int) _IOW('C', 1, void *))
#define VIOCTL_FN(id)  ((unsigned int) _IOW('V', (id), struct minikafs_ioblock))
#define CIOCTL_FN(id)  ((unsigned int) _IOW('C', (id), struct minikafs_ioblock))
#define OIOCTL_FN(id)  ((unsigned int) _IOW('O', (id), struct minikafs_ioblock))
#define AIOCTL_FN(id)  ((unsigned int) _IOW('A', (id), struct minikafs_ioblock))

/* A structure specifying parameters to the VIOCTL_SYSCALL ioctl.  An array
 * would do as well, but this makes the order of items clearer. */
struct minikafs_procdata {
	long param4;
	long param3;
	long param2;
	long param1;
	long function;
};

/* A structure specifying input/output buffers to pioctl functions. */
struct minikafs_ioblock {
	char *in, *out;
	uint16_t insize, outsize;
};

/* The portion of a token which includes our own key and other bookkeeping
 * stuff.  Along with a magic blob used by rxkad, the guts of rxkad tokens. */
struct minikafs_plain_token {
	uint32_t kvno;
	char key[8];
	uint32_t uid;
	uint32_t start, end; /* must be odd (?) */
};

/* Functions called through minikafs_syscall().  Might not port to your system. */
enum minikafs_subsys {
	minikafs_subsys_pioctl = 20,
	minikafs_subsys_setpag = 21,
};

/* Subfunctions called through minikafs_pioctl(). */
enum minikafs_pioctl_fn {
	minikafs_pioctl_bogus = VIOCTL_FN(0),
	minikafs_pioctl_settoken = VIOCTL_FN(3),
	minikafs_pioctl_flush = VIOCTL_FN(6),
	minikafs_pioctl_gettoken = VIOCTL_FN(8),
	minikafs_pioctl_unlog = VIOCTL_FN(9),
	minikafs_pioctl_whereis = VIOCTL_FN(14),
	minikafs_pioctl_unpag = VIOCTL_FN(21),
	minikafs_pioctl_getcelloffile = VIOCTL_FN(30),
	minikafs_pioctl_getwscell = VIOCTL_FN(31),
	minikafs_pioctl_gettoken2 = CIOCTL_FN(7),
	minikafs_pioctl_settoken2 = CIOCTL_FN(8),
	minikafs_pioctl_getprop = CIOCTL_FN(10),
	minikafs_pioctl_setprop = CIOCTL_FN(11),
};

/* Forward declarations. */
static int minikafs_5settoken2(const char *cell, krb5_creds *creds, int32_t id);

/* Call AFS using an ioctl. Might not port to your system. */
static int
minikafs_ioctlcall(long function, long arg1, long arg2, long arg3, long arg4)
{
	int fd, ret, saved_errno;
	struct minikafs_procdata data;
	fd = open(minikafs_procpath, O_RDWR);
	if (fd == -1) {
		errno = EINVAL;
		return -1;
	}
	data.function = function;
	data.param1 = arg1;
	data.param2 = arg2;
	data.param3 = arg3;
	data.param4 = arg4;
	ret = ioctl(fd, VIOCTL_SYSCALL, &data);
	saved_errno = errno;
	close(fd);
	errno = saved_errno;
	return ret;
}

/* Call the AFS syscall. Might not port to your system. */
static int
minikafs_syscall(long function, long arg1, long arg2, long arg3, long arg4)
{
#ifdef __NR_afs_syscall
	return syscall(__NR_afs_syscall, function, arg1, arg2, arg3, arg4);
#else
	errno = ENOSYS;
	return -1;
#endif
}

/* Call into AFS, somehow. */
static int
minikafs_call(long function, long arg1, long arg2, long arg3, long arg4)
{
	if (minikafs_procpath != NULL) {
		return minikafs_ioctlcall(function, arg1, arg2, arg3, arg4);
	}
	return minikafs_syscall(function, arg1, arg2, arg3, arg4);
}

/* Make an AFS pioctl. Might not port to your system. */
static int
minikafs_pioctl(char *file, enum minikafs_pioctl_fn subfunction,
		struct minikafs_ioblock *iob)
{
	return minikafs_call(minikafs_subsys_pioctl, (long) file,
			     subfunction, (long) iob, 0);
}

/* Determine in which cell a given file resides.  Returns 0 on success. */
int
minikafs_cell_of_file(const char *file, char *cell, size_t length)
{
	struct minikafs_ioblock iob;
	char *wfile;
	int i;

	wfile = xstrdup(file ? file : "/afs");

	memset(&iob, 0, sizeof(iob));
	iob.in = wfile;
	iob.insize = strlen(wfile) + 1;
	iob.out = cell;
	iob.outsize = length;

	i = minikafs_pioctl(wfile, minikafs_pioctl_getcelloffile, &iob);

	xstrfree(wfile);
	return i;
}

/* Do minikafs_cell_of_file, but if we can't find out, walk up the filesystem
 * tree until we either get an answer or hit the root directory. */
int
minikafs_cell_of_file_walk_up(const char *file, char *cell, size_t length)
{
	char *p, dir[PATH_MAX + 1];
	int i;

	snprintf(dir, sizeof(dir), "%s", file);
	do {
		memset(cell, '\0', length);
		i = minikafs_cell_of_file(dir, cell, length);
		if (i != 0) {
			p = strrchr(dir, '/');
			if (p != NULL) {
				*p = '\0';
			} else {
				strcpy(dir, "");
			}
		}
	} while ((i != 0) && (strlen(dir) > 0));
	return i;
}

/* Determine if AFS is running. Unlike most other functions, return 0 on
 * FAILURE. */
int
minikafs_has_afs(void)
{
	char cell[PATH_MAX];
	int fd, i, ret;
	struct sigaction news, olds;

	fd = -1;

#ifdef OPENAFS_AFS_IOCTL_FILE
	if (fd == -1) {
		fd = open(OPENAFS_AFS_IOCTL_FILE, O_RDWR);
		if (fd != -1) {
			minikafs_procpath = OPENAFS_AFS_IOCTL_FILE;
			close(fd);
			return 1;
		}
	}
#endif
#ifdef ARLA_AFS_IOCTL_FILE
	if (fd == -1) {
		fd = open(ARLA_AFS_IOCTL_FILE, O_RDWR);
		if (fd != -1) {
			minikafs_procpath = ARLA_AFS_IOCTL_FILE;
			close(fd);
			return 1;
		}
	}
#endif
	if (fd == -1) {
		return 0;
	}

	memset(&news, 0, sizeof(news));
	news.sa_handler = SIG_IGN;
	i = sigaction(SIGSYS, &news, &olds);
	if (i != 0) {
		return 0;
	}

	ret = 0;
	i = minikafs_cell_of_file(NULL, cell, sizeof(cell));
	if ((i == 0) || ((i == -1) && (errno != ENOSYS))) {
		ret = 1;
	}

	sigaction(SIGSYS, &olds, NULL);

	return ret;
}

/* Determine in which realm a cell exists.  We do this by obtaining the address
 * of the fileserver which holds /afs/cellname (assuming that the root.cell
 * volume from the cell is mounted there), converting the address to a host
 * name, and then asking libkrb5 to tell us to which realm the host belongs. */
static int
minikafs_realm_of_cell_with_ctx(krb5_context ctx,
				struct _pam_krb5_options *options,
				const char *cell,
				char *realm, size_t length)
{
	struct minikafs_ioblock iob;
	struct sockaddr_in sin;
	in_addr_t *address;
	krb5_context use_ctx;
	char *path, host[HOSTNAME_SIZE], **realms;
	int i, n_addresses, ret;

	if (cell) {
		path = malloc(strlen(cell) + 6);
	} else {
		path = malloc(5);
	}
	if (path == NULL) {
		return -1;
	}
	if (cell) {
		sprintf(path, "/afs/%s", cell);
	} else {
		sprintf(path, "/afs");
	}

	n_addresses = 16;
	do {
		/* allocate the output buffer for the address [list] */
		address = malloc(n_addresses * sizeof(address[0]));
		if (address == NULL) {
			ret = -1;
			break;
		}
		memset(address, 0, n_addresses * sizeof(address[0]));
		memset(&iob, 0, sizeof(iob));
		iob.in = path;
		iob.insize = strlen(path) + 1;
		iob.out = (char*) &address[0];
		iob.outsize = n_addresses * sizeof(address[0]);
		ret = minikafs_pioctl(path, minikafs_pioctl_whereis, &iob);
		/* if we failed, free the address [list], and if the error was
		 * E2BIG, increase the size we'll use next time, up to a
		 * hard-coded limit */
		if (ret != 0) {
			if (options->debug) {
				debug("error during whereis pioctl: %s",
				      strerror(errno));
			}
			free(address);
			address = NULL;
			if (errno == E2BIG) {
				if (n_addresses > 256) {
					if (options->debug) {
						debug("giving up");
					}
					break;
				}
				if (options->debug) {
					debug("retrying");
				}
				n_addresses *= 2;
			}
		}
	} while ((ret != 0) && (errno == E2BIG));

	if (ret != 0) {
		if (options->debug) {
			debug("got error %d (%s) determining file server for "
			      "\"%s\"", errno, v5_error_message(errno), path);
		}
		free(path);
		return ret;
	}
	free(path);

	sin.sin_family = AF_INET;
	if (options->debug) {
		for (i = 0; (i < n_addresses) && (address[i] != 0); i++) {
			debug("file server for \"/afs/%s\" is %u.%u.%u.%u",
			      cell,
			      (address[i] >>  0) & 0xff,
			      (address[i] >>  8) & 0xff,
			      (address[i] >> 16) & 0xff,
			      (address[i] >> 24) & 0xff);
		}
	}

	if (ctx == NULL) {
		if (_pam_krb5_init_ctx(&use_ctx, 0, NULL) != 0) {
			free(address);
			return -1;
		}
	} else {
		use_ctx = ctx;
	}

	for (i = 0; (i < n_addresses) && (address[i] != 0); i++) {
		memcpy(&sin.sin_addr, &address[i], sizeof(address[i]));
		if (getnameinfo((const struct sockaddr*) &sin, sizeof(sin),
				host, sizeof(host), NULL, 0,
				NI_NAMEREQD) == 0) {
			if (options->debug) {
				debug("file server %d.%d.%d.%d has name %s",
				      (address[i] >>  0) & 0xff,
				      (address[i] >>  8) & 0xff,
				      (address[i] >> 16) & 0xff,
				      (address[i] >> 24) & 0xff,
				      host);
			}
			if (krb5_get_host_realm(use_ctx, host, &realms) == 0) {
				strncpy(realm, realms[0], length - 1);
				realm[length - 1] = '\0';
				krb5_free_host_realm(use_ctx, realms);
				if (options->debug) {
					debug("%s is in realm \"%s\"",
					      host, realm);
				}
				i = 0;
				break;
			}
		} else {
			if (options->debug) {
				debug("error %d(%s) determining realm for %s",
				      i, v5_error_message(i), host);
			}
		}
	}

	if (use_ctx != ctx) {
		_pam_krb5_free_ctx(use_ctx);
	}

	free(address);

	return i;
}

/* Create a new PAG. */
int
minikafs_setpag(void)
{
	return minikafs_call(minikafs_subsys_setpag, 0, 0, 0, 0);
}

#if 0
/* Leave any PAG. It turns out this results in an unlog(), which is not what we
 * wanted here. */
static int
minikafs_unpag(void)
{
	struct minikafs_ioblock iob;
	char wfile[] = "/afs";
	int i;

	memset(&iob, 0, sizeof(iob));
	iob.in = wfile;
	iob.insize = sizeof(wfile);
	iob.out = wfile;
	iob.outsize = sizeof(wfile);

	i = minikafs_pioctl(wfile, minikafs_pioctl_unpag, &iob);
	return i;
}
#endif

/* Determine which cell is the default on this workstation. */
int
minikafs_ws_cell(char *cell, size_t length)
{
	struct minikafs_ioblock iob;
	char wfile[] = "/afs";
	int i;

	memset(&iob, 0, sizeof(iob));
	iob.in = wfile;
	iob.insize = strlen(wfile) + 1;
	iob.out = cell;
	iob.outsize = length - 1;
	memset(cell, '\0', length);

	i = minikafs_pioctl(wfile, minikafs_pioctl_getwscell, &iob);
	
	return i;
}

/* Stuff a ticket and DES key into the kernel. */
static int
minikafs_settoken(const void *ticket, uint32_t ticket_size,
		  int kvno, const unsigned char *key,
		  uint32_t uid, uint32_t start, uint32_t end, uint32_t flags,
		  const char *cell)
{
	char *buffer;
	struct minikafs_plain_token plain_token;
	struct minikafs_ioblock iob;
	uint32_t size;
	int i;

	/* Allocate the input buffer. */
	buffer = malloc(4 + ticket_size +
			4 + sizeof(struct minikafs_plain_token) +
			4 +
			strlen(cell) + 1);
	if (buffer == NULL) {
		return -1;
	}

	/* their copy of the session key, encrypted with their key */
	size = ticket_size;
	memcpy(buffer, &size, 4);
	memcpy(buffer + 4, ticket, size);

	/* our copy of the session key, plus housekeeping */
	plain_token.kvno = kvno;
	memcpy(&plain_token.key, key, 8);
	plain_token.uid = uid;
	plain_token.start = start;
	plain_token.end = end;
	if (((end - start) % 2) != 0) {
		plain_token.end--;
	}

	size = sizeof(plain_token);
	memcpy(buffer + 4 + ticket_size, &size, 4);
	memcpy(buffer + 4 + ticket_size + 4, &plain_token, size);

	/* flags */
	size = flags;
	memcpy(buffer + 4 + ticket_size + 4 + sizeof(plain_token), &size, 4);

	/* the name of the cell */
	memcpy(buffer + 4 + ticket_size + 4 + sizeof(plain_token) + 4,
	       cell, strlen(cell) + 1);

	/* the regular stuff */
	memset(&iob, 0, sizeof(iob));
	iob.in = buffer;
	iob.insize = 4 + ticket_size +
		     4 + sizeof(struct minikafs_plain_token) +
		     4 + strlen(cell) + 1;
	iob.out = NULL;
	iob.outsize = 0;

	i = minikafs_pioctl(NULL, minikafs_pioctl_settoken, &iob);
	free(buffer);
	return i;
}

/* Stuff the ticket and key from a credentials structure into the kernel. */
static int
minikafs_5settoken(const char *cell, krb5_creds *creds, uid_t uid)
{
	/* Assume that the only 8-byte keys are DES keys, and sanity-check. */
	if (v5_creds_key_length(creds) != 8) {
		return -1;
	}
	return minikafs_settoken(creds->ticket.data,
				 creds->ticket.length,
				 0x100, /* magic number, signals OpenAFS
					 * 1.2.8 and later that the ticket
					 * is actually a v5 ticket */
				 v5_creds_key_contents(creds),
				 uid,
				 creds->times.starttime,
				 creds->times.endtime,
				 0,
				 cell);
}

/* Clear our tokens. */
int
minikafs_unlog(void)
{
	return minikafs_pioctl(NULL, minikafs_pioctl_unlog, NULL);
}

/* Ask the kernel which ciphers it supports for use with rxk5. */
static int
minikafs_get_property(const char *property, char *value, int length)
{
	struct minikafs_ioblock iob;
	int i;

	iob.in = property ? (char *) property : "*";
	iob.insize = strlen(property) + 1;
	iob.out = value;
	iob.outsize = length;
	i = minikafs_pioctl(NULL, minikafs_pioctl_getprop, &iob);
	return i;
}

static int
minikafs_get_rxk5_enctypes(krb5_enctype *etypes, int n_etypes)
{
	int n;
	uint32_t i;
	long l;
	const char *property = "rxk5.enctypes", *p, *v;
	char enctypes[1024], *q;
	n = -1;
	memset(enctypes, '\0', sizeof(enctypes));
	if (minikafs_get_property(property,
				  enctypes, sizeof(enctypes) - 1) == 0) {
		p = enctypes;
		n = 0;
		while ((p != NULL) && (*p != '\0') && (n < n_etypes)) {
			v = p + strlen(p) + 1;
			if (strcmp(p, property) == 0) {
				p = v;
				while ((p != NULL) && (*p != '\0') &&
				       (n < n_etypes)) {
					l = strtol(p, &q, 10);
					if ((q != NULL) &&
					    ((*q == ' ') || (*q == '\0'))) {
						i = l & 0xffffffff;
						if (i != 0) {
							etypes[n++] = i;
						}
						p = q + strcspn(q,
								"0123456789");
					} else {
						break;
					}
				}
			}
			p = v + strlen(v) + 1;
		}
	}
	return n;
}

/* Try to set a token for the given cell using creds for the named principal. */
static int
minikafs_5log_with_principal(krb5_context ctx,
			     struct _pam_krb5_options *options,
			     krb5_ccache ccache,
			     const char *cell,
			     const char *principal,
			     uid_t uid,
			     int use_rxk5,
			     int use_v5_2b)
{
	krb5_principal server, client;
	krb5_creds mcreds, creds, *new_creds;
	char *unparsed_client;
	krb5_enctype v5_2b_etypes[] = {
		ENCTYPE_DES_CBC_CRC,
		ENCTYPE_DES_CBC_MD4,
		ENCTYPE_DES_CBC_MD5,
	};
	krb5_enctype rxk5_enctypes[16];
	krb5_enctype *etypes;
	int i, n_etypes;
	int tmp;

	memset(&client, 0, sizeof(client));
	memset(&server, 0, sizeof(server));
	if (use_rxk5) {
		n_etypes = minikafs_get_rxk5_enctypes(rxk5_enctypes,
						      sizeof(rxk5_enctypes) /
						      sizeof(rxk5_enctypes[0]) -
						      1);
#if 1
		n_etypes = 0;
#endif
		if (n_etypes > 0) {
			etypes = rxk5_enctypes;
			rxk5_enctypes[n_etypes] = 0;
		} else {
			etypes = NULL;
			n_etypes = 1; /* hack: we want to try at least once */
		}
	} else {
		etypes = v5_2b_etypes;
		n_etypes = sizeof(v5_2b_etypes) / sizeof(v5_2b_etypes[0]);
#ifdef HAVE_KRB5_ALLOW_WEAK_CRYPTO
		if (krb5_allow_weak_crypto(ctx, TRUE) != 0) { /* XXX */
			warn("error enabling weak crypto (DES), continuing");
		}
#endif
	}

	if (krb5_cc_get_principal(ctx, ccache, &client) != 0) {
		if (options->debug) {
			debug("error determining default principal name "
			      "for ccache");
		}
		return -1;
	}
	unparsed_client = NULL;
	if (krb5_unparse_name(ctx, client, &unparsed_client) != 0) {
		warn("error unparsing client principal name from ccache");
		krb5_free_principal(ctx, client);
		return -1;
	}
	if (v5_parse_name(ctx, options, principal, &server) != 0) {
		warn("error parsing principal name '%s'", principal);
		v5_free_unparsed_name(ctx, unparsed_client);
		krb5_free_principal(ctx, client);
		return -1;
	}

	/* Check if we already have a suitable credential. */
	for (i = 0; i < n_etypes; i++) {
		memset(&mcreds, 0, sizeof(mcreds));
		memset(&creds, 0, sizeof(creds));
		mcreds.client = client;
		mcreds.server = server;
		if (etypes != NULL) {
#ifdef HAVE_KRB5_ENCTYPE_ENABLE
			if (krb5_enctype_enable(ctx, etypes[i]) != 0) {
				char etype[32];
				/* Whether or not enctype_to_string
				 * nul-terminates varies between
				 * implementations and versions. */
				memset(etype, '\0', sizeof(etype));
				if (v5_enctype_to_string(ctx, etypes[i], etype,
							 sizeof(etype) - 1) != 0) {
					warn("error enabling enctype %d, "
					     "continuing", etypes[i]);
				} else {
					warn("error enabling enctype %s "
					     "continuing", etype);
				}
			}
#endif
			v5_creds_set_etype(ctx, &mcreds, etypes[i]);
		}
		if (krb5_cc_retrieve_cred(ctx, ccache, v5_cc_retrieve_match(),
					  &mcreds, &creds) == 0) {
			if (use_rxk5 &&
			    (minikafs_5settoken2(cell, &creds, uid) == 0)) {
				krb5_free_cred_contents(ctx, &creds);
				v5_free_unparsed_name(ctx, unparsed_client);
				krb5_free_principal(ctx, client);
				krb5_free_principal(ctx, server);
				return 0;
			} else
			if (use_v5_2b &&
			    (minikafs_5settoken(cell, &creds, uid) == 0)) {
				krb5_free_cred_contents(ctx, &creds);
				v5_free_unparsed_name(ctx, unparsed_client);
				krb5_free_principal(ctx, client);
				krb5_free_principal(ctx, server);
				return 0;
			}
			krb5_free_cred_contents(ctx, &creds);
		}
	}

	/* Try to obtain a suitable credential. */
	for (i = 0; i < n_etypes; i++) {
		memset(&mcreds, 0, sizeof(mcreds));
		mcreds.client = client;
		mcreds.server = server;
		if (etypes != NULL) {
			v5_creds_set_etype(ctx, &mcreds, etypes[i]);
		}
		new_creds = NULL;
		tmp = krb5_get_credentials(ctx, 0, ccache,
					   &mcreds, &new_creds);
		if (tmp == 0) {
			if (use_rxk5 &&
			    (minikafs_5settoken2(cell, new_creds, uid) == 0)) {
				krb5_free_creds(ctx, new_creds);
				v5_free_unparsed_name(ctx, unparsed_client);
				krb5_free_principal(ctx, client);
				krb5_free_principal(ctx, server);
				return 0;
			} else
			if (use_v5_2b &&
			    (minikafs_5settoken(cell, new_creds, uid) == 0)) {
				krb5_free_creds(ctx, new_creds);
				v5_free_unparsed_name(ctx, unparsed_client);
				krb5_free_principal(ctx, client);
				krb5_free_principal(ctx, server);
				return 0;
			}
			krb5_free_creds(ctx, new_creds);
		} else {
			if (options->debug) {
				if (etypes != NULL) {
					debug("error obtaining credentials for "
					      "'%s' (enctype=%d) on behalf of "
					      "'%s': %s",
					      principal, etypes[i],
					      unparsed_client,
					      v5_error_message(tmp));
				} else {
					debug("error obtaining credentials for "
					      "'%s' on behalf of "
					      "'%s': %s",
					      principal,
					      unparsed_client,
					      v5_error_message(tmp));
				}
			}
		}
	}

	v5_free_unparsed_name(ctx, unparsed_client);
	krb5_free_principal(ctx, client);
	krb5_free_principal(ctx, server);

	return -1;
}

/* Try to obtain tokens for the named cell using the default ccache and
 * configuration settings. */
static int
minikafs_5log(krb5_context context, krb5_ccache ccache,
	      struct _pam_krb5_options *options,
	      const char *cell, const char *hint_principal,
	      uid_t uid, int use_rxk5, int use_v5_2b)
{
	krb5_context ctx;
	krb5_ccache use_ccache;
	int ret;
	unsigned int i;
	char *principal, *defaultrealm, realm[PATH_MAX];
	size_t principal_size, base_size;
	const char *base_rxkad[] = {"afs", "afsx"};
	const char *base_rxk5[] = {"afs-k5"};
	const char **base;

	if (context == NULL) {
		if (_pam_krb5_init_ctx(&ctx, 0, NULL) != 0) {
			return -1;
		}
	} else {
		ctx = context;
	}

	if (use_rxk5) {
		base = base_rxk5;
		base_size = sizeof(base_rxk5) / sizeof(base_rxk5[0]);
	} else {
		base = base_rxkad;
		base_size = sizeof(base_rxkad) / sizeof(base_rxkad[0]);
	}

	memset(&use_ccache, 0, sizeof(use_ccache));
	if (ccache != NULL) {
		use_ccache = ccache;
	} else {
		if (krb5_cc_default(ctx, &use_ccache) != 0) {
			if (ctx != context) {
				_pam_krb5_free_ctx(ctx);
			}
			return -1;
		}
	}

	/* If we were given a principal name, try it. */
	if ((hint_principal != NULL) && (strlen(hint_principal) > 0)) {
		if (options->debug) {
			debug("attempting to obtain tokens for \"%s\" "
			      "(hint \"%s\")",
			      cell, hint_principal);
		}
		ret = minikafs_5log_with_principal(ctx, options, use_ccache,
						   cell, hint_principal, uid,
						   use_rxk5, use_v5_2b);
		if (ret == 0) {
			if (use_ccache != ccache) {
				krb5_cc_close(ctx, use_ccache);
			}
			if (ctx != context) {
				_pam_krb5_free_ctx(ctx);
			}
			return 0;
		}
	}

	defaultrealm = NULL;
	if (krb5_get_default_realm(ctx, &defaultrealm) != 0) {
		defaultrealm = NULL;
	}

	if (options->debug) {
		debug("attempting to determine realm for \"%s\"", cell);
	}
	if (minikafs_realm_of_cell_with_ctx(ctx, options, cell,
					    realm, sizeof(realm)) != 0) {
		strncpy(realm, cell, sizeof(realm));
		realm[sizeof(realm) - 1] = '\0';
		for (i = 0; i < sizeof(realm); i++) {
			realm[i] = toupper(realm[i]);
		}
	}

	principal_size = strlen("/@") + 1;
	ret = -1;
	for (i = 0; (ret != 0) && (i < base_size); i++) {
		principal_size += strlen(base[i]);
	}
	principal_size += strlen(cell);
	principal_size += strlen(realm);
	if (defaultrealm != NULL) {
		principal_size += strlen(defaultrealm);
	}
	principal = malloc(principal_size);
	if (principal == NULL) {
		if (use_ccache != ccache) {
			krb5_cc_close(ctx, use_ccache);
		}
		if (defaultrealm != NULL) {
			v5_free_default_realm(ctx, defaultrealm);
		}
		if (ctx != context) {
			_pam_krb5_free_ctx(ctx);
		}
		return -1;
	}

	for (i = 0; (ret != 0) && (i < base_size); i++) {
		/* If the realm name and cell name are similar, and null_afs
		 * is set, try the NULL instance. */
		if ((strcasecmp(realm, cell) == 0) && options->null_afs_first) {
			snprintf(principal, principal_size, "%s@%s",
				 base[i], realm);
			if (options->debug) {
				debug("attempting to obtain tokens for \"%s\" "
				      "(\"%s\")", cell, principal);
			}
			ret = minikafs_5log_with_principal(ctx, options,
							   use_ccache,
							   cell, principal, uid,
							   use_rxk5, use_v5_2b);
		}
		if (ret == 0) {
			break;
		}
		/* Try the cell instance in the cell's realm. */
		snprintf(principal, principal_size, "%s/%s@%s",
			 base[i], cell, realm);
		if (options->debug) {
			debug("attempting to obtain tokens for \"%s\" (\"%s\")",
			      cell, principal);
		}
		ret = minikafs_5log_with_principal(ctx, options, use_ccache,
						   cell, principal, uid,
						   use_rxk5, use_v5_2b);
		if (ret == 0) {
			break;
		}
		/* If the realm name and cell name are similar, and null_afs
		 * is not set, try the NULL instance. */
		if ((strcasecmp(realm, cell) == 0) &&
		    !options->null_afs_first) {
			snprintf(principal, principal_size, "%s@%s",
				 base[i], realm);
			if (options->debug) {
				debug("attempting to obtain tokens for \"%s\" "
				      "(\"%s\")", cell, principal);
			}
			ret = minikafs_5log_with_principal(ctx, options,
							   use_ccache,
							   cell, principal, uid,
							   use_rxk5, use_v5_2b);
		}
		if (ret == 0) {
			break;
		}
		/* Repeat the last two attempts, but using the default realm. */
		if ((defaultrealm != NULL) &&
		    (strcmp(defaultrealm, realm) != 0)) {
			/* If the default realm name and cell name are similar,
			 * and null_afs is set, try the NULL instance. */
			if ((strcasecmp(defaultrealm, cell) == 0) &&
			    options->null_afs_first) {
				snprintf(principal, principal_size, "%s@%s",
					 base[i], defaultrealm);
				if (options->debug) {
					debug("attempting to obtain tokens for "
					      "\"%s\" (\"%s\")",
					      cell, principal);
				}
				ret = minikafs_5log_with_principal(ctx, options,
								   use_ccache,
								   cell,
								   principal,
								   uid,
								   use_rxk5,
								   use_v5_2b);
			}
			if (ret == 0) {
				break;
			}
			/* Try the cell instance in the default realm. */
			snprintf(principal, principal_size, "%s/%s@%s",
				 base[i], cell, defaultrealm);
			if (options->debug) {
				debug("attempting to obtain tokens for \"%s\" "
				      "(\"%s\")", cell, principal);
			}
			ret = minikafs_5log_with_principal(ctx, options,
							   use_ccache,
							   cell, principal, uid,
							   use_rxk5, use_v5_2b);
			if (ret == 0) {
				break;
			}
			/* If the default realm name and cell name are similar,
			 * and null_afs isn't set, try the NULL instance. */
			if ((strcasecmp(defaultrealm, cell) == 0) &&
			    !options->null_afs_first) {
				snprintf(principal, principal_size, "%s@%s",
					 base[i], defaultrealm);
				if (options->debug) {
					debug("attempting to obtain tokens for "
					      "\"%s\" (\"%s\")",
					      cell, principal);
				}
				ret = minikafs_5log_with_principal(ctx, options,
								   use_ccache,
								   cell,
								   principal,
								   uid,
								   use_rxk5,
								   use_v5_2b);
			}
			if (ret == 0) {
				break;
			}
		}
	}

	if (use_ccache != ccache) {
		krb5_cc_close(ctx, use_ccache);
	}
	if (defaultrealm != NULL) {
		v5_free_default_realm(ctx, defaultrealm);
	}
	if (ctx != context) {
		_pam_krb5_free_ctx(ctx);
	}
	free(principal);

	return ret;
}

/* Try to get tokens for the named cell using every available mechanism. */
int
minikafs_log(krb5_context ctx, krb5_ccache ccache,
	     struct _pam_krb5_options *options,
	     const char *cell, const char *hint_principal,
	     uid_t uid, const int *methods, int n_methods)
{
	int i, method;
	if (n_methods == -1) {
		for (i = 0; methods[i] != 0; i++) {
			continue;
		}
		n_methods = i;
	}
	for (method = 0; method < n_methods; method++) {
		i = -1;
		switch (methods[method]) {
		case MINIKAFS_METHOD_V5_2B:
			if (options->debug) {
				debug("trying with ticket (2b)");
			}
			i = minikafs_5log(ctx, ccache, options, cell,
					  hint_principal, uid, 0, 1);
			if (i != 0) {
				if (options->debug) {
					debug("afslog (2b) failed to \"%s\"",
					      cell);
				}
			}
			break;
		case MINIKAFS_METHOD_RXK5:
			if (options->debug) {
				debug("trying with ticket (rxk5)");
			}
			i = minikafs_5log(ctx, ccache, options, cell,
					  hint_principal, uid, 1, 0);
			if (i != 0) {
				if (options->debug) {
					debug("afslog (rxk5) failed to \"%s\"",
					      cell);
				}
			}
			break;
		default:
			break;
		}
		if (i == 0) {
			break;
		}
	}
	if (method < n_methods) {
		if (options->debug) {
			debug("got tokens for cell \"%s\"", cell);
		}
		return 0;
	} else {
		return -1;
	}
}

/* We do the XDR here to avoid deps on what might not be a standard part of
 * glibc, and we don't need the decode or free functionality. */
static int
encode_int32(char *buffer, int32_t num)
{
	int32_t net;
	if (buffer) {
		net = ntohl(num);
		memcpy(buffer, &net, 4);
	}
	return 4;
}
static int
encode_boolean(char *buffer, krb5_boolean b)
{
	return encode_int32(buffer, b ? 1 : 0);
}
static int
encode_uint64(char *buffer, uint64_t num)
{
	int32_t net;
	if (buffer) {
		net = ntohl(num >> 32);
		memcpy(buffer + 0, &net, 4);
		net = ntohl(num & 0xffffffff);
		memcpy(buffer + 4, &net, 4);
	}
	return 8;
}
static int
encode_bytes(char *buffer, const char *bytes, int32_t num)
{
	int32_t pad;
	pad = (num % 4) ? (4 - (num % 4)) : 0;
	if (buffer) {
		if (bytes && num) {
			memcpy(buffer, bytes, num);
			memset(buffer + num, 0, pad);
		}
	}
	return num + pad;
}
static int
encode_ubytes(char *buffer, const unsigned char *bytes, int32_t num)
{
	int32_t pad;
	pad = (num % 4) ? (4 - (num % 4)) : 0;
	if (buffer) {
		if (bytes && num) {
			memcpy(buffer, bytes, num);
			memset(buffer + num, 0, pad);
		}
	}
	return num + pad;
}
#define encode_fixed(_op, _buffer, _item) \
	{ \
		int _length; \
		_length = _op(_buffer, _item); \
		if (_buffer) { \
			_buffer += _length; \
		} \
		total += _length; \
	}
#define encode_fixed_with_arg(_op, _buffer, _item, _size) \
	{ \
		int _length; \
		_length = _op(_buffer, _item, _size); \
		if (_buffer) { \
			_buffer += _length; \
		} \
		total += _length; \
	}
#define encode_variable(_op, _buffer, _item, _size) \
	{ \
		int _length; \
		_length = _op(_buffer, _item, _size); \
		if (_buffer) { \
			_buffer += _length; \
		} \
		total += _length; \
	}
#define encode_opaque(_op, _buffer, _item, _size) \
	encode_fixed(encode_int32, _buffer, _size) \
	encode_variable(_op, _buffer, _item, _size)
static int
encode_data(char *buffer, krb5_data *data)
{
	int32_t total = 0;
	encode_opaque(encode_bytes, buffer, data->data, data->length);
	return total;
}
static int
encode_string(char *buffer, const char *string, ssize_t length)
{
	int32_t total = 0;
	if (length == -1) {
		length = strlen(string);
	}
	encode_opaque(encode_bytes, buffer, string, length);
	return total;
}
static int
encode_creds_keyblock(char *buffer, krb5_creds *creds)
{
	int32_t total = 0;
	encode_fixed(encode_int32, buffer, v5_creds_get_etype(creds));
	encode_opaque(encode_ubytes, buffer, v5_creds_key_contents(creds),
		      v5_creds_key_length(creds));
	return total;
}
static int
encode_principal(char *buffer, krb5_principal princ)
{
	int32_t total = 0;
	int i;
	encode_fixed(encode_int32, buffer, v5_princ_component_count(princ));
	for (i = 0; i < v5_princ_component_count(princ); i++) {
		encode_opaque(encode_bytes, buffer,
			      v5_princ_component_contents(princ, i),
			      v5_princ_component_length(princ, i));
	}
	encode_opaque(encode_bytes, buffer,
		      v5_princ_realm_contents(princ),
		      v5_princ_realm_length(princ));
	return total;
}
static int
encode_token_rxkad(char *buffer, krb5_creds *creds, int32_t viceid)
{
	int32_t total = 0;

	encode_fixed(encode_int32, buffer, viceid);
	encode_fixed(encode_int32, buffer, 0x100 - 0x2b);
	encode_opaque(encode_ubytes, buffer, v5_creds_key_contents(creds),
		      v5_creds_key_length(creds));
	encode_fixed(encode_int32, buffer, creds->times.starttime);
	encode_fixed(encode_int32, buffer, creds->times.endtime);
	encode_fixed(encode_boolean, buffer, 0);
	encode_fixed(encode_data, buffer, &creds->ticket);
	return total;
}
static int
encode_token_rxk5(char *buffer, krb5_creds *creds)
{
	int32_t total = 0;
	int i; 

	encode_fixed(encode_principal, buffer, creds->client);
	encode_fixed(encode_principal, buffer, creds->server);
	encode_fixed(encode_creds_keyblock, buffer, creds);
	encode_fixed(encode_uint64, buffer, creds->times.authtime);
	encode_fixed(encode_uint64, buffer, creds->times.starttime);
	encode_fixed(encode_uint64, buffer, creds->times.endtime);
	encode_fixed(encode_uint64, buffer, creds->times.renew_till);
	encode_fixed(encode_boolean, buffer, v5_creds_get_is_skey(creds));
	encode_fixed(encode_int32, buffer, v5_creds_get_flags(creds));

	encode_fixed(encode_int32, buffer, v5_creds_address_count(creds));
	for (i = 0; i < v5_creds_address_count(creds); i++) {
		encode_fixed(encode_int32, buffer,
			     v5_creds_address_type(creds, i));
		encode_opaque(encode_ubytes, buffer,
			      v5_creds_address_contents(creds, i),
			      v5_creds_address_length(creds, i));
	}

	encode_fixed(encode_data, buffer, &creds->ticket);
	encode_fixed(encode_data, buffer, &creds->second_ticket);

	encode_fixed(encode_int32, buffer, v5_creds_authdata_count(creds));
	for (i = 0; i < v5_creds_authdata_count(creds); i++) {
		encode_fixed(encode_int32, buffer,
			     v5_creds_authdata_type(creds, i));
		encode_opaque(encode_ubytes, buffer,
			      v5_creds_authdata_contents(creds, i),
			      v5_creds_authdata_length(creds, i));
	}
	return total;
}
#define AFSTOKEN_UNION_NOAUTH	0
#define AFSTOKEN_UNION_NONE	AFSTOKEN_UNION_NOAUTH
#define AFSTOKEN_UNION_KAD	2
#define AFSTOKEN_UNION_RXKAD	AFSTOKEN_UNION_KAD
#define AFSTOKEN_UNION_RXGK	4
#define AFSTOKEN_UNION_GK	AFSTOKEN_UNION_RXGK
#define AFSTOKEN_UNION_RXK5	5
#define AFSTOKEN_UNION_K5	AFSTOKEN_UNION_RXK5
static int
encode_token_union(char *buffer, krb5_creds *creds, int token_union_type,
		   int32_t uid)
{
	int32_t total = 0;
	encode_fixed(encode_int32, buffer, token_union_type);
	switch (token_union_type) {
	case AFSTOKEN_UNION_RXKAD:
		encode_fixed_with_arg(encode_token_rxkad, buffer, creds, uid);
		break;
	case AFSTOKEN_UNION_RXK5:
		encode_fixed(encode_token_rxk5, buffer, creds);
		break;
	default:
		break;
	}
	return total;
}

/* Stuff a ticket and keyblock into the kernel. */
#define AFSTOKEN_EX_SETPAG	0x00000001 /* not supported */
#define AFSTOKEN_EX_ADD		0x00000002
static int
minikafs_5settoken2(const char *cell, krb5_creds *creds, int32_t uid)
{
	struct minikafs_ioblock iob;
	int i, bufsize, token_union_size;
	char *buffer, *bufptr;

	token_union_size = encode_token_union(NULL, creds, AFSTOKEN_UNION_K5,
					      uid);
	bufsize = encode_int32(NULL, 0) +
		  encode_string(NULL, cell, -1) +
		  encode_int32(NULL, 1) +
		  encode_int32(NULL, token_union_size) +
		  token_union_size;
	buffer = malloc(bufsize);
	i = -1;
	if (buffer != NULL) {
		bufptr = buffer;
		bufptr += encode_int32(bufptr, 0); /* flags - AFSTOKEN_EX_... */
		bufptr += encode_string(bufptr, cell, -1); /* cell */
		bufptr += encode_int32(bufptr, 1); /* number of tokens */
		bufptr += encode_int32(bufptr, token_union_size); /* size of token */
		bufptr += encode_token_union(bufptr, creds, AFSTOKEN_UNION_K5,
					     uid); /* token */
		iob.in = buffer;
		iob.insize = bufptr - buffer;
		iob.out = NULL;
		iob.outsize = 0;
		i = minikafs_pioctl(NULL, minikafs_pioctl_settoken2, &iob);
		free(buffer);
	}
	return i;
}