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;
}