diff options
author | Cy Schubert <cy@FreeBSD.org> | 2017-07-07 17:03:42 +0000 |
---|---|---|
committer | Cy Schubert <cy@FreeBSD.org> | 2017-07-07 17:03:42 +0000 |
commit | 33a9b234e7087f573ef08cd7318c6497ba08b439 (patch) | |
tree | d0ea40ad3bf5463a3c55795977c71bcb7d781b4b /src/lib/krb5 | |
download | src-33a9b234e7087f573ef08cd7318c6497ba08b439.tar.gz src-33a9b234e7087f573ef08cd7318c6497ba08b439.zip |
Import MIT KRB5 1.15.1, which will gracefully replace KTH Heimdal.vendor/krb5/1.15.1
The tarball used in this import is the same tarball used in
ports/krb5-115 r435378.
Obtained from: http://web.mit.edu/kerberos/dist/
Thanks to: pfg (for all your tireless behind-the-scenes effort)
Notes
Notes:
svn path=/vendor-crypto/krb5/dist/; revision=320790
svn path=/vendor-crypto/krb5/1.15.1/; revision=320791; tag=vendor/krb5/1.15.1
Diffstat (limited to 'src/lib/krb5')
334 files changed, 125010 insertions, 0 deletions
diff --git a/src/lib/krb5/Makefile.in b/src/lib/krb5/Makefile.in new file mode 100644 index 000000000000..1b8f2d72107b --- /dev/null +++ b/src/lib/krb5/Makefile.in @@ -0,0 +1,74 @@ +mydir=lib$(S)krb5 +BUILDTOP=$(REL)..$(S).. +LOCALINCLUDES = -I$(srcdir)/ccache -I$(srcdir)/keytab -I$(srcdir)/rcache -I$(srcdir)/os -I$(srcdir)/unicode +SUBDIRS= error_tables asn.1 ccache keytab krb os rcache unicode +WINSUBDIRS= $(SUBDIRS) posix +DEFINES=-DLOCALEDIR=\"$(KRB5_LOCALEDIR)\" + +##DOSBUILDTOP = ..\.. +##DOSLIBNAME=$(OUTPRE)krb5.lib +##DOSOBJFILEDEP=$(OUTPRE)asn1.lst $(OUTPRE)ccache.lst $(OUTPRE)err_tbls.lst $(OUTPRE)keytab.lst $(OUTPRE)krb.lst $(OUTPRE)os.lst $(OUTPRE)posix.lst $(OUTPRE)rcache.lst $(OUTPRE)krb5.lst $(OUTPRE)unicode.lst +##DOSOBJFILELIST=@$(OUTPRE)asn1.lst @$(OUTPRE)ccache.lst @$(OUTPRE)err_tbls.lst @$(OUTPRE)keytab.lst @$(OUTPRE)krb.lst @$(OUTPRE)os.lst @$(OUTPRE)posix.lst @$(OUTPRE)rcache.lst @$(OUTPRE)krb5.lst @$(OUTPRE)unicode.lst +##DOSOBJFILE=$(OUTPRE)krb5.lst +##DOSLIBOBJS=$(OBJS) +##DOSLOCALINCLUDES=-Iccache\ccapi -I..\..\windows\lib -Iccache -Ikeytab -Ircache -Ios + +TST=if test -n "`cat DONE`" ; then + +STLIBOBJS=krb5_libinit.o + +LIBBASE=krb5 +LIBMAJOR=3 +LIBMINOR=3 +LIBINITFUNC=profile_library_initializer krb5int_lib_init +LIBFINIFUNC=profile_library_finalizer krb5int_lib_fini + +STOBJLISTS= \ + OBJS.ST \ + error_tables/OBJS.ST \ + asn.1/OBJS.ST \ + ccache/OBJS.ST \ + keytab/OBJS.ST \ + krb/OBJS.ST \ + rcache/OBJS.ST \ + unicode/OBJS.ST \ + os/OBJS.ST \ + $(BUILDTOP)/util/profile/OBJS.ST + +SUBDIROBJLISTS= \ + error_tables/OBJS.ST \ + asn.1/OBJS.ST \ + ccache/OBJS.ST \ + keytab/OBJS.ST \ + krb/OBJS.ST \ + rcache/OBJS.ST \ + unicode/OBJS.ST \ + os/OBJS.ST \ + $(BUILDTOP)/util/profile/OBJS.ST + +OBJS=\ + $(OUTPRE)krb5_libinit.$(OBJEXT) + +SRCS=\ + $(srcdir)/krb5_libinit.c + +RELDIR=krb5 +SHLIB_EXPDEPS = \ + $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ + $(COM_ERR_DEPLIB) $(SUPPORT_DEPLIB) +SHLIB_EXPLIBS=-lk5crypto -lcom_err $(SUPPORT_LIB) @GEN_LIB@ $(LIBS) + +all-unix: all-liblinks + +all-windows: + +clean-unix:: clean-liblinks clean-libs clean-libobjs + +clean-windows:: + $(RM) $(OUTPRE)krb5.lib krb5.bak + +install-unix: install-libs + +@lib_frag@ +@libobj_frag@ + diff --git a/src/lib/krb5/asn.1/KRB5-asn.py b/src/lib/krb5/asn.1/KRB5-asn.py new file mode 100644 index 000000000000..e455fd9a1923 --- /dev/null +++ b/src/lib/krb5/asn.1/KRB5-asn.py @@ -0,0 +1,436 @@ +-- lib/krb5/asn.1/KRB5-asn.py +-- +-- Copyright 1989 by the Massachusetts Institute of Technology. +-- +-- Export of this software from the United States of America may +-- require a specific license from the United States Government. +-- It is the responsibility of any person or organization contemplating +-- export to obtain such a license before exporting. +-- +-- WITHIN THAT CONSTRAINT, permission to use, copy, modify, and +-- distribute this software and its documentation for any purpose and +-- without fee is hereby granted, provided that the above copyright +-- notice appear in all copies and that both that copyright notice and +-- this permission notice appear in supporting documentation, and that +-- the name of M.I.T. not be used in advertising or publicity pertaining +-- to distribution of the software without specific, written prior +-- permission. Furthermore if you modify this software you must label +-- your software as modified software and not distribute it in such a +-- fashion that it might be confused with the original M.I.T. software. +-- M.I.T. makes no representations about the suitability of +-- this software for any purpose. It is provided "as is" without express +-- or implied warranty. +-- +-- ASN.1 definitions for the kerberos network objects +-- +-- Do not change the order of any structure containing some +-- element_KRB5_xx unless the corresponding translation code is also +-- changed. +-- + +KRB5 DEFINITIONS ::= +BEGIN + +-- needed to do the Right Thing with pepsy; this isn't a valid ASN.1 +-- token, however. + +SECTIONS encode decode none + +-- the order of stuff in this file matches the order in the draft RFC + +Realm ::= GeneralString + +HostAddress ::= SEQUENCE { + addr-type[0] INTEGER, + address[1] OCTET STRING +} + +HostAddresses ::= SEQUENCE OF SEQUENCE { + addr-type[0] INTEGER, + address[1] OCTET STRING +} + +AuthorizationData ::= SEQUENCE OF SEQUENCE { + ad-type[0] INTEGER, + ad-data[1] OCTET STRING +} + +KDCOptions ::= BIT STRING { + reserved(0), + forwardable(1), + forwarded(2), + proxiable(3), + proxy(4), + allow-postdate(5), + postdated(6), + unused7(7), + renewable(8), + unused9(9), + renewable-ok(27), + enc-tkt-in-skey(28), + renew(30), + validate(31) +} + +LastReq ::= SEQUENCE OF SEQUENCE { + lr-type[0] INTEGER, + lr-value[1] KerberosTime +} + +KerberosTime ::= GeneralizedTime -- Specifying UTC time zone (Z) + +PrincipalName ::= SEQUENCE{ + name-type[0] INTEGER, + name-string[1] SEQUENCE OF GeneralString +} + +Ticket ::= [APPLICATION 1] SEQUENCE { + tkt-vno[0] INTEGER, + realm[1] Realm, + sname[2] PrincipalName, + enc-part[3] EncryptedData -- EncTicketPart +} + +TransitedEncoding ::= SEQUENCE { + tr-type[0] INTEGER, -- Only supported value is 1 == DOMAIN-COMPRESS + contents[1] OCTET STRING +} + +-- Encrypted part of ticket +EncTicketPart ::= [APPLICATION 3] SEQUENCE { + flags[0] TicketFlags, + key[1] EncryptionKey, + crealm[2] Realm, + cname[3] PrincipalName, + transited[4] TransitedEncoding, + authtime[5] KerberosTime, + starttime[6] KerberosTime OPTIONAL, + endtime[7] KerberosTime, + renew-till[8] KerberosTime OPTIONAL, + caddr[9] HostAddresses OPTIONAL, + authorization-data[10] AuthorizationData OPTIONAL +} + +-- Unencrypted authenticator +Authenticator ::= [APPLICATION 2] SEQUENCE { + authenticator-vno[0] INTEGER, + crealm[1] Realm, + cname[2] PrincipalName, + cksum[3] Checksum OPTIONAL, + cusec[4] INTEGER, + ctime[5] KerberosTime, + subkey[6] EncryptionKey OPTIONAL, + seq-number[7] INTEGER OPTIONAL, + authorization-data[8] AuthorizationData OPTIONAL +} + +TicketFlags ::= BIT STRING { + reserved(0), + forwardable(1), + forwarded(2), + proxiable(3), + proxy(4), + may-postdate(5), + postdated(6), + invalid(7), + renewable(8), + initial(9) +} + +AS-REQ ::= [APPLICATION 10] KDC-REQ +TGS-REQ ::= [APPLICATION 12] KDC-REQ + +KDC-REQ ::= SEQUENCE { + pvno[1] INTEGER, + msg-type[2] INTEGER, + padata[3] SEQUENCE OF PA-DATA OPTIONAL, + req-body[4] KDC-REQ-BODY +} + +PA-DATA ::= SEQUENCE { + padata-type[1] INTEGER, + pa-data[2] OCTET STRING -- might be encoded AP-REQ +} + +KDC-REQ-BODY ::= SEQUENCE { + kdc-options[0] KDCOptions, + cname[1] PrincipalName OPTIONAL, -- Used only in AS-REQ + realm[2] Realm, -- Server's realm Also client's in AS-REQ + sname[3] PrincipalName OPTIONAL, + from[4] KerberosTime OPTIONAL, + till[5] KerberosTime, + rtime[6] KerberosTime OPTIONAL, + nonce[7] INTEGER, + etype[8] SEQUENCE OF INTEGER, -- EncryptionType, + -- in preference order + addresses[9] HostAddresses OPTIONAL, + enc-authorization-data[10] EncryptedData OPTIONAL, + -- AuthorizationData + additional-tickets[11] SEQUENCE OF Ticket OPTIONAL +} + +AS-REP ::= [APPLICATION 11] KDC-REP +TGS-REP ::= [APPLICATION 13] KDC-REP +KDC-REP ::= SEQUENCE { + pvno[0] INTEGER, + msg-type[1] INTEGER, + padata[2] SEQUENCE OF PA-DATA OPTIONAL, + crealm[3] Realm, + cname[4] PrincipalName, + ticket[5] Ticket, -- Ticket + enc-part[6] EncryptedData -- EncKDCRepPart +} + +EncASRepPart ::= [APPLICATION 25] EncKDCRepPart +EncTGSRepPart ::= [APPLICATION 26] EncKDCRepPart +EncKDCRepPart ::= SEQUENCE { + key[0] EncryptionKey, + last-req[1] LastReq, + nonce[2] INTEGER, + key-expiration[3] KerberosTime OPTIONAL, + flags[4] TicketFlags, + authtime[5] KerberosTime, + starttime[6] KerberosTime OPTIONAL, + endtime[7] KerberosTime, + renew-till[8] KerberosTime OPTIONAL, + srealm[9] Realm, + sname[10] PrincipalName, + caddr[11] HostAddresses OPTIONAL +} + +AP-REQ ::= [APPLICATION 14] SEQUENCE { + pvno[0] INTEGER, + msg-type[1] INTEGER, + ap-options[2] APOptions, + ticket[3] Ticket, + authenticator[4] EncryptedData -- Authenticator +} + +APOptions ::= BIT STRING { + reserved(0), + use-session-key(1), + mutual-required(2) +} + +AP-REP ::= [APPLICATION 15] SEQUENCE { + pvno[0] INTEGER, + msg-type[1] INTEGER, + enc-part[2] EncryptedData -- EncAPRepPart +} + +EncAPRepPart ::= [APPLICATION 27] SEQUENCE { + ctime[0] KerberosTime, + cusec[1] INTEGER, + subkey[2] EncryptionKey OPTIONAL, + seq-number[3] INTEGER OPTIONAL +} + +KRB-SAFE ::= [APPLICATION 20] SEQUENCE { + pvno[0] INTEGER, + msg-type[1] INTEGER, + safe-body[2] KRB-SAFE-BODY, + cksum[3] Checksum +} + +KRB-SAFE-BODY ::= SEQUENCE { + user-data[0] OCTET STRING, + timestamp[1] KerberosTime OPTIONAL, + usec[2] INTEGER OPTIONAL, + seq-number[3] INTEGER OPTIONAL, + s-address[4] HostAddress, -- sender's addr + r-address[5] HostAddress OPTIONAL -- recip's addr +} + +KRB-PRIV ::= [APPLICATION 21] SEQUENCE { + pvno[0] INTEGER, + msg-type[1] INTEGER, + enc-part[3] EncryptedData -- EncKrbPrivPart +} + +EncKrbPrivPart ::= [APPLICATION 28] SEQUENCE { + user-data[0] OCTET STRING, + timestamp[1] KerberosTime OPTIONAL, + usec[2] INTEGER OPTIONAL, + seq-number[3] INTEGER OPTIONAL, + s-address[4] HostAddress, -- sender's addr + r-address[5] HostAddress OPTIONAL -- recip's addr +} + +-- The KRB-CRED message allows easy forwarding of credentials. + +KRB-CRED ::= [APPLICATION 22] SEQUENCE { + pvno[0] INTEGER, + msg-type[1] INTEGER, -- KRB_CRED + tickets[2] SEQUENCE OF Ticket, + enc-part[3] EncryptedData -- EncKrbCredPart +} + +EncKrbCredPart ::= [APPLICATION 29] SEQUENCE { + ticket-info[0] SEQUENCE OF KRB-CRED-INFO, + nonce[1] INTEGER OPTIONAL, + timestamp[2] KerberosTime OPTIONAL, + usec[3] INTEGER OPTIONAL, + s-address[4] HostAddress OPTIONAL, + r-address[5] HostAddress OPTIONAL +} + +KRB-CRED-INFO ::= SEQUENCE { + key[0] EncryptionKey, + prealm[1] Realm OPTIONAL, + pname[2] PrincipalName OPTIONAL, + flags[3] TicketFlags OPTIONAL, + authtime[4] KerberosTime OPTIONAL, + starttime[5] KerberosTime OPTIONAL, + endtime[6] KerberosTime OPTIONAL, + renew-till[7] KerberosTime OPTIONAL, + srealm[8] Realm OPTIONAL, + sname[9] PrincipalName OPTIONAL, + caddr[10] HostAddresses OPTIONAL +} + +KRB-ERROR ::= [APPLICATION 30] SEQUENCE { + pvno[0] INTEGER, + msg-type[1] INTEGER, + ctime[2] KerberosTime OPTIONAL, + cusec[3] INTEGER OPTIONAL, + stime[4] KerberosTime, + susec[5] INTEGER, + error-code[6] INTEGER, + crealm[7] Realm OPTIONAL, + cname[8] PrincipalName OPTIONAL, + realm[9] Realm, -- Correct realm + sname[10] PrincipalName, -- Correct name + e-text[11] GeneralString OPTIONAL, + e-data[12] OCTET STRING OPTIONAL +} + +EncryptedData ::= SEQUENCE { + etype[0] INTEGER, -- EncryptionType + kvno[1] INTEGER OPTIONAL, + cipher[2] OCTET STRING -- CipherText +} + +EncryptionKey ::= SEQUENCE { + keytype[0] INTEGER, + keyvalue[1] OCTET STRING +} + +Checksum ::= SEQUENCE { + cksumtype[0] INTEGER, + checksum[1] OCTET STRING +} + +METHOD-DATA ::= SEQUENCE { + method-type[0] INTEGER, + method-data[1] OCTET STRING OPTIONAL +} + +ETYPE-INFO-ENTRY ::= SEQUENCE { + etype[0] INTEGER, + salt[1] OCTET STRING OPTIONAL +} + +ETYPE-INFO ::= SEQUENCE OF ETYPE-INFO-ENTRY + +PA-ENC-TS-ENC ::= SEQUENCE { + patimestamp[0] KerberosTime, -- client's time + pausec[1] INTEGER OPTIONAL +} + +-- These ASN.1 definitions are NOT part of the official Kerberos protocol... + +-- New ASN.1 definitions for the kadmin protocol. +-- Originally contributed from the Sandia modifications + +PasswdSequence ::= SEQUENCE { + passwd[0] OCTET STRING, + phrase[1] OCTET STRING +} + +PasswdData ::= SEQUENCE { + passwd-sequence-count[0] INTEGER, + passwd-sequence[1] SEQUENCE OF PasswdSequence +} + +-- encodings from +-- Integrating Single-use Authentication Mechanisms with Kerberos + +PA-SAM-CHALLENGE ::= SEQUENCE { + sam-type[0] INTEGER, + sam-flags[1] SAMFlags, + sam-type-name[2] GeneralString OPTIONAL, + sam-track-id[3] GeneralString OPTIONAL, + sam-challenge-label[4] GeneralString OPTIONAL, + sam-challenge[5] GeneralString OPTIONAL, + sam-response-prompt[6] GeneralString OPTIONAL, + sam-pk-for-sad[7] OCTET STRING OPTIONAL, + sam-nonce[8] INTEGER OPTIONAL, + sam-cksum[9] Checksum OPTIONAL +} + +PA-SAM-CHALLENGE-2 ::= SEQUENCE { + sam-body[0] PA-SAM-CHALLENGE-2-BODY, + sam-cksum[1] SEQUENCE (1..MAX) OF Checksum, + ... +} + +PA-SAM-CHALLENGE-2-BODY ::= SEQUENCE { + sam-type[0] INTEGER, + sam-flags[1] SAMFlags, + sam-type-name[2] GeneralString OPTIONAL, + sam-track-id[3] GeneralString OPTIONAL, + sam-challenge-label[4] GeneralString OPTIONAL, + sam-challenge[5] GeneralString OPTIONAL, + sam-response-prompt[6] GeneralString OPTIONAL, + sam-pk-for-sad[7] EncryptionKey OPTIONAL, + sam-nonce[8] INTEGER, + sam-etype[9] INTEGER, + ... +} + +-- these are [0].. [2] in the draft +SAMFlags ::= BIT STRING (SIZE (32..MAX)) + -- use-sad-as-key(0) + -- send-encrypted-sad(1) + -- must-pk-encrypt-sad(2) + +PA-SAM-RESPONSE ::= SEQUENCE { + sam-type[0] INTEGER, + sam-flags[1] SAMFlags, + sam-track-id[2] GeneralString OPTIONAL, + -- sam-enc-key is reserved for future use, so I'm making it OPTIONAL - mwe + sam-enc-key[3] EncryptedData, + -- PA-ENC-SAM-KEY + sam-enc-nonce-or-ts[4] EncryptedData, + -- PA-ENC-SAM-RESPONSE-ENC + sam-nonce[5] INTEGER OPTIONAL, + sam-patimestamp[6] KerberosTime OPTIONAL +} + +PA-SAM-RESPONSE-2 ::= SEQUENCE { + sam-type[0] INTEGER, + sam-flags[1] SAMFlags, + sam-track-id[2] GeneralString OPTIONAL, + sam-enc-nonce-or-sad[3] EncryptedData, + -- PA-ENC-SAM-RESPONSE-ENC + sam-nonce[4] INTEGER, + ... +} + +PA-ENC-SAM-KEY ::= SEQUENCE { + sam-key[0] EncryptionKey +} + +PA-ENC-SAM-RESPONSE-ENC ::= SEQUENCE { + sam-nonce[0] INTEGER OPTIONAL, + sam-timestamp[1] KerberosTime OPTIONAL, + sam-usec[2] INTEGER OPTIONAL, + sam-passcode[3] GeneralString OPTIONAL +} + +PA-ENC-SAM-RESPONSE-ENC-2 ::= SEQUENCE { + sam-nonce[0] INTEGER, + sam-sad[1] GeneralString OPTIONAL, + ... +} +END diff --git a/src/lib/krb5/asn.1/Makefile.in b/src/lib/krb5/asn.1/Makefile.in new file mode 100644 index 000000000000..e2e6a354d220 --- /dev/null +++ b/src/lib/krb5/asn.1/Makefile.in @@ -0,0 +1,35 @@ +mydir=lib$(S)krb5$(S)asn.1 +BUILDTOP=$(REL)..$(S)..$(S).. + +##DOS##BUILDTOP = ..\..\.. +##DOS##PREFIXDIR=asn.1 +##DOS##OBJFILE=..\$(OUTPRE)asn1.lst + +EHDRDIR=$(BUILDTOP)/include/krb5/asn.1 + +STLIBOBJS= \ + asn1_encode.o\ + asn1buf.o\ + asn1_k_encode.o\ + ldap_key_seq.o + +SRCS= \ + $(srcdir)/asn1_encode.c\ + $(srcdir)/asn1buf.c\ + $(srcdir)/asn1_k_encode.c\ + $(srcdir)/ldap_key_seq.c + +OBJS= \ + $(OUTPRE)asn1_encode.$(OBJEXT)\ + $(OUTPRE)asn1buf.$(OBJEXT)\ + $(OUTPRE)asn1_k_encode.$(OBJEXT)\ + $(OUTPRE)ldap_key_seq.$(OBJEXT) + +##DOS##LIBOBJS = $(OBJS) + +all-unix: all-libobjs + +clean-unix:: clean-libobjs + +@libobj_frag@ + diff --git a/src/lib/krb5/asn.1/README.asn1 b/src/lib/krb5/asn.1/README.asn1 new file mode 100644 index 000000000000..fcc7b78532fc --- /dev/null +++ b/src/lib/krb5/asn.1/README.asn1 @@ -0,0 +1,577 @@ +These notes attempt to explain how to use the ASN.1 infrastructure to +add new ASN.1 types. ASN.1 is complicated and easy to get wrong, so +it is best to verify your results against another tool (such as asn1c) +if at all possible. These notes are up to date as of 2012-02-13. + +If you are trying to debug a problem that shows up in the ASN.1 +encoder or decoder, skip to the last section. + + +General +------- + +For the moment, a developer must hand-translate the ASN.1 module into +macro invocations that generate data structures used by the encoder +and decoder. Ideally we would have a tool to compile an ASN.1 module +(and probably some additional information about C identifier mappings) +and generate the macro invocations. + +Currently the ASN.1 infrastructure is not visible to applications or +plugins. For plugin modules shipped as part of the krb5 tree, the +types can be added to asn1_k_encode.c and exported from libkrb5. +Plugin modules built separately from the krb5 tree must use another +tool (such as asn1c) for now if they need to do ASN.1 encoding or +decoding. + + +Tags +---- + +Before you start writing macro invocations, it is important to +understand a little bit about ASN.1 tags. You will most commonly see +tag notation in a sequence definition, like: + + TypeName ::= SEQUENCE { + field-name [0] IMPLICIT OCTET STRING OPTIONAL + } + +Contrary to intuition, the tag notation "[0] IMPLICIT" is not a +property of the sequence field; instead, it specifies a type that +wraps the type to the right (OCTET STRING). The right way to think +about the above definition is: + + TypeName is defined as a sequence type + which has an optional field named field-name + whose type is a tagged type + the tag's class is context-specific (by default) + the tag's number is 0 + it is an implicit tag + the tagged type wraps OCTET STRING + +The other case you are likely to see tag notation is something like: + + AS-REQ ::= [APPLICATION 10] KDC-REQ + +This example defines AS-REQ to be a tagged type whose class is +application, whose tag number is 10, and whose base type is KDC-REQ. +The tag may be implicit or explicit depending on the module's tag +environment, which we will get to in a moment. + +Tags can have one of four classes: universal, application, private, +and context-specific. Universal tags are used for built-in ASN.1 +types. Application and context-specific tags are the most common to +see in ASN.1 modules; private is rarely used. If no tag class is +specified, the default is context-specific. + +Tags can be explicit or implicit, and the distinction is important to +the wire encoding. If a tag's closing bracket is followed by the word +IMPLICIT or EXPLICIT, then it is clear which kind of tag it is, but +usually there will be no such annotation. If not, the default depends +on the header of the ASN.1 module. Look at the top of the module for +the word DEFINITIONS. It may be followed by one of three phrases: + +* EXPLICIT TAGS -- in this case, tags default to explicit +* IMPLICIT TAGS -- in this case, tags default to implicit (usually) +* AUTOMATIC TAGS -- tags default to implicit (usually) and are also + automatically added to sequence fields (usually) + +If none of those phrases appear, the default is explicit tags. + +Even if a module defaults to implicit tags, a tag defaults to explicit +if its base type is a choice type or ANY type (or the information +object equivalent of an ANY type). + +If the module's default is AUTOMATIC TAGS, sequence and set fields +should have ascending context-specific tags wrapped around the field +types, starting from 0, unless one of the fields of the sequence or +set is already a tagged type. See ITU X.680 section 24.2 for details, +particularly if COMPONENTS OF is used in the sequence definition. + + +Basic types +----------- + +In our infrastructure, a type descriptor specifies a mapping between +an ASN.1 type and a C type. The first step is to ensure that type +descriptors are defined for the basic types used by your ASN.1 module, +as mapped to the C types used in your structures, in asn1_k_encode.c. +If not, you will need to create it. For a BOOLEAN or INTEGER ASN.1 +type, you will use one of these macros: + + DEFBOOLTYPE(descname, ctype) + DEFINTTYPE(descname, ctype) + DEFUINTTYPE(descname, ctype) + +where "descname" is an identifier you make up and "ctype" is the +integer type of the C object you want to map the ASN.1 value to. For +integers, use DEFINTTYPE if the C type is a signed integer type and +DEFUINTTYPE if it is an unsigned type. (For booleans, the distinction +is unimportant since all integer types can hold the values 0 and 1.) +We don't generally define integer mappings for every typedef name of +an integer type. For example, we use the type descriptor int32, which +maps an ASN.1 INTEGER to a krb5_int32, for krb5_enctype values. + +String types are a little more complicated. Our practice is to store +strings in a krb5_data structure (rather than a zero-terminated C +string), so our infrastructure currently assumes that all strings are +represented as "counted types", meaning the C representation is a +combination of a pointer and an integer type. So, first you must +declare a counted type descriptor (we will describe those in more +detail later) with something like: + + DEFCOUNTEDSTRINGTYPE(generalstring, char *, unsigned int, + k5_asn1_encode_bytestring, k5_asn1_decode_bytestring, + ASN1_GENERALSTRING); + +The first parameter is an identifier you make up. The second and +third parameters are the C types of the pointer and integer holding +the string; for a krb5_data object, those should be the types in the +example. The pointer type must be char * or unsigned char *. The +fourth and fifth parameters reference primitive encoder and decoder +functions; these should almost always be the ones in the example, +unless the ASN.1 type is BIT STRING. The sixth parameter is the +universal tag number of the ASN.1 type, as defined in krbasn1.h. + +Once you have defined the counted type, you can define a normal type +descriptor to wrap it in a krb5_data structure with something like: + + DEFCOUNTEDTYPE(gstring_data, krb5_data, data, length, generalstring); + + +Sequences +--------- + +In our infrastructure, we model ASN.1 sequences using an array of +normal type descriptors. Each type descriptor is applied in turn to +the C object to generate (or consume) an encoding of an ASN.1 value. + +Of course, each value needs to be stored in a different place within +the C object, or they would just overwrite each other. To address +this, you must create an offset type wrapper for each sequence field: + + DEFOFFSETTYPE(descname, structuretype, fieldname, basedesc) + +where "descname" is an identifier you make up, "structuretype" and +"fieldtype" are used to compute the offset and type-check the +structure field, and "basedesc" is the type of the ASN.1 object to be +stored at that offset. + +If your C structure contains a pointer to another C object, you will +need to first define a pointer wrapper, which is very simple: + + DEFPTRTYPE(descname, basedesc) + +Then wrap the defined pointer type in an offset type as described +above. Once a pointer descriptor is defined for a base descriptor, it +can be reused many times, so pointer descriptors are usually defined +right after the types they wrap. When decoding, pointer wrappers +cause a pointer to be allocated with a block of memory equal to the +size of the C type corresponding to the base type. (For offset types, +the corresponding C type is the structure type inside which the offset +is computed.) It is okay for several fields of a sequence to +reference the same pointer field within a structure, as long as the +pointer types all wrap base types with the same corresponding C type. + +If the sequence field has a context tag attached to its type, you will +also need to create a tag wrapper for it: + + DEFCTAGGEDTYPE(descname, tagnum, basedesc) + DEFCTAGGEDTYPE_IMPLICIT(descname, tagnum, basedesc) + +Use the first macro for explicit context tags and the second for +implicit context tags. "tagnum" is the number of the context-specific +tag, and "basedesc" is the name you chose for the offset type above. + +You don't actually need to separately write out DEFOFFSETTYPE and +DEFCTAGGEDTYPE for each field. The combination of offset and context +tag is so common that we have a macro to combine them: + + DEFFIELD(descname, structuretype, fieldname, tagnum, basedesc) + DEFFIELD_IMPLICIT(descname, structuretype, fieldname, tagnum, basedesc) + +Once you have defined tag and offset wrappers for each sequence field, +combine them together in an array and use the DEFSEQTYPE macro to +define the sequence type descriptor: + + static const struct atype_info *my_sequence_fields[] = { + &k5_atype_my_sequence_0, &k5_atype_my_sequence_1, + }; + DEFSEQTYPE(my_sequence, structuretype, my_sequence_fields) + +Each field name must by prefixed by "&k5_atype_" to get a pointer to +the actual variable used to hold the type descriptor. + +ASN.1 sequence types may or may not be defined to be extensible, and +may group extensions together in blocks which must appear together. +Our model does not distinguish these cases. Our decoder treats all +sequence types as extensible. Extension blocks must be modeled by +making all of the extension fields optional, and the decoder will not +enforce that they appear together. + +If your ASN.1 sequence contains optional fields, keep reading. + + +Optional sequence fields +------------------------ + +ASN.1 sequence fields can be annotated with OPTIONAL or, less +commonly, with DEFAULT VALUE. (Be aware that if DEFAULT VALUE is +specified for a sequence field, DER mandates that fields with that +value not be encoded within the sequence. Most standards in the +Kerberos ecosystem avoid the use of DEFAULT VALUE for this reason.) +Although optionality is a property of sequence or set fields, not +types, we still model optional sequence fields using type wrappers. +Optional type wrappers must only be used as members of a sequence, +although they can be nested in offset or pointer wrappers first. + +The simplest way to represent an optional value in a C structure is +with a pointer which takes the value NULL if the field is not present. +In this case, you can just use DEFOPTIONALZEROTYPE to wrap the pointer +type: + + DEFPTRTYPE(ptr_basetype, basetype); + DEFOPTIONALZEROTYPE(opt_ptr_basetype, ptr_basetype); + +and then use opt_ptr_basetype in the DEFFIELD invocation for the +sequence field. DEFOPTIONALZEROTYPE can also be used for integer +types, if it is okay for the value 0 to represent that the +corresponding ASN.1 value is omitted. Optional-zero wrappers, like +pointer wrappers, are usually defined just after the types they wrap. + +For null-terminated sequences, you can use a wrapper like this: + + DEFOPTIONALEMPTYTYPE(opt_seqof_basetype, seqof_basetype) + +to omit the sequence if it is either NULL or of zero length. + +A more general way to wrap optional types is: + + DEFOPTIONALTYPE(descname, predicatefn, initfn, basedesc); + +where "predicatefn" has the signature "int (*fn)(const void *p)" and +is used by the encoder to test whether the ASN.1 value is present in +the C object. "initfn" has the signature "void (*fn)(void *p)" and is +used by the decoder to initialize the C object field if the +corresponding ASN.1 value is omitted in the wire encoding. "initfn" +can be NULL, in which case the C object will simply be left alone. +All C objects are initialized to zero-filled memory when they are +allocated by the decoder. + +An optional string type, represented in a krb5_data structure, can be +wrapped using the nonempty_data function already defined in +asn1_k_encode.c, like so: + + DEFOPTIONALTYPE(opt_ostring_data, nonempty_data, NULL, ostring_data); + + +Sequence-of types +----------------- + +ASN.1 sequence-of types can be represented as C types in two ways. +The simplest is to use an array of pointers terminated in a null +pointer. A descriptor for a sequence-of represented this way is +defined in three steps: + + DEFPTRTYPE(ptr_basetype, basetype); + DEFNULLTERMSEQOFTYPE(seqof_basetype, ptr_basetype); + DEFPTRTYPE(ptr_seqof_basetype, seqof_basetype); + +If the C type corresponding to basetype is "ctype", then the C type +corresponding to ptr_seqof_basetype will be "ctype **". The middle +type sort of corresponds to "ctype *", but not exactly, as it +describes an object of variable size. + +You can also use DEFNONEMPTYNULLTERMSEQOFTYPE in the second step. In +this case, the encoder will throw an error if the sequence is empty. +For historical reasons, the decoder will *not* throw an error if the +sequence is empty, so the calling code must check before assuming a +first element is present. + +The other way of representing sequences is through a combination of +pointer and count. This pattern is most often used for compactness +when the base type is an integer type. A descriptor for a sequence-of +represented this way is defined using a counted type descriptor: + + DEFCOUNTEDSEQOFTYPE(descname, lentype, basedesc) + +where "lentype" is the C type of the length and "basedesc" is a +pointer wrapper for the sequence element type (*not* the element type +itself). For example, an array of 32-bit signed integers is defined +as: + + DEFINTTYPE(int32, krb5_int32); + DEFPTRTYPE(int32_ptr, int32); + DEFCOUNTEDSEQOFTYPE(cseqof_int32, krb5_int32, int32_ptr); + +To use a counted sequence-of type in a sequence, use DEFCOUNTEDTYPE: + + DEFCOUNTEDTYPE(descname, structuretype, ptrfield, lenfield, cdesc) + +where "structuretype", "ptrfield", and "lenfield" are used to compute +the field offsets and type-check the structure fields, and "cdesc" is +the name of the counted type descriptor. + +The combination of DEFCOUNTEDTYPE and DEFCTAGGEDTYPE can be +abbreviated using DEFCNFIELD: + + DEFCNFIELD(descname, structuretype, ptrfield, lenfield, tagnum, cdesc) + + +Tag wrappers +------------ + +We've previously covered DEFCTAGGEDTYPE and DEFCTAGGEDTYPE_IMPLICIT, +which are used to define context-specific tag wrappers. There are +two other macros for creating tag wrappers. The first is: + + DEFAPPTAGGEDTYPE(descname, tagnum, basedesc) + +Use this macro to model an "[APPLICATION tagnum]" tag wrapper in an +ASN.1 module. + +There is also a general tag wrapper macro: + + DEFTAGGEDTYPE(descname, class, construction, tag, implicit, basedesc) + +where "class" is one of UNIVERSAL, APPLICATION, CONTEXT_SPECIFIC, or +PRIVATE, "construction" is one of PRIMITIVE or CONSTRUCTED, "tag" is +the tag number, "implicit" is 1 for an implicit tag and 0 for an +explicit tag, and "basedesc" is the wrapped type. Note that that +primitive vs. constructed is not a concept within the abstract ASN.1 +type model, but is instead a concept used in DER. In general, all +explicit tags should be constructed (but see the section on "Dirty +tricks" below). The construction parameter is ignored for implicit +tags. + + +Choice types +------------ + +ASN.1 CHOICE types are represented in C using a signed integer +distinguisher and a union. Modeling a choice type happens in three +steps: + +1. Define type descriptors for each alternative of the choice, +typically using DEFCTAGGEDTYPE to create a tag wrapper for an existing +type. There is no need to create offset type wrappers, as union +fields always have an offset of 0. For example: + + DEFCTAGGEDTYPE(my_choice_0, 0, firstbasedesc); + DEFCTAGGEDTYPE(my_choice_1, 1, secondbasedesc); + +2. Assemble them into an array, similar to how you would for a +sequence, and use DEFCHOICETYPE to create a counted type descriptor: + + static const struct atype_info *my_choice_alternatives[] = { + &k5_atype_my_choice_0, &k5_atype_my_choice_1 + }; + DEFCHOICETYPE(my_choice, union my_choice_choices, enum my_choice_selector, + my_choice_alternatives); + +The second and third parameters to DEFCHOICETYPE are the C types of +the union and distinguisher fields. + +3. Wrap the counted type descriptor in a type descriptor for the +structure containing the distinguisher and union: + + DEFCOUNTEDTYPE_SIGNED(descname, structuretype, u, choice, my_choice); + +The third and fourth parameters to DEFCOUNTEDTYPE_SIGNED are the field +names of the union and distinguisher fields within structuretype. + +ASN.1 choice types may be defined to be extensible, or may not be. +Our model does not distinguish between the two cases. Our decoder +treats all choice types as extensible. + +Our encoder will throw an error if the distinguisher is not within the +range of valid offsets of the alternatives array. Our decoder will +set the distinguisher to -1 if the tag of the ASN.1 value is not +matched by any of the alternatives, and will leave the union +zero-filled in that case. + + +Counted type descriptors +------------------------ + +Several times in earlier sections we've referred to the notion of +"counted type descriptors" without defining what they are. Counted +type descriptors live in a separate namespace from normal type +descriptors, and specify a mapping between an ASN.1 type and two C +objects, one of them having integer type. There are four kinds of +counted type descriptors, defined using the following macros: + + DEFCOUNTEDSTRINGTYPE(descname, ptrtype, lentype, encfn, decfn, tagnum) + DEFCOUNTEDDERTYPE(descname, ptrtype, lentype) + DEFCOUNTEDSEQOFTYPE(descname, lentype, baseptrdesc) + DEFCHOICETYPE(descname, uniontype, distinguishertype, fields) + +DEFCOUNTEDDERTYPE is described in the "Dirty tricks" section below. +The other three kinds of counted types have been covered previously. + +Counted types are always used by wrapping them in a normal type +descriptor with one of these macros: + + DEFCOUNTEDTYPE(descname, structuretype, datafield, countfield, cdesc) + DEFCOUNTEDTYPE_SIGNED(descname, structuretype, datafield, countfield, cdesc) + +These macros are similar in concept to an offset type, only with two +offsets. Use DEFCOUNTEDTYPE if the count field is unsigned, +DEFCOUNTEDTYPE_SIGNED if it is signed. + + +Defining encoder and decoder functions +-------------------------------------- + +After you have created a type descriptor for your types, you need to +create encoder or decoder functions for the ones you want calling code +to be able to process. Do this with one of the following macros: + + MAKE_ENCODER(funcname, desc) + MAKE_DECODER(funcname, desc) + MAKE_CODEC(typename, desc) + +MAKE_ENCODER and MAKE_DECODER allow you to choose function names. +MAKE_CODEC defines encoder and decoder functions with the names +"encode_typename" and "decode_typename". + +If you are defining functions for a null-terminated sequence, use the +descriptor created with DEFNULLTERMSEQOFTYPE or +DEFNONEMPTYNULLTERMSEQOFTYPE, rather than the pointer to it. This is +because encoder and decoder functions implicitly traffic in pointers +to the C object being encoded or decoded. + +Encoder and decoder functions must be prototyped separately, either in +k5-int.h or in a subsidiary included by it. Encoder functions have +the prototype: + + krb5_error_code encode_typename(const ctype *rep, krb5_data **code_out); + +where "ctype" is the C type corresponding to desc. Decoder functions +have the prototype: + + krb5_error_code decode_typename(const krb5_data *code, ctype **rep_out); + +Decoder functions allocate a container for the C type of the object +being decoded and return a pointer to it in *rep_out. + + +Writing test cases +------------------ + +New ASN.1 types in libkrb5 will typically only be accepted with test +cases. Our current test framework lives in src/tests/asn.1. Adding +new types to this framework involves the following steps: + +1. Define an initializer for a sample value of the type in ktest.c, +named ktest_make_sample_typename(). Also define a contents-destructor +for it, named ktest_empty_typename(). Prototype these functions in +ktest.h. + +2. Define an equality test for the type in ktest_equal.c. Prototype +this in ktest_equal.h. (This step is not necessary if the type has no +decoder.) + +3. Add a test case to krb5_encode_test.c, following the examples of +existing test cases there. Update reference_encode.out and +trval_reference.out to contain the output generated by your test case. + +4. Add a test case to krb5_decode_test.c, following the examples of +existing test cases there, and using the output generated by your +encode test. + +5. Add a test case to krb5_decode_leak.c, following the examples of +existing test cases there. + +Following these steps will not ensure the correctness of your +translation of the ASN.1 module to macro invocations; it only lets us +detect unintentional changes to the encodings after they are defined. +To ensure that your translations are correct, you should extend +tests/asn.1/make-vectors.c and use "make test-vectors" to create +vectors using asn1c. + + +Dirty tricks +------------ + +In rare cases you may want to represent the raw DER encoding of a +value in the C structure. If so, you can use DEFCOUNTEDDERTYPE (or +more likely, the existing der_data type descriptor). The encoder and +decoder will throw errors if the wire encoding doesn't have a valid +outermost tag, so be sure to use valid DER encodings in your test +cases (see ktest_make_sample_algorithm_identifier for an example). + +Conversely, the ASN.1 module may define an OCTET STRING wrapper around +a DER encoding which you want to represent as the decoded value. (The +existing example of this is in PKINIT hash agility, where the +PartyUInfo and PartyVInfo fields of OtherInfo are defined as octet +strings which contain the DER encodings of KRB5PrincipalName values.) +In this case you can use a DEFTAGGEDTYPE wrapper like so: + + DEFTAGGEDTYPE(descname, UNIVERSAL, PRIMITIVE, ASN1_OCTETSTRING, 0, + basedesc) + + +Limitations +----------- + +We cannot currently encode or decode SET or SET OF types. + +We cannot model self-referential types (like "MATHSET ::= SET OF +MATHSET"). + +If a sequence uses an optional field that is a choice field (without +a context tag wrapper), or an optional field that uses a stored DER +encoding (again, without a context tag wrapper), our decoder may +assign a value to the choice or stored-DER field when the correct +behavior is to skip that field and assign the value to a subsequent +field. It should be very rare for ASN.1 modules to use choice or open +types this way. + +For historical interoperability reasons, our decoder accepts the +indefinite length form for constructed tags, which is allowed by BER +but not DER. We still require the primitive forms of basic scalar +types, however, so we do not accept all BER encodings of ASN.1 values. + + +Debugging +--------- + +If you are looking at a stack trace with a bunch of ASN.1 encoder or +decoder calls at the top, here are some notes that might help with +debugging: + +1. You may have noticed that the entry point into the encoder is +defined by a macro like MAKE_CODEC. Don't worry about this; those +macros just define thin wrappers around k5_asn1_full_encode and +k5_asn1_full_decode. If you are stepping through code and hit a +wrapper function, just enter "step" to get into the actual encoder or +decoder function. + +2. If you are in the encoder, look for stack frames in +encode_sequence(), and print the value of i within those stack frames. +You should be able to subtract 1 from those values and match them up +with the sequence field offsets in asn1_k_encode.c for the type being +encoded. For example, if an as-req is being encoded and the i values +(starting with the one closest to encode_krb5_as_req) are 4, 2, and 2, +you could match those up as following: + +* as_req_encode wraps untagged_as_req, whose field at offset 3 is the + descriptor for kdc_req_4, which wraps kdc_req_body. + +* kdc_req_body is a function wrapper around kdc_req_hack, whose field + at offset 1 is the descriptor for req_body_1, which wraps + opt_principal. + +* opt_principal wraps principal, which wraps principal_data, whose + field at offset 1 is the descriptor for princname_1. + +* princname_1 is a sequence of general strings represented in the data + and length fields of the krb5_principal_data structure. + +So the problem would likely be in the data components of the client +principal in the kdc_req structure. + +3. If you are in the decoder, look for stacks frames in +decode_sequence(), and again print the values of i. You can match +these up just as above, except without subtracting 1 from the i +values. diff --git a/src/lib/krb5/asn.1/TODO.asn1 b/src/lib/krb5/asn.1/TODO.asn1 new file mode 100644 index 000000000000..6459f6440e1e --- /dev/null +++ b/src/lib/krb5/asn.1/TODO.asn1 @@ -0,0 +1,75 @@ +-*- text -*- + +Stuff that should still be done on the ASN.1 encoder conversion: + +* Make offsetof uses conforming. Currently we may use foo.bar or + foo[0] as fields. + +* Script to generate the tables. Then each type or field entry can + generate multiple bits of code, instead of forcing us to bury the + type consistency checking into the structure initializer + expression. For example, we might generate these bits of code from + one field descriptor: + + * Field table entry. + + * Type-checking code: Create a pointer of the expected type and a + pointer of the actual type (address of field of automatic struct), + and verify consistency with comparison, assignment, or conditional + expr. Plenty of comments to indicate what's being compared and + what a compiler complain means. + + * Range-checking code for bitfields: Create an automatic field info + struct, fill in the computed offset or whatever, read it back, + make sure it matches. Also with comments. + + * Possibly header declarations describing the types that could be + imported, with correct handles *and* C types. + + * Static declarations for non-exported types to keep symbol table + sizes down. + + Then similar bits of code (e.g., all the field table entries) can be + pulled together into the appropriate places. + +* Some kind of "module" system for exporting and importing encoders, + better than relying on the "type_*" variable names. Probably use + meaningful strings that indicate both the ASN.1 type and the + associated C type. Find a way to fit "imported type" into this + scheme so that we can cleanly move the PKINIT types into the PKINIT + plugin, the LDAP types into the LDAP plugin, etc., and still let + them use the encoders in the code. Only a subset of types would be + exported probably. + +* More compact encoding: For struct atype and struct cntype, we could + use structures with a common base type (similar to Xlib events) + instead of a base structure with a void pointer, to save the cost of + a pointer for each type. Doing this might not be strictly correct + C. + +* Pie in the sky: A verbose mode that can tell you "missing field + KDC-REP.cname.name-string[1].data" or some such. This would require + tracking the stack of pending encodes and adding strings with type + and field names. + +* For ALL_POINTERS_ARE_THE_SAME mode (which is not strictly conforming + with the C standard, and thus not default currently, but makes + things a little smaller and faster), eliminate the loadptr structure + entry. (Note that if this infrastructure becomes exposed to + plugins, ALL_POINTERS_ARE_THE_SAME changes the ABI.) + +* Maybe: Reorganize the data of a "module" so everything needing + relocation is put in some tables, referenced by index from other + structures without relocations. E.g., for krb5_data, here's the + offset for the data pointer, here's the offset for the length value, + here's the index into the pointer reader function table, here's the + index into the length reader function table, here's an index into + the string-type encoder table. + + Using an index into a set of pointer types, with a single function + taking an integer parameter used to switch between various + ptr-to-ptr-to-type code paths, will be a lot smaller -- with a good + compiler the function will probably collapse to a simple + fetch-a-pointer function ignoring the integer argument, while at the + C level it's strictly conforming by using the correct types for + access. diff --git a/src/lib/krb5/asn.1/asn1_encode.c b/src/lib/krb5/asn.1/asn1_encode.c new file mode 100644 index 000000000000..a7423b642a48 --- /dev/null +++ b/src/lib/krb5/asn.1/asn1_encode.c @@ -0,0 +1,1636 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/asn.1/asn1_encode.c */ +/* + * Copyright 1994, 2008 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include "asn1_encode.h" + +/**** Functions for encoding primitive types ****/ + +asn1_error_code +k5_asn1_encode_bool(asn1buf *buf, intmax_t val, size_t *len_out) +{ + asn1_octet bval = val ? 0xFF : 0x00; + + *len_out = 1; + return asn1buf_insert_octet(buf, bval); +} + +asn1_error_code +k5_asn1_encode_int(asn1buf *buf, intmax_t val, size_t *len_out) +{ + asn1_error_code ret; + size_t len = 0; + long valcopy; + int digit; + + valcopy = val; + do { + digit = valcopy & 0xFF; + ret = asn1buf_insert_octet(buf, digit); + if (ret) + return ret; + len++; + valcopy = valcopy >> 8; + } while (valcopy != 0 && valcopy != ~0); + + if (val > 0 && (digit & 0x80) == 0x80) { /* make sure the high bit is */ + ret = asn1buf_insert_octet(buf, 0); /* of the proper signed-ness */ + if (ret) + return ret; + len++; + } else if (val < 0 && (digit & 0x80) != 0x80) { + ret = asn1buf_insert_octet(buf, 0xFF); + if (ret) + return ret; + len++; + } + + + *len_out = len; + return 0; +} + +asn1_error_code +k5_asn1_encode_uint(asn1buf *buf, uintmax_t val, size_t *len_out) +{ + asn1_error_code ret; + size_t len = 0; + uintmax_t valcopy; + int digit; + + valcopy = val; + do { + digit = valcopy & 0xFF; + ret = asn1buf_insert_octet(buf, digit); + if (ret) + return ret; + len++; + valcopy = valcopy >> 8; + } while (valcopy != 0); + + if (digit & 0x80) { /* make sure the high bit is */ + ret = asn1buf_insert_octet(buf, 0); /* of the proper signed-ness */ + if (ret) + return ret; + len++; + } + + *len_out = len; + return 0; +} + +asn1_error_code +k5_asn1_encode_bytestring(asn1buf *buf, unsigned char *const *val, size_t len, + size_t *len_out) +{ + if (len > 0 && val == NULL) + return ASN1_MISSING_FIELD; + *len_out = len; + return asn1buf_insert_octetstring(buf, len, *val); +} + +asn1_error_code +k5_asn1_encode_generaltime(asn1buf *buf, time_t val, size_t *len_out) +{ + struct tm *gtime, gtimebuf; + char s[16]; + unsigned char *sp; + time_t gmt_time = val; + int len; + + /* + * Time encoding: YYYYMMDDhhmmssZ + */ + if (gmt_time == 0) { + sp = (unsigned char *)"19700101000000Z"; + } else { + /* + * Sanity check this just to be paranoid, as gmtime can return NULL, + * and some bogus implementations might overrun on the sprintf. + */ +#ifdef HAVE_GMTIME_R +#ifdef GMTIME_R_RETURNS_INT + if (gmtime_r(&gmt_time, >imebuf) != 0) + return ASN1_BAD_GMTIME; +#else + if (gmtime_r(&gmt_time, >imebuf) == NULL) + return ASN1_BAD_GMTIME; +#endif +#else /* HAVE_GMTIME_R */ + gtime = gmtime(&gmt_time); + if (gtime == NULL) + return ASN1_BAD_GMTIME; + memcpy(>imebuf, gtime, sizeof(gtimebuf)); +#endif /* HAVE_GMTIME_R */ + gtime = >imebuf; + + if (gtime->tm_year > 8099 || gtime->tm_mon > 11 || + gtime->tm_mday > 31 || gtime->tm_hour > 23 || + gtime->tm_min > 59 || gtime->tm_sec > 59) + return ASN1_BAD_GMTIME; + len = snprintf(s, sizeof(s), "%04d%02d%02d%02d%02d%02dZ", + 1900 + gtime->tm_year, gtime->tm_mon + 1, + gtime->tm_mday, gtime->tm_hour, + gtime->tm_min, gtime->tm_sec); + if (SNPRINTF_OVERFLOW(len, sizeof(s))) + /* Shouldn't be possible given above tests. */ + return ASN1_BAD_GMTIME; + sp = (unsigned char *)s; + } + + return k5_asn1_encode_bytestring(buf, &sp, 15, len_out); +} + +asn1_error_code +k5_asn1_encode_bitstring(asn1buf *buf, unsigned char *const *val, size_t len, + size_t *len_out) +{ + asn1_error_code ret; + + ret = asn1buf_insert_octetstring(buf, len, *val); + if (ret) + return ret; + *len_out = len + 1; + return asn1buf_insert_octet(buf, '\0'); +} + +/**** Functions for decoding primitive types ****/ + +asn1_error_code +k5_asn1_decode_bool(const unsigned char *asn1, size_t len, intmax_t *val) +{ + if (len != 1) + return ASN1_BAD_LENGTH; + *val = (*asn1 != 0); + return 0; +} + +/* Decode asn1/len as the contents of a DER integer, placing the signed result + * in val. */ +asn1_error_code +k5_asn1_decode_int(const unsigned char *asn1, size_t len, intmax_t *val) +{ + intmax_t n; + size_t i; + + if (len == 0) + return ASN1_BAD_LENGTH; + n = (asn1[0] & 0x80) ? -1 : 0; + /* Check length; allow extra octet if first octet is 0. */ + if (len > sizeof(intmax_t) + (asn1[0] == 0)) + return ASN1_OVERFLOW; + for (i = 0; i < len; i++) + n = (n << 8) | asn1[i]; + *val = n; + return 0; +} + +/* Decode asn1/len as the contents of a DER integer, placing the unsigned + * result in val. */ +asn1_error_code +k5_asn1_decode_uint(const unsigned char *asn1, size_t len, uintmax_t *val) +{ + uintmax_t n; + size_t i; + + if (len == 0) + return ASN1_BAD_LENGTH; + /* Check for negative values and check length. */ + if ((asn1[0] & 0x80) || len > sizeof(uintmax_t) + (asn1[0] == 0)) + return ASN1_OVERFLOW; + for (i = 0, n = 0; i < len; i++) + n = (n << 8) | asn1[i]; + *val = n; + return 0; +} + +asn1_error_code +k5_asn1_decode_bytestring(const unsigned char *asn1, size_t len, + unsigned char **str_out, size_t *len_out) +{ + unsigned char *str; + + *str_out = NULL; + *len_out = 0; + if (len == 0) + return 0; + str = malloc(len); + if (str == NULL) + return ENOMEM; + memcpy(str, asn1, len); + *str_out = str; + *len_out = len; + return 0; +} + +asn1_error_code +k5_asn1_decode_generaltime(const unsigned char *asn1, size_t len, + time_t *time_out) +{ + const char *s = (char *)asn1; + struct tm ts; + time_t t; + + *time_out = 0; + if (len != 15) + return ASN1_BAD_LENGTH; + /* Time encoding: YYYYMMDDhhmmssZ */ + if (s[14] != 'Z') + return ASN1_BAD_FORMAT; + if (memcmp(s, "19700101000000Z", 15) == 0) { + *time_out = 0; + return 0; + } +#define c2i(c) ((c) - '0') + ts.tm_year = 1000 * c2i(s[0]) + 100 * c2i(s[1]) + 10 * c2i(s[2]) + + c2i(s[3]) - 1900; + ts.tm_mon = 10 * c2i(s[4]) + c2i(s[5]) - 1; + ts.tm_mday = 10 * c2i(s[6]) + c2i(s[7]); + ts.tm_hour = 10 * c2i(s[8]) + c2i(s[9]); + ts.tm_min = 10 * c2i(s[10]) + c2i(s[11]); + ts.tm_sec = 10 * c2i(s[12]) + c2i(s[13]); + ts.tm_isdst = -1; + t = krb5int_gmt_mktime(&ts); + if (t == -1) + return ASN1_BAD_TIMEFORMAT; + *time_out = t; + return 0; +} + +/* + * Note: we return the number of bytes, not bits, in the bit string. If the + * number of bits is not a multiple of 8 we effectively round up to the next + * multiple of 8. + */ +asn1_error_code +k5_asn1_decode_bitstring(const unsigned char *asn1, size_t len, + unsigned char **bits_out, size_t *len_out) +{ + unsigned char unused, *bits; + + *bits_out = NULL; + *len_out = 0; + if (len == 0) + return ASN1_BAD_LENGTH; + unused = *asn1++; + len--; + if (unused > 7) + return ASN1_BAD_FORMAT; + + bits = malloc(len); + if (bits == NULL) + return ENOMEM; + memcpy(bits, asn1, len); + if (len > 1) + bits[len - 1] &= (0xff << unused); + + *bits_out = bits; + *len_out = len; + return 0; +} + +/**** Functions for encoding and decoding tags ****/ + +/* Encode a DER tag into buf with the tag parameters in t and the content + * length len. Place the length of the encoded tag in *retlen. */ +static asn1_error_code +make_tag(asn1buf *buf, const taginfo *t, size_t len, size_t *retlen) +{ + asn1_error_code ret; + asn1_tagnum tag_copy; + size_t sum = 0, length, len_copy; + + if (t->tagnum > ASN1_TAGNUM_MAX) + return ASN1_OVERFLOW; + + /* Encode the length of the content within the tag. */ + if (len < 128) { + ret = asn1buf_insert_octet(buf, len & 0x7F); + if (ret) + return ret; + length = 1; + } else { + length = 0; + for (len_copy = len; len_copy != 0; len_copy >>= 8) { + ret = asn1buf_insert_octet(buf, len_copy & 0xFF); + if (ret) + return ret; + length++; + } + ret = asn1buf_insert_octet(buf, 0x80 | (length & 0x7F)); + if (ret) + return ret; + length++; + } + sum += length; + + /* Encode the tag and construction bit. */ + if (t->tagnum < 31) { + ret = asn1buf_insert_octet(buf, + t->asn1class | t->construction | t->tagnum); + if (ret) + return ret; + length = 1; + } else { + tag_copy = t->tagnum; + length = 0; + ret = asn1buf_insert_octet(buf, tag_copy & 0x7F); + if (ret) + return ret; + tag_copy >>= 7; + length++; + + for (; tag_copy != 0; tag_copy >>= 7) { + ret = asn1buf_insert_octet(buf, 0x80 | (tag_copy & 0x7F)); + if (ret) + return ret; + length++; + } + + ret = asn1buf_insert_octet(buf, t->asn1class | t->construction | 0x1F); + if (ret) + return ret; + length++; + } + sum += length; + + *retlen = sum; + return 0; +} + +/* + * Read a BER tag and length from asn1/len. Place the tag parameters in + * tag_out. Set contents_out/clen_out to the octet range of the tag's + * contents, and remainder_out/rlen_out to the octet range after the end of the + * BER encoding. + * + * (krb5 ASN.1 encodings should be in DER, but for compatibility with some + * really ancient implementations we handle the indefinite length form in tags. + * However, we still insist on the primitive form of string types.) + */ +static asn1_error_code +get_tag(const unsigned char *asn1, size_t len, taginfo *tag_out, + const unsigned char **contents_out, size_t *clen_out, + const unsigned char **remainder_out, size_t *rlen_out) +{ + asn1_error_code ret; + unsigned char o; + const unsigned char *c, *p, *tag_start = asn1; + size_t clen, llen, i; + taginfo t; + + *contents_out = *remainder_out = NULL; + *clen_out = *rlen_out = 0; + if (len == 0) + return ASN1_OVERRUN; + o = *asn1++; + len--; + tag_out->asn1class = o & 0xC0; + tag_out->construction = o & 0x20; + if ((o & 0x1F) != 0x1F) { + tag_out->tagnum = o & 0x1F; + } else { + tag_out->tagnum = 0; + do { + if (len == 0) + return ASN1_OVERRUN; + o = *asn1++; + len--; + tag_out->tagnum = (tag_out->tagnum << 7) | (o & 0x7F); + } while (o & 0x80); + } + + if (len == 0) + return ASN1_OVERRUN; + o = *asn1++; + len--; + + if (o == 0x80) { + /* Indefinite form (should not be present in DER, but we accept it). */ + if (tag_out->construction != CONSTRUCTED) + return ASN1_MISMATCH_INDEF; + p = asn1; + while (!(len >= 2 && p[0] == 0 && p[1] == 0)) { + ret = get_tag(p, len, &t, &c, &clen, &p, &len); + if (ret) + return ret; + } + tag_out->tag_end_len = 2; + *contents_out = asn1; + *clen_out = p - asn1; + *remainder_out = p + 2; + *rlen_out = len - 2; + } else if ((o & 0x80) == 0) { + /* Short form (first octet gives content length). */ + if (o > len) + return ASN1_OVERRUN; + tag_out->tag_end_len = 0; + *contents_out = asn1; + *clen_out = o; + *remainder_out = asn1 + *clen_out; + *rlen_out = len - (*remainder_out - asn1); + } else { + /* Long form (first octet gives number of base-256 length octets). */ + llen = o & 0x7F; + if (llen > len) + return ASN1_OVERRUN; + if (llen > sizeof(*clen_out)) + return ASN1_OVERFLOW; + for (i = 0, clen = 0; i < llen; i++) + clen = (clen << 8) | asn1[i]; + if (clen > len - llen) + return ASN1_OVERRUN; + tag_out->tag_end_len = 0; + *contents_out = asn1 + llen; + *clen_out = clen; + *remainder_out = *contents_out + clen; + *rlen_out = len - (*remainder_out - asn1); + } + tag_out->tag_len = *contents_out - tag_start; + return 0; +} + +#ifdef POINTERS_ARE_ALL_THE_SAME +#define LOADPTR(PTR, TYPE) (*(const void *const *)(PTR)) +#define STOREPTR(PTR, TYPE, VAL) (*(void **)(VAL) = (PTR)) +#else +#define LOADPTR(PTR, PTRINFO) \ + (assert((PTRINFO)->loadptr != NULL), (PTRINFO)->loadptr(PTR)) +#define STOREPTR(PTR, PTRINFO, VAL) \ + (assert((PTRINFO)->storeptr != NULL), (PTRINFO)->storeptr(PTR, VAL)) +#endif + +static size_t +get_nullterm_sequence_len(const void *valp, const struct atype_info *seq) +{ + size_t i; + const struct atype_info *a; + const struct ptr_info *ptr; + const void *elt, *eltptr; + + a = seq; + i = 0; + assert(a->type == atype_ptr); + assert(seq->size != 0); + ptr = a->tinfo; + + while (1) { + eltptr = (const char *)valp + i * seq->size; + elt = LOADPTR(eltptr, ptr); + if (elt == NULL) + break; + i++; + } + return i; +} +static asn1_error_code +encode_sequence_of(asn1buf *buf, size_t seqlen, const void *val, + const struct atype_info *eltinfo, size_t *len_out); + +static asn1_error_code +encode_nullterm_sequence_of(asn1buf *buf, const void *val, + const struct atype_info *type, + int can_be_empty, size_t *len_out) +{ + size_t len = get_nullterm_sequence_len(val, type); + + if (!can_be_empty && len == 0) + return ASN1_MISSING_FIELD; + return encode_sequence_of(buf, len, val, type, len_out); +} + +static intmax_t +load_int(const void *val, size_t size) +{ + switch (size) { + case 1: return *(signed char *)val; + case 2: return *(krb5_int16 *)val; + case 4: return *(krb5_int32 *)val; + case 8: return *(int64_t *)val; + default: abort(); + } +} + +static uintmax_t +load_uint(const void *val, size_t size) +{ + switch (size) { + case 1: return *(unsigned char *)val; + case 2: return *(krb5_ui_2 *)val; + case 4: return *(krb5_ui_4 *)val; + case 8: return *(uint64_t *)val; + default: abort(); + } +} + +static asn1_error_code +load_count(const void *val, const struct counted_info *counted, + size_t *count_out) +{ + const void *countptr = (const char *)val + counted->lenoff; + + assert(sizeof(size_t) <= sizeof(uintmax_t)); + if (counted->lensigned) { + intmax_t xlen = load_int(countptr, counted->lensize); + if (xlen < 0 || (uintmax_t)xlen > SIZE_MAX) + return EINVAL; + *count_out = xlen; + } else { + uintmax_t xlen = load_uint(countptr, counted->lensize); + if ((size_t)xlen != xlen || xlen > SIZE_MAX) + return EINVAL; + *count_out = xlen; + } + return 0; +} + +static asn1_error_code +store_int(intmax_t intval, size_t size, void *val) +{ + switch (size) { + case 1: + if ((signed char)intval != intval) + return ASN1_OVERFLOW; + *(signed char *)val = intval; + return 0; + case 2: + if ((krb5_int16)intval != intval) + return ASN1_OVERFLOW; + *(krb5_int16 *)val = intval; + return 0; + case 4: + if ((krb5_int32)intval != intval) + return ASN1_OVERFLOW; + *(krb5_int32 *)val = intval; + return 0; + case 8: + if ((int64_t)intval != intval) + return ASN1_OVERFLOW; + *(int64_t *)val = intval; + return 0; + default: + abort(); + } +} + +static asn1_error_code +store_uint(uintmax_t intval, size_t size, void *val) +{ + switch (size) { + case 1: + if ((unsigned char)intval != intval) + return ASN1_OVERFLOW; + *(unsigned char *)val = intval; + return 0; + case 2: + if ((krb5_ui_2)intval != intval) + return ASN1_OVERFLOW; + *(krb5_ui_2 *)val = intval; + return 0; + case 4: + if ((krb5_ui_4)intval != intval) + return ASN1_OVERFLOW; + *(krb5_ui_4 *)val = intval; + return 0; + case 8: + if ((uint64_t)intval != intval) + return ASN1_OVERFLOW; + *(uint64_t *)val = intval; + return 0; + default: + abort(); + } +} + +/* Store a count value in an integer field of a structure. If count is + * SIZE_MAX and the target is a signed field, store -1. */ +static asn1_error_code +store_count(size_t count, const struct counted_info *counted, void *val) +{ + void *countptr = (char *)val + counted->lenoff; + + if (counted->lensigned) { + if (count == SIZE_MAX) + return store_int(-1, counted->lensize, countptr); + else if ((intmax_t)count < 0) + return ASN1_OVERFLOW; + else + return store_int(count, counted->lensize, countptr); + } else + return store_uint(count, counted->lensize, countptr); +} + +/* Split a DER encoding into tag and contents. Insert the contents into buf, + * then return the length of the contents and the tag. */ +static asn1_error_code +split_der(asn1buf *buf, unsigned char *const *der, size_t len, + taginfo *tag_out, size_t *len_out) +{ + asn1_error_code ret; + const unsigned char *contents, *remainder; + size_t clen, rlen; + + ret = get_tag(*der, len, tag_out, &contents, &clen, &remainder, &rlen); + if (ret) + return ret; + if (rlen != 0) + return ASN1_BAD_LENGTH; + *len_out = clen; + return asn1buf_insert_bytestring(buf, clen, contents); +} + +/* + * Store the DER encoding given by t and asn1/len into the char * or + * unsigned char * pointed to by val. Set *count_out to the length of the + * DER encoding. + */ +static asn1_error_code +store_der(const taginfo *t, const unsigned char *asn1, size_t len, void *val, + size_t *count_out) +{ + unsigned char *der; + size_t der_len; + + *count_out = 0; + der_len = t->tag_len + len + t->tag_end_len; + der = malloc(der_len); + if (der == NULL) + return ENOMEM; + memcpy(der, asn1 - t->tag_len, der_len); + *(unsigned char **)val = der; + *count_out = der_len; + return 0; +} + +static asn1_error_code +encode_sequence(asn1buf *buf, const void *val, const struct seq_info *seq, + size_t *len_out); +static asn1_error_code +encode_cntype(asn1buf *buf, const void *val, size_t len, + const struct cntype_info *c, taginfo *tag_out, size_t *len_out); + +/* Encode a value (contents only, no outer tag) according to a type, and return + * its encoded tag information. */ +static asn1_error_code +encode_atype(asn1buf *buf, const void *val, const struct atype_info *a, + taginfo *tag_out, size_t *len_out) +{ + asn1_error_code ret; + + if (val == NULL) + return ASN1_MISSING_FIELD; + + switch (a->type) { + case atype_fn: { + const struct fn_info *fn = a->tinfo; + assert(fn->enc != NULL); + return fn->enc(buf, val, tag_out, len_out); + } + case atype_sequence: + assert(a->tinfo != NULL); + ret = encode_sequence(buf, val, a->tinfo, len_out); + if (ret) + return ret; + tag_out->asn1class = UNIVERSAL; + tag_out->construction = CONSTRUCTED; + tag_out->tagnum = ASN1_SEQUENCE; + break; + case atype_ptr: { + const struct ptr_info *ptr = a->tinfo; + assert(ptr->basetype != NULL); + return encode_atype(buf, LOADPTR(val, ptr), ptr->basetype, tag_out, + len_out); + } + case atype_offset: { + const struct offset_info *off = a->tinfo; + assert(off->basetype != NULL); + return encode_atype(buf, (const char *)val + off->dataoff, + off->basetype, tag_out, len_out); + } + case atype_optional: { + const struct optional_info *opt = a->tinfo; + assert(opt->is_present != NULL); + if (opt->is_present(val)) + return encode_atype(buf, val, opt->basetype, tag_out, len_out); + else + return ASN1_OMITTED; + } + case atype_counted: { + const struct counted_info *counted = a->tinfo; + const void *dataptr = (const char *)val + counted->dataoff; + size_t count; + assert(counted->basetype != NULL); + ret = load_count(val, counted, &count); + if (ret) + return ret; + return encode_cntype(buf, dataptr, count, counted->basetype, tag_out, + len_out); + } + case atype_nullterm_sequence_of: + case atype_nonempty_nullterm_sequence_of: + assert(a->tinfo != NULL); + ret = encode_nullterm_sequence_of(buf, val, a->tinfo, + a->type == + atype_nullterm_sequence_of, + len_out); + if (ret) + return ret; + tag_out->asn1class = UNIVERSAL; + tag_out->construction = CONSTRUCTED; + tag_out->tagnum = ASN1_SEQUENCE; + break; + case atype_tagged_thing: { + const struct tagged_info *tag = a->tinfo; + ret = encode_atype(buf, val, tag->basetype, tag_out, len_out); + if (ret) + return ret; + if (!tag->implicit) { + size_t tlen; + ret = make_tag(buf, tag_out, *len_out, &tlen); + if (ret) + return ret; + *len_out += tlen; + tag_out->construction = tag->construction; + } + tag_out->asn1class = tag->tagtype; + tag_out->tagnum = tag->tagval; + break; + } + case atype_bool: + ret = k5_asn1_encode_bool(buf, load_int(val, a->size), len_out); + if (ret) + return ret; + tag_out->asn1class = UNIVERSAL; + tag_out->construction = PRIMITIVE; + tag_out->tagnum = ASN1_BOOLEAN; + break; + case atype_int: + ret = k5_asn1_encode_int(buf, load_int(val, a->size), len_out); + if (ret) + return ret; + tag_out->asn1class = UNIVERSAL; + tag_out->construction = PRIMITIVE; + tag_out->tagnum = ASN1_INTEGER; + break; + case atype_uint: + ret = k5_asn1_encode_uint(buf, load_uint(val, a->size), len_out); + if (ret) + return ret; + tag_out->asn1class = UNIVERSAL; + tag_out->construction = PRIMITIVE; + tag_out->tagnum = ASN1_INTEGER; + break; + case atype_int_immediate: { + const struct immediate_info *imm = a->tinfo; + ret = k5_asn1_encode_int(buf, imm->val, len_out); + if (ret) + return ret; + tag_out->asn1class = UNIVERSAL; + tag_out->construction = PRIMITIVE; + tag_out->tagnum = ASN1_INTEGER; + break; + } + default: + assert(a->type > atype_min); + assert(a->type < atype_max); + abort(); + } + + return 0; +} + +static asn1_error_code +encode_atype_and_tag(asn1buf *buf, const void *val, const struct atype_info *a, + size_t *len_out) +{ + taginfo t; + asn1_error_code ret; + size_t clen, tlen; + + ret = encode_atype(buf, val, a, &t, &clen); + if (ret) + return ret; + ret = make_tag(buf, &t, clen, &tlen); + if (ret) + return ret; + *len_out = clen + tlen; + return 0; +} + +/* + * Encode an object and count according to a cntype_info structure. val is a + * pointer to the object being encoded, which in most cases is itself a + * pointer (but is a union in the cntype_choice case). + */ +static asn1_error_code +encode_cntype(asn1buf *buf, const void *val, size_t count, + const struct cntype_info *c, taginfo *tag_out, size_t *len_out) +{ + asn1_error_code ret; + + switch (c->type) { + case cntype_string: { + const struct string_info *string = c->tinfo; + assert(string->enc != NULL); + ret = string->enc(buf, val, count, len_out); + if (ret) + return ret; + tag_out->asn1class = UNIVERSAL; + tag_out->construction = PRIMITIVE; + tag_out->tagnum = string->tagval; + break; + } + case cntype_der: + return split_der(buf, val, count, tag_out, len_out); + case cntype_seqof: { + const struct atype_info *a = c->tinfo; + const struct ptr_info *ptr = a->tinfo; + assert(a->type == atype_ptr); + val = LOADPTR(val, ptr); + ret = encode_sequence_of(buf, count, val, ptr->basetype, len_out); + if (ret) + return ret; + tag_out->asn1class = UNIVERSAL; + tag_out->construction = CONSTRUCTED; + tag_out->tagnum = ASN1_SEQUENCE; + break; + } + case cntype_choice: { + const struct choice_info *choice = c->tinfo; + if (count >= choice->n_options) + return ASN1_MISSING_FIELD; + return encode_atype(buf, val, choice->options[count], tag_out, + len_out); + } + + default: + assert(c->type > cntype_min); + assert(c->type < cntype_max); + abort(); + } + + return 0; +} + +static asn1_error_code +encode_sequence(asn1buf *buf, const void *val, const struct seq_info *seq, + size_t *len_out) +{ + asn1_error_code ret; + size_t i, len, sum = 0; + + for (i = seq->n_fields; i > 0; i--) { + ret = encode_atype_and_tag(buf, val, seq->fields[i - 1], &len); + if (ret == ASN1_OMITTED) + continue; + else if (ret != 0) + return ret; + sum += len; + } + *len_out = sum; + return 0; +} + +static asn1_error_code +encode_sequence_of(asn1buf *buf, size_t seqlen, const void *val, + const struct atype_info *eltinfo, size_t *len_out) +{ + asn1_error_code ret; + size_t sum = 0, i, len; + const void *eltptr; + + assert(eltinfo->size != 0); + for (i = seqlen; i > 0; i--) { + eltptr = (const char *)val + (i - 1) * eltinfo->size; + ret = encode_atype_and_tag(buf, eltptr, eltinfo, &len); + if (ret) + return ret; + sum += len; + } + *len_out = sum; + return 0; +} + +/**** Functions for freeing C objects based on type info ****/ + +static void free_atype_ptr(const struct atype_info *a, void *val); +static void free_sequence(const struct seq_info *seq, void *val); +static void free_sequence_of(const struct atype_info *eltinfo, void *val, + size_t count); +static void free_cntype(const struct cntype_info *a, void *val, size_t count); + +/* + * Free a C object according to a type description. Do not free pointers at + * the first level; they may be referenced by other fields of a sequence, and + * will be freed by free_atype_ptr in a second pass. + */ +static void +free_atype(const struct atype_info *a, void *val) +{ + switch (a->type) { + case atype_fn: { + const struct fn_info *fn = a->tinfo; + if (fn->free_func != NULL) + fn->free_func(val); + break; + } + case atype_sequence: + free_sequence(a->tinfo, val); + break; + case atype_ptr: { + const struct ptr_info *ptrinfo = a->tinfo; + void *ptr = LOADPTR(val, ptrinfo); + if (ptr != NULL) { + free_atype(ptrinfo->basetype, ptr); + free_atype_ptr(ptrinfo->basetype, ptr); + } + break; + } + case atype_offset: { + const struct offset_info *off = a->tinfo; + assert(off->basetype != NULL); + free_atype(off->basetype, (char *)val + off->dataoff); + break; + } + case atype_optional: { + const struct optional_info *opt = a->tinfo; + free_atype(opt->basetype, val); + break; + } + case atype_counted: { + const struct counted_info *counted = a->tinfo; + void *dataptr = (char *)val + counted->dataoff; + size_t count; + if (load_count(val, counted, &count) == 0) + free_cntype(counted->basetype, dataptr, count); + break; + } + case atype_nullterm_sequence_of: + case atype_nonempty_nullterm_sequence_of: { + size_t count = get_nullterm_sequence_len(val, a->tinfo); + free_sequence_of(a->tinfo, val, count); + break; + } + case atype_tagged_thing: { + const struct tagged_info *tag = a->tinfo; + free_atype(tag->basetype, val); + break; + } + case atype_bool: + case atype_int: + case atype_uint: + case atype_int_immediate: + break; + default: + abort(); + } +} + +static void +free_atype_ptr(const struct atype_info *a, void *val) +{ + switch (a->type) { + case atype_fn: + case atype_sequence: + case atype_counted: + case atype_nullterm_sequence_of: + case atype_nonempty_nullterm_sequence_of: + case atype_bool: + case atype_int: + case atype_uint: + case atype_int_immediate: + break; + case atype_ptr: { + const struct ptr_info *ptrinfo = a->tinfo; + void *ptr = LOADPTR(val, ptrinfo); + free(ptr); + STOREPTR(NULL, ptrinfo, val); + break; + } + case atype_offset: { + const struct offset_info *off = a->tinfo; + assert(off->basetype != NULL); + free_atype_ptr(off->basetype, (char *)val + off->dataoff); + break; + } + case atype_optional: { + const struct optional_info *opt = a->tinfo; + free_atype_ptr(opt->basetype, val); + break; + } + case atype_tagged_thing: { + const struct tagged_info *tag = a->tinfo; + free_atype_ptr(tag->basetype, val); + break; + } + default: + abort(); + } +} + +static void +free_cntype(const struct cntype_info *c, void *val, size_t count) +{ + switch (c->type) { + case cntype_string: + case cntype_der: + free(*(char **)val); + *(char **)val = NULL; + break; + case cntype_seqof: { + const struct atype_info *a = c->tinfo; + const struct ptr_info *ptrinfo = a->tinfo; + void *seqptr = LOADPTR(val, ptrinfo); + free_sequence_of(ptrinfo->basetype, seqptr, count); + free(seqptr); + STOREPTR(NULL, ptrinfo, val); + break; + } + case cntype_choice: { + const struct choice_info *choice = c->tinfo; + if (count < choice->n_options) { + free_atype(choice->options[count], val); + free_atype_ptr(choice->options[count], val); + } + break; + } + default: + abort(); + } +} + +static void +free_sequence(const struct seq_info *seq, void *val) +{ + size_t i; + + for (i = 0; i < seq->n_fields; i++) + free_atype(seq->fields[i], val); + for (i = 0; i < seq->n_fields; i++) + free_atype_ptr(seq->fields[i], val); +} + +static void +free_sequence_of(const struct atype_info *eltinfo, void *val, size_t count) +{ + void *eltptr; + + assert(eltinfo->size != 0); + while (count-- > 0) { + eltptr = (char *)val + count * eltinfo->size; + free_atype(eltinfo, eltptr); + free_atype_ptr(eltinfo, eltptr); + } +} + +/**** Functions for decoding objects based on type info ****/ + +/* Return nonzero if t is an expected tag for an ASN.1 object of type a. */ +static int +check_atype_tag(const struct atype_info *a, const taginfo *t) +{ + switch (a->type) { + case atype_fn: { + const struct fn_info *fn = a->tinfo; + assert(fn->check_tag != NULL); + return fn->check_tag(t); + } + case atype_sequence: + case atype_nullterm_sequence_of: + case atype_nonempty_nullterm_sequence_of: + return (t->asn1class == UNIVERSAL && t->construction == CONSTRUCTED && + t->tagnum == ASN1_SEQUENCE); + case atype_ptr: { + const struct ptr_info *ptrinfo = a->tinfo; + return check_atype_tag(ptrinfo->basetype, t); + } + case atype_offset: { + const struct offset_info *off = a->tinfo; + return check_atype_tag(off->basetype, t); + } + case atype_optional: { + const struct optional_info *opt = a->tinfo; + return check_atype_tag(opt->basetype, t); + } + case atype_counted: { + const struct counted_info *counted = a->tinfo; + switch (counted->basetype->type) { + case cntype_string: { + const struct string_info *string = counted->basetype->tinfo; + return (t->asn1class == UNIVERSAL && + t->construction == PRIMITIVE && + t->tagnum == string->tagval); + } + case cntype_seqof: + return (t->asn1class == UNIVERSAL && + t->construction == CONSTRUCTED && + t->tagnum == ASN1_SEQUENCE); + case cntype_der: + /* + * We treat any tag as matching a stored DER encoding. In some + * cases we know what the tag should be; in others, we truly want + * to accept any tag. If it ever becomes an issue, we could add + * optional tag info to the type and check it here. + */ + return 1; + case cntype_choice: + /* + * ASN.1 choices may or may not be extensible. For now, we treat + * all choices as extensible and match any tag. We should consider + * modeling whether choices are extensible before making the + * encoder visible to plugins. + */ + return 1; + default: + abort(); + } + } + case atype_tagged_thing: { + const struct tagged_info *tag = a->tinfo; + /* NOTE: Doesn't check construction bit for implicit tags. */ + if (!tag->implicit && t->construction != tag->construction) + return 0; + return (t->asn1class == tag->tagtype && t->tagnum == tag->tagval); + } + case atype_bool: + return (t->asn1class == UNIVERSAL && t->construction == PRIMITIVE && + t->tagnum == ASN1_BOOLEAN); + case atype_int: + case atype_uint: + case atype_int_immediate: + return (t->asn1class == UNIVERSAL && t->construction == PRIMITIVE && + t->tagnum == ASN1_INTEGER); + default: + abort(); + } +} + +static asn1_error_code +decode_cntype(const taginfo *t, const unsigned char *asn1, size_t len, + const struct cntype_info *c, void *val, size_t *count_out); +static asn1_error_code +decode_atype_to_ptr(const taginfo *t, const unsigned char *asn1, size_t len, + const struct atype_info *basetype, void **ptr_out); +static asn1_error_code +decode_sequence(const unsigned char *asn1, size_t len, + const struct seq_info *seq, void *val); +static asn1_error_code +decode_sequence_of(const unsigned char *asn1, size_t len, + const struct atype_info *elemtype, void **seq_out, + size_t *count_out); + +/* Given the enclosing tag t, decode from asn1/len the contents of the ASN.1 + * type specified by a, placing the result into val (caller-allocated). */ +static asn1_error_code +decode_atype(const taginfo *t, const unsigned char *asn1, + size_t len, const struct atype_info *a, void *val) +{ + asn1_error_code ret; + + switch (a->type) { + case atype_fn: { + const struct fn_info *fn = a->tinfo; + assert(fn->dec != NULL); + return fn->dec(t, asn1, len, val); + } + case atype_sequence: + return decode_sequence(asn1, len, a->tinfo, val); + case atype_ptr: { + const struct ptr_info *ptrinfo = a->tinfo; + void *ptr = LOADPTR(val, ptrinfo); + assert(ptrinfo->basetype != NULL); + if (ptr != NULL) { + /* Container was already allocated by a previous sequence field. */ + return decode_atype(t, asn1, len, ptrinfo->basetype, ptr); + } else { + ret = decode_atype_to_ptr(t, asn1, len, ptrinfo->basetype, &ptr); + if (ret) + return ret; + STOREPTR(ptr, ptrinfo, val); + break; + } + } + case atype_offset: { + const struct offset_info *off = a->tinfo; + assert(off->basetype != NULL); + return decode_atype(t, asn1, len, off->basetype, + (char *)val + off->dataoff); + } + case atype_optional: { + const struct optional_info *opt = a->tinfo; + return decode_atype(t, asn1, len, opt->basetype, val); + } + case atype_counted: { + const struct counted_info *counted = a->tinfo; + void *dataptr = (char *)val + counted->dataoff; + size_t count; + assert(counted->basetype != NULL); + ret = decode_cntype(t, asn1, len, counted->basetype, dataptr, &count); + if (ret) + return ret; + return store_count(count, counted, val); + } + case atype_tagged_thing: { + const struct tagged_info *tag = a->tinfo; + taginfo inner_tag; + const taginfo *tp = t; + const unsigned char *rem; + size_t rlen; + if (!tag->implicit) { + ret = get_tag(asn1, len, &inner_tag, &asn1, &len, &rem, &rlen); + if (ret) + return ret; + /* Note: we don't check rlen (it should be 0). */ + tp = &inner_tag; + if (!check_atype_tag(tag->basetype, tp)) + return ASN1_BAD_ID; + } + return decode_atype(tp, asn1, len, tag->basetype, val); + } + case atype_bool: { + intmax_t intval; + ret = k5_asn1_decode_bool(asn1, len, &intval); + if (ret) + return ret; + return store_int(intval, a->size, val); + } + case atype_int: { + intmax_t intval; + ret = k5_asn1_decode_int(asn1, len, &intval); + if (ret) + return ret; + return store_int(intval, a->size, val); + } + case atype_uint: { + uintmax_t intval; + ret = k5_asn1_decode_uint(asn1, len, &intval); + if (ret) + return ret; + return store_uint(intval, a->size, val); + } + case atype_int_immediate: { + const struct immediate_info *imm = a->tinfo; + intmax_t intval; + ret = k5_asn1_decode_int(asn1, len, &intval); + if (ret) + return ret; + if (intval != imm->val && imm->err != 0) + return imm->err; + break; + } + default: + /* Null-terminated sequence types are handled in decode_atype_to_ptr, + * since they create variable-sized objects. */ + assert(a->type != atype_nullterm_sequence_of); + assert(a->type != atype_nonempty_nullterm_sequence_of); + assert(a->type > atype_min); + assert(a->type < atype_max); + abort(); + } + return 0; +} + +/* + * Given the enclosing tag t, decode from asn1/len the contents of the + * ASN.1 type described by c, placing the counted result into val/count_out. + * If the resulting count should be -1 (for an unknown union distinguisher), + * set *count_out to SIZE_MAX. + */ +static asn1_error_code +decode_cntype(const taginfo *t, const unsigned char *asn1, size_t len, + const struct cntype_info *c, void *val, size_t *count_out) +{ + asn1_error_code ret; + + switch (c->type) { + case cntype_string: { + const struct string_info *string = c->tinfo; + assert(string->dec != NULL); + return string->dec(asn1, len, val, count_out); + } + case cntype_der: + return store_der(t, asn1, len, val, count_out); + case cntype_seqof: { + const struct atype_info *a = c->tinfo; + const struct ptr_info *ptrinfo = a->tinfo; + void *seq; + assert(a->type == atype_ptr); + ret = decode_sequence_of(asn1, len, ptrinfo->basetype, &seq, + count_out); + if (ret) + return ret; + STOREPTR(seq, ptrinfo, val); + break; + } + case cntype_choice: { + const struct choice_info *choice = c->tinfo; + size_t i; + for (i = 0; i < choice->n_options; i++) { + if (check_atype_tag(choice->options[i], t)) { + ret = decode_atype(t, asn1, len, choice->options[i], val); + if (ret) + return ret; + *count_out = i; + return 0; + } + } + /* SIZE_MAX will be stored as -1 in the distinguisher. If we start + * modeling non-extensible choices we should check that here. */ + *count_out = SIZE_MAX; + break; + } + default: + assert(c->type > cntype_min); + assert(c->type < cntype_max); + abort(); + } + return 0; +} + +/* Add a null pointer to the end of a sequence. ptr is consumed on success + * (to be replaced by *ptr_out), left alone on failure. */ +static asn1_error_code +null_terminate(const struct atype_info *eltinfo, void *ptr, size_t count, + void **ptr_out) +{ + const struct ptr_info *ptrinfo = eltinfo->tinfo; + void *endptr; + + assert(eltinfo->type == atype_ptr); + ptr = realloc(ptr, (count + 1) * eltinfo->size); + if (ptr == NULL) + return ENOMEM; + endptr = (char *)ptr + count * eltinfo->size; + STOREPTR(NULL, ptrinfo, endptr); + *ptr_out = ptr; + return 0; +} + +static asn1_error_code +decode_atype_to_ptr(const taginfo *t, const unsigned char *asn1, + size_t len, const struct atype_info *a, + void **ptr_out) +{ + asn1_error_code ret; + void *ptr; + size_t count; + + *ptr_out = NULL; + switch (a->type) { + case atype_nullterm_sequence_of: + case atype_nonempty_nullterm_sequence_of: + ret = decode_sequence_of(asn1, len, a->tinfo, &ptr, &count); + if (ret) + return ret; + ret = null_terminate(a->tinfo, ptr, count, &ptr); + if (ret) { + free_sequence_of(a->tinfo, ptr, count); + return ret; + } + /* Historically we do not enforce non-emptiness of sequences when + * decoding, even when it is required by the ASN.1 type. */ + break; + default: + ptr = calloc(a->size, 1); + if (ptr == NULL) + return ENOMEM; + ret = decode_atype(t, asn1, len, a, ptr); + if (ret) { + free(ptr); + return ret; + } + break; + } + *ptr_out = ptr; + return 0; +} + +/* Initialize a C object when the corresponding ASN.1 type was omitted within a + * sequence. If the ASN.1 type is not optional, return ASN1_MISSING_FIELD. */ +static asn1_error_code +omit_atype(const struct atype_info *a, void *val) +{ + switch (a->type) + { + case atype_fn: + case atype_sequence: + case atype_nullterm_sequence_of: + case atype_nonempty_nullterm_sequence_of: + case atype_counted: + case atype_bool: + case atype_int: + case atype_uint: + case atype_int_immediate: + return ASN1_MISSING_FIELD; + case atype_ptr: { + const struct ptr_info *ptrinfo = a->tinfo; + return omit_atype(ptrinfo->basetype, val); + } + case atype_offset: { + const struct offset_info *off = a->tinfo; + return omit_atype(off->basetype, (char *)val + off->dataoff); + } + case atype_tagged_thing: { + const struct tagged_info *tag = a->tinfo; + return omit_atype(tag->basetype, val); + } + case atype_optional: { + const struct optional_info *opt = a->tinfo; + if (opt->init != NULL) + opt->init(val); + return 0; + } + default: + abort(); + } +} + +/* Decode an ASN.1 sequence into a C object. */ +static asn1_error_code +decode_sequence(const unsigned char *asn1, size_t len, + const struct seq_info *seq, void *val) +{ + asn1_error_code ret; + const unsigned char *contents; + size_t i, j, clen; + taginfo t; + + assert(seq->n_fields > 0); + for (i = 0; i < seq->n_fields; i++) { + if (len == 0) + break; + ret = get_tag(asn1, len, &t, &contents, &clen, &asn1, &len); + if (ret) + goto error; + /* + * Find the applicable sequence field. This logic is a little + * oversimplified; we could match an element to an optional extensible + * choice or optional stored-DER type when we ought to match a + * subsequent non-optional field. But it's unwise and (hopefully) very + * rare for ASN.1 modules to require such precision. + */ + for (; i < seq->n_fields; i++) { + if (check_atype_tag(seq->fields[i], &t)) + break; + ret = omit_atype(seq->fields[i], val); + if (ret) + goto error; + } + /* We currently model all sequences as extensible. We should consider + * changing this before making the encoder visible to plugins. */ + if (i == seq->n_fields) + break; + ret = decode_atype(&t, contents, clen, seq->fields[i], val); + if (ret) + goto error; + } + /* Initialize any fields in the C object which were not accounted for in + * the sequence. Error out if any of them aren't optional. */ + for (; i < seq->n_fields; i++) { + ret = omit_atype(seq->fields[i], val); + if (ret) + goto error; + } + return 0; + +error: + /* Free what we've decoded so far. Free pointers in a second pass in + * case multiple fields refer to the same pointer. */ + for (j = 0; j < i; j++) + free_atype(seq->fields[j], val); + for (j = 0; j < i; j++) + free_atype_ptr(seq->fields[j], val); + return ret; +} + +static asn1_error_code +decode_sequence_of(const unsigned char *asn1, size_t len, + const struct atype_info *elemtype, void **seq_out, + size_t *count_out) +{ + asn1_error_code ret; + void *seq = NULL, *elem, *newseq; + const unsigned char *contents; + size_t clen, count = 0; + taginfo t; + + *seq_out = NULL; + *count_out = 0; + while (len > 0) { + ret = get_tag(asn1, len, &t, &contents, &clen, &asn1, &len); + if (ret) + goto error; + if (!check_atype_tag(elemtype, &t)) { + ret = ASN1_BAD_ID; + goto error; + } + newseq = realloc(seq, (count + 1) * elemtype->size); + if (newseq == NULL) { + ret = ENOMEM; + goto error; + } + seq = newseq; + elem = (char *)seq + count * elemtype->size; + memset(elem, 0, elemtype->size); + ret = decode_atype(&t, contents, clen, elemtype, elem); + if (ret) + goto error; + count++; + } + *seq_out = seq; + *count_out = count; + return 0; + +error: + free_sequence_of(elemtype, seq, count); + free(seq); + return ret; +} + +/* These three entry points are only needed for the kdc_req_body hack and may + * go away at some point. Define them here so we can use short names above. */ + +asn1_error_code +k5_asn1_encode_atype(asn1buf *buf, const void *val, const struct atype_info *a, + taginfo *tag_out, size_t *len_out) +{ + return encode_atype(buf, val, a, tag_out, len_out); +} + +asn1_error_code +k5_asn1_decode_atype(const taginfo *t, const unsigned char *asn1, + size_t len, const struct atype_info *a, void *val) +{ + return decode_atype(t, asn1, len, a, val); +} + +krb5_error_code +k5_asn1_full_encode(const void *rep, const struct atype_info *a, + krb5_data **code_out) +{ + size_t len; + asn1_error_code ret; + asn1buf *buf = NULL; + krb5_data *d; + + *code_out = NULL; + + if (rep == NULL) + return ASN1_MISSING_FIELD; + ret = asn1buf_create(&buf); + if (ret) + return ret; + ret = encode_atype_and_tag(buf, rep, a, &len); + if (ret) + goto cleanup; + ret = asn12krb5_buf(buf, &d); + if (ret) + goto cleanup; + *code_out = d; +cleanup: + asn1buf_destroy(&buf); + return ret; +} + +asn1_error_code +k5_asn1_full_decode(const krb5_data *code, const struct atype_info *a, + void **retrep) +{ + asn1_error_code ret; + const unsigned char *contents, *remainder; + size_t clen, rlen; + taginfo t; + + *retrep = NULL; + ret = get_tag((unsigned char *)code->data, code->length, &t, &contents, + &clen, &remainder, &rlen); + if (ret) + return ret; + /* rlen should be 0, but we don't check it (and due to padding in + * non-length-preserving enctypes, it will sometimes be nonzero). */ + if (!check_atype_tag(a, &t)) + return ASN1_BAD_ID; + return decode_atype_to_ptr(&t, contents, clen, a, retrep); +} diff --git a/src/lib/krb5/asn.1/asn1_encode.h b/src/lib/krb5/asn.1/asn1_encode.h new file mode 100644 index 000000000000..d95f65473c3a --- /dev/null +++ b/src/lib/krb5/asn.1/asn1_encode.h @@ -0,0 +1,588 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/asn.1/asn1_encode.h */ +/* + * Copyright 1994, 2008 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#ifndef __ASN1_ENCODE_H__ +#define __ASN1_ENCODE_H__ + +#include "k5-int.h" +#include "krbasn1.h" +#include "asn1buf.h" +#include <time.h> + +typedef struct { + asn1_class asn1class; + asn1_construction construction; + asn1_tagnum tagnum; + + /* When decoding, stores the leading and trailing lengths of a tag. Used + * by store_der(). */ + size_t tag_len; + size_t tag_end_len; +} taginfo; + +/* These functions are referenced by encoder structures. They handle the + * encoding of primitive ASN.1 types. */ +asn1_error_code k5_asn1_encode_bool(asn1buf *buf, intmax_t val, + size_t *len_out); +asn1_error_code k5_asn1_encode_int(asn1buf *buf, intmax_t val, + size_t *len_out); +asn1_error_code k5_asn1_encode_uint(asn1buf *buf, uintmax_t val, + size_t *len_out); +asn1_error_code k5_asn1_encode_bytestring(asn1buf *buf, + unsigned char *const *val, + size_t len, size_t *len_out); +asn1_error_code k5_asn1_encode_bitstring(asn1buf *buf, + unsigned char *const *val, + size_t len, size_t *len_out); +asn1_error_code k5_asn1_encode_generaltime(asn1buf *buf, time_t val, + size_t *len_out); + +/* These functions are referenced by encoder structures. They handle the + * decoding of primitive ASN.1 types. */ +asn1_error_code k5_asn1_decode_bool(const unsigned char *asn1, size_t len, + intmax_t *val); +asn1_error_code k5_asn1_decode_int(const unsigned char *asn1, size_t len, + intmax_t *val); +asn1_error_code k5_asn1_decode_uint(const unsigned char *asn1, size_t len, + uintmax_t *val); +asn1_error_code k5_asn1_decode_generaltime(const unsigned char *asn1, + size_t len, time_t *time_out); +asn1_error_code k5_asn1_decode_bytestring(const unsigned char *asn1, + size_t len, unsigned char **str_out, + size_t *len_out); +asn1_error_code k5_asn1_decode_bitstring(const unsigned char *asn1, size_t len, + unsigned char **bits_out, + size_t *len_out); + +/* + * An atype_info structure specifies how to map a C object to an ASN.1 value. + * + * We wind up with a lot of load-time relocations being done, which is + * a bit annoying. Be careful about "fixing" that at the cost of too + * much run-time performance. It might work to have a master "module" + * descriptor with pointers to various arrays (type descriptors, + * strings, field descriptors, functions) most of which don't need + * relocation themselves, and replace most of the pointers with table + * indices. + * + * It's a work in progress. + */ + +enum atype_type { + /* For bounds checking only. By starting with 2, we guarantee that + * zero-initialized storage will be recognized as invalid. */ + atype_min = 1, + /* Use a function table to handle encoding or decoding. tinfo is a struct + * fn_info *. */ + atype_fn, + /* C object is a pointer to the object to be encoded or decoded. tinfo is + * a struct ptr_info *. */ + atype_ptr, + /* C object to be encoded or decoded is at an offset from the original + * pointer. tinfo is a struct offset_info *. */ + atype_offset, + /* + * Indicates a sequence field which may or may not be present in the C + * object or ASN.1 sequence. tinfo is a struct optional_info *. Must be + * used within a sequence, although the optional type may be nested within + * offset, ptr, and/or tag types. + */ + atype_optional, + /* + * C object contains an integer and another C object at specified offsets, + * to be combined and encoded or decoded as specified by a cntype_info + * structure. tinfo is a struct counted_info *. + */ + atype_counted, + /* Sequence. tinfo is a struct seq_info *. */ + atype_sequence, + /* + * Sequence-of, with pointer to base type descriptor, represented as a + * null-terminated array of pointers (and thus the "base" type descriptor + * is actually an atype_ptr node). tinfo is a struct atype_info * giving + * the base type. + */ + atype_nullterm_sequence_of, + atype_nonempty_nullterm_sequence_of, + /* Tagged version of another type. tinfo is a struct tagged_info *. */ + atype_tagged_thing, + /* Boolean value. tinfo is NULL (size field determines C type width). */ + atype_bool, + /* Signed or unsigned integer. tinfo is NULL. */ + atype_int, + atype_uint, + /* + * Integer value taken from the type info, not from the object being + * encoded. tinfo is a struct immediate_info * giving the integer value + * and error code to return if a decoded object doesn't match it (or 0 if + * the value shouldn't be checked on decode). + */ + atype_int_immediate, + /* Unused except for bounds checking. */ + atype_max +}; + +struct atype_info { + enum atype_type type; + size_t size; /* Used for sequence-of processing */ + const void *tinfo; /* Points to type-specific structure */ +}; + +struct fn_info { + asn1_error_code (*enc)(asn1buf *, const void *, taginfo *, size_t *); + asn1_error_code (*dec)(const taginfo *, const unsigned char *, size_t, + void *); + int (*check_tag)(const taginfo *); + void (*free_func)(void *); +}; + +struct ptr_info { + void *(*loadptr)(const void *); + void (*storeptr)(void *, void *); + const struct atype_info *basetype; +}; + +struct offset_info { + unsigned int dataoff : 9; + const struct atype_info *basetype; +}; + +struct optional_info { + int (*is_present)(const void *); + void (*init)(void *); + const struct atype_info *basetype; +}; + +struct counted_info { + unsigned int dataoff : 9; + unsigned int lenoff : 9; + unsigned int lensigned : 1; + unsigned int lensize : 5; + const struct cntype_info *basetype; +}; + +struct tagged_info { + unsigned int tagval : 16, tagtype : 8, construction : 6, implicit : 1; + const struct atype_info *basetype; +}; + +struct immediate_info { + intmax_t val; + asn1_error_code err; +}; + +/* A cntype_info structure specifies how to map a C object and count (length or + * union distinguisher) to an ASN.1 value. */ + +enum cntype_type { + cntype_min = 1, + + /* + * Apply an encoder function (contents only) and wrap it in a universal + * primitive tag. The C object must be a char * or unsigned char *. tinfo + * is a struct string_info *. + */ + cntype_string, + + /* + * The C object is a DER encoding (with tag), to be simply inserted on + * encode or stored on decode. The C object must be a char * or unsigned + * char *. tinfo is NULL. + */ + cntype_der, + + /* An ASN.1 sequence-of value, represtened in C as a counted array. struct + * atype_info * giving the base type, which must be of type atype_ptr. */ + cntype_seqof, + + /* An ASN.1 choice, represented in C as a distinguisher and union. tinfo + * is a struct choice_info *. */ + cntype_choice, + + cntype_max +}; + +struct cntype_info { + enum cntype_type type; + const void *tinfo; +}; + +struct string_info { + asn1_error_code (*enc)(asn1buf *, unsigned char *const *, size_t, + size_t *); + asn1_error_code (*dec)(const unsigned char *, size_t, unsigned char **, + size_t *); + unsigned int tagval : 5; +}; + +struct choice_info { + const struct atype_info **options; + size_t n_options; +}; + +struct seq_info { + const struct atype_info **fields; + size_t n_fields; + /* Currently all sequences are assumed to be extensible. */ +}; + +/* + * The various DEF*TYPE macros must: + * + * + Define a type named aux_type_##DESCNAME, for use in any types derived from + * the type being defined. + * + * + Define an atype_info struct named k5_atype_##DESCNAME + * + * + Define a type-specific structure, referenced by the tinfo field + * of the atype_info structure. + * + * + Define any extra stuff needed in the type descriptor, like + * pointer-load functions. + * + * + Accept a following semicolon syntactically, to keep Emacs parsing + * (and indentation calculating) code happy. + * + * Nothing else should directly define the atype_info structures. + */ + +/* Define a type using a function table. */ +#define DEFFNTYPE(DESCNAME, CTYPENAME, ENCFN, DECFN, CHECKFN, FREEFN) \ + typedef CTYPENAME aux_type_##DESCNAME; \ + static const struct fn_info aux_info_##DESCNAME = { \ + ENCFN, DECFN, CHECKFN, FREEFN \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_fn, sizeof(CTYPENAME), &aux_info_##DESCNAME \ + } +/* A sequence, defined by the indicated series of types, and an optional + * function indicating which fields are not present. */ +#define DEFSEQTYPE(DESCNAME, CTYPENAME, FIELDS) \ + typedef CTYPENAME aux_type_##DESCNAME; \ + static const struct seq_info aux_seqinfo_##DESCNAME = { \ + FIELDS, sizeof(FIELDS)/sizeof(FIELDS[0]) \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_sequence, sizeof(CTYPENAME), &aux_seqinfo_##DESCNAME \ + } +/* A boolean type. */ +#define DEFBOOLTYPE(DESCNAME, CTYPENAME) \ + typedef CTYPENAME aux_type_##DESCNAME; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_bool, sizeof(CTYPENAME), NULL \ + } +/* Integer types. */ +#define DEFINTTYPE(DESCNAME, CTYPENAME) \ + typedef CTYPENAME aux_type_##DESCNAME; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_int, sizeof(CTYPENAME), NULL \ + } +#define DEFUINTTYPE(DESCNAME, CTYPENAME) \ + typedef CTYPENAME aux_type_##DESCNAME; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_uint, sizeof(CTYPENAME), NULL \ + } +#define DEFINT_IMMEDIATE(DESCNAME, VAL, ERR) \ + typedef int aux_type_##DESCNAME; \ + static const struct immediate_info aux_info_##DESCNAME = { \ + VAL, ERR \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_int_immediate, 0, &aux_info_##DESCNAME \ + } + +/* Pointers to other types, to be encoded as those other types. */ +#ifdef POINTERS_ARE_ALL_THE_SAME +#define DEFPTRTYPE(DESCNAME,BASEDESCNAME) \ + typedef aux_type_##BASEDESCNAME *aux_type_##DESCNAME; \ + static const struct ptr_info aux_info_##DESCNAME = { \ + NULL, NULL, &k5_atype_##BASEDESCNAME \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_ptr, sizeof(aux_type_##DESCNAME), \ + &aux_info_##DESCNAME \ + } +#else +#define DEFPTRTYPE(DESCNAME,BASEDESCNAME) \ + typedef aux_type_##BASEDESCNAME *aux_type_##DESCNAME; \ + static void * \ + aux_loadptr_##DESCNAME(const void *p) \ + { \ + return *(aux_type_##DESCNAME *)p; \ + } \ + static void \ + aux_storeptr_##DESCNAME(void *ptr, void *val) \ + { \ + *(aux_type_##DESCNAME *)val = ptr; \ + } \ + static const struct ptr_info aux_info_##DESCNAME = { \ + aux_loadptr_##DESCNAME, aux_storeptr_##DESCNAME, \ + &k5_atype_##BASEDESCNAME \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_ptr, sizeof(aux_type_##DESCNAME), \ + &aux_info_##DESCNAME \ + } +#endif +#define DEFOFFSETTYPE(DESCNAME, STYPE, FIELDNAME, BASEDESC) \ + typedef STYPE aux_type_##DESCNAME; \ + static const struct offset_info aux_info_##DESCNAME = { \ + OFFOF(STYPE, FIELDNAME, aux_type_##BASEDESC), \ + &k5_atype_##BASEDESC \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_offset, sizeof(aux_type_##DESCNAME), \ + &aux_info_##DESCNAME \ + } +#define DEFCOUNTEDTYPE_base(DESCNAME, STYPE, DATAFIELD, COUNTFIELD, SIGNED, \ + CDESC) \ + typedef STYPE aux_type_##DESCNAME; \ + const struct counted_info aux_info_##DESCNAME = { \ + OFFOF(STYPE, DATAFIELD, aux_ptrtype_##CDESC), \ + OFFOF(STYPE, COUNTFIELD, aux_counttype_##CDESC), \ + SIGNED, sizeof(((STYPE*)0)->COUNTFIELD), \ + &k5_cntype_##CDESC \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_counted, sizeof(STYPE), \ + &aux_info_##DESCNAME \ + } +#define DEFCOUNTEDTYPE(DESCNAME, STYPE, DATAFIELD, COUNTFIELD, CDESC) \ + DEFCOUNTEDTYPE_base(DESCNAME, STYPE, DATAFIELD, COUNTFIELD, 0, CDESC) +#define DEFCOUNTEDTYPE_SIGNED(DESCNAME, STYPE, DATAFIELD, COUNTFIELD, CDESC) \ + DEFCOUNTEDTYPE_base(DESCNAME, STYPE, DATAFIELD, COUNTFIELD, 1, CDESC) + +/* Optional sequence fields. The basic form allows arbitrary test and + * initializer functions to be used. INIT may be null. */ +#define DEFOPTIONALTYPE(DESCNAME, PRESENT, INIT, BASEDESC) \ + typedef aux_type_##BASEDESC aux_type_##DESCNAME; \ + static const struct optional_info aux_info_##DESCNAME = { \ + PRESENT, INIT, &k5_atype_##BASEDESC \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_optional, sizeof(aux_type_##DESCNAME), \ + &aux_info_##DESCNAME \ + } +/* This form defines an is_present function for a zero-valued integer or null + * pointer of the base type's C type. */ +#define DEFOPTIONALZEROTYPE(DESCNAME, BASEDESC) \ + static int \ + aux_present_##DESCNAME(const void *p) \ + { \ + return *(aux_type_##BASEDESC *)p != 0; \ + } \ + DEFOPTIONALTYPE(DESCNAME, aux_present_##DESCNAME, NULL, BASEDESC) +/* This form defines an is_present function for a null or empty null-terminated + * array of the base type's C type. */ +#define DEFOPTIONALEMPTYTYPE(DESCNAME, BASEDESC) \ + static int \ + aux_present_##DESCNAME(const void *p) \ + { \ + const aux_type_##BASEDESC *val = p; \ + return (*val != NULL && **val != NULL); \ + } \ + DEFOPTIONALTYPE(DESCNAME, aux_present_##DESCNAME, NULL, BASEDESC) + +/* + * This encodes a pointer-to-pointer-to-thing where the passed-in + * value points to a null-terminated list of pointers to objects to be + * encoded, and encodes a (possibly empty) SEQUENCE OF these objects. + * + * BASEDESCNAME is a descriptor name for the pointer-to-thing + * type. + * + * When dealing with a structure containing a + * pointer-to-pointer-to-thing field, make a DEFPTRTYPE of this type, + * and use that type for the structure field. + */ +#define DEFNULLTERMSEQOFTYPE(DESCNAME,BASEDESCNAME) \ + typedef aux_type_##BASEDESCNAME aux_type_##DESCNAME; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_nullterm_sequence_of, sizeof(aux_type_##DESCNAME), \ + &k5_atype_##BASEDESCNAME \ + } +#define DEFNONEMPTYNULLTERMSEQOFTYPE(DESCNAME,BASEDESCNAME) \ + typedef aux_type_##BASEDESCNAME aux_type_##DESCNAME; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_nonempty_nullterm_sequence_of, \ + sizeof(aux_type_##DESCNAME), \ + &k5_atype_##BASEDESCNAME \ + } + +/* Objects with an explicit or implicit tag. (Implicit tags will ignore the + * construction field.) */ +#define DEFTAGGEDTYPE(DESCNAME, CLASS, CONSTRUCTION, TAG, IMPLICIT, BASEDESC) \ + typedef aux_type_##BASEDESC aux_type_##DESCNAME; \ + static const struct tagged_info aux_info_##DESCNAME = { \ + TAG, CLASS, CONSTRUCTION, IMPLICIT, &k5_atype_##BASEDESC \ + }; \ + const struct atype_info k5_atype_##DESCNAME = { \ + atype_tagged_thing, sizeof(aux_type_##DESCNAME), \ + &aux_info_##DESCNAME \ + } +/* Objects with an explicit APPLICATION tag added. */ +#define DEFAPPTAGGEDTYPE(DESCNAME, TAG, BASEDESC) \ + DEFTAGGEDTYPE(DESCNAME, APPLICATION, CONSTRUCTED, TAG, 0, BASEDESC) +/* Object with a context-specific tag added */ +#define DEFCTAGGEDTYPE(DESCNAME, TAG, BASEDESC) \ + DEFTAGGEDTYPE(DESCNAME, CONTEXT_SPECIFIC, CONSTRUCTED, TAG, 0, BASEDESC) +#define DEFCTAGGEDTYPE_IMPLICIT(DESCNAME, TAG, BASEDESC) \ + DEFTAGGEDTYPE(DESCNAME, CONTEXT_SPECIFIC, CONSTRUCTED, TAG, 1, BASEDESC) + +/* Define an offset type with an explicit context tag wrapper (the usual case + * for an RFC 4120 sequence field). */ +#define DEFFIELD(NAME, STYPE, FIELDNAME, TAG, DESC) \ + DEFOFFSETTYPE(NAME##_untagged, STYPE, FIELDNAME, DESC); \ + DEFCTAGGEDTYPE(NAME, TAG, NAME##_untagged) +/* Define a counted type with an explicit context tag wrapper. */ +#define DEFCNFIELD(NAME, STYPE, DATAFIELD, LENFIELD, TAG, CDESC) \ + DEFCOUNTEDTYPE(NAME##_untagged, STYPE, DATAFIELD, LENFIELD, CDESC); \ + DEFCTAGGEDTYPE(NAME, TAG, NAME##_untagged) +/* Like DEFFIELD but with an implicit context tag. */ +#define DEFFIELD_IMPLICIT(NAME, STYPE, FIELDNAME, TAG, DESC) \ + DEFOFFSETTYPE(NAME##_untagged, STYPE, FIELDNAME, DESC); \ + DEFCTAGGEDTYPE_IMPLICIT(NAME, TAG, NAME##_untagged) + +/* + * DEFCOUNTED*TYPE macros must: + * + * + Define types named aux_ptrtype_##DESCNAME and aux_counttype_##DESCNAME, to + * allow type checking when the counted type is referenced with structure + * field offsets in DEFCOUNTEDTYPE. + * + * + Define a cntype_info struct named k5_cntype_##DESCNAME + * + * + Define a type-specific structure, referenced by the tinfo field of the + * cntype_info structure. + * + * + Accept a following semicolon syntactically. + */ + +#define DEFCOUNTEDSTRINGTYPE(DESCNAME, DTYPE, LTYPE, ENCFN, DECFN, TAGVAL) \ + typedef DTYPE aux_ptrtype_##DESCNAME; \ + typedef LTYPE aux_counttype_##DESCNAME; \ + static const struct string_info aux_info_##DESCNAME = { \ + ENCFN, DECFN, TAGVAL \ + }; \ + const struct cntype_info k5_cntype_##DESCNAME = { \ + cntype_string, &aux_info_##DESCNAME \ + } + +#define DEFCOUNTEDDERTYPE(DESCNAME, DTYPE, LTYPE) \ + typedef DTYPE aux_ptrtype_##DESCNAME; \ + typedef LTYPE aux_counttype_##DESCNAME; \ + const struct cntype_info k5_cntype_##DESCNAME = { \ + cntype_der, NULL \ + } + +#define DEFCOUNTEDSEQOFTYPE(DESCNAME, LTYPE, BASEDESC) \ + typedef aux_type_##BASEDESC aux_ptrtype_##DESCNAME; \ + typedef LTYPE aux_counttype_##DESCNAME; \ + const struct cntype_info k5_cntype_##DESCNAME = { \ + cntype_seqof, &k5_atype_##BASEDESC \ + } + +#define DEFCHOICETYPE(DESCNAME, UTYPE, DTYPE, FIELDS) \ + typedef UTYPE aux_ptrtype_##DESCNAME; \ + typedef DTYPE aux_counttype_##DESCNAME; \ + static const struct choice_info aux_info_##DESCNAME = { \ + FIELDS, sizeof(FIELDS) / sizeof(FIELDS[0]) \ + }; \ + const struct cntype_info k5_cntype_##DESCNAME = { \ + cntype_choice, &aux_info_##DESCNAME \ + } + +/* + * Declare an externally-defined type. This is a hack we should do + * away with once we move to generating code from a script. For now, + * this macro is unfortunately not compatible with the defining macros + * above, since you can't do the typedefs twice and we need the + * declarations to produce typedefs. (We could eliminate the typedefs + * from the DEF* macros, but then every DEF* macro use, even the ones + * for internal type nodes we only use to build other types, would + * need an accompanying declaration which explicitly lists the + * type.) + */ +#define IMPORT_TYPE(DESCNAME, CTYPENAME) \ + typedef CTYPENAME aux_type_##DESCNAME; \ + extern const struct atype_info k5_atype_##DESCNAME + +/* Partially encode the contents of a type and return its tag information. + * Used only by kdc_req_body. */ +asn1_error_code +k5_asn1_encode_atype(asn1buf *buf, const void *val, const struct atype_info *a, + taginfo *tag_out, size_t *len_out); + +/* Decode the tag and contents of a type, storing the result in the + * caller-allocated C object val. Used only by kdc_req_body. */ +asn1_error_code +k5_asn1_decode_atype(const taginfo *t, const unsigned char *asn1, + size_t len, const struct atype_info *a, void *val); + +/* Returns a completed encoding, with tag and in the correct byte order, in an + * allocated krb5_data. */ +extern krb5_error_code +k5_asn1_full_encode(const void *rep, const struct atype_info *a, + krb5_data **code_out); +asn1_error_code +k5_asn1_full_decode(const krb5_data *code, const struct atype_info *a, + void **rep_out); + +#define MAKE_ENCODER(FNAME, DESC) \ + krb5_error_code \ + FNAME(const aux_type_##DESC *rep, krb5_data **code_out) \ + { \ + return k5_asn1_full_encode(rep, &k5_atype_##DESC, code_out); \ + } \ + extern int dummy /* gobble semicolon */ + +#define MAKE_DECODER(FNAME, DESC) \ + krb5_error_code \ + FNAME(const krb5_data *code, aux_type_##DESC **rep_out) \ + { \ + asn1_error_code ret; \ + void *rep; \ + *rep_out = NULL; \ + ret = k5_asn1_full_decode(code, &k5_atype_##DESC, &rep); \ + if (ret) \ + return ret; \ + *rep_out = rep; \ + return 0; \ + } \ + extern int dummy /* gobble semicolon */ + +#include <stddef.h> +/* + * Ugly hack! + * Like "offsetof", but with type checking. + */ +#define WARN_IF_TYPE_MISMATCH(LVALUE, TYPE) \ + (sizeof(0 ? (TYPE *) 0 : &(LVALUE))) +#define OFFOF(TYPE,FIELD,FTYPE) \ + (offsetof(TYPE, FIELD) \ + + 0 * WARN_IF_TYPE_MISMATCH(((TYPE*)0)->FIELD, FTYPE)) + +#endif diff --git a/src/lib/krb5/asn.1/asn1_k_encode.c b/src/lib/krb5/asn.1/asn1_k_encode.c new file mode 100644 index 000000000000..a827ca6083e8 --- /dev/null +++ b/src/lib/krb5/asn.1/asn1_k_encode.c @@ -0,0 +1,1817 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/asn.1/asn1_k_encode.c */ +/* + * Copyright 1994, 2008 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include "asn1_encode.h" +#include <assert.h> + +DEFINT_IMMEDIATE(krb5_version, KVNO, KRB5KDC_ERR_BAD_PVNO); + +static int +int32_not_minus1(const void *p) +{ + return (*(krb5_int32 *)p != -1); +} + +static void +init_int32_minus1(void *p) +{ + *(krb5_int32 *)p = -1; +} + +DEFBOOLTYPE(boolean, krb5_boolean); +DEFINTTYPE(int32, krb5_int32); +DEFPTRTYPE(int32_ptr, int32); +DEFCOUNTEDSEQOFTYPE(cseqof_int32, krb5_int32, int32_ptr); +DEFOPTIONALZEROTYPE(opt_int32, int32); +DEFOPTIONALTYPE(opt_int32_minus1, int32_not_minus1, init_int32_minus1, int32); + +DEFUINTTYPE(uint, unsigned int); +DEFUINTTYPE(octet, krb5_octet); +DEFUINTTYPE(ui_4, krb5_ui_4); +DEFOPTIONALZEROTYPE(opt_uint, uint); + +static int +nonempty_data(const void *p) +{ + const krb5_data *val = p; + return (val->data != NULL && val->length != 0); +} + +DEFCOUNTEDDERTYPE(der, char *, unsigned int); +DEFCOUNTEDTYPE(der_data, krb5_data, data, length, der); +DEFOPTIONALTYPE(opt_der_data, nonempty_data, NULL, der_data); + +DEFCOUNTEDSTRINGTYPE(octetstring, unsigned char *, unsigned int, + k5_asn1_encode_bytestring, k5_asn1_decode_bytestring, + ASN1_OCTETSTRING); +DEFCOUNTEDSTRINGTYPE(s_octetstring, char *, unsigned int, + k5_asn1_encode_bytestring, k5_asn1_decode_bytestring, + ASN1_OCTETSTRING); +DEFCOUNTEDTYPE(ostring_data, krb5_data, data, length, s_octetstring); +DEFPTRTYPE(ostring_data_ptr, ostring_data); +DEFOPTIONALTYPE(opt_ostring_data, nonempty_data, NULL, ostring_data); +DEFOPTIONALZEROTYPE(opt_ostring_data_ptr, ostring_data_ptr); + +DEFCOUNTEDSTRINGTYPE(generalstring, char *, unsigned int, + k5_asn1_encode_bytestring, k5_asn1_decode_bytestring, + ASN1_GENERALSTRING); +DEFCOUNTEDSTRINGTYPE(u_generalstring, unsigned char *, unsigned int, + k5_asn1_encode_bytestring, k5_asn1_decode_bytestring, + ASN1_GENERALSTRING); +DEFCOUNTEDTYPE(gstring_data, krb5_data, data, length, generalstring); +DEFOPTIONALTYPE(opt_gstring_data, nonempty_data, NULL, gstring_data); +DEFPTRTYPE(gstring_data_ptr, gstring_data); +DEFCOUNTEDSEQOFTYPE(cseqof_gstring_data, krb5_int32, gstring_data_ptr); + +DEFCOUNTEDSTRINGTYPE(utf8string, char *, unsigned int, + k5_asn1_encode_bytestring, k5_asn1_decode_bytestring, + ASN1_UTF8STRING); +DEFCOUNTEDTYPE(utf8_data, krb5_data, data, length, utf8string); +DEFOPTIONALTYPE(opt_utf8_data, nonempty_data, NULL, utf8_data); +DEFPTRTYPE(utf8_data_ptr, utf8_data); +DEFNULLTERMSEQOFTYPE(seqof_utf8_data, utf8_data_ptr); + +DEFCOUNTEDSTRINGTYPE(object_identifier, char *, unsigned int, + k5_asn1_encode_bytestring, k5_asn1_decode_bytestring, + ASN1_OBJECTIDENTIFIER); +DEFCOUNTEDTYPE(oid_data, krb5_data, data, length, object_identifier); +DEFPTRTYPE(oid_data_ptr, oid_data); + +DEFOFFSETTYPE(realm_of_principal_data, krb5_principal_data, realm, + gstring_data); +DEFPTRTYPE(realm_of_principal, realm_of_principal_data); +DEFOPTIONALZEROTYPE(opt_realm_of_principal, realm_of_principal); + +DEFFIELD(princname_0, krb5_principal_data, type, 0, int32); +DEFCNFIELD(princname_1, krb5_principal_data, data, length, 1, + cseqof_gstring_data); +static const struct atype_info *princname_fields[] = { + &k5_atype_princname_0, &k5_atype_princname_1 +}; +DEFSEQTYPE(principal_data, krb5_principal_data, princname_fields); +DEFPTRTYPE(principal, principal_data); +DEFOPTIONALZEROTYPE(opt_principal, principal); + +/* + * Define the seqno type, which is an ASN.1 integer represented in a krb5_ui_4. + * When decoding, negative 32-bit numbers are accepted for interoperability + * with old implementations. + */ +static asn1_error_code +encode_seqno(asn1buf *buf, const void *p, taginfo *rettag, size_t *len_out) +{ + krb5_ui_4 val = *(krb5_ui_4 *)p; + rettag->asn1class = UNIVERSAL; + rettag->construction = PRIMITIVE; + rettag->tagnum = ASN1_INTEGER; + return k5_asn1_encode_uint(buf, val, len_out); +} +static asn1_error_code +decode_seqno(const taginfo *t, const unsigned char *asn1, size_t len, void *p) +{ + asn1_error_code ret; + intmax_t val; + ret = k5_asn1_decode_int(asn1, len, &val); + if (ret) + return ret; + if (val < KRB5_INT32_MIN || val > 0xFFFFFFFF) + return ASN1_OVERFLOW; + /* Negative values will cast correctly to krb5_ui_4. */ + *(krb5_ui_4 *)p = val; + return 0; +} +static int +check_seqno(const taginfo *t) +{ + return (t->asn1class == UNIVERSAL && t->construction == PRIMITIVE && + t->tagnum == ASN1_INTEGER); +} +DEFFNTYPE(seqno, krb5_ui_4, encode_seqno, decode_seqno, check_seqno, NULL); +DEFOPTIONALZEROTYPE(opt_seqno, seqno); + +/* Define the kerberos_time type, which is an ASN.1 generaltime represented in + * a krb5_timestamp. */ +static asn1_error_code +encode_kerberos_time(asn1buf *buf, const void *p, taginfo *rettag, + size_t *len_out) +{ + /* Range checking for time_t vs krb5_timestamp? */ + time_t val = *(krb5_timestamp *)p; + rettag->asn1class = UNIVERSAL; + rettag->construction = PRIMITIVE; + rettag->tagnum = ASN1_GENERALTIME; + return k5_asn1_encode_generaltime(buf, val, len_out); +} +static asn1_error_code +decode_kerberos_time(const taginfo *t, const unsigned char *asn1, size_t len, + void *p) +{ + asn1_error_code ret; + time_t val; + ret = k5_asn1_decode_generaltime(asn1, len, &val); + if (ret) + return ret; + *(krb5_timestamp *)p = val; + return 0; +} +static int +check_kerberos_time(const taginfo *t) +{ + return (t->asn1class == UNIVERSAL && t->construction == PRIMITIVE && + t->tagnum == ASN1_GENERALTIME); +} +DEFFNTYPE(kerberos_time, krb5_timestamp, encode_kerberos_time, + decode_kerberos_time, check_kerberos_time, NULL); +DEFOPTIONALZEROTYPE(opt_kerberos_time, kerberos_time); + +DEFFIELD(address_0, krb5_address, addrtype, 0, int32); +DEFCNFIELD(address_1, krb5_address, contents, length, 1, octetstring); +const static struct atype_info *address_fields[] = { + &k5_atype_address_0, &k5_atype_address_1 +}; +DEFSEQTYPE(address, krb5_address, address_fields); +DEFPTRTYPE(address_ptr, address); +DEFOPTIONALZEROTYPE(opt_address_ptr, address_ptr); + +DEFNULLTERMSEQOFTYPE(seqof_host_addresses, address_ptr); +DEFPTRTYPE(ptr_seqof_host_addresses, seqof_host_addresses); +DEFOPTIONALEMPTYTYPE(opt_ptr_seqof_host_addresses, ptr_seqof_host_addresses); + +/* + * krb5_kvno is defined as unsigned int, but historically (MIT krb5 through 1.6 + * in the encoder, and through 1.10 in the decoder) we treat it as signed, in + * violation of RFC 4120. kvno values large enough to be problematic are only + * likely to be seen with Windows read-only domain controllers, which overload + * the high 16-bits of kvno values for krbtgt principals. Since Windows + * encodes kvnos as signed 32-bit values, for interoperability it's best if we + * do the same. + */ +DEFINTTYPE(kvno, krb5_kvno); +DEFOPTIONALZEROTYPE(opt_kvno, kvno); + +DEFFIELD(enc_data_0, krb5_enc_data, enctype, 0, int32); +DEFFIELD(enc_data_1, krb5_enc_data, kvno, 1, opt_kvno); +DEFFIELD(enc_data_2, krb5_enc_data, ciphertext, 2, ostring_data); +static const struct atype_info *encrypted_data_fields[] = { + &k5_atype_enc_data_0, &k5_atype_enc_data_1, &k5_atype_enc_data_2 +}; +DEFSEQTYPE(encrypted_data, krb5_enc_data, encrypted_data_fields); +static int +nonempty_enc_data(const void *p) +{ + const krb5_enc_data *val = p; + return (val->ciphertext.data != NULL); +} +DEFOPTIONALTYPE(opt_encrypted_data, nonempty_enc_data, NULL, encrypted_data); + +/* Define the krb5_flags type, which is an ASN.1 bit string represented in a + * 32-bit integer. */ +static asn1_error_code +encode_krb5_flags(asn1buf *buf, const void *p, taginfo *rettag, + size_t *len_out) +{ + unsigned char cbuf[4], *cptr = cbuf; + store_32_be((krb5_ui_4)*(const krb5_flags *)p, cbuf); + rettag->asn1class = UNIVERSAL; + rettag->construction = PRIMITIVE; + rettag->tagnum = ASN1_BITSTRING; + return k5_asn1_encode_bitstring(buf, &cptr, 4, len_out); +} +static asn1_error_code +decode_krb5_flags(const taginfo *t, const unsigned char *asn1, size_t len, + void *val) +{ + asn1_error_code ret; + size_t i, blen; + krb5_flags f = 0; + unsigned char *bits; + ret = k5_asn1_decode_bitstring(asn1, len, &bits, &blen); + if (ret) + return ret; + /* Copy up to 32 bits into f, starting at the most significant byte. */ + for (i = 0; i < blen && i < 4; i++) + f |= bits[i] << (8 * (3 - i)); + *(krb5_flags *)val = f; + free(bits); + return 0; +} +static int +check_krb5_flags(const taginfo *t) +{ + return (t->asn1class == UNIVERSAL && t->construction == PRIMITIVE && + t->tagnum == ASN1_BITSTRING); +} +DEFFNTYPE(krb5_flags, krb5_flags, encode_krb5_flags, decode_krb5_flags, + check_krb5_flags, NULL); +DEFOPTIONALZEROTYPE(opt_krb5_flags, krb5_flags); + +DEFFIELD(authdata_0, krb5_authdata, ad_type, 0, int32); +DEFCNFIELD(authdata_1, krb5_authdata, contents, length, 1, octetstring); +static const struct atype_info *authdata_elt_fields[] = { + &k5_atype_authdata_0, &k5_atype_authdata_1 +}; +DEFSEQTYPE(authdata_elt, krb5_authdata, authdata_elt_fields); +DEFPTRTYPE(authdata_elt_ptr, authdata_elt); +DEFNONEMPTYNULLTERMSEQOFTYPE(auth_data, authdata_elt_ptr); +DEFPTRTYPE(auth_data_ptr, auth_data); +DEFOPTIONALEMPTYTYPE(opt_auth_data_ptr, auth_data_ptr); + +/* authdata_types retrieves just the types of authdata elements in an array. */ +DEFCTAGGEDTYPE(authdata_elt_type_0, 0, int32); +static const struct atype_info *authdata_elt_type_fields[] = { + &k5_atype_authdata_elt_type_0 +}; +DEFSEQTYPE(authdata_elt_type, krb5_authdatatype, authdata_elt_type_fields); +DEFPTRTYPE(ptr_authdata_elt_type, authdata_elt_type); +DEFCOUNTEDSEQOFTYPE(cseqof_authdata_elt_type, unsigned int, + ptr_authdata_elt_type); +struct authdata_types { + krb5_authdatatype *types; + unsigned int ntypes; +}; +DEFCOUNTEDTYPE(authdata_types, struct authdata_types, types, ntypes, + cseqof_authdata_elt_type); + +DEFFIELD(keyblock_0, krb5_keyblock, enctype, 0, int32); +DEFCNFIELD(keyblock_1, krb5_keyblock, contents, length, 1, octetstring); +static const struct atype_info *encryption_key_fields[] = { + &k5_atype_keyblock_0, &k5_atype_keyblock_1 +}; +DEFSEQTYPE(encryption_key, krb5_keyblock, encryption_key_fields); +DEFPTRTYPE(ptr_encryption_key, encryption_key); +DEFOPTIONALZEROTYPE(opt_ptr_encryption_key, ptr_encryption_key); + +DEFFIELD(checksum_0, krb5_checksum, checksum_type, 0, int32); +DEFCNFIELD(checksum_1, krb5_checksum, contents, length, 1, octetstring); +static const struct atype_info *checksum_fields[] = { + &k5_atype_checksum_0, &k5_atype_checksum_1 +}; +DEFSEQTYPE(checksum, krb5_checksum, checksum_fields); +DEFPTRTYPE(checksum_ptr, checksum); +DEFNULLTERMSEQOFTYPE(seqof_checksum, checksum_ptr); +DEFPTRTYPE(ptr_seqof_checksum, seqof_checksum); +DEFOPTIONALZEROTYPE(opt_checksum_ptr, checksum_ptr); + +/* Define the last_req_type type, which is a krb5_int32 with some massaging + * on decode for backward compatibility. */ +static asn1_error_code +encode_lr_type(asn1buf *buf, const void *p, taginfo *rettag, size_t *len_out) +{ + krb5_int32 val = *(krb5_int32 *)p; + rettag->asn1class = UNIVERSAL; + rettag->construction = PRIMITIVE; + rettag->tagnum = ASN1_INTEGER; + return k5_asn1_encode_int(buf, val, len_out); +} +static asn1_error_code +decode_lr_type(const taginfo *t, const unsigned char *asn1, size_t len, + void *p) +{ + asn1_error_code ret; + intmax_t val; + ret = k5_asn1_decode_int(asn1, len, &val); + if (ret) + return ret; + if (val > KRB5_INT32_MAX || val < KRB5_INT32_MIN) + return ASN1_OVERFLOW; +#ifdef KRB5_GENEROUS_LR_TYPE + /* If type is in the 128-255 range, treat it as a negative 8-bit value. */ + if (val >= 128 && val <= 255) + val -= 256; +#endif + *(krb5_int32 *)p = val; + return 0; +} +static int +check_lr_type(const taginfo *t) +{ + return (t->asn1class == UNIVERSAL && t->construction == PRIMITIVE && + t->tagnum == ASN1_INTEGER); +} +DEFFNTYPE(last_req_type, krb5_int32, encode_lr_type, decode_lr_type, + check_lr_type, NULL); + +DEFFIELD(last_req_0, krb5_last_req_entry, lr_type, 0, last_req_type); +DEFFIELD(last_req_1, krb5_last_req_entry, value, 1, kerberos_time); +static const struct atype_info *lr_fields[] = { + &k5_atype_last_req_0, &k5_atype_last_req_1 +}; +DEFSEQTYPE(last_req_ent, krb5_last_req_entry, lr_fields); + +DEFPTRTYPE(last_req_ent_ptr, last_req_ent); +DEFNONEMPTYNULLTERMSEQOFTYPE(last_req, last_req_ent_ptr); +DEFPTRTYPE(last_req_ptr, last_req); + +DEFCTAGGEDTYPE(ticket_0, 0, krb5_version); +DEFFIELD(ticket_1, krb5_ticket, server, 1, realm_of_principal); +DEFFIELD(ticket_2, krb5_ticket, server, 2, principal); +DEFFIELD(ticket_3, krb5_ticket, enc_part, 3, encrypted_data); +static const struct atype_info *ticket_fields[] = { + &k5_atype_ticket_0, &k5_atype_ticket_1, &k5_atype_ticket_2, + &k5_atype_ticket_3 +}; +DEFSEQTYPE(untagged_ticket, krb5_ticket, ticket_fields); +DEFAPPTAGGEDTYPE(ticket, 1, untagged_ticket); + +/* First context tag is 1, not 0. */ +DEFFIELD(pa_data_1, krb5_pa_data, pa_type, 1, int32); +DEFCNFIELD(pa_data_2, krb5_pa_data, contents, length, 2, octetstring); +static const struct atype_info *pa_data_fields[] = { + &k5_atype_pa_data_1, &k5_atype_pa_data_2 +}; +DEFSEQTYPE(pa_data, krb5_pa_data, pa_data_fields); +DEFPTRTYPE(pa_data_ptr, pa_data); + +DEFNULLTERMSEQOFTYPE(seqof_pa_data, pa_data_ptr); +DEFPTRTYPE(ptr_seqof_pa_data, seqof_pa_data); +DEFOPTIONALEMPTYTYPE(opt_ptr_seqof_pa_data, ptr_seqof_pa_data); + +DEFPTRTYPE(ticket_ptr, ticket); +DEFNONEMPTYNULLTERMSEQOFTYPE(seqof_ticket,ticket_ptr); +DEFPTRTYPE(ptr_seqof_ticket, seqof_ticket); +DEFOPTIONALEMPTYTYPE(opt_ptr_seqof_ticket, ptr_seqof_ticket); + +static int +is_enc_kdc_rep_start_set(const void *p) +{ + const krb5_enc_kdc_rep_part *val = p; + return (val->times.starttime != 0); +} +static void +init_enc_kdc_rep_start(void *p) +{ + krb5_enc_kdc_rep_part *val = p; + val->times.starttime = val->times.authtime; +} +static int +is_renewable_flag_set(const void *p) +{ + const krb5_enc_kdc_rep_part *val = p; + return (val->flags & TKT_FLG_RENEWABLE); +} +DEFFIELD(enc_kdc_rep_0, krb5_enc_kdc_rep_part, session, 0, ptr_encryption_key); +DEFFIELD(enc_kdc_rep_1, krb5_enc_kdc_rep_part, last_req, 1, last_req_ptr); +DEFFIELD(enc_kdc_rep_2, krb5_enc_kdc_rep_part, nonce, 2, int32); +DEFFIELD(enc_kdc_rep_3, krb5_enc_kdc_rep_part, key_exp, 3, opt_kerberos_time); +DEFFIELD(enc_kdc_rep_4, krb5_enc_kdc_rep_part, flags, 4, krb5_flags); +DEFFIELD(enc_kdc_rep_5, krb5_enc_kdc_rep_part, times.authtime, 5, + kerberos_time); +DEFFIELD(enc_kdc_rep_6_def, krb5_enc_kdc_rep_part, times.starttime, 6, + kerberos_time); +DEFOPTIONALTYPE(enc_kdc_rep_6, is_enc_kdc_rep_start_set, + init_enc_kdc_rep_start, enc_kdc_rep_6_def); +DEFFIELD(enc_kdc_rep_7, krb5_enc_kdc_rep_part, times.endtime, 7, + kerberos_time); +DEFFIELD(enc_kdc_rep_8_def, krb5_enc_kdc_rep_part, times.renew_till, 8, + kerberos_time); +DEFOPTIONALTYPE(enc_kdc_rep_8, is_renewable_flag_set, NULL, enc_kdc_rep_8_def); +DEFFIELD(enc_kdc_rep_9, krb5_enc_kdc_rep_part, server, 9, realm_of_principal); +DEFFIELD(enc_kdc_rep_10, krb5_enc_kdc_rep_part, server, 10, principal); +DEFFIELD(enc_kdc_rep_11, krb5_enc_kdc_rep_part, caddrs, 11, + opt_ptr_seqof_host_addresses); +DEFFIELD(enc_kdc_rep_12, krb5_enc_kdc_rep_part, enc_padata, 12, + opt_ptr_seqof_pa_data); +static const struct atype_info *enc_kdc_rep_part_fields[] = { + &k5_atype_enc_kdc_rep_0, &k5_atype_enc_kdc_rep_1, &k5_atype_enc_kdc_rep_2, + &k5_atype_enc_kdc_rep_3, &k5_atype_enc_kdc_rep_4, &k5_atype_enc_kdc_rep_5, + &k5_atype_enc_kdc_rep_6, &k5_atype_enc_kdc_rep_7, &k5_atype_enc_kdc_rep_8, + &k5_atype_enc_kdc_rep_9, &k5_atype_enc_kdc_rep_10, + &k5_atype_enc_kdc_rep_11, &k5_atype_enc_kdc_rep_12 +}; +DEFSEQTYPE(enc_kdc_rep_part, krb5_enc_kdc_rep_part, enc_kdc_rep_part_fields); + +/* + * Yuck! Eventually push this *up* above the encoder API and make the + * rest of the library put the realm name in one consistent place. At + * the same time, might as well add the msg-type field and encode both + * AS-REQ and TGS-REQ through the same descriptor. + */ +typedef struct kdc_req_hack { + krb5_kdc_req v; + krb5_data server_realm; +} kdc_req_hack; +DEFFIELD(req_body_0, kdc_req_hack, v.kdc_options, 0, krb5_flags); +DEFFIELD(req_body_1, kdc_req_hack, v.client, 1, opt_principal); +DEFFIELD(req_body_2, kdc_req_hack, server_realm, 2, gstring_data); +DEFFIELD(req_body_3, kdc_req_hack, v.server, 3, opt_principal); +DEFFIELD(req_body_4, kdc_req_hack, v.from, 4, opt_kerberos_time); +DEFFIELD(req_body_5, kdc_req_hack, v.till, 5, kerberos_time); +DEFFIELD(req_body_6, kdc_req_hack, v.rtime, 6, opt_kerberos_time); +DEFFIELD(req_body_7, kdc_req_hack, v.nonce, 7, int32); +DEFCNFIELD(req_body_8, kdc_req_hack, v.ktype, v.nktypes, 8, cseqof_int32); +DEFFIELD(req_body_9, kdc_req_hack, v.addresses, 9, + opt_ptr_seqof_host_addresses); +DEFFIELD(req_body_10, kdc_req_hack, v.authorization_data, 10, + opt_encrypted_data); +DEFFIELD(req_body_11, kdc_req_hack, v.second_ticket, 11, opt_ptr_seqof_ticket); +static const struct atype_info *kdc_req_hack_fields[] = { + &k5_atype_req_body_0, &k5_atype_req_body_1, &k5_atype_req_body_2, + &k5_atype_req_body_3, &k5_atype_req_body_4, &k5_atype_req_body_5, + &k5_atype_req_body_6, &k5_atype_req_body_7, &k5_atype_req_body_8, + &k5_atype_req_body_9, &k5_atype_req_body_10, &k5_atype_req_body_11 +}; +DEFSEQTYPE(kdc_req_body_hack, kdc_req_hack, kdc_req_hack_fields); +static asn1_error_code +encode_kdc_req_body(asn1buf *buf, const void *p, taginfo *tag_out, + size_t *len_out) +{ + const krb5_kdc_req *val = p; + kdc_req_hack h; + h.v = *val; + if (val->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) { + if (val->second_ticket != NULL && val->second_ticket[0] != NULL) + h.server_realm = val->second_ticket[0]->server->realm; + else + return ASN1_MISSING_FIELD; + } else if (val->server != NULL) + h.server_realm = val->server->realm; + else + return ASN1_MISSING_FIELD; + return k5_asn1_encode_atype(buf, &h, &k5_atype_kdc_req_body_hack, tag_out, + len_out); +} +static void +free_kdc_req_body(void *val) +{ + krb5_kdc_req *req = val; + krb5_free_principal(NULL, req->client); + krb5_free_principal(NULL, req->server); + free(req->ktype); + krb5_free_addresses(NULL, req->addresses); + free(req->authorization_data.ciphertext.data); + krb5_free_tickets(NULL, req->second_ticket); +} +static asn1_error_code +decode_kdc_req_body(const taginfo *t, const unsigned char *asn1, size_t len, + void *val) +{ + asn1_error_code ret; + kdc_req_hack h; + krb5_kdc_req *b = val; + memset(&h, 0, sizeof(h)); + ret = k5_asn1_decode_atype(t, asn1, len, &k5_atype_kdc_req_body_hack, &h); + if (ret) + return ret; + b->kdc_options = h.v.kdc_options; + b->client = h.v.client; + b->server = h.v.server; + b->from = h.v.from; + b->till = h.v.till; + b->rtime = h.v.rtime; + b->nonce = h.v.nonce; + b->ktype = h.v.ktype; + b->nktypes = h.v.nktypes; + b->addresses = h.v.addresses; + b->authorization_data = h.v.authorization_data; + b->second_ticket = h.v.second_ticket; + if (b->client != NULL && b->server != NULL) { + ret = krb5int_copy_data_contents(NULL, &h.server_realm, + &b->client->realm); + if (ret) { + free_kdc_req_body(b); + free(h.server_realm.data); + memset(&h, 0, sizeof(h)); + return ret; + } + b->server->realm = h.server_realm; + } else if (b->client != NULL) + b->client->realm = h.server_realm; + else if (b->server != NULL) + b->server->realm = h.server_realm; + else + free(h.server_realm.data); + return 0; +} +static int +check_kdc_req_body(const taginfo *t) +{ + return (t->asn1class == UNIVERSAL && t->construction == CONSTRUCTED && + t->tagnum == ASN1_SEQUENCE); +} +DEFFNTYPE(kdc_req_body, krb5_kdc_req, encode_kdc_req_body, decode_kdc_req_body, + check_kdc_req_body, free_kdc_req_body); +/* end ugly hack */ + +DEFFIELD(transited_0, krb5_transited, tr_type, 0, octet); +DEFFIELD(transited_1, krb5_transited, tr_contents, 1, ostring_data); +static const struct atype_info *transited_fields[] = { + &k5_atype_transited_0, &k5_atype_transited_1 +}; +DEFSEQTYPE(transited, krb5_transited, transited_fields); + +static int +is_safe_timestamp_set(const void *p) +{ + const krb5_safe *val = p; + return (val->timestamp != 0); +} +DEFFIELD(safe_body_0, krb5_safe, user_data, 0, ostring_data); +DEFFIELD(safe_body_1, krb5_safe, timestamp, 1, opt_kerberos_time); +DEFFIELD(safe_body_2_def, krb5_safe, usec, 2, int32); +DEFOPTIONALTYPE(safe_body_2, is_safe_timestamp_set, NULL, safe_body_2_def); +DEFFIELD(safe_body_3, krb5_safe, seq_number, 3, opt_seqno); +DEFFIELD(safe_body_4, krb5_safe, s_address, 4, address_ptr); +DEFFIELD(safe_body_5, krb5_safe, r_address, 5, opt_address_ptr); +static const struct atype_info *safe_body_fields[] = { + &k5_atype_safe_body_0, &k5_atype_safe_body_1, &k5_atype_safe_body_2, + &k5_atype_safe_body_3, &k5_atype_safe_body_4, &k5_atype_safe_body_5 +}; +DEFSEQTYPE(safe_body, krb5_safe, safe_body_fields); + +DEFFIELD(cred_info_0, krb5_cred_info, session, 0, ptr_encryption_key); +DEFFIELD(cred_info_1, krb5_cred_info, client, 1, opt_realm_of_principal); +DEFFIELD(cred_info_2, krb5_cred_info, client, 2, opt_principal); +DEFFIELD(cred_info_3, krb5_cred_info, flags, 3, opt_krb5_flags); +DEFFIELD(cred_info_4, krb5_cred_info, times.authtime, 4, opt_kerberos_time); +DEFFIELD(cred_info_5, krb5_cred_info, times.starttime, 5, opt_kerberos_time); +DEFFIELD(cred_info_6, krb5_cred_info, times.endtime, 6, opt_kerberos_time); +DEFFIELD(cred_info_7, krb5_cred_info, times.renew_till, 7, opt_kerberos_time); +DEFFIELD(cred_info_8, krb5_cred_info, server, 8, opt_realm_of_principal); +DEFFIELD(cred_info_9, krb5_cred_info, server, 9, opt_principal); +DEFFIELD(cred_info_10, krb5_cred_info, caddrs, 10, + opt_ptr_seqof_host_addresses); +static const struct atype_info *krb_cred_info_fields[] = { + &k5_atype_cred_info_0, &k5_atype_cred_info_1, &k5_atype_cred_info_2, + &k5_atype_cred_info_3, &k5_atype_cred_info_4, &k5_atype_cred_info_5, + &k5_atype_cred_info_6, &k5_atype_cred_info_7, &k5_atype_cred_info_8, + &k5_atype_cred_info_9, &k5_atype_cred_info_10 +}; +DEFSEQTYPE(cred_info, krb5_cred_info, krb_cred_info_fields); +DEFPTRTYPE(cred_info_ptr, cred_info); +DEFNULLTERMSEQOFTYPE(seqof_cred_info, cred_info_ptr); + +DEFPTRTYPE(ptrseqof_cred_info, seqof_cred_info); + +static int +is_salt_present(const void *p) +{ + const krb5_etype_info_entry *val = p; + return (val->length != KRB5_ETYPE_NO_SALT); +} +static void +init_no_salt(void *p) +{ + krb5_etype_info_entry *val = p; + val->length = KRB5_ETYPE_NO_SALT; +} +DEFFIELD(etype_info_0, krb5_etype_info_entry, etype, 0, int32); +DEFCNFIELD(etype_info_1_def, krb5_etype_info_entry, salt, length, 1, + octetstring); +DEFOPTIONALTYPE(etype_info_1, is_salt_present, init_no_salt, etype_info_1_def); +static const struct atype_info *etype_info_entry_fields[] = { + &k5_atype_etype_info_0, &k5_atype_etype_info_1 +}; +DEFSEQTYPE(etype_info_entry, krb5_etype_info_entry, etype_info_entry_fields); + +/* First field is the same as etype-info. */ +DEFCNFIELD(etype_info2_1_def, krb5_etype_info_entry, salt, length, 1, + u_generalstring); +DEFOPTIONALTYPE(etype_info2_1, is_salt_present, init_no_salt, + etype_info2_1_def); +DEFFIELD(etype_info2_2, krb5_etype_info_entry, s2kparams, 2, opt_ostring_data); +static const struct atype_info *etype_info2_entry_fields[] = { + &k5_atype_etype_info_0, &k5_atype_etype_info2_1, &k5_atype_etype_info2_2 +}; +DEFSEQTYPE(etype_info2_entry, krb5_etype_info_entry, etype_info2_entry_fields); + +DEFPTRTYPE(etype_info_entry_ptr, etype_info_entry); +DEFNULLTERMSEQOFTYPE(etype_info, etype_info_entry_ptr); + +DEFPTRTYPE(etype_info2_entry_ptr, etype_info2_entry); +DEFNULLTERMSEQOFTYPE(etype_info2, etype_info2_entry_ptr); + +DEFFIELD(sch_0, krb5_sam_challenge_2, sam_challenge_2_body, 0, der_data); +DEFFIELD(sch_1, krb5_sam_challenge_2, sam_cksum, 1, ptr_seqof_checksum); +static const struct atype_info *sam_challenge_2_fields[] = { + &k5_atype_sch_0, &k5_atype_sch_1 +}; +DEFSEQTYPE(sam_challenge_2, krb5_sam_challenge_2, sam_challenge_2_fields); + +DEFFIELD(schb_0, krb5_sam_challenge_2_body, sam_type, 0, int32); +DEFFIELD(schb_1, krb5_sam_challenge_2_body, sam_flags, 1, krb5_flags); +DEFFIELD(schb_2, krb5_sam_challenge_2_body, sam_type_name, 2, + opt_ostring_data); +DEFFIELD(schb_3, krb5_sam_challenge_2_body, sam_track_id, 3, opt_ostring_data); +DEFFIELD(schb_4, krb5_sam_challenge_2_body, sam_challenge_label, 4, + opt_ostring_data); +DEFFIELD(schb_5, krb5_sam_challenge_2_body, sam_challenge, 5, + opt_ostring_data); +DEFFIELD(schb_6, krb5_sam_challenge_2_body, sam_response_prompt, 6, + opt_ostring_data); +DEFFIELD(schb_7, krb5_sam_challenge_2_body, sam_pk_for_sad, 7, + opt_ostring_data); +DEFFIELD(schb_8, krb5_sam_challenge_2_body, sam_nonce, 8, int32); +DEFFIELD(schb_9, krb5_sam_challenge_2_body, sam_etype, 9, int32); +static const struct atype_info *sam_challenge_2_body_fields[] = { + &k5_atype_schb_0, &k5_atype_schb_1, &k5_atype_schb_2, &k5_atype_schb_3, + &k5_atype_schb_4, &k5_atype_schb_5, &k5_atype_schb_6, &k5_atype_schb_7, + &k5_atype_schb_8, &k5_atype_schb_9 +}; +DEFSEQTYPE(sam_challenge_2_body,krb5_sam_challenge_2_body, + sam_challenge_2_body_fields); + +DEFFIELD(esre_0, krb5_enc_sam_response_enc_2, sam_nonce, 0, int32); +DEFFIELD(esre_1, krb5_enc_sam_response_enc_2, sam_sad, 1, opt_ostring_data); +static const struct atype_info *enc_sam_response_enc_2_fields[] = { + &k5_atype_esre_0, &k5_atype_esre_1 +}; +DEFSEQTYPE(enc_sam_response_enc_2, krb5_enc_sam_response_enc_2, + enc_sam_response_enc_2_fields); + +DEFFIELD(sam_resp_0, krb5_sam_response_2, sam_type, 0, int32); +DEFFIELD(sam_resp_1, krb5_sam_response_2, sam_flags, 1, krb5_flags); +DEFFIELD(sam_resp_2, krb5_sam_response_2, sam_track_id, 2, opt_ostring_data); +DEFFIELD(sam_resp_3, krb5_sam_response_2, sam_enc_nonce_or_sad, 3, + encrypted_data); +DEFFIELD(sam_resp_4, krb5_sam_response_2, sam_nonce, 4, int32); +static const struct atype_info *sam_response_2_fields[] = { + &k5_atype_sam_resp_0, &k5_atype_sam_resp_1, &k5_atype_sam_resp_2, + &k5_atype_sam_resp_3, &k5_atype_sam_resp_4 +}; +DEFSEQTYPE(sam_response_2, krb5_sam_response_2, sam_response_2_fields); + +DEFCTAGGEDTYPE(authenticator_0, 0, krb5_version); +DEFFIELD(authenticator_1, krb5_authenticator, client, 1, realm_of_principal); +DEFFIELD(authenticator_2, krb5_authenticator, client, 2, principal); +DEFFIELD(authenticator_3, krb5_authenticator, checksum, 3, opt_checksum_ptr); +DEFFIELD(authenticator_4, krb5_authenticator, cusec, 4, int32); +DEFFIELD(authenticator_5, krb5_authenticator, ctime, 5, kerberos_time); +DEFFIELD(authenticator_6, krb5_authenticator, subkey, 6, + opt_ptr_encryption_key); +DEFFIELD(authenticator_7, krb5_authenticator, seq_number, 7, opt_seqno); +DEFFIELD(authenticator_8, krb5_authenticator, authorization_data, 8, + opt_auth_data_ptr); +static const struct atype_info *authenticator_fields[] = { + &k5_atype_authenticator_0, &k5_atype_authenticator_1, + &k5_atype_authenticator_2, &k5_atype_authenticator_3, + &k5_atype_authenticator_4, &k5_atype_authenticator_5, + &k5_atype_authenticator_6, &k5_atype_authenticator_7, + &k5_atype_authenticator_8 +}; +DEFSEQTYPE(untagged_authenticator, krb5_authenticator, authenticator_fields); +DEFAPPTAGGEDTYPE(authenticator, 2, untagged_authenticator); + +DEFFIELD(enc_tkt_0, krb5_enc_tkt_part, flags, 0, krb5_flags); +DEFFIELD(enc_tkt_1, krb5_enc_tkt_part, session, 1, ptr_encryption_key); +DEFFIELD(enc_tkt_2, krb5_enc_tkt_part, client, 2, realm_of_principal); +DEFFIELD(enc_tkt_3, krb5_enc_tkt_part, client, 3, principal); +DEFFIELD(enc_tkt_4, krb5_enc_tkt_part, transited, 4, transited); +DEFFIELD(enc_tkt_5, krb5_enc_tkt_part, times.authtime, 5, kerberos_time); +DEFFIELD(enc_tkt_6, krb5_enc_tkt_part, times.starttime, 6, opt_kerberos_time); +DEFFIELD(enc_tkt_7, krb5_enc_tkt_part, times.endtime, 7, kerberos_time); +DEFFIELD(enc_tkt_8, krb5_enc_tkt_part, times.renew_till, 8, opt_kerberos_time); +DEFFIELD(enc_tkt_9, krb5_enc_tkt_part, caddrs, 9, + opt_ptr_seqof_host_addresses); +DEFFIELD(enc_tkt_10, krb5_enc_tkt_part, authorization_data, 10, + opt_auth_data_ptr); +static const struct atype_info *enc_tkt_part_fields[] = { + &k5_atype_enc_tkt_0, &k5_atype_enc_tkt_1, &k5_atype_enc_tkt_2, + &k5_atype_enc_tkt_3, &k5_atype_enc_tkt_4, &k5_atype_enc_tkt_5, + &k5_atype_enc_tkt_6, &k5_atype_enc_tkt_7, &k5_atype_enc_tkt_8, + &k5_atype_enc_tkt_9, &k5_atype_enc_tkt_10 +}; +DEFSEQTYPE(untagged_enc_tkt_part, krb5_enc_tkt_part, enc_tkt_part_fields); +DEFAPPTAGGEDTYPE(enc_tkt_part, 3, untagged_enc_tkt_part); + +DEFAPPTAGGEDTYPE(enc_as_rep_part, 25, enc_kdc_rep_part); +DEFAPPTAGGEDTYPE(enc_tgs_rep_part, 26, enc_kdc_rep_part); + +DEFCTAGGEDTYPE(kdc_rep_0, 0, krb5_version); +DEFFIELD(kdc_rep_1, krb5_kdc_rep, msg_type, 1, uint); +DEFFIELD(kdc_rep_2, krb5_kdc_rep, padata, 2, opt_ptr_seqof_pa_data); +DEFFIELD(kdc_rep_3, krb5_kdc_rep, client, 3, realm_of_principal); +DEFFIELD(kdc_rep_4, krb5_kdc_rep, client, 4, principal); +DEFFIELD(kdc_rep_5, krb5_kdc_rep, ticket, 5, ticket_ptr); +DEFFIELD(kdc_rep_6, krb5_kdc_rep, enc_part, 6, encrypted_data); +static const struct atype_info *kdc_rep_fields[] = { + &k5_atype_kdc_rep_0, &k5_atype_kdc_rep_1, &k5_atype_kdc_rep_2, + &k5_atype_kdc_rep_3, &k5_atype_kdc_rep_4, &k5_atype_kdc_rep_5, + &k5_atype_kdc_rep_6 +}; +DEFSEQTYPE(kdc_rep, krb5_kdc_rep, kdc_rep_fields); +DEFAPPTAGGEDTYPE(as_rep, 11, kdc_rep); +DEFAPPTAGGEDTYPE(tgs_rep, 13, kdc_rep); + +DEFINT_IMMEDIATE(ap_req_msg_type, ASN1_KRB_AP_REQ, 0); +DEFCTAGGEDTYPE(ap_req_0, 0, krb5_version); +DEFCTAGGEDTYPE(ap_req_1, 1, ap_req_msg_type); +DEFFIELD(ap_req_2, krb5_ap_req, ap_options, 2, krb5_flags); +DEFFIELD(ap_req_3, krb5_ap_req, ticket, 3, ticket_ptr); +DEFFIELD(ap_req_4, krb5_ap_req, authenticator, 4, encrypted_data); +static const struct atype_info *ap_req_fields[] = { + &k5_atype_ap_req_0, &k5_atype_ap_req_1, &k5_atype_ap_req_2, + &k5_atype_ap_req_3, &k5_atype_ap_req_4 +}; +DEFSEQTYPE(untagged_ap_req, krb5_ap_req, ap_req_fields); +DEFAPPTAGGEDTYPE(ap_req, 14, untagged_ap_req); + +DEFINT_IMMEDIATE(ap_rep_msg_type, ASN1_KRB_AP_REP, 0); +DEFCTAGGEDTYPE(ap_rep_0, 0, krb5_version); +DEFCTAGGEDTYPE(ap_rep_1, 1, ap_rep_msg_type); +DEFFIELD(ap_rep_2, krb5_ap_rep, enc_part, 2, encrypted_data); +static const struct atype_info *ap_rep_fields[] = { + &k5_atype_ap_rep_0, &k5_atype_ap_rep_1, &k5_atype_ap_rep_2 +}; +DEFSEQTYPE(untagged_ap_rep, krb5_ap_rep, ap_rep_fields); +DEFAPPTAGGEDTYPE(ap_rep, 15, untagged_ap_rep); + +DEFFIELD(ap_rep_enc_part_0, krb5_ap_rep_enc_part, ctime, 0, kerberos_time); +DEFFIELD(ap_rep_enc_part_1, krb5_ap_rep_enc_part, cusec, 1, int32); +DEFFIELD(ap_rep_enc_part_2, krb5_ap_rep_enc_part, subkey, 2, + opt_ptr_encryption_key); +DEFFIELD(ap_rep_enc_part_3, krb5_ap_rep_enc_part, seq_number, 3, opt_seqno); +static const struct atype_info *ap_rep_enc_part_fields[] = { + &k5_atype_ap_rep_enc_part_0, &k5_atype_ap_rep_enc_part_1, + &k5_atype_ap_rep_enc_part_2, &k5_atype_ap_rep_enc_part_3 +}; +DEFSEQTYPE(untagged_ap_rep_enc_part, krb5_ap_rep_enc_part, + ap_rep_enc_part_fields); +DEFAPPTAGGEDTYPE(ap_rep_enc_part, 27, untagged_ap_rep_enc_part); + +/* First context tag is 1. Fourth field is the encoding of the krb5_kdc_req + * structure as a KDC-REQ-BODY. */ +DEFCTAGGEDTYPE(kdc_req_1, 1, krb5_version); +DEFFIELD(kdc_req_2, krb5_kdc_req, msg_type, 2, uint); +DEFFIELD(kdc_req_3, krb5_kdc_req, padata, 3, opt_ptr_seqof_pa_data); +DEFCTAGGEDTYPE(kdc_req_4, 4, kdc_req_body); +static const struct atype_info *kdc_req_fields[] = { + &k5_atype_kdc_req_1, &k5_atype_kdc_req_2, &k5_atype_kdc_req_3, + &k5_atype_kdc_req_4 +}; +DEFSEQTYPE(kdc_req, krb5_kdc_req, kdc_req_fields); +DEFAPPTAGGEDTYPE(as_req, 10, kdc_req); +DEFAPPTAGGEDTYPE(tgs_req, 12, kdc_req); + +/* This is only needed because libkrb5 doesn't set msg_type when encoding + * krb5_kdc_reqs. If we fix that, we can use the above types for encoding. */ +DEFINT_IMMEDIATE(as_req_msg_type, KRB5_AS_REQ, 0); +DEFCTAGGEDTYPE(as_req_2, 2, as_req_msg_type); +DEFINT_IMMEDIATE(tgs_req_msg_type, KRB5_TGS_REQ, 0); +DEFCTAGGEDTYPE(tgs_req_2, 2, tgs_req_msg_type); +static const struct atype_info *as_req_fields[] = { + &k5_atype_kdc_req_1, &k5_atype_as_req_2, &k5_atype_kdc_req_3, + &k5_atype_kdc_req_4 +}; +static const struct atype_info *tgs_req_fields[] = { + &k5_atype_kdc_req_1, &k5_atype_tgs_req_2, &k5_atype_kdc_req_3, + &k5_atype_kdc_req_4 +}; +DEFSEQTYPE(untagged_as_req, krb5_kdc_req, as_req_fields); +DEFAPPTAGGEDTYPE(as_req_encode, 10, untagged_as_req); +DEFSEQTYPE(untagged_tgs_req, krb5_kdc_req, tgs_req_fields); +DEFAPPTAGGEDTYPE(tgs_req_encode, 12, untagged_tgs_req); + +DEFINT_IMMEDIATE(safe_msg_type, ASN1_KRB_SAFE, 0); +DEFCTAGGEDTYPE(safe_0, 0, krb5_version); +DEFCTAGGEDTYPE(safe_1, 1, safe_msg_type); +DEFCTAGGEDTYPE(safe_2, 2, safe_body); +DEFFIELD(safe_3, krb5_safe, checksum, 3, checksum_ptr); +static const struct atype_info *safe_fields[] = { + &k5_atype_safe_0, &k5_atype_safe_1, &k5_atype_safe_2, &k5_atype_safe_3 +}; +DEFSEQTYPE(untagged_safe, krb5_safe, safe_fields); +DEFAPPTAGGEDTYPE(safe, 20, untagged_safe); + +/* Hack to encode a KRB-SAFE with a pre-specified body encoding. The integer- + * immediate fields are borrowed from krb5_safe_fields above. */ +DEFPTRTYPE(saved_safe_body_ptr, der_data); +DEFOFFSETTYPE(safe_checksum_only, krb5_safe, checksum, checksum_ptr); +DEFPTRTYPE(safe_checksum_only_ptr, safe_checksum_only); +DEFFIELD(safe_with_body_2, struct krb5_safe_with_body, body, 2, + saved_safe_body_ptr); +DEFFIELD(safe_with_body_3, struct krb5_safe_with_body, safe, 3, + safe_checksum_only_ptr); +static const struct atype_info *safe_with_body_fields[] = { + &k5_atype_safe_0, &k5_atype_safe_1, &k5_atype_safe_with_body_2, + &k5_atype_safe_with_body_3 +}; +DEFSEQTYPE(untagged_safe_with_body, struct krb5_safe_with_body, + safe_with_body_fields); +DEFAPPTAGGEDTYPE(safe_with_body, 20, untagged_safe_with_body); + +/* Third tag is [3] instead of [2]. */ +DEFINT_IMMEDIATE(priv_msg_type, ASN1_KRB_PRIV, 0); +DEFCTAGGEDTYPE(priv_0, 0, krb5_version); +DEFCTAGGEDTYPE(priv_1, 1, priv_msg_type); +DEFFIELD(priv_3, krb5_priv, enc_part, 3, encrypted_data); +static const struct atype_info *priv_fields[] = { + &k5_atype_priv_0, &k5_atype_priv_1, &k5_atype_priv_3 +}; +DEFSEQTYPE(untagged_priv, krb5_priv, priv_fields); +DEFAPPTAGGEDTYPE(priv, 21, untagged_priv); + +static int +is_priv_timestamp_set(const void *p) +{ + const krb5_priv_enc_part *val = p; + return (val->timestamp != 0); +} +DEFFIELD(priv_enc_part_0, krb5_priv_enc_part, user_data, 0, ostring_data); +DEFFIELD(priv_enc_part_1, krb5_priv_enc_part, timestamp, 1, opt_kerberos_time); +DEFFIELD(priv_enc_part_2_def, krb5_priv_enc_part, usec, 2, int32); +DEFOPTIONALTYPE(priv_enc_part_2, is_priv_timestamp_set, NULL, + priv_enc_part_2_def); +DEFFIELD(priv_enc_part_3, krb5_priv_enc_part, seq_number, 3, opt_seqno); +DEFFIELD(priv_enc_part_4, krb5_priv_enc_part, s_address, 4, address_ptr); +DEFFIELD(priv_enc_part_5, krb5_priv_enc_part, r_address, 5, opt_address_ptr); +static const struct atype_info *priv_enc_part_fields[] = { + &k5_atype_priv_enc_part_0, &k5_atype_priv_enc_part_1, + &k5_atype_priv_enc_part_2, &k5_atype_priv_enc_part_3, + &k5_atype_priv_enc_part_4, &k5_atype_priv_enc_part_5 +}; +DEFSEQTYPE(untagged_priv_enc_part, krb5_priv_enc_part, priv_enc_part_fields); +DEFAPPTAGGEDTYPE(priv_enc_part, 28, untagged_priv_enc_part); + +DEFINT_IMMEDIATE(cred_msg_type, ASN1_KRB_CRED, 0); +DEFCTAGGEDTYPE(cred_0, 0, krb5_version); +DEFCTAGGEDTYPE(cred_1, 1, cred_msg_type); +DEFFIELD(cred_2, krb5_cred, tickets, 2, ptr_seqof_ticket); +DEFFIELD(cred_3, krb5_cred, enc_part, 3, encrypted_data); +static const struct atype_info *cred_fields[] = { + &k5_atype_cred_0, &k5_atype_cred_1, &k5_atype_cred_2, &k5_atype_cred_3 +}; +DEFSEQTYPE(untagged_cred, krb5_cred, cred_fields); +DEFAPPTAGGEDTYPE(krb5_cred, 22, untagged_cred); + +static int +is_cred_timestamp_set(const void *p) +{ + const krb5_cred_enc_part *val = p; + return (val->timestamp != 0); +} +DEFFIELD(enc_cred_part_0, krb5_cred_enc_part, ticket_info, 0, + ptrseqof_cred_info); +DEFFIELD(enc_cred_part_1, krb5_cred_enc_part, nonce, 1, opt_int32); +DEFFIELD(enc_cred_part_2, krb5_cred_enc_part, timestamp, 2, opt_kerberos_time); +DEFFIELD(enc_cred_part_3_def, krb5_cred_enc_part, usec, 3, int32); +DEFOPTIONALTYPE(enc_cred_part_3, is_cred_timestamp_set, NULL, + enc_cred_part_3_def); +DEFFIELD(enc_cred_part_4, krb5_cred_enc_part, s_address, 4, opt_address_ptr); +DEFFIELD(enc_cred_part_5, krb5_cred_enc_part, r_address, 5, opt_address_ptr); +static const struct atype_info *enc_cred_part_fields[] = { + &k5_atype_enc_cred_part_0, &k5_atype_enc_cred_part_1, + &k5_atype_enc_cred_part_2, &k5_atype_enc_cred_part_3, + &k5_atype_enc_cred_part_4, &k5_atype_enc_cred_part_5 +}; +DEFSEQTYPE(untagged_enc_cred_part, krb5_cred_enc_part, enc_cred_part_fields); +DEFAPPTAGGEDTYPE(enc_cred_part, 29, untagged_enc_cred_part); + +DEFINT_IMMEDIATE(error_msg_type, ASN1_KRB_ERROR, 0); +DEFCTAGGEDTYPE(error_0, 0, krb5_version); +DEFCTAGGEDTYPE(error_1, 1, error_msg_type); +DEFFIELD(error_2, krb5_error, ctime, 2, opt_kerberos_time); +DEFFIELD(error_3, krb5_error, cusec, 3, opt_int32); +DEFFIELD(error_4, krb5_error, stime, 4, kerberos_time); +DEFFIELD(error_5, krb5_error, susec, 5, int32); +DEFFIELD(error_6, krb5_error, error, 6, ui_4); +DEFFIELD(error_7, krb5_error, client, 7, opt_realm_of_principal); +DEFFIELD(error_8, krb5_error, client, 8, opt_principal); +DEFFIELD(error_9, krb5_error, server, 9, realm_of_principal); +DEFFIELD(error_10, krb5_error, server, 10, principal); +DEFFIELD(error_11, krb5_error, text, 11, opt_gstring_data); +DEFFIELD(error_12, krb5_error, e_data, 12, opt_ostring_data); +static const struct atype_info *error_fields[] = { + &k5_atype_error_0, &k5_atype_error_1, &k5_atype_error_2, &k5_atype_error_3, + &k5_atype_error_4, &k5_atype_error_5, &k5_atype_error_6, &k5_atype_error_7, + &k5_atype_error_8, &k5_atype_error_9, &k5_atype_error_10, + &k5_atype_error_11, &k5_atype_error_12 +}; +DEFSEQTYPE(untagged_krb5_error, krb5_error, error_fields); +DEFAPPTAGGEDTYPE(krb5_error, 30, untagged_krb5_error); + +DEFFIELD(pa_enc_ts_0, krb5_pa_enc_ts, patimestamp, 0, kerberos_time); +DEFFIELD(pa_enc_ts_1, krb5_pa_enc_ts, pausec, 1, opt_int32); +static const struct atype_info *pa_enc_ts_fields[] = { + &k5_atype_pa_enc_ts_0, &k5_atype_pa_enc_ts_1 +}; +DEFSEQTYPE(pa_enc_ts, krb5_pa_enc_ts, pa_enc_ts_fields); + +DEFFIELD(setpw_0, struct krb5_setpw_req, password, 0, ostring_data); +DEFFIELD(setpw_1, struct krb5_setpw_req, target, 1, principal); +DEFFIELD(setpw_2, struct krb5_setpw_req, target, 2, realm_of_principal); +static const struct atype_info *setpw_req_fields[] = { + &k5_atype_setpw_0, &k5_atype_setpw_1, &k5_atype_setpw_2 +}; +DEFSEQTYPE(setpw_req, struct krb5_setpw_req, setpw_req_fields); + +/* [MS-SFU] Section 2.2.1. */ +DEFFIELD(pa_for_user_0, krb5_pa_for_user, user, 0, principal); +DEFFIELD(pa_for_user_1, krb5_pa_for_user, user, 1, realm_of_principal); +DEFFIELD(pa_for_user_2, krb5_pa_for_user, cksum, 2, checksum); +DEFFIELD(pa_for_user_3, krb5_pa_for_user, auth_package, 3, gstring_data); +static const struct atype_info *pa_for_user_fields[] = { + &k5_atype_pa_for_user_0, &k5_atype_pa_for_user_1, &k5_atype_pa_for_user_2, + &k5_atype_pa_for_user_3, +}; +DEFSEQTYPE(pa_for_user, krb5_pa_for_user, pa_for_user_fields); + +/* [MS-SFU] Section 2.2.2. */ +/* The user principal name may be absent, but the realm is required. */ +static int +is_s4u_principal_present(const void *p) +{ + krb5_const_principal val = *(krb5_const_principal *)p; + return (val->length != 0); +} +DEFOPTIONALTYPE(opt_s4u_principal, is_s4u_principal_present, NULL, principal); +DEFFIELD(s4u_userid_0, krb5_s4u_userid, nonce, 0, int32); +DEFFIELD(s4u_userid_1, krb5_s4u_userid, user, 1, opt_s4u_principal); +DEFFIELD(s4u_userid_2, krb5_s4u_userid, user, 2, realm_of_principal); +DEFFIELD(s4u_userid_3, krb5_s4u_userid, subject_cert, 3, opt_ostring_data); +DEFFIELD(s4u_userid_4, krb5_s4u_userid, options, 4, opt_krb5_flags); +static const struct atype_info *s4u_userid_fields[] = { + &k5_atype_s4u_userid_0, &k5_atype_s4u_userid_1, &k5_atype_s4u_userid_2, + &k5_atype_s4u_userid_3, &k5_atype_s4u_userid_4 +}; +DEFSEQTYPE(s4u_userid, krb5_s4u_userid, s4u_userid_fields); + +DEFFIELD(pa_s4u_x509_user_0, krb5_pa_s4u_x509_user, user_id, 0, s4u_userid); +DEFFIELD(pa_s4u_x509_user_1, krb5_pa_s4u_x509_user, cksum, 1, checksum); +static const struct atype_info *pa_s4u_x509_user_fields[] = { + &k5_atype_pa_s4u_x509_user_0, &k5_atype_pa_s4u_x509_user_1 +}; +DEFSEQTYPE(pa_s4u_x509_user, krb5_pa_s4u_x509_user, pa_s4u_x509_user_fields); + +DEFFIELD(pa_pac_req_0, krb5_pa_pac_req, include_pac, 0, boolean); +static const struct atype_info *pa_pac_req_fields[] = { + &k5_atype_pa_pac_req_0 +}; +DEFSEQTYPE(pa_pac_req, krb5_pa_pac_req, pa_pac_req_fields); + +/* RFC 4537 */ +DEFCOUNTEDTYPE(etype_list, krb5_etype_list, etypes, length, cseqof_int32); + +/* draft-ietf-krb-wg-preauth-framework-09 */ +DEFFIELD(fast_armor_0, krb5_fast_armor, armor_type, 0, int32); +DEFFIELD(fast_armor_1, krb5_fast_armor, armor_value, 1, ostring_data); +static const struct atype_info *fast_armor_fields[] = { + &k5_atype_fast_armor_0, &k5_atype_fast_armor_1 +}; +DEFSEQTYPE(fast_armor, krb5_fast_armor, fast_armor_fields); +DEFPTRTYPE(ptr_fast_armor, fast_armor); +DEFOPTIONALZEROTYPE(opt_ptr_fast_armor, ptr_fast_armor); + +DEFFIELD(fast_armored_req_0, krb5_fast_armored_req, armor, 0, + opt_ptr_fast_armor); +DEFFIELD(fast_armored_req_1, krb5_fast_armored_req, req_checksum, 1, checksum); +DEFFIELD(fast_armored_req_2, krb5_fast_armored_req, enc_part, 2, + encrypted_data); +static const struct atype_info *fast_armored_req_fields[] = { + &k5_atype_fast_armored_req_0, &k5_atype_fast_armored_req_1, + &k5_atype_fast_armored_req_2 +}; +DEFSEQTYPE(fast_armored_req, krb5_fast_armored_req, fast_armored_req_fields); + +/* This is a CHOICE type with only one choice (so far) and we're not using a + * distinguisher/union for it. */ +DEFTAGGEDTYPE(pa_fx_fast_request, CONTEXT_SPECIFIC, CONSTRUCTED, 0, 0, + fast_armored_req); + +DEFOFFSETTYPE(fast_req_padata, krb5_kdc_req, padata, ptr_seqof_pa_data); +DEFPTRTYPE(ptr_fast_req_padata, fast_req_padata); +DEFPTRTYPE(ptr_kdc_req_body, kdc_req_body); +DEFFIELD(fast_req_0, krb5_fast_req, fast_options, 0, krb5_flags); +DEFFIELD(fast_req_1, krb5_fast_req, req_body, 1, ptr_fast_req_padata); +DEFFIELD(fast_req_2, krb5_fast_req, req_body, 2, ptr_kdc_req_body); +static const struct atype_info *fast_req_fields[] = { + &k5_atype_fast_req_0, &k5_atype_fast_req_1, &k5_atype_fast_req_2 +}; +DEFSEQTYPE(fast_req, krb5_fast_req, fast_req_fields); + +DEFFIELD(fast_finished_0, krb5_fast_finished, timestamp, 0, kerberos_time); +DEFFIELD(fast_finished_1, krb5_fast_finished, usec, 1, int32); +DEFFIELD(fast_finished_2, krb5_fast_finished, client, 2, realm_of_principal); +DEFFIELD(fast_finished_3, krb5_fast_finished, client, 3, principal); +DEFFIELD(fast_finished_4, krb5_fast_finished, ticket_checksum, 4, checksum); +static const struct atype_info *fast_finished_fields[] = { + &k5_atype_fast_finished_0, &k5_atype_fast_finished_1, + &k5_atype_fast_finished_2, &k5_atype_fast_finished_3, + &k5_atype_fast_finished_4 +}; +DEFSEQTYPE(fast_finished, krb5_fast_finished, fast_finished_fields); +DEFPTRTYPE(ptr_fast_finished, fast_finished); +DEFOPTIONALZEROTYPE(opt_ptr_fast_finished, ptr_fast_finished); + +DEFFIELD(fast_response_0, krb5_fast_response, padata, 0, ptr_seqof_pa_data); +DEFFIELD(fast_response_1, krb5_fast_response, strengthen_key, 1, + opt_ptr_encryption_key); +DEFFIELD(fast_response_2, krb5_fast_response, finished, 2, + opt_ptr_fast_finished); +DEFFIELD(fast_response_3, krb5_fast_response, nonce, 3, int32); +static const struct atype_info *fast_response_fields[] = { + &k5_atype_fast_response_0, &k5_atype_fast_response_1, + &k5_atype_fast_response_2, &k5_atype_fast_response_3 +}; +DEFSEQTYPE(fast_response, krb5_fast_response, fast_response_fields); + +DEFCTAGGEDTYPE(fast_rep_0, 0, encrypted_data); +static const struct atype_info *fast_rep_fields[] = { + &k5_atype_fast_rep_0 +}; +DEFSEQTYPE(fast_rep, krb5_enc_data, fast_rep_fields); + +/* This is a CHOICE type with only one choice (so far) and we're not using a + * distinguisher/union for it. */ +DEFTAGGEDTYPE(pa_fx_fast_reply, CONTEXT_SPECIFIC, CONSTRUCTED, 0, 0, + fast_rep); + +DEFFIELD(ad_kdcissued_0, krb5_ad_kdcissued, ad_checksum, 0, checksum); +DEFFIELD(ad_kdcissued_1, krb5_ad_kdcissued, i_principal, 1, + opt_realm_of_principal); +DEFFIELD(ad_kdcissued_2, krb5_ad_kdcissued, i_principal, 2, opt_principal); +DEFFIELD(ad_kdcissued_3, krb5_ad_kdcissued, elements, 3, auth_data_ptr); +static const struct atype_info *ad_kdcissued_fields[] = { + &k5_atype_ad_kdcissued_0, &k5_atype_ad_kdcissued_1, + &k5_atype_ad_kdcissued_2, &k5_atype_ad_kdcissued_3 +}; +DEFSEQTYPE(ad_kdc_issued, krb5_ad_kdcissued, ad_kdcissued_fields); + +DEFCTAGGEDTYPE(princ_plus_realm_0, 0, principal_data); +DEFCTAGGEDTYPE(princ_plus_realm_1, 1, realm_of_principal_data); +static const struct atype_info *princ_plus_realm_fields[] = { + &k5_atype_princ_plus_realm_0, &k5_atype_princ_plus_realm_1 +}; +DEFSEQTYPE(princ_plus_realm_data, krb5_principal_data, + princ_plus_realm_fields); +DEFPTRTYPE(princ_plus_realm, princ_plus_realm_data); +DEFNULLTERMSEQOFTYPE(seqof_princ_plus_realm, princ_plus_realm); +DEFPTRTYPE(ptr_seqof_princ_plus_realm, seqof_princ_plus_realm); +DEFOPTIONALEMPTYTYPE(opt_ptr_seqof_princ_plus_realm, + ptr_seqof_princ_plus_realm); + +DEFFIELD(spdata_0, krb5_ad_signedpath_data, client, 0, princ_plus_realm); +DEFFIELD(spdata_1, krb5_ad_signedpath_data, authtime, 1, kerberos_time); +DEFFIELD(spdata_2, krb5_ad_signedpath_data, delegated, 2, + opt_ptr_seqof_princ_plus_realm); +DEFFIELD(spdata_3, krb5_ad_signedpath_data, method_data, 3, + opt_ptr_seqof_pa_data); +DEFFIELD(spdata_4, krb5_ad_signedpath_data, authorization_data, 4, + opt_auth_data_ptr); +static const struct atype_info *ad_signedpath_data_fields[] = { + &k5_atype_spdata_0, &k5_atype_spdata_1, &k5_atype_spdata_2, + &k5_atype_spdata_3, &k5_atype_spdata_4 +}; +DEFSEQTYPE(ad_signedpath_data, krb5_ad_signedpath_data, + ad_signedpath_data_fields); + +DEFFIELD(signedpath_0, krb5_ad_signedpath, enctype, 0, int32); +DEFFIELD(signedpath_1, krb5_ad_signedpath, checksum, 1, checksum); +DEFFIELD(signedpath_2, krb5_ad_signedpath, delegated, 2, + opt_ptr_seqof_princ_plus_realm); +DEFFIELD(signedpath_3, krb5_ad_signedpath, method_data, 3, + opt_ptr_seqof_pa_data); +static const struct atype_info *ad_signedpath_fields[] = { + &k5_atype_signedpath_0, &k5_atype_signedpath_1, &k5_atype_signedpath_2, + &k5_atype_signedpath_3 +}; +DEFSEQTYPE(ad_signedpath, krb5_ad_signedpath, ad_signedpath_fields); + +/* First context tag is 1, not 0. */ +DEFFIELD(iakerb_header_1, krb5_iakerb_header, target_realm, 1, ostring_data); +DEFFIELD(iakerb_header_2, krb5_iakerb_header, cookie, 2, opt_ostring_data_ptr); +static const struct atype_info *iakerb_header_fields[] = { + &k5_atype_iakerb_header_1, &k5_atype_iakerb_header_2 +}; +DEFSEQTYPE(iakerb_header, krb5_iakerb_header, iakerb_header_fields); + +/* First context tag is 1, not 0. */ +DEFFIELD(iakerb_finished_0, krb5_iakerb_finished, checksum, 1, checksum); +static const struct atype_info *iakerb_finished_fields[] = { + &k5_atype_iakerb_finished_0 +}; +DEFSEQTYPE(iakerb_finished, krb5_iakerb_finished, iakerb_finished_fields); + +/* Exported complete encoders -- these produce a krb5_data with + the encoding in the correct byte order. */ + +MAKE_ENCODER(encode_krb5_authenticator, authenticator); +MAKE_DECODER(decode_krb5_authenticator, authenticator); +MAKE_ENCODER(encode_krb5_ticket, ticket); +MAKE_DECODER(decode_krb5_ticket, ticket); +MAKE_ENCODER(encode_krb5_encryption_key, encryption_key); +MAKE_DECODER(decode_krb5_encryption_key, encryption_key); +MAKE_ENCODER(encode_krb5_enc_tkt_part, enc_tkt_part); +MAKE_DECODER(decode_krb5_enc_tkt_part, enc_tkt_part); + +krb5_error_code KRB5_CALLCONV +krb5_decode_ticket(const krb5_data *code, krb5_ticket **repptr) +{ + return decode_krb5_ticket(code, repptr); +} + +/* + * For backwards compatibility, we encode both EncASRepPart and EncTGSRepPart + * with application tag 26. On decode, we accept either app tag and set the + * msg_type field of the resulting structure. This could be simplified and + * pushed up into libkrb5. + */ +MAKE_ENCODER(encode_krb5_enc_kdc_rep_part, enc_tgs_rep_part); +krb5_error_code +decode_krb5_enc_kdc_rep_part(const krb5_data *code, + krb5_enc_kdc_rep_part **rep_out) +{ + asn1_error_code ret; + krb5_enc_kdc_rep_part *rep; + void *rep_ptr; + krb5_msgtype msg_type = KRB5_TGS_REP; + + *rep_out = NULL; + ret = k5_asn1_full_decode(code, &k5_atype_enc_tgs_rep_part, &rep_ptr); + if (ret == ASN1_BAD_ID) { + msg_type = KRB5_AS_REP; + ret = k5_asn1_full_decode(code, &k5_atype_enc_as_rep_part, &rep_ptr); + } + if (ret) + return ret; + rep = rep_ptr; + rep->msg_type = msg_type; + *rep_out = rep; + return 0; +} + +MAKE_ENCODER(encode_krb5_as_rep, as_rep); +MAKE_DECODER(decode_krb5_as_rep, as_rep); +MAKE_ENCODER(encode_krb5_tgs_rep, tgs_rep); +MAKE_DECODER(decode_krb5_tgs_rep, tgs_rep); +MAKE_ENCODER(encode_krb5_ap_req, ap_req); +MAKE_DECODER(decode_krb5_ap_req, ap_req); +MAKE_ENCODER(encode_krb5_ap_rep, ap_rep); +MAKE_DECODER(decode_krb5_ap_rep, ap_rep); +MAKE_ENCODER(encode_krb5_ap_rep_enc_part, ap_rep_enc_part); +MAKE_DECODER(decode_krb5_ap_rep_enc_part, ap_rep_enc_part); +MAKE_ENCODER(encode_krb5_as_req, as_req_encode); +MAKE_DECODER(decode_krb5_as_req, as_req); +MAKE_ENCODER(encode_krb5_tgs_req, tgs_req_encode); +MAKE_DECODER(decode_krb5_tgs_req, tgs_req); +MAKE_ENCODER(encode_krb5_kdc_req_body, kdc_req_body); +MAKE_DECODER(decode_krb5_kdc_req_body, kdc_req_body); +MAKE_ENCODER(encode_krb5_safe, safe); +MAKE_DECODER(decode_krb5_safe, safe); + +/* encode_krb5_safe_with_body takes a saved KRB-SAFE-BODY encoding to avoid + * mismatches from re-encoding if the sender isn't quite DER-compliant. */ +MAKE_ENCODER(encode_krb5_safe_with_body, safe_with_body); + +/* + * decode_krb5_safe_with_body fully decodes a KRB-SAFE, but also returns + * the KRB-SAFE-BODY encoding. This interface was designed for an earlier + * generation of decoder and should probably be re-thought. + */ +krb5_error_code +decode_krb5_safe_with_body(const krb5_data *code, krb5_safe **rep_out, + krb5_data **body_out) +{ + asn1_error_code ret; + void *swb_ptr, *safe_ptr; + struct krb5_safe_with_body *swb; + krb5_safe *safe; + + ret = k5_asn1_full_decode(code, &k5_atype_safe_with_body, &swb_ptr); + if (ret) + return ret; + swb = swb_ptr; + ret = k5_asn1_full_decode(swb->body, &k5_atype_safe_body, &safe_ptr); + if (ret) { + krb5_free_safe(NULL, swb->safe); + krb5_free_data(NULL, swb->body); + free(swb); + return ret; + } + safe = safe_ptr; + safe->checksum = swb->safe->checksum; + free(swb->safe); + *rep_out = safe; + *body_out = swb->body; + free(swb); + return 0; +} + +MAKE_ENCODER(encode_krb5_priv, priv); +MAKE_DECODER(decode_krb5_priv, priv); +MAKE_ENCODER(encode_krb5_enc_priv_part, priv_enc_part); +MAKE_DECODER(decode_krb5_enc_priv_part, priv_enc_part); +MAKE_ENCODER(encode_krb5_checksum, checksum); +MAKE_DECODER(decode_krb5_checksum, checksum); + +MAKE_ENCODER(encode_krb5_cred, krb5_cred); +MAKE_DECODER(decode_krb5_cred, krb5_cred); +MAKE_ENCODER(encode_krb5_enc_cred_part, enc_cred_part); +MAKE_DECODER(decode_krb5_enc_cred_part, enc_cred_part); +MAKE_ENCODER(encode_krb5_error, krb5_error); +MAKE_DECODER(decode_krb5_error, krb5_error); +MAKE_ENCODER(encode_krb5_authdata, auth_data); +MAKE_DECODER(decode_krb5_authdata, auth_data); +MAKE_ENCODER(encode_krb5_etype_info, etype_info); +MAKE_DECODER(decode_krb5_etype_info, etype_info); +MAKE_ENCODER(encode_krb5_etype_info2, etype_info2); +MAKE_DECODER(decode_krb5_etype_info2, etype_info2); +MAKE_ENCODER(encode_krb5_enc_data, encrypted_data); +MAKE_DECODER(decode_krb5_enc_data, encrypted_data); +MAKE_ENCODER(encode_krb5_pa_enc_ts, pa_enc_ts); +MAKE_DECODER(decode_krb5_pa_enc_ts, pa_enc_ts); +MAKE_ENCODER(encode_krb5_padata_sequence, seqof_pa_data); +MAKE_DECODER(decode_krb5_padata_sequence, seqof_pa_data); +/* sam preauth additions */ +MAKE_ENCODER(encode_krb5_sam_challenge_2, sam_challenge_2); +MAKE_DECODER(decode_krb5_sam_challenge_2, sam_challenge_2); +MAKE_ENCODER(encode_krb5_sam_challenge_2_body, sam_challenge_2_body); +MAKE_DECODER(decode_krb5_sam_challenge_2_body, sam_challenge_2_body); +MAKE_ENCODER(encode_krb5_enc_sam_response_enc_2, enc_sam_response_enc_2); +MAKE_DECODER(decode_krb5_enc_sam_response_enc_2, enc_sam_response_enc_2); +MAKE_ENCODER(encode_krb5_sam_response_2, sam_response_2); +MAKE_DECODER(decode_krb5_sam_response_2, sam_response_2); + +/* setpw_req has an odd decoder interface which should probably be + * normalized. */ +MAKE_ENCODER(encode_krb5_setpw_req, setpw_req); +krb5_error_code +decode_krb5_setpw_req(const krb5_data *code, krb5_data **password_out, + krb5_principal *target_out) +{ + asn1_error_code ret; + void *req_ptr; + struct krb5_setpw_req *req; + krb5_data *data; + + *password_out = NULL; + *target_out = NULL; + data = malloc(sizeof(*data)); + if (data == NULL) + return ENOMEM; + ret = k5_asn1_full_decode(code, &k5_atype_setpw_req, &req_ptr); + if (ret) { + free(data); + return ret; + } + req = req_ptr; + *data = req->password; + *password_out = data; + *target_out = req->target; + return 0; +} + +MAKE_ENCODER(encode_krb5_pa_for_user, pa_for_user); +MAKE_DECODER(decode_krb5_pa_for_user, pa_for_user); +MAKE_ENCODER(encode_krb5_s4u_userid, s4u_userid); +MAKE_ENCODER(encode_krb5_pa_s4u_x509_user, pa_s4u_x509_user); +MAKE_DECODER(decode_krb5_pa_s4u_x509_user, pa_s4u_x509_user); +MAKE_ENCODER(encode_krb5_pa_pac_req, pa_pac_req); +MAKE_DECODER(decode_krb5_pa_pac_req, pa_pac_req); +MAKE_ENCODER(encode_krb5_etype_list, etype_list); +MAKE_DECODER(decode_krb5_etype_list, etype_list); + +MAKE_ENCODER(encode_krb5_pa_fx_fast_request, pa_fx_fast_request); +MAKE_DECODER(decode_krb5_pa_fx_fast_request, pa_fx_fast_request); +MAKE_ENCODER(encode_krb5_fast_req, fast_req); +MAKE_DECODER(decode_krb5_fast_req, fast_req); +MAKE_ENCODER(encode_krb5_pa_fx_fast_reply, pa_fx_fast_reply); +MAKE_DECODER(decode_krb5_pa_fx_fast_reply, pa_fx_fast_reply); +MAKE_ENCODER(encode_krb5_fast_response, fast_response); +MAKE_DECODER(decode_krb5_fast_response, fast_response); + +MAKE_ENCODER(encode_krb5_ad_kdcissued, ad_kdc_issued); +MAKE_DECODER(decode_krb5_ad_kdcissued, ad_kdc_issued); +MAKE_ENCODER(encode_krb5_ad_signedpath_data, ad_signedpath_data); +MAKE_ENCODER(encode_krb5_ad_signedpath, ad_signedpath); +MAKE_DECODER(decode_krb5_ad_signedpath, ad_signedpath); +MAKE_ENCODER(encode_krb5_iakerb_header, iakerb_header); +MAKE_DECODER(decode_krb5_iakerb_header, iakerb_header); +MAKE_ENCODER(encode_krb5_iakerb_finished, iakerb_finished); +MAKE_DECODER(decode_krb5_iakerb_finished, iakerb_finished); + +krb5_error_code KRB5_CALLCONV +krb5int_get_authdata_containee_types(krb5_context context, + const krb5_authdata *authdata, + unsigned int *num_out, + krb5_authdatatype **types_out) +{ + asn1_error_code ret; + struct authdata_types *atypes; + void *atypes_ptr; + krb5_data d = make_data(authdata->contents, authdata->length); + + ret = k5_asn1_full_decode(&d, &k5_atype_authdata_types, &atypes_ptr); + if (ret) + return ret; + atypes = atypes_ptr; + *num_out = atypes->ntypes; + *types_out = atypes->types; + free(atypes); + return 0; +} + +/* RFC 3280. No context tags. */ +DEFOFFSETTYPE(algid_0, krb5_algorithm_identifier, algorithm, oid_data); +DEFOFFSETTYPE(algid_1, krb5_algorithm_identifier, parameters, opt_der_data); +static const struct atype_info *algorithm_identifier_fields[] = { + &k5_atype_algid_0, &k5_atype_algid_1 +}; +DEFSEQTYPE(algorithm_identifier, krb5_algorithm_identifier, + algorithm_identifier_fields); +DEFPTRTYPE(ptr_algorithm_identifier, algorithm_identifier); +DEFOPTIONALZEROTYPE(opt_ptr_algorithm_identifier, ptr_algorithm_identifier); +DEFNULLTERMSEQOFTYPE(seqof_algorithm_identifier, ptr_algorithm_identifier); +DEFPTRTYPE(ptr_seqof_algorithm_identifier, seqof_algorithm_identifier); +DEFOPTIONALEMPTYTYPE(opt_ptr_seqof_algorithm_identifier, + ptr_seqof_algorithm_identifier); + +/* + * PKINIT + */ + +#ifndef DISABLE_PKINIT + +DEFCTAGGEDTYPE(kdf_alg_id_0, 0, oid_data); +static const struct atype_info *kdf_alg_id_fields[] = { + &k5_atype_kdf_alg_id_0 +}; +DEFSEQTYPE(kdf_alg_id, krb5_data, kdf_alg_id_fields); +DEFPTRTYPE(ptr_kdf_alg_id, kdf_alg_id); +DEFNONEMPTYNULLTERMSEQOFTYPE(supported_kdfs, ptr_kdf_alg_id); +DEFPTRTYPE(ptr_supported_kdfs, supported_kdfs); +DEFOPTIONALZEROTYPE(opt_ptr_kdf_alg_id, ptr_kdf_alg_id); +DEFOPTIONALZEROTYPE(opt_ptr_supported_kdfs, ptr_supported_kdfs); + +/* KRB5PrincipalName from RFC 4556 (*not* PrincipalName from RFC 4120) */ +DEFCTAGGEDTYPE(pkinit_princ_0, 0, realm_of_principal_data); +DEFCTAGGEDTYPE(pkinit_princ_1, 1, principal_data); +static const struct atype_info *pkinit_krb5_principal_name_fields[] = { + &k5_atype_pkinit_princ_0, &k5_atype_pkinit_princ_1 +}; +DEFSEQTYPE(pkinit_krb5_principal_name_data, krb5_principal_data, + pkinit_krb5_principal_name_fields); +DEFPTRTYPE(pkinit_krb5_principal_name, pkinit_krb5_principal_name_data); + +/* SP80056A OtherInfo, for pkinit agility. No context tag on first field. */ +DEFTAGGEDTYPE(pkinit_krb5_principal_name_wrapped, UNIVERSAL, PRIMITIVE, + ASN1_OCTETSTRING, 0, pkinit_krb5_principal_name); +DEFOFFSETTYPE(oinfo_notag, krb5_sp80056a_other_info, algorithm_identifier, + algorithm_identifier); +DEFFIELD(oinfo_0, krb5_sp80056a_other_info, party_u_info, 0, + pkinit_krb5_principal_name_wrapped); +DEFFIELD(oinfo_1, krb5_sp80056a_other_info, party_v_info, 1, + pkinit_krb5_principal_name_wrapped); +DEFFIELD(oinfo_2, krb5_sp80056a_other_info, supp_pub_info, 2, ostring_data); +static const struct atype_info *sp80056a_other_info_fields[] = { + &k5_atype_oinfo_notag, &k5_atype_oinfo_0, &k5_atype_oinfo_1, + &k5_atype_oinfo_2 +}; +DEFSEQTYPE(sp80056a_other_info, krb5_sp80056a_other_info, + sp80056a_other_info_fields); + +/* For PkinitSuppPubInfo, for pkinit agility */ +DEFFIELD(supp_pub_0, krb5_pkinit_supp_pub_info, enctype, 0, int32); +DEFFIELD(supp_pub_1, krb5_pkinit_supp_pub_info, as_req, 1, ostring_data); +DEFFIELD(supp_pub_2, krb5_pkinit_supp_pub_info, pk_as_rep, 2, ostring_data); +static const struct atype_info *pkinit_supp_pub_info_fields[] = { + &k5_atype_supp_pub_0, &k5_atype_supp_pub_1, &k5_atype_supp_pub_2 +}; +DEFSEQTYPE(pkinit_supp_pub_info, krb5_pkinit_supp_pub_info, + pkinit_supp_pub_info_fields); + +MAKE_ENCODER(encode_krb5_pkinit_supp_pub_info, pkinit_supp_pub_info); +MAKE_ENCODER(encode_krb5_sp80056a_other_info, sp80056a_other_info); + +/* A krb5_checksum encoded as an OCTET STRING, for PKAuthenticator. */ +DEFCOUNTEDTYPE(ostring_checksum, krb5_checksum, contents, length, octetstring); + +DEFFIELD(pk_authenticator_0, krb5_pk_authenticator, cusec, 0, int32); +DEFFIELD(pk_authenticator_1, krb5_pk_authenticator, ctime, 1, kerberos_time); +DEFFIELD(pk_authenticator_2, krb5_pk_authenticator, nonce, 2, int32); +DEFFIELD(pk_authenticator_3, krb5_pk_authenticator, paChecksum, 3, + ostring_checksum); +static const struct atype_info *pk_authenticator_fields[] = { + &k5_atype_pk_authenticator_0, &k5_atype_pk_authenticator_1, + &k5_atype_pk_authenticator_2, &k5_atype_pk_authenticator_3 +}; +DEFSEQTYPE(pk_authenticator, krb5_pk_authenticator, pk_authenticator_fields); + +DEFFIELD(pkauth9_0, krb5_pk_authenticator_draft9, kdcName, 0, principal); +DEFFIELD(pkauth9_1, krb5_pk_authenticator_draft9, kdcName, 1, + realm_of_principal); +DEFFIELD(pkauth9_2, krb5_pk_authenticator_draft9, cusec, 2, int32); +DEFFIELD(pkauth9_3, krb5_pk_authenticator_draft9, ctime, 3, kerberos_time); +DEFFIELD(pkauth9_4, krb5_pk_authenticator_draft9, nonce, 4, int32); +static const struct atype_info *pk_authenticator_draft9_fields[] = { + &k5_atype_pkauth9_0, &k5_atype_pkauth9_1, &k5_atype_pkauth9_2, + &k5_atype_pkauth9_3, &k5_atype_pkauth9_4 +}; +DEFSEQTYPE(pk_authenticator_draft9, krb5_pk_authenticator_draft9, + pk_authenticator_draft9_fields); + +DEFCOUNTEDSTRINGTYPE(s_bitstring, char *, unsigned int, + k5_asn1_encode_bitstring, k5_asn1_decode_bitstring, + ASN1_BITSTRING); +DEFCOUNTEDTYPE(bitstring_data, krb5_data, data, length, s_bitstring); + +/* RFC 3280. No context tags. */ +DEFOFFSETTYPE(spki_0, krb5_subject_pk_info, algorithm, algorithm_identifier); +DEFOFFSETTYPE(spki_1, krb5_subject_pk_info, subjectPublicKey, bitstring_data); +static const struct atype_info *subject_pk_info_fields[] = { + &k5_atype_spki_0, &k5_atype_spki_1 +}; +DEFSEQTYPE(subject_pk_info, krb5_subject_pk_info, subject_pk_info_fields); +DEFPTRTYPE(subject_pk_info_ptr, subject_pk_info); +DEFOPTIONALZEROTYPE(opt_subject_pk_info_ptr, subject_pk_info_ptr); + +DEFFIELD(auth_pack_0, krb5_auth_pack, pkAuthenticator, 0, pk_authenticator); +DEFFIELD(auth_pack_1, krb5_auth_pack, clientPublicValue, 1, + opt_subject_pk_info_ptr); +DEFFIELD(auth_pack_2, krb5_auth_pack, supportedCMSTypes, 2, + opt_ptr_seqof_algorithm_identifier); +DEFFIELD(auth_pack_3, krb5_auth_pack, clientDHNonce, 3, opt_ostring_data); +DEFFIELD(auth_pack_4, krb5_auth_pack, supportedKDFs, 4, + opt_ptr_supported_kdfs); +static const struct atype_info *auth_pack_fields[] = { + &k5_atype_auth_pack_0, &k5_atype_auth_pack_1, &k5_atype_auth_pack_2, + &k5_atype_auth_pack_3, &k5_atype_auth_pack_4 +}; +DEFSEQTYPE(auth_pack, krb5_auth_pack, auth_pack_fields); + +DEFFIELD(auth_pack9_0, krb5_auth_pack_draft9, pkAuthenticator, 0, + pk_authenticator_draft9); +DEFFIELD(auth_pack9_1, krb5_auth_pack_draft9, clientPublicValue, 1, + opt_subject_pk_info_ptr); +static const struct atype_info *auth_pack_draft9_fields[] = { + &k5_atype_auth_pack9_0, &k5_atype_auth_pack9_1 +}; +DEFSEQTYPE(auth_pack_draft9, krb5_auth_pack_draft9, auth_pack_draft9_fields); + +DEFFIELD_IMPLICIT(extprinc_0, krb5_external_principal_identifier, + subjectName, 0, opt_ostring_data); +DEFFIELD_IMPLICIT(extprinc_1, krb5_external_principal_identifier, + issuerAndSerialNumber, 1, opt_ostring_data); +DEFFIELD_IMPLICIT(extprinc_2, krb5_external_principal_identifier, + subjectKeyIdentifier, 2, opt_ostring_data); +static const struct atype_info *external_principal_identifier_fields[] = { + &k5_atype_extprinc_0, &k5_atype_extprinc_1, &k5_atype_extprinc_2 +}; +DEFSEQTYPE(external_principal_identifier, krb5_external_principal_identifier, + external_principal_identifier_fields); +DEFPTRTYPE(external_principal_identifier_ptr, external_principal_identifier); + +DEFNULLTERMSEQOFTYPE(seqof_external_principal_identifier, + external_principal_identifier_ptr); +DEFPTRTYPE(ptr_seqof_external_principal_identifier, + seqof_external_principal_identifier); +DEFOPTIONALZEROTYPE(opt_ptr_seqof_external_principal_identifier, + ptr_seqof_external_principal_identifier); + +DEFFIELD_IMPLICIT(pa_pk_as_req_0, krb5_pa_pk_as_req, signedAuthPack, 0, + ostring_data); +DEFFIELD(pa_pk_as_req_1, krb5_pa_pk_as_req, trustedCertifiers, 1, + opt_ptr_seqof_external_principal_identifier); +DEFFIELD_IMPLICIT(pa_pk_as_req_2, krb5_pa_pk_as_req, kdcPkId, 2, + opt_ostring_data); +static const struct atype_info *pa_pk_as_req_fields[] = { + &k5_atype_pa_pk_as_req_0, &k5_atype_pa_pk_as_req_1, + &k5_atype_pa_pk_as_req_2 +}; +DEFSEQTYPE(pa_pk_as_req, krb5_pa_pk_as_req, pa_pk_as_req_fields); + +/* + * In draft-ietf-cat-kerberos-pk-init-09, this sequence has four fields, but we + * only ever use the first and third. The fields are specified as explicitly + * tagged, but our historical behavior is to pretend that they are wrapped in + * IMPLICIT OCTET STRING (i.e., generate primitive context tags), and we don't + * want to change that without interop testing. + */ +DEFFIELD_IMPLICIT(pa_pk_as_req9_0, krb5_pa_pk_as_req_draft9, signedAuthPack, 0, + ostring_data); +DEFFIELD_IMPLICIT(pa_pk_as_req9_2, krb5_pa_pk_as_req_draft9, kdcCert, 2, + opt_ostring_data); +static const struct atype_info *pa_pk_as_req_draft9_fields[] = { + &k5_atype_pa_pk_as_req9_0, &k5_atype_pa_pk_as_req9_2 +}; +DEFSEQTYPE(pa_pk_as_req_draft9, krb5_pa_pk_as_req_draft9, + pa_pk_as_req_draft9_fields); +/* For decoding, we only care about the first field; we can ignore the rest. */ +static const struct atype_info *pa_pk_as_req_draft9_decode_fields[] = { + &k5_atype_pa_pk_as_req9_0 +}; +DEFSEQTYPE(pa_pk_as_req_draft9_decode, krb5_pa_pk_as_req_draft9, + pa_pk_as_req_draft9_decode_fields); + +DEFFIELD_IMPLICIT(dh_rep_info_0, krb5_dh_rep_info, dhSignedData, 0, + ostring_data); +DEFFIELD(dh_rep_info_1, krb5_dh_rep_info, serverDHNonce, 1, opt_ostring_data); +DEFFIELD(dh_rep_info_2, krb5_dh_rep_info, kdfID, 2, opt_ptr_kdf_alg_id); +static const struct atype_info *dh_rep_info_fields[] = { + &k5_atype_dh_rep_info_0, &k5_atype_dh_rep_info_1, &k5_atype_dh_rep_info_2 +}; +DEFSEQTYPE(dh_rep_info, krb5_dh_rep_info, dh_rep_info_fields); + +DEFFIELD(dh_key_0, krb5_kdc_dh_key_info, subjectPublicKey, 0, bitstring_data); +DEFFIELD(dh_key_1, krb5_kdc_dh_key_info, nonce, 1, int32); +DEFFIELD(dh_key_2, krb5_kdc_dh_key_info, dhKeyExpiration, 2, + opt_kerberos_time); +static const struct atype_info *kdc_dh_key_info_fields[] = { + &k5_atype_dh_key_0, &k5_atype_dh_key_1, &k5_atype_dh_key_2 +}; +DEFSEQTYPE(kdc_dh_key_info, krb5_kdc_dh_key_info, kdc_dh_key_info_fields); + +DEFFIELD(reply_key_pack_0, krb5_reply_key_pack, replyKey, 0, encryption_key); +DEFFIELD(reply_key_pack_1, krb5_reply_key_pack, asChecksum, 1, checksum); +static const struct atype_info *reply_key_pack_fields[] = { + &k5_atype_reply_key_pack_0, &k5_atype_reply_key_pack_1 +}; +DEFSEQTYPE(reply_key_pack, krb5_reply_key_pack, reply_key_pack_fields); + +DEFFIELD(key_pack9_0, krb5_reply_key_pack_draft9, replyKey, 0, encryption_key); +DEFFIELD(key_pack9_1, krb5_reply_key_pack_draft9, nonce, 1, int32); +static const struct atype_info *reply_key_pack_draft9_fields[] = { + &k5_atype_key_pack9_0, &k5_atype_key_pack9_1 +}; +DEFSEQTYPE(reply_key_pack_draft9, krb5_reply_key_pack_draft9, + reply_key_pack_draft9_fields); + +DEFCTAGGEDTYPE(pa_pk_as_rep_0, 0, dh_rep_info); +DEFCTAGGEDTYPE_IMPLICIT(pa_pk_as_rep_1, 1, ostring_data); +static const struct atype_info *pa_pk_as_rep_alternatives[] = { + &k5_atype_pa_pk_as_rep_0, &k5_atype_pa_pk_as_rep_1 +}; +DEFCHOICETYPE(pa_pk_as_rep_choice, union krb5_pa_pk_as_rep_choices, + enum krb5_pa_pk_as_rep_selection, pa_pk_as_rep_alternatives); +DEFCOUNTEDTYPE_SIGNED(pa_pk_as_rep, krb5_pa_pk_as_rep, u, choice, + pa_pk_as_rep_choice); + +/* + * draft-ietf-cat-kerberos-pk-init-09 specifies these alternatives as + * explicitly tagged SignedData and EnvelopedData respectively, which means + * they should have constructed context tags. However, our historical behavior + * is to use primitive context tags, and we don't want to change that behavior + * without interop testing. We have the encodings for each alternative in a + * krb5_data object; pretend that they are wrapped in IMPLICIT OCTET STRING in + * order to wrap them in primitive [0] and [1] tags. + */ +DEFCTAGGEDTYPE_IMPLICIT(pa_pk_as_rep9_0, 0, ostring_data); +DEFCTAGGEDTYPE_IMPLICIT(pa_pk_as_rep9_1, 1, ostring_data); +static const struct atype_info *pa_pk_as_rep_draft9_alternatives[] = { + &k5_atype_pa_pk_as_rep9_0, &k5_atype_pa_pk_as_rep9_1 +}; +DEFCHOICETYPE(pa_pk_as_rep_draft9_choice, + union krb5_pa_pk_as_rep_draft9_choices, + enum krb5_pa_pk_as_rep_draft9_selection, + pa_pk_as_rep_draft9_alternatives); +DEFCOUNTEDTYPE_SIGNED(pa_pk_as_rep_draft9, krb5_pa_pk_as_rep_draft9, u, choice, + pa_pk_as_rep_draft9_choice); + +MAKE_ENCODER(encode_krb5_pa_pk_as_req, pa_pk_as_req); +MAKE_DECODER(decode_krb5_pa_pk_as_req, pa_pk_as_req); +MAKE_ENCODER(encode_krb5_pa_pk_as_req_draft9, pa_pk_as_req_draft9); +MAKE_DECODER(decode_krb5_pa_pk_as_req_draft9, pa_pk_as_req_draft9_decode); +MAKE_ENCODER(encode_krb5_pa_pk_as_rep, pa_pk_as_rep); +MAKE_DECODER(decode_krb5_pa_pk_as_rep, pa_pk_as_rep); +MAKE_ENCODER(encode_krb5_pa_pk_as_rep_draft9, pa_pk_as_rep_draft9); +MAKE_ENCODER(encode_krb5_auth_pack, auth_pack); +MAKE_DECODER(decode_krb5_auth_pack, auth_pack); +MAKE_ENCODER(encode_krb5_auth_pack_draft9, auth_pack_draft9); +MAKE_DECODER(decode_krb5_auth_pack_draft9, auth_pack_draft9); +MAKE_ENCODER(encode_krb5_kdc_dh_key_info, kdc_dh_key_info); +MAKE_DECODER(decode_krb5_kdc_dh_key_info, kdc_dh_key_info); +MAKE_ENCODER(encode_krb5_reply_key_pack, reply_key_pack); +MAKE_DECODER(decode_krb5_reply_key_pack, reply_key_pack); +MAKE_ENCODER(encode_krb5_reply_key_pack_draft9, reply_key_pack_draft9); +MAKE_DECODER(decode_krb5_reply_key_pack_draft9, reply_key_pack_draft9); +MAKE_ENCODER(encode_krb5_td_trusted_certifiers, + seqof_external_principal_identifier); +MAKE_DECODER(decode_krb5_td_trusted_certifiers, + seqof_external_principal_identifier); +MAKE_ENCODER(encode_krb5_td_dh_parameters, seqof_algorithm_identifier); +MAKE_DECODER(decode_krb5_td_dh_parameters, seqof_algorithm_identifier); +MAKE_DECODER(decode_krb5_principal_name, pkinit_krb5_principal_name_data); + +#else /* DISABLE_PKINIT */ + +/* Stubs for exported pkinit encoder functions. */ + +krb5_error_code +encode_krb5_sp80056a_other_info(const krb5_sp80056a_other_info *rep, + krb5_data **code) +{ + return EINVAL; +} + +krb5_error_code +encode_krb5_pkinit_supp_pub_info(const krb5_pkinit_supp_pub_info *rep, + krb5_data **code) +{ + return EINVAL; +} + +#endif /* not DISABLE_PKINIT */ + +DEFFIELD(typed_data_0, krb5_pa_data, pa_type, 0, int32); +DEFCNFIELD(typed_data_1, krb5_pa_data, contents, length, 1, octetstring); +static const struct atype_info *typed_data_fields[] = { + &k5_atype_typed_data_0, &k5_atype_typed_data_1 +}; +DEFSEQTYPE(typed_data, krb5_pa_data, typed_data_fields); +DEFPTRTYPE(typed_data_ptr, typed_data); + +DEFNULLTERMSEQOFTYPE(seqof_typed_data, typed_data_ptr); +MAKE_ENCODER(encode_krb5_typed_data, seqof_typed_data); +MAKE_DECODER(decode_krb5_typed_data, seqof_typed_data); + +/* Definitions for OTP preauth (RFC 6560) */ + +DEFFIELD_IMPLICIT(tokinfo_0, krb5_otp_tokeninfo, flags, 0, krb5_flags); +DEFFIELD_IMPLICIT(tokinfo_1, krb5_otp_tokeninfo, vendor, 1, opt_utf8_data); +DEFFIELD_IMPLICIT(tokinfo_2, krb5_otp_tokeninfo, challenge, 2, + opt_ostring_data); +DEFFIELD_IMPLICIT(tokinfo_3, krb5_otp_tokeninfo, length, 3, opt_int32_minus1); +DEFFIELD_IMPLICIT(tokinfo_4, krb5_otp_tokeninfo, format, 4, opt_int32_minus1); +DEFFIELD_IMPLICIT(tokinfo_5, krb5_otp_tokeninfo, token_id, 5, + opt_ostring_data); +DEFFIELD_IMPLICIT(tokinfo_6, krb5_otp_tokeninfo, alg_id, 6, opt_utf8_data); +DEFFIELD_IMPLICIT(tokinfo_7, krb5_otp_tokeninfo, supported_hash_alg, 7, + opt_ptr_seqof_algorithm_identifier); +DEFFIELD_IMPLICIT(tokinfo_8, krb5_otp_tokeninfo, iteration_count, 8, + opt_int32_minus1); +static const struct atype_info *otp_tokeninfo_fields[] = { + &k5_atype_tokinfo_0, &k5_atype_tokinfo_1, &k5_atype_tokinfo_2, + &k5_atype_tokinfo_3, &k5_atype_tokinfo_4, &k5_atype_tokinfo_5, + &k5_atype_tokinfo_6, &k5_atype_tokinfo_7, &k5_atype_tokinfo_8 +}; +DEFSEQTYPE(otp_tokeninfo, krb5_otp_tokeninfo, otp_tokeninfo_fields); +MAKE_ENCODER(encode_krb5_otp_tokeninfo, otp_tokeninfo); +MAKE_DECODER(decode_krb5_otp_tokeninfo, otp_tokeninfo); + +DEFPTRTYPE(otp_tokeninfo_ptr, otp_tokeninfo); +DEFNONEMPTYNULLTERMSEQOFTYPE(seqof_otp_tokeninfo, otp_tokeninfo_ptr); +DEFPTRTYPE(ptr_seqof_otp_tokeninfo, seqof_otp_tokeninfo); + +DEFFIELD_IMPLICIT(otp_ch_0, krb5_pa_otp_challenge, nonce, 0, ostring_data); +DEFFIELD_IMPLICIT(otp_ch_1, krb5_pa_otp_challenge, service, 1, opt_utf8_data); +DEFFIELD_IMPLICIT(otp_ch_2, krb5_pa_otp_challenge, tokeninfo, 2, + ptr_seqof_otp_tokeninfo); +DEFFIELD_IMPLICIT(otp_ch_3, krb5_pa_otp_challenge, salt, 3, opt_gstring_data); +DEFFIELD_IMPLICIT(otp_ch_4, krb5_pa_otp_challenge, s2kparams, 4, + opt_ostring_data); +static const struct atype_info *pa_otp_challenge_fields[] = { + &k5_atype_otp_ch_0, &k5_atype_otp_ch_1, &k5_atype_otp_ch_2, + &k5_atype_otp_ch_3, &k5_atype_otp_ch_4 +}; +DEFSEQTYPE(pa_otp_challenge, krb5_pa_otp_challenge, pa_otp_challenge_fields); +MAKE_ENCODER(encode_krb5_pa_otp_challenge, pa_otp_challenge); +MAKE_DECODER(decode_krb5_pa_otp_challenge, pa_otp_challenge); + +DEFFIELD_IMPLICIT(otp_req_0, krb5_pa_otp_req, flags, 0, krb5_flags); +DEFFIELD_IMPLICIT(otp_req_1, krb5_pa_otp_req, nonce, 1, opt_ostring_data); +DEFFIELD_IMPLICIT(otp_req_2, krb5_pa_otp_req, enc_data, 2, encrypted_data); +DEFFIELD_IMPLICIT(otp_req_3, krb5_pa_otp_req, hash_alg, 3, + opt_ptr_algorithm_identifier); +DEFFIELD_IMPLICIT(otp_req_4, krb5_pa_otp_req, iteration_count, 4, + opt_int32_minus1); +DEFFIELD_IMPLICIT(otp_req_5, krb5_pa_otp_req, otp_value, 5, opt_ostring_data); +DEFFIELD_IMPLICIT(otp_req_6, krb5_pa_otp_req, pin, 6, opt_utf8_data); +DEFFIELD_IMPLICIT(otp_req_7, krb5_pa_otp_req, challenge, 7, opt_ostring_data); +DEFFIELD_IMPLICIT(otp_req_8, krb5_pa_otp_req, time, 8, opt_kerberos_time); +DEFFIELD_IMPLICIT(otp_req_9, krb5_pa_otp_req, counter, 9, opt_ostring_data); +DEFFIELD_IMPLICIT(otp_req_10, krb5_pa_otp_req, format, 10, opt_int32_minus1); +DEFFIELD_IMPLICIT(otp_req_11, krb5_pa_otp_req, token_id, 11, opt_ostring_data); +DEFFIELD_IMPLICIT(otp_req_12, krb5_pa_otp_req, alg_id, 12, opt_utf8_data); +DEFFIELD_IMPLICIT(otp_req_13, krb5_pa_otp_req, vendor, 13, opt_utf8_data); +static const struct atype_info *pa_otp_req_fields[] = { + &k5_atype_otp_req_0, &k5_atype_otp_req_1, &k5_atype_otp_req_2, + &k5_atype_otp_req_3, &k5_atype_otp_req_4, &k5_atype_otp_req_5, + &k5_atype_otp_req_6, &k5_atype_otp_req_7, &k5_atype_otp_req_8, + &k5_atype_otp_req_9, &k5_atype_otp_req_10, &k5_atype_otp_req_11, + &k5_atype_otp_req_12, &k5_atype_otp_req_13 +}; +DEFSEQTYPE(pa_otp_req, krb5_pa_otp_req, pa_otp_req_fields); +MAKE_ENCODER(encode_krb5_pa_otp_req, pa_otp_req); +MAKE_DECODER(decode_krb5_pa_otp_req, pa_otp_req); + +DEFCTAGGEDTYPE_IMPLICIT(pa_otp_enc_req_0, 0, ostring_data); +static const struct atype_info *pa_otp_enc_req_fields[] = { + &k5_atype_pa_otp_enc_req_0 +}; +DEFSEQTYPE(pa_otp_enc_req, krb5_data, pa_otp_enc_req_fields); +MAKE_ENCODER(encode_krb5_pa_otp_enc_req, pa_otp_enc_req); +MAKE_DECODER(decode_krb5_pa_otp_enc_req, pa_otp_enc_req); + +DEFFIELD(kkdcp_message_0, krb5_kkdcp_message, + kerb_message, 0, ostring_data); +DEFFIELD(kkdcp_message_1, krb5_kkdcp_message, + target_domain, 1, opt_gstring_data); +DEFFIELD(kkdcp_message_2, krb5_kkdcp_message, + dclocator_hint, 2, opt_int32); +static const struct atype_info *kkdcp_message_fields[] = { + &k5_atype_kkdcp_message_0, &k5_atype_kkdcp_message_1, + &k5_atype_kkdcp_message_2 +}; +DEFSEQTYPE(kkdcp_message, krb5_kkdcp_message, + kkdcp_message_fields); +MAKE_ENCODER(encode_krb5_kkdcp_message, kkdcp_message); +MAKE_DECODER(decode_krb5_kkdcp_message, kkdcp_message); + +DEFFIELD(vmac_0, krb5_verifier_mac, princ, 0, opt_principal); +DEFFIELD(vmac_1, krb5_verifier_mac, kvno, 1, opt_kvno); +DEFFIELD(vmac_2, krb5_verifier_mac, enctype, 2, opt_int32); +DEFFIELD(vmac_3, krb5_verifier_mac, checksum, 3, checksum); +static const struct atype_info *vmac_fields[] = { + &k5_atype_vmac_0, &k5_atype_vmac_1, &k5_atype_vmac_2, &k5_atype_vmac_3 +}; +DEFSEQTYPE(vmac, krb5_verifier_mac, vmac_fields); +DEFPTRTYPE(vmac_ptr, vmac); +DEFOPTIONALZEROTYPE(opt_vmac_ptr, vmac_ptr); +DEFNONEMPTYNULLTERMSEQOFTYPE(vmacs, vmac_ptr); +DEFPTRTYPE(vmacs_ptr, vmacs); +DEFOPTIONALEMPTYTYPE(opt_vmacs_ptr, vmacs_ptr); + +DEFFIELD(cammac_0, krb5_cammac, elements, 0, auth_data_ptr); +DEFFIELD(cammac_1, krb5_cammac, kdc_verifier, 1, opt_vmac_ptr); +DEFFIELD(cammac_2, krb5_cammac, svc_verifier, 2, opt_vmac_ptr); +DEFFIELD(cammac_3, krb5_cammac, other_verifiers, 3, opt_vmacs_ptr); +static const struct atype_info *cammac_fields[] = { + &k5_atype_cammac_0, &k5_atype_cammac_1, &k5_atype_cammac_2, + &k5_atype_cammac_3 +}; +DEFSEQTYPE(cammac, krb5_cammac, cammac_fields); + +MAKE_ENCODER(encode_krb5_cammac, cammac); +MAKE_DECODER(decode_krb5_cammac, cammac); + +MAKE_ENCODER(encode_utf8_strings, seqof_utf8_data); +MAKE_DECODER(decode_utf8_strings, seqof_utf8_data); + +/* + * SecureCookie ::= SEQUENCE { + * time INTEGER, + * data SEQUENCE OF PA-DATA, + * ... + * } + */ +DEFINTTYPE(inttime, time_t); +DEFOFFSETTYPE(secure_cookie_0, krb5_secure_cookie, time, inttime); +DEFOFFSETTYPE(secure_cookie_1, krb5_secure_cookie, data, ptr_seqof_pa_data); +static const struct atype_info *secure_cookie_fields[] = { + &k5_atype_secure_cookie_0, &k5_atype_secure_cookie_1 +}; +DEFSEQTYPE(secure_cookie, krb5_secure_cookie, secure_cookie_fields); +MAKE_ENCODER(encode_krb5_secure_cookie, secure_cookie); +MAKE_DECODER(decode_krb5_secure_cookie, secure_cookie); diff --git a/src/lib/krb5/asn.1/asn1buf.c b/src/lib/krb5/asn.1/asn1buf.c new file mode 100644 index 000000000000..b93753034e57 --- /dev/null +++ b/src/lib/krb5/asn.1/asn1buf.c @@ -0,0 +1,209 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* Coding Buffer Implementation */ + +/* + * Implementation + * + * Encoding mode + * + * The encoding buffer is filled from bottom (lowest address) to top + * (highest address). This makes it easier to expand the buffer, + * since realloc preserves the existing portion of the buffer. + * + * Note: Since ASN.1 encoding must be done in reverse, this means + * that you can't simply memcpy out the buffer data, since it will be + * backwards. You need to reverse-iterate through it, instead. + * + * ***This decision may have been a mistake. In practice, the + * implementation will probably be tuned such that reallocation is + * rarely necessary. Also, the realloc probably has recopy the + * buffer itself, so we don't really gain that much by avoiding an + * explicit copy of the buffer. --Keep this in mind for future reference. + * + * + * Decoding mode + * + * The decoding buffer is in normal order and is created by wrapping + * an asn1buf around a krb5_data structure. + */ + +/* + * Abstraction Function + * + * Programs should use just pointers to asn1buf's (e.g. asn1buf *mybuf). + * These pointers must always point to a valid, allocated asn1buf + * structure or be NULL. + * + * The contents of the asn1buf represent an octet string. This string + * begins at base and continues to the octet immediately preceding next. + * If next == base or mybuf == NULL, then the asn1buf represents an empty + * octet string. + */ + +/* + * Representation Invariant + * + * Pointers to asn1buf's must always point to a valid, allocated + * asn1buf structure or be NULL. + * + * base points to a valid, allocated octet array or is NULL + * bound, if non-NULL, points to the last valid octet + * next >= base + * next <= bound+2 (i.e. next should be able to step just past the bound, + * but no further. (The bound should move out in response + * to being crossed by next.)) + */ + +#define ASN1BUF_OMIT_INLINE_FUNCS +#include "asn1buf.h" +#include <stdio.h> + +#ifdef USE_VALGRIND +#include <valgrind/memcheck.h> +#else +#define VALGRIND_CHECK_READABLE(PTR,SIZE) ((void)0) +#endif + +#if !defined(__GNUC__) || defined(CONFIG_SMALL) +/* + * Declare private procedures as static if they're not used for inline + * expansion of other stuff elsewhere. + */ +static unsigned int asn1buf_free(const asn1buf *); +static asn1_error_code asn1buf_ensure_space(asn1buf *, unsigned int); +static asn1_error_code asn1buf_expand(asn1buf *, unsigned int); +#endif + +#define asn1_is_eoc(class, num, indef) \ + ((class) == UNIVERSAL && !(num) && !(indef)) + +asn1_error_code +asn1buf_create(asn1buf **buf) +{ + *buf = (asn1buf*)malloc(sizeof(asn1buf)); + if (*buf == NULL) return ENOMEM; + (*buf)->base = NULL; + (*buf)->bound = NULL; + (*buf)->next = NULL; + return 0; +} + +void +asn1buf_destroy(asn1buf **buf) +{ + if (*buf != NULL) { + free((*buf)->base); + free(*buf); + *buf = NULL; + } +} + +#ifdef asn1buf_insert_octet +#undef asn1buf_insert_octet +#endif +asn1_error_code +asn1buf_insert_octet(asn1buf *buf, const int o) +{ + asn1_error_code retval; + + retval = asn1buf_ensure_space(buf,1U); + if (retval) return retval; + *(buf->next) = (char)o; + (buf->next)++; + return 0; +} + +asn1_error_code +asn1buf_insert_bytestring(asn1buf *buf, const unsigned int len, const void *sv) +{ + asn1_error_code retval; + unsigned int length; + const char *s = sv; + + retval = asn1buf_ensure_space(buf,len); + if (retval) return retval; + VALGRIND_CHECK_READABLE(sv, len); + for (length=1; length<=len; length++,(buf->next)++) + *(buf->next) = (s[len-length]); + return 0; +} + +asn1_error_code +asn12krb5_buf(const asn1buf *buf, krb5_data **code) +{ + unsigned int i; + krb5_data *d; + + *code = NULL; + + d = calloc(1, sizeof(krb5_data)); + if (d == NULL) + return ENOMEM; + d->length = asn1buf_len(buf); + d->data = malloc(d->length + 1); + if (d->data == NULL) { + free(d); + return ENOMEM; + } + for (i=0; i < d->length; i++) + d->data[i] = buf->base[d->length - i - 1]; + d->data[d->length] = '\0'; + d->magic = KV5M_DATA; + *code = d; + return 0; +} + +/****************************************************************/ +/* Private Procedures */ + +static int +asn1buf_size(const asn1buf *buf) +{ + if (buf == NULL || buf->base == NULL) return 0; + return buf->bound - buf->base + 1; +} + +#undef asn1buf_free +unsigned int +asn1buf_free(const asn1buf *buf) +{ + if (buf == NULL || buf->base == NULL) return 0; + else return buf->bound - buf->next + 1; +} + +#undef asn1buf_ensure_space +asn1_error_code +asn1buf_ensure_space(asn1buf *buf, const unsigned int amount) +{ + unsigned int avail = asn1buf_free(buf); + if (avail >= amount) + return 0; + return asn1buf_expand(buf, amount-avail); +} + +asn1_error_code +asn1buf_expand(asn1buf *buf, unsigned int inc) +{ +#define STANDARD_INCREMENT 200 + int next_offset = buf->next - buf->base; + int bound_offset; + if (buf->base == NULL) bound_offset = -1; + else bound_offset = buf->bound - buf->base; + + if (inc < STANDARD_INCREMENT) + inc = STANDARD_INCREMENT; + + buf->base = realloc(buf->base, + (asn1buf_size(buf)+inc) * sizeof(asn1_octet)); + if (buf->base == NULL) return ENOMEM; /* XXX leak */ + buf->bound = (buf->base) + bound_offset + inc; + buf->next = (buf->base) + next_offset; + return 0; +} + +#undef asn1buf_len +int +asn1buf_len(const asn1buf *buf) +{ + return buf->next - buf->base; +} diff --git a/src/lib/krb5/asn.1/asn1buf.h b/src/lib/krb5/asn.1/asn1buf.h new file mode 100644 index 000000000000..0d7138d20775 --- /dev/null +++ b/src/lib/krb5/asn.1/asn1buf.h @@ -0,0 +1,147 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* Coding Buffer Specifications */ +#ifndef __ASN1BUF_H__ +#define __ASN1BUF_H__ + +#include "k5-int.h" +#include "krbasn1.h" + +typedef struct code_buffer_rep { + char *base, *bound, *next; +} asn1buf; + + +/**************** Private Procedures ****************/ + +#if (__GNUC__ >= 2) && !defined(CONFIG_SMALL) +unsigned int asn1buf_free(const asn1buf *buf); +/* + * requires *buf is allocated + * effects Returns the number of unused, allocated octets in *buf. + */ +#define asn1buf_free(buf) \ + (((buf) == NULL || (buf)->base == NULL) \ + ? 0U \ + : (unsigned int)((buf)->bound - (buf)->next + 1)) + + +asn1_error_code asn1buf_ensure_space(asn1buf *buf, const unsigned int amount); +/* + * requires *buf is allocated + * modifies *buf + * effects If buf has less than amount octets of free space, then it is + * expanded to have at least amount octets of free space. + * Returns ENOMEM memory is exhausted. + */ +#define asn1buf_ensure_space(buf,amount) \ + ((asn1buf_free(buf) < (amount)) \ + ? (asn1buf_expand((buf), (amount)-asn1buf_free(buf))) \ + : 0) + +asn1_error_code asn1buf_expand(asn1buf *buf, unsigned int inc); +/* + * requires *buf is allocated + * modifies *buf + * effects Expands *buf by allocating space for inc more octets. + * Returns ENOMEM if memory is exhausted. + */ +#endif + +int asn1buf_len(const asn1buf *buf); +/* + * requires *buf is allocated + * effects Returns the length of the encoding in *buf. + */ +#define asn1buf_len(buf) ((buf)->next - (buf)->base) + +/****** End of private procedures *****/ + +/* + * Overview + * + * The coding buffer is an array of char (to match a krb5_data structure) + * with 3 reference pointers: + * 1) base - The bottom of the octet array. Used for memory management + * operations on the array (e.g. alloc, realloc, free). + * 2) next - Points to the next available octet position in the array. + * During encoding, this is the next free position, and it + * advances as octets are added to the array. + * During decoding, this is the next unread position, and it + * advances as octets are read from the array. + * 3) bound - Points to the top of the array. Used for bounds-checking. + * + * All pointers to encoding buffers should be initalized to NULL. + * + * Operations + * + * asn1buf_create + * asn1buf_wrap_data + * asn1buf_destroy + * asn1buf_insert_octet + * asn1buf_insert_charstring + * asn1buf_remove_octet + * asn1buf_remove_charstring + * asn1buf_unparse + * asn1buf_hex_unparse + * asn12krb5_buf + * asn1buf_remains + * + * (asn1buf_size) + * (asn1buf_free) + * (asn1buf_ensure_space) + * (asn1buf_expand) + * (asn1buf_len) + */ + +asn1_error_code asn1buf_create(asn1buf **buf); +/* + * effects Creates a new encoding buffer pointed to by *buf. + * Returns ENOMEM if the buffer can't be created. + */ + +void asn1buf_destroy(asn1buf **buf); +/* effects Deallocates **buf, sets *buf to NULL. */ + +/* + * requires *buf is allocated + * effects Inserts o into the buffer *buf, expanding the buffer if + * necessary. Returns ENOMEM memory is exhausted. + */ +#if ((__GNUC__ >= 2) && !defined(ASN1BUF_OMIT_INLINE_FUNCS)) && !defined(CONFIG_SMALL) +static inline asn1_error_code +asn1buf_insert_octet(asn1buf *buf, const int o) +{ + asn1_error_code retval; + + retval = asn1buf_ensure_space(buf,1U); + if (retval) return retval; + *(buf->next) = (char)o; + (buf->next)++; + return 0; +} +#else +asn1_error_code asn1buf_insert_octet(asn1buf *buf, const int o); +#endif + +asn1_error_code +asn1buf_insert_bytestring( + asn1buf *buf, + const unsigned int len, + const void *s); +/* + * requires *buf is allocated + * modifies *buf + * effects Inserts the contents of s (an array of length len) + * into the buffer *buf, expanding the buffer if necessary. + * Returns ENOMEM if memory is exhausted. + */ + +#define asn1buf_insert_octetstring asn1buf_insert_bytestring + +asn1_error_code asn12krb5_buf(const asn1buf *buf, krb5_data **code); +/* + * modifies *code + * effects Instantiates **code with the krb5_data representation of **buf. + */ + +#endif diff --git a/src/lib/krb5/asn.1/deps b/src/lib/krb5/asn.1/deps new file mode 100644 index 000000000000..47050d699f33 --- /dev/null +++ b/src/lib/krb5/asn.1/deps @@ -0,0 +1,48 @@ +# +# Generated makefile dependencies follow. +# +asn1_encode.so asn1_encode.po $(OUTPRE)asn1_encode.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + asn1_encode.c asn1_encode.h asn1buf.h krbasn1.h +asn1buf.so asn1buf.po $(OUTPRE)asn1buf.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \ + $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \ + $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \ + $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \ + $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h asn1buf.c asn1buf.h \ + krbasn1.h +asn1_k_encode.so asn1_k_encode.po $(OUTPRE)asn1_k_encode.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/krb5.h \ + $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \ + $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \ + asn1_encode.h asn1_k_encode.c asn1buf.h krbasn1.h +ldap_key_seq.so ldap_key_seq.po $(OUTPRE)ldap_key_seq.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/kdb.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \ + $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h asn1_encode.h \ + asn1buf.h krbasn1.h ldap_key_seq.c diff --git a/src/lib/krb5/asn.1/krbasn1.h b/src/lib/krb5/asn.1/krbasn1.h new file mode 100644 index 000000000000..1755784115f4 --- /dev/null +++ b/src/lib/krb5/asn.1/krbasn1.h @@ -0,0 +1,74 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +#ifndef __KRBASN1_H__ +#define __KRBASN1_H__ + +#include "k5-int.h" +#include <stdio.h> +#include <errno.h> +#include <limits.h> /* For INT_MAX */ +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +/* + * If KRB5_MSGTYPE_STRICT is defined, then be strict about checking + * the msgtype fields. Unfortunately, there old versions of Kerberos + * don't set these fields correctly, so we have to make allowances for + * them. + */ +/* #define KRB5_MSGTYPE_STRICT */ + +/* + * If KRB5_GENEROUS_LR_TYPE is defined, then we are generous about + * accepting a one byte negative lr_type - which is not sign + * extended. Prior to July 2000, we were sending a negative lr_type as + * a positve single byte value - instead of a signed integer. This + * allows us to receive the old value and deal + */ +#define KRB5_GENEROUS_LR_TYPE + +typedef krb5_octet asn1_octet; +typedef krb5_error_code asn1_error_code; + +typedef enum { PRIMITIVE = 0x00, CONSTRUCTED = 0x20 } asn1_construction; + +typedef enum { UNIVERSAL = 0x00, APPLICATION = 0x40, + CONTEXT_SPECIFIC = 0x80, PRIVATE = 0xC0 } asn1_class; + +typedef int asn1_tagnum; +#define ASN1_TAGNUM_CEILING INT_MAX +#define ASN1_TAGNUM_MAX (ASN1_TAGNUM_CEILING-1) + +/* This is Kerberos Version 5 */ +#define KVNO 5 + +/* Universal Tag Numbers */ +#define ASN1_BOOLEAN 1 +#define ASN1_INTEGER 2 +#define ASN1_BITSTRING 3 +#define ASN1_OCTETSTRING 4 +#define ASN1_NULL 5 +#define ASN1_OBJECTIDENTIFIER 6 +#define ASN1_ENUMERATED 10 +#define ASN1_UTF8STRING 12 +#define ASN1_SEQUENCE 16 +#define ASN1_SET 17 +#define ASN1_PRINTABLESTRING 19 +#define ASN1_IA5STRING 22 +#define ASN1_UTCTIME 23 +#define ASN1_GENERALTIME 24 +#define ASN1_GENERALSTRING 27 + +/* Kerberos Message Types */ +#define ASN1_KRB_AS_REQ 10 +#define ASN1_KRB_AS_REP 11 +#define ASN1_KRB_TGS_REQ 12 +#define ASN1_KRB_TGS_REP 13 +#define ASN1_KRB_AP_REQ 14 +#define ASN1_KRB_AP_REP 15 +#define ASN1_KRB_SAFE 20 +#define ASN1_KRB_PRIV 21 +#define ASN1_KRB_CRED 22 +#define ASN1_KRB_ERROR 30 + +#endif diff --git a/src/lib/krb5/asn.1/ldap_key_seq.c b/src/lib/krb5/asn.1/ldap_key_seq.c new file mode 100644 index 000000000000..74569d9e2c51 --- /dev/null +++ b/src/lib/krb5/asn.1/ldap_key_seq.c @@ -0,0 +1,127 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* ... copyright ... */ + +/* + * Novell key-format scheme: + * + * KrbKeySet ::= SEQUENCE { + * attribute-major-vno [0] UInt16, + * attribute-minor-vno [1] UInt16, + * kvno [2] UInt32, + * mkvno [3] UInt32 OPTIONAL, + * keys [4] SEQUENCE OF KrbKey, + * ... + * } + * + * KrbKey ::= SEQUENCE { + * salt [0] KrbSalt OPTIONAL, + * key [1] EncryptionKey, + * s2kparams [2] OCTET STRING OPTIONAL, + * ... + * } + * + * KrbSalt ::= SEQUENCE { + * type [0] Int32, + * salt [1] OCTET STRING OPTIONAL + * } + * + * EncryptionKey ::= SEQUENCE { + * keytype [0] Int32, + * keyvalue [1] OCTET STRING + * } + * + */ + +#include <k5-int.h> +#include <kdb.h> + +#include "krbasn1.h" +#include "asn1_encode.h" + +#ifdef ENABLE_LDAP + +/************************************************************************/ +/* Encode the Principal's keys */ +/************************************************************************/ + +/* + * Imports from asn1_k_encode.c. + * XXX Must be manually synchronized for now. + */ +IMPORT_TYPE(int32, krb5_int32); + +DEFINTTYPE(int16, krb5_int16); +DEFINTTYPE(uint16, krb5_ui_2); + +DEFCOUNTEDSTRINGTYPE(ui2_octetstring, unsigned char *, krb5_ui_2, + k5_asn1_encode_bytestring, k5_asn1_decode_bytestring, + ASN1_OCTETSTRING); + +static int +is_value_present(const void *p) +{ + const krb5_key_data *val = p; + return (val->key_data_length[1] != 0); +} +DEFCOUNTEDTYPE(krbsalt_salt, krb5_key_data, key_data_contents[1], + key_data_length[1], ui2_octetstring); +DEFOPTIONALTYPE(krbsalt_salt_if_present, is_value_present, NULL, krbsalt_salt); +DEFFIELD(krbsalt_0, krb5_key_data, key_data_type[1], 0, int16); +DEFCTAGGEDTYPE(krbsalt_1, 1, krbsalt_salt_if_present); +static const struct atype_info *krbsalt_fields[] = { + &k5_atype_krbsalt_0, &k5_atype_krbsalt_1 +}; +DEFSEQTYPE(krbsalt, krb5_key_data, krbsalt_fields); + +DEFFIELD(encryptionkey_0, krb5_key_data, key_data_type[0], 0, int16); +DEFCNFIELD(encryptionkey_1, krb5_key_data, key_data_contents[0], + key_data_length[0], 1, ui2_octetstring); +static const struct atype_info *encryptionkey_fields[] = { + &k5_atype_encryptionkey_0, &k5_atype_encryptionkey_1 +}; +DEFSEQTYPE(encryptionkey, krb5_key_data, encryptionkey_fields); + +static int +is_salt_present(const void *p) +{ + const krb5_key_data *val = p; + return val->key_data_ver > 1; +} +static void +no_salt(void *p) +{ + krb5_key_data *val = p; + val->key_data_ver = 1; +} +DEFOPTIONALTYPE(key_data_salt_if_present, is_salt_present, no_salt, krbsalt); +DEFCTAGGEDTYPE(key_data_0, 0, key_data_salt_if_present); +DEFCTAGGEDTYPE(key_data_1, 1, encryptionkey); +#if 0 /* We don't support this field currently. */ +DEFCTAGGEDTYPE(key_data_2, 2, s2kparams), +#endif +static const struct atype_info *key_data_fields[] = { + &k5_atype_key_data_0, &k5_atype_key_data_1 +}; +DEFSEQTYPE(key_data, krb5_key_data, key_data_fields); +DEFPTRTYPE(ptr_key_data, key_data); +DEFCOUNTEDSEQOFTYPE(cseqof_key_data, krb5_int16, ptr_key_data); + +DEFINT_IMMEDIATE(one, 1, ASN1_BAD_FORMAT); +DEFCTAGGEDTYPE(ldap_key_seq_0, 0, one); +DEFCTAGGEDTYPE(ldap_key_seq_1, 1, one); +DEFFIELD(ldap_key_seq_2, ldap_seqof_key_data, kvno, 2, uint16); +DEFFIELD(ldap_key_seq_3, ldap_seqof_key_data, mkvno, 3, int32); +DEFCNFIELD(ldap_key_seq_4, ldap_seqof_key_data, key_data, n_key_data, 4, + cseqof_key_data); +static const struct atype_info *ldap_key_seq_fields[] = { + &k5_atype_ldap_key_seq_0, &k5_atype_ldap_key_seq_1, + &k5_atype_ldap_key_seq_2, &k5_atype_ldap_key_seq_3, + &k5_atype_ldap_key_seq_4 +}; +DEFSEQTYPE(ldap_key_seq, ldap_seqof_key_data, ldap_key_seq_fields); + +/* Export a function to do the whole encoding. */ +MAKE_ENCODER(krb5int_ldap_encode_sequence_of_keys, ldap_key_seq); +MAKE_DECODER(krb5int_ldap_decode_sequence_of_keys, ldap_key_seq); + +#endif diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in new file mode 100644 index 000000000000..5ac870728d9d --- /dev/null +++ b/src/lib/krb5/ccache/Makefile.in @@ -0,0 +1,159 @@ +mydir=lib$(S)krb5$(S)ccache +BUILDTOP=$(REL)..$(S)..$(S).. +SUBDIRS = # ccapi +WINSUBDIRS = ccapi +##WIN32##DEFINES = -DUSE_CCAPI -DUSE_CCAPI_V3 + +LOCALINCLUDES = -I$(srcdir)$(S)ccapi -I$(srcdir) -I. $(WIN_INCLUDES) + +##DOS##WIN_INCLUDES = -I$(top_srcdir)\windows\lib + +##DOS##BUILDTOP = ..\..\.. +##DOS##PREFIXDIR=ccache +##DOS##OBJFILE=..\$(OUTPRE)$(PREFIXDIR).lst + +##WIN32##MSLSA_OBJ = $(OUTPRE)cc_mslsa.$(OBJEXT) +##WIN32##MSLSA_SRC = $(srcdir)/cc_mslsa.c + +##WIN32##!if 0 +KCMRPC_DEPS-osx = kcmrpc.h kcmrpc_types.h +KCMRPC_OBJ-osx = kcmrpc.o +KCMRPC_DEPS-no = # empty +KCMRPC_OBJ-no = # empty + +KCMRPC_DEPS = $(KCMRPC_DEPS-@OSX@) +KCMRPC_OBJ = $(KCMRPC_OBJ-@OSX@) +##WIN32##!endif + + +STLIBOBJS= \ + ccbase.o \ + cccopy.o \ + cccursor.o \ + ccdefault.o \ + ccdefops.o \ + ccmarshal.o \ + ccselect.o \ + ccselect_k5identity.o \ + ccselect_realm.o \ + cc_dir.o \ + cc_retr.o \ + cc_file.o \ + cc_kcm.o \ + cc_memory.o \ + cc_keyring.o \ + ccfns.o \ + ser_cc.o $(KCMRPC_OBJ) + +OBJS= $(OUTPRE)ccbase.$(OBJEXT) \ + $(OUTPRE)cccopy.$(OBJEXT) \ + $(OUTPRE)cccursor.$(OBJEXT) \ + $(OUTPRE)ccdefault.$(OBJEXT) \ + $(OUTPRE)ccdefops.$(OBJEXT) \ + $(OUTPRE)ccmarshal.$(OBJEXT) \ + $(OUTPRE)ccselect.$(OBJEXT) \ + $(OUTPRE)ccselect_k5identity.$(OBJEXT) \ + $(OUTPRE)ccselect_realm.$(OBJEXT) \ + $(OUTPRE)cc_dir.$(OBJEXT) \ + $(OUTPRE)cc_retr.$(OBJEXT) \ + $(OUTPRE)cc_file.$(OBJEXT) \ + $(OUTPRE)cc_kcm.$(OBJEXT) \ + $(OUTPRE)cc_memory.$(OBJEXT) \ + $(OUTPRE)cc_keyring.$(OBJEXT) \ + $(OUTPRE)ccfns.$(OBJEXT) \ + $(OUTPRE)ser_cc.$(OBJEXT) $(MSLSA_OBJ) + +SRCS= $(srcdir)/ccbase.c \ + $(srcdir)/cccopy.c \ + $(srcdir)/cccursor.c \ + $(srcdir)/ccdefault.c \ + $(srcdir)/ccdefops.c \ + $(srcdir)/ccmarshal.c \ + $(srcdir)/ccselect.c \ + $(srcdir)/ccselect_k5identity.c \ + $(srcdir)/ccselect_realm.c \ + $(srcdir)/cc_dir.c \ + $(srcdir)/cc_retr.c \ + $(srcdir)/cc_file.c \ + $(srcdir)/cc_kcm.c \ + $(srcdir)/cc_memory.c \ + $(srcdir)/cc_keyring.c \ + $(srcdir)/ccfns.c \ + $(srcdir)/ser_cc.c $(MSLSA_SRC) + +EXTRADEPSRCS= \ + $(srcdir)/t_cc.c \ + $(srcdir)/t_cccol.c \ + $(srcdir)/t_cccursor.c \ + $(srcdir)/t_marshal.c + +##DOS##OBJS=$(OBJS) $(OUTPRE)ccfns.$(OBJEXT) + +all-unix: all-libobjs + +all-windows: subdirs $(OBJFILE) + +##DOS##subdirs: ccapi\$(OUTPRE)file.lst + +##DOS##ccapi\$(OUTPRE)file.lst: +##DOS## cd ccapi +##DOS## @echo Making in krb5\ccache\ccapi +##DOS## $(MAKE) -$(MFLAGS) +##DOS## cd .. + +##DOS##$(OBJFILE): $(OBJS) ccapi\$(OUTPRE)file.lst +##DOS## $(RM) $(OBJFILE) +##WIN32## $(LIBECHO) -p $(PREFIXDIR)\ $(OUTPRE)*.obj \ +##WIN32## ccapi\$(OUTPRE)*.obj > $(OBJFILE) + +kcmrpc.h kcmrpc.c: kcmrpc.defs + mig -header kcmrpc.h -user kcmrpc.c -sheader /dev/null \ + -server /dev/null -I$(srcdir) $(srcdir)/kcmrpc.defs + +clean-unix:: clean-libobjs + +clean-windows:: + cd ccapi + @echo Making clean in krb5\ccache\ccapi + $(MAKE) -$(MFLAGS) clean + cd .. + @echo Making clean in krb5\ccache + $(RM) $(OBJFILE) + +T_CC_OBJS=t_cc.o + +t_cc: $(T_CC_OBJS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o t_cc $(T_CC_OBJS) $(KRB5_BASE_LIBS) + +T_CCCOL_OBJS = t_cccol.o +t_cccol: $(T_CCCOL_OBJS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ $(T_CCCOL_OBJS) $(KRB5_BASE_LIBS) + +T_CCCURSOR_OBJS = t_cccursor.o +t_cccursor: $(T_CCCURSOR_OBJS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ $(T_CCCURSOR_OBJS) $(KRB5_BASE_LIBS) + +T_MARSHAL_OBJS = t_marshal.o +t_marshal: $(T_MARSHAL_OBJS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ $(T_MARSHAL_OBJS) $(KRB5_BASE_LIBS) + +check-unix: t_cc t_marshal + $(RUN_TEST) ./t_cc + $(RUN_TEST) ./t_marshal testcache + +check-pytests: t_cccursor t_cccol + $(RUNPYTEST) $(srcdir)/t_cccol.py $(PYTESTFLAGS) + +clean-unix:: + $(RM) t_cc t_cc.o t_cccursor t_cccursor.o t_cccol t_cccol.o + $(RM) t_marshal t_marshal.o testcache kcmrpc.c kcmrpc.h + +depend: $(KCMRPC_DEPS) + +##WIN32##$(OUTPRE)cc_mslsa.$(OBJEXT): cc_mslsa.c $(top_srcdir)/include/k5-int.h $(BUILDTOP)/include/krb5.h $(COM_ERR_DEPS) + +cc_kcm.so cc_kcm.o: $(KCMRPC_DEPS) +kcmrpc.so kcmrpc.o: kcmrpc.h kcmrpc_types.h + +@libobj_frag@ + diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h new file mode 100644 index 000000000000..ee9b5e0e97a1 --- /dev/null +++ b/src/lib/krb5/ccache/cc-int.h @@ -0,0 +1,210 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/cc-int.h */ +/* + * Copyright 1990,1991 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +/* This file contains constant and function declarations used in the + * file-based credential cache routines. */ + +#ifndef __KRB5_CCACHE_H__ +#define __KRB5_CCACHE_H__ + +#include "k5-int.h" + +struct _krb5_ccache { + krb5_magic magic; + const struct _krb5_cc_ops *ops; + krb5_pointer data; +}; + +krb5_error_code +k5_cc_retrieve_cred_default(krb5_context, krb5_ccache, krb5_flags, + krb5_creds *, krb5_creds *); + +krb5_boolean +krb5int_cc_creds_match_request(krb5_context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds); + +int +krb5int_cc_initialize(void); + +void +krb5int_cc_finalize(void); + +/* + * Cursor for iterating over ccache types + */ +struct krb5_cc_typecursor; +typedef struct krb5_cc_typecursor *krb5_cc_typecursor; + +krb5_error_code +krb5int_cc_typecursor_new(krb5_context context, krb5_cc_typecursor *cursor); + +krb5_error_code +krb5int_cc_typecursor_next( + krb5_context context, + krb5_cc_typecursor cursor, + const struct _krb5_cc_ops **ops); + +krb5_error_code +krb5int_cc_typecursor_free( + krb5_context context, + krb5_cc_typecursor *cursor); + +/* reentrant mutex used by krb5_cc_* functions */ +typedef struct _k5_cc_mutex { + k5_mutex_t lock; + krb5_context owner; + krb5_int32 refcount; +} k5_cc_mutex; + +#define K5_CC_MUTEX_PARTIAL_INITIALIZER \ + { K5_MUTEX_PARTIAL_INITIALIZER, NULL, 0 } + +krb5_error_code +k5_cc_mutex_init(k5_cc_mutex *m); + +krb5_error_code +k5_cc_mutex_finish_init(k5_cc_mutex *m); + +#define k5_cc_mutex_destroy(M) \ + k5_mutex_destroy(&(M)->lock); + +void +k5_cc_mutex_assert_locked(krb5_context context, k5_cc_mutex *m); + +void +k5_cc_mutex_assert_unlocked(krb5_context context, k5_cc_mutex *m); + +void +k5_cc_mutex_lock(krb5_context context, k5_cc_mutex *m); + +void +k5_cc_mutex_unlock(krb5_context context, k5_cc_mutex *m); + +extern k5_cc_mutex krb5int_mcc_mutex; +extern k5_cc_mutex krb5int_krcc_mutex; +extern k5_cc_mutex krb5int_cc_file_mutex; + +#ifdef USE_CCAPI_V3 +extern krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_lock +(krb5_context context); + +extern krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_unlock +(krb5_context context); +#endif + +void +k5_cc_mutex_force_unlock(k5_cc_mutex *m); + +void +k5_cccol_force_unlock(void); + +krb5_error_code +krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id); + +krb5_error_code +ccselect_realm_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + +krb5_error_code +ccselect_k5identity_initvt(krb5_context context, int maj_ver, int min_ver, + krb5_plugin_vtable vtable); + +krb5_error_code +k5_unmarshal_cred(const unsigned char *data, size_t len, int version, + krb5_creds *creds); + +krb5_error_code +k5_unmarshal_princ(const unsigned char *data, size_t len, int version, + krb5_principal *princ_out); + +void +k5_marshal_cred(struct k5buf *buf, int version, krb5_creds *creds); + +void +k5_marshal_mcred(struct k5buf *buf, krb5_creds *mcred); + +void +k5_marshal_princ(struct k5buf *buf, int version, krb5_principal princ); + +/* + * Per-type ccache cursor. + */ +struct krb5_cc_ptcursor_s { + const struct _krb5_cc_ops *ops; + krb5_pointer data; +}; +typedef struct krb5_cc_ptcursor_s *krb5_cc_ptcursor; + +struct _krb5_cc_ops { + krb5_magic magic; + char *prefix; + const char * (KRB5_CALLCONV *get_name)(krb5_context, krb5_ccache); + krb5_error_code (KRB5_CALLCONV *resolve)(krb5_context, krb5_ccache *, + const char *); + krb5_error_code (KRB5_CALLCONV *gen_new)(krb5_context, krb5_ccache *); + krb5_error_code (KRB5_CALLCONV *init)(krb5_context, krb5_ccache, + krb5_principal); + krb5_error_code (KRB5_CALLCONV *destroy)(krb5_context, krb5_ccache); + krb5_error_code (KRB5_CALLCONV *close)(krb5_context, krb5_ccache); + krb5_error_code (KRB5_CALLCONV *store)(krb5_context, krb5_ccache, + krb5_creds *); + krb5_error_code (KRB5_CALLCONV *retrieve)(krb5_context, krb5_ccache, + krb5_flags, krb5_creds *, + krb5_creds *); + krb5_error_code (KRB5_CALLCONV *get_princ)(krb5_context, krb5_ccache, + krb5_principal *); + krb5_error_code (KRB5_CALLCONV *get_first)(krb5_context, krb5_ccache, + krb5_cc_cursor *); + krb5_error_code (KRB5_CALLCONV *get_next)(krb5_context, krb5_ccache, + krb5_cc_cursor *, krb5_creds *); + krb5_error_code (KRB5_CALLCONV *end_get)(krb5_context, krb5_ccache, + krb5_cc_cursor *); + krb5_error_code (KRB5_CALLCONV *remove_cred)(krb5_context, krb5_ccache, + krb5_flags, krb5_creds *); + krb5_error_code (KRB5_CALLCONV *set_flags)(krb5_context, krb5_ccache, + krb5_flags); + krb5_error_code (KRB5_CALLCONV *get_flags)(krb5_context, krb5_ccache, + krb5_flags *); + krb5_error_code (KRB5_CALLCONV *ptcursor_new)(krb5_context, + krb5_cc_ptcursor *); + krb5_error_code (KRB5_CALLCONV *ptcursor_next)(krb5_context, + krb5_cc_ptcursor, + krb5_ccache *); + krb5_error_code (KRB5_CALLCONV *ptcursor_free)(krb5_context, + krb5_cc_ptcursor *); + krb5_error_code (KRB5_CALLCONV *move)(krb5_context, krb5_ccache, + krb5_ccache); + krb5_error_code (KRB5_CALLCONV *lastchange)(krb5_context, + krb5_ccache, krb5_timestamp *); + krb5_error_code (KRB5_CALLCONV *wasdefault)(krb5_context, krb5_ccache, + krb5_timestamp *); + krb5_error_code (KRB5_CALLCONV *lock)(krb5_context, krb5_ccache); + krb5_error_code (KRB5_CALLCONV *unlock)(krb5_context, krb5_ccache); + krb5_error_code (KRB5_CALLCONV *switch_to)(krb5_context, krb5_ccache); +}; + +extern const krb5_cc_ops *krb5_cc_dfl_ops; + +#endif /* __KRB5_CCACHE_H__ */ diff --git a/src/lib/krb5/ccache/cc_dir.c b/src/lib/krb5/ccache/cc_dir.c new file mode 100644 index 000000000000..bba64e516f96 --- /dev/null +++ b/src/lib/krb5/ccache/cc_dir.c @@ -0,0 +1,772 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/cc_dir.c - Directory-based credential cache collection */ +/* + * Copyright (C) 2011 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +/* + * This credential cache type represents a set of file-based caches with a + * switchable primary cache. An alternate form of the type represents a + * subsidiary file cache within the directory. + * + * A cache name of the form DIR:dirname identifies a directory containing the + * cache set. Resolving a name of this form results in dirname's primary + * cache. If a context's default cache is of this form, the global cache + * collection will contain dirname's cache set, and new unique caches of type + * DIR will be created within dirname. + * + * A cache name of the form DIR::filepath represents a single cache within the + * directory. Switching to a ccache of this type causes the directory's + * primary cache to be set to the named cache. + * + * Within the directory, cache names begin with 'tkt'. The file "primary" + * contains a single line naming the primary cache. The directory must already + * exist when the DIR ccache is resolved, but the primary file will be created + * automatically if it does not exist. + */ + +#include "k5-int.h" +#include "cc-int.h" + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +/* This is Unix-only for now. To work on Windows, we will need opendir/readdir + * replacements and possibly more flexible newline handling. */ +#ifndef _WIN32 + +#include <dirent.h> + +extern const krb5_cc_ops krb5_dcc_ops; +extern const krb5_cc_ops krb5_fcc_ops; + +/* Fields are not modified after creation, so no lock is necessary. */ +typedef struct dcc_data_st { + char *residual; /* dirname or :filename */ + krb5_ccache fcc; /* File cache for actual cache ops */ +} dcc_data; + +static inline krb5_boolean +filename_is_cache(const char *filename) +{ + return (strncmp(filename, "tkt", 3) == 0); +} + +/* Compose the pathname of the primary file within a cache directory. */ +static inline krb5_error_code +primary_pathname(const char *dirname, char **path_out) +{ + return k5_path_join(dirname, "primary", path_out); +} + +/* Compose a residual string for a subsidiary path with the specified directory + * name and filename. */ +static krb5_error_code +subsidiary_residual(const char *dirname, const char *filename, char **out) +{ + krb5_error_code ret; + char *path, *residual; + + *out = NULL; + ret = k5_path_join(dirname, filename, &path); + if (ret) + return ret; + ret = asprintf(&residual, ":%s", path); + free(path); + if (ret < 0) + return ENOMEM; + *out = residual; + return 0; +} + +static inline krb5_error_code +split_path(krb5_context context, const char *path, char **dirname_out, + char **filename_out) +{ + krb5_error_code ret; + char *dirname, *filename; + + *dirname_out = NULL; + *filename_out = NULL; + ret = k5_path_split(path, &dirname, &filename); + if (ret) + return ret; + + if (*dirname == '\0') { + ret = KRB5_CC_BADNAME; + k5_setmsg(context, ret, + _("Subsidiary cache path %s has no parent directory"), path); + goto error; + } + if (!filename_is_cache(filename)) { + ret = KRB5_CC_BADNAME; + k5_setmsg(context, ret, + _("Subsidiary cache path %s filename does not begin with " + "\"tkt\""), path); + goto error; + } + + *dirname_out = dirname; + *filename_out = filename; + return 0; + +error: + free(dirname); + free(filename); + return ret; +} + +/* Read the primary file and compose the residual string for the primary + * subsidiary cache file. */ +static krb5_error_code +read_primary_file(krb5_context context, const char *primary_path, + const char *dirname, char **residual_out) +{ + FILE *fp; + char buf[64], *ret; + size_t len; + + *residual_out = NULL; + + /* Open the file and read its first line. */ + fp = fopen(primary_path, "r"); + if (fp == NULL) + return ENOENT; + ret = fgets(buf, sizeof(buf), fp); + fclose(fp); + if (ret == NULL) + return KRB5_CC_IO; + len = strlen(buf); + + /* Check if line is too long, doesn't look like a subsidiary cache + * filename, or isn't a single-component filename. */ + if (buf[len - 1] != '\n' || !filename_is_cache(buf) || + strchr(buf, '/') || strchr(buf, '\\')) { + k5_setmsg(context, KRB5_CC_FORMAT, _("%s contains invalid filename"), + primary_path); + return KRB5_CC_FORMAT; + } + buf[len - 1] = '\0'; + + return subsidiary_residual(dirname, buf, residual_out); +} + +/* Create or update the primary file with a line containing contents. */ +static krb5_error_code +write_primary_file(const char *primary_path, const char *contents) +{ + krb5_error_code ret = KRB5_CC_IO; + char *newpath = NULL; + FILE *fp = NULL; + int fd = -1, status; + + if (asprintf(&newpath, "%s.XXXXXX", primary_path) < 0) + return ENOMEM; + fd = mkstemp(newpath); + if (fd < 0) + goto cleanup; +#ifdef HAVE_CHMOD + chmod(newpath, S_IRUSR | S_IWUSR); +#endif + fp = fdopen(fd, "w"); + if (fp == NULL) + goto cleanup; + fd = -1; + if (fprintf(fp, "%s\n", contents) < 0) + goto cleanup; + status = fclose(fp); + fp = NULL; + if (status == EOF) + goto cleanup; + fp = NULL; + if (rename(newpath, primary_path) != 0) + goto cleanup; + ret = 0; + +cleanup: + if (fd >= 0) + close(fd); + if (fp != NULL) + fclose(fp); + free(newpath); + return ret; +} + +/* Verify or create a cache directory path. */ +static krb5_error_code +verify_dir(krb5_context context, const char *dirname) +{ + struct stat st; + + if (stat(dirname, &st) < 0) { + if (errno == ENOENT && mkdir(dirname, S_IRWXU) == 0) + return 0; + k5_setmsg(context, KRB5_FCC_NOFILE, + _("Credential cache directory %s does not exist"), + dirname); + return KRB5_FCC_NOFILE; + } + if (!S_ISDIR(st.st_mode)) { + k5_setmsg(context, KRB5_CC_FORMAT, + _("Credential cache directory %s exists but is not a " + "directory"), dirname); + return KRB5_CC_FORMAT; + } + return 0; +} + +/* + * If the default ccache name for context is a directory collection, set + * *dirname_out to the directory name for that collection. Otherwise set + * *dirname_out to NULL. + */ +static krb5_error_code +get_context_default_dir(krb5_context context, char **dirname_out) +{ + const char *defname; + char *dirname; + + *dirname_out = NULL; + defname = krb5_cc_default_name(context); + if (defname == NULL) + return 0; + if (strncmp(defname, "DIR:", 4) != 0 || + defname[4] == ':' || defname[4] == '\0') + return 0; + dirname = strdup(defname + 4); + if (dirname == NULL) + return ENOMEM; + *dirname_out = dirname; + return 0; +} + +/* + * If the default ccache name for context is a subsidiary file in a directory + * collection, set *subsidiary_out to the residual value. Otherwise set + * *subsidiary_out to NULL. + */ +static krb5_error_code +get_context_subsidiary_file(krb5_context context, char **subsidiary_out) +{ + const char *defname; + char *residual; + + *subsidiary_out = NULL; + defname = krb5_cc_default_name(context); + if (defname == NULL || strncmp(defname, "DIR::", 5) != 0) + return 0; + residual = strdup(defname + 4); + if (residual == NULL) + return ENOMEM; + *subsidiary_out = residual; + return 0; +} + +static const char * KRB5_CALLCONV +dcc_get_name(krb5_context context, krb5_ccache cache) +{ + dcc_data *data = cache->data; + + return data->residual; +} + +/* Construct a cache object given a residual string and file ccache. Take + * ownership of fcc on success. */ +static krb5_error_code +make_cache(const char *residual, krb5_ccache fcc, krb5_ccache *cache_out) +{ + krb5_ccache cache = NULL; + dcc_data *data = NULL; + char *residual_copy = NULL; + + cache = malloc(sizeof(*cache)); + if (cache == NULL) + goto oom; + data = malloc(sizeof(*data)); + if (data == NULL) + goto oom; + residual_copy = strdup(residual); + if (residual_copy == NULL) + goto oom; + + data->residual = residual_copy; + data->fcc = fcc; + cache->ops = &krb5_dcc_ops; + cache->data = data; + cache->magic = KV5M_CCACHE; + *cache_out = cache; + return 0; + +oom: + free(cache); + free(data); + free(residual_copy); + return ENOMEM; +} + +static krb5_error_code KRB5_CALLCONV +dcc_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual) +{ + krb5_error_code ret; + krb5_ccache fcc; + char *primary_path = NULL, *sresidual = NULL, *dirname, *filename; + + *cache_out = NULL; + + if (*residual == ':') { + /* This is a subsidiary cache within the directory. */ + ret = split_path(context, residual + 1, &dirname, &filename); + if (ret) + return ret; + + ret = verify_dir(context, dirname); + free(dirname); + free(filename); + if (ret) + return ret; + } else { + /* This is the directory itself; resolve to the primary cache. */ + ret = verify_dir(context, residual); + if (ret) + return ret; + + ret = primary_pathname(residual, &primary_path); + if (ret) + goto cleanup; + + ret = read_primary_file(context, primary_path, residual, &sresidual); + if (ret == ENOENT) { + /* Create an initial primary file. */ + ret = write_primary_file(primary_path, "tkt"); + if (ret) + goto cleanup; + ret = subsidiary_residual(residual, "tkt", &sresidual); + } + if (ret) + goto cleanup; + residual = sresidual; + } + + ret = krb5_fcc_ops.resolve(context, &fcc, residual + 1); + if (ret) + goto cleanup; + ret = make_cache(residual, fcc, cache_out); + if (ret) + krb5_fcc_ops.close(context, fcc); + +cleanup: + free(primary_path); + free(sresidual); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +dcc_gen_new(krb5_context context, krb5_ccache *cache_out) +{ + krb5_error_code ret; + char *dirname = NULL, *template = NULL, *residual = NULL; + krb5_ccache fcc = NULL; + + *cache_out = NULL; + ret = get_context_default_dir(context, &dirname); + if (ret) + return ret; + if (dirname == NULL) { + k5_setmsg(context, KRB5_DCC_CANNOT_CREATE, + _("Can't create new subsidiary cache because default cache " + "is not a directory collection")); + return KRB5_DCC_CANNOT_CREATE; + } + ret = verify_dir(context, dirname); + if (ret) + goto cleanup; + ret = k5_path_join(dirname, "tktXXXXXX", &template); + if (ret) + goto cleanup; + ret = krb5int_fcc_new_unique(context, template, &fcc); + if (ret) + goto cleanup; + if (asprintf(&residual, ":%s", template) < 0) { + ret = ENOMEM; + goto cleanup; + } + ret = make_cache(residual, fcc, cache_out); + if (ret) + goto cleanup; + fcc = NULL; + +cleanup: + if (fcc != NULL) + krb5_fcc_ops.destroy(context, fcc); + free(dirname); + free(template); + free(residual); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +dcc_init(krb5_context context, krb5_ccache cache, krb5_principal princ) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.init(context, data->fcc, princ); +} + +static krb5_error_code KRB5_CALLCONV +dcc_destroy(krb5_context context, krb5_ccache cache) +{ + dcc_data *data = cache->data; + krb5_error_code ret; + + ret = krb5_fcc_ops.destroy(context, data->fcc); + free(data->residual); + free(data); + free(cache); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +dcc_close(krb5_context context, krb5_ccache cache) +{ + dcc_data *data = cache->data; + krb5_error_code ret; + + ret = krb5_fcc_ops.close(context, data->fcc); + free(data->residual); + free(data); + free(cache); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +dcc_store(krb5_context context, krb5_ccache cache, krb5_creds *creds) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.store(context, data->fcc, creds); +} + +static krb5_error_code KRB5_CALLCONV +dcc_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags, + krb5_creds *mcreds, krb5_creds *creds) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.retrieve(context, data->fcc, flags, mcreds, + creds); +} + +static krb5_error_code KRB5_CALLCONV +dcc_get_princ(krb5_context context, krb5_ccache cache, + krb5_principal *princ_out) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.get_princ(context, data->fcc, princ_out); +} + +static krb5_error_code KRB5_CALLCONV +dcc_get_first(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.get_first(context, data->fcc, cursor); +} + +static krb5_error_code KRB5_CALLCONV +dcc_get_next(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor, + krb5_creds *creds) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.get_next(context, data->fcc, cursor, creds); +} + +static krb5_error_code KRB5_CALLCONV +dcc_end_get(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.end_get(context, data->fcc, cursor); +} + +static krb5_error_code KRB5_CALLCONV +dcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags, + krb5_creds *creds) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.remove_cred(context, data->fcc, flags, creds); +} + +static krb5_error_code KRB5_CALLCONV +dcc_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.set_flags(context, data->fcc, flags); +} + +static krb5_error_code KRB5_CALLCONV +dcc_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.get_flags(context, data->fcc, flags_out); +} + +struct dcc_ptcursor_data { + char *primary; + char *dirname; + DIR *dir; + krb5_boolean first; +}; + +/* Construct a cursor, taking ownership of dirname, primary, and dir on + * success. */ +static krb5_error_code +make_cursor(char *dirname, char *primary, DIR *dir, + krb5_cc_ptcursor *cursor_out) +{ + krb5_cc_ptcursor cursor; + struct dcc_ptcursor_data *data; + + *cursor_out = NULL; + + data = malloc(sizeof(*data)); + if (data == NULL) + return ENOMEM; + cursor = malloc(sizeof(*cursor)); + if (cursor == NULL) { + free(data); + return ENOMEM; + } + + data->dirname = dirname; + data->primary = primary; + data->dir = dir; + data->first = TRUE; + cursor->ops = &krb5_dcc_ops; + cursor->data = data; + *cursor_out = cursor; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +dcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out) +{ + krb5_error_code ret; + char *dirname = NULL, *primary_path = NULL, *primary = NULL; + DIR *dir = NULL; + + *cursor_out = NULL; + + /* If the default cache is a subsidiary file, make a cursor with the + * specified file as the primary but with no directory collection. */ + ret = get_context_subsidiary_file(context, &primary); + if (ret) + goto cleanup; + if (primary != NULL) { + ret = make_cursor(NULL, primary, NULL, cursor_out); + if (ret) + free(primary); + return ret; + } + + /* Open the directory for the context's default cache. */ + ret = get_context_default_dir(context, &dirname); + if (ret || dirname == NULL) + goto cleanup; + dir = opendir(dirname); + if (dir == NULL) + goto cleanup; + + /* Fetch the primary cache name if possible. */ + ret = primary_pathname(dirname, &primary_path); + if (ret) + goto cleanup; + ret = read_primary_file(context, primary_path, dirname, &primary); + if (ret) + krb5_clear_error_message(context); + + ret = make_cursor(dirname, primary, dir, cursor_out); + if (ret) + goto cleanup; + dirname = primary = NULL; + dir = NULL; + +cleanup: + free(dirname); + free(primary_path); + free(primary); + if (dir) + closedir(dir); + /* Return an empty cursor if we fail for any reason. */ + if (*cursor_out == NULL) + return make_cursor(NULL, NULL, NULL, cursor_out); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +dcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, + krb5_ccache *cache_out) +{ + struct dcc_ptcursor_data *data = cursor->data; + struct dirent *ent; + char *residual; + krb5_error_code ret; + struct stat sb; + + *cache_out = NULL; + + /* Return the primary or specified subsidiary cache if we haven't yet. */ + if (data->first) { + data->first = FALSE; + if (data->primary != NULL && stat(data->primary + 1, &sb) == 0) + return dcc_resolve(context, cache_out, data->primary); + } + + if (data->dir == NULL) /* No directory collection */ + return 0; + + /* Look for the next filename of the correct form, without repeating the + * primary cache. */ + while ((ent = readdir(data->dir)) != NULL) { + if (!filename_is_cache(ent->d_name)) + continue; + ret = subsidiary_residual(data->dirname, ent->d_name, &residual); + if (ret) + return ret; + if (data->primary != NULL && strcmp(residual, data->primary) == 0) { + free(residual); + continue; + } + ret = dcc_resolve(context, cache_out, residual); + free(residual); + return ret; + } + + /* We exhausted the directory without finding a cache to yield. */ + closedir(data->dir); + data->dir = NULL; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +dcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor) +{ + struct dcc_ptcursor_data *data = (*cursor)->data; + + if (data->dir) + closedir(data->dir); + free(data->dirname); + free(data->primary); + free(data); + free(*cursor); + *cursor = NULL; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +dcc_lastchange(krb5_context context, krb5_ccache cache, + krb5_timestamp *time_out) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.lastchange(context, data->fcc, time_out); +} + +static krb5_error_code KRB5_CALLCONV +dcc_lock(krb5_context context, krb5_ccache cache) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.lock(context, data->fcc); +} + +static krb5_error_code KRB5_CALLCONV +dcc_unlock(krb5_context context, krb5_ccache cache) +{ + dcc_data *data = cache->data; + + return krb5_fcc_ops.unlock(context, data->fcc); +} + +static krb5_error_code KRB5_CALLCONV +dcc_switch_to(krb5_context context, krb5_ccache cache) +{ + dcc_data *data = cache->data; + char *primary_path = NULL, *dirname = NULL, *filename = NULL; + krb5_error_code ret; + + ret = split_path(context, data->residual + 1, &dirname, &filename); + if (ret) + return ret; + + ret = primary_pathname(dirname, &primary_path); + if (ret) + goto cleanup; + + ret = write_primary_file(primary_path, filename); + +cleanup: + free(primary_path); + free(dirname); + free(filename); + return ret; +} + +const krb5_cc_ops krb5_dcc_ops = { + 0, + "DIR", + dcc_get_name, + dcc_resolve, + dcc_gen_new, + dcc_init, + dcc_destroy, + dcc_close, + dcc_store, + dcc_retrieve, + dcc_get_princ, + dcc_get_first, + dcc_get_next, + dcc_end_get, + dcc_remove_cred, + dcc_set_flags, + dcc_get_flags, + dcc_ptcursor_new, + dcc_ptcursor_next, + dcc_ptcursor_free, + NULL, /* move */ + dcc_lastchange, + NULL, /* wasdefault */ + dcc_lock, + dcc_unlock, + dcc_switch_to, +}; + +#endif /* not _WIN32 */ diff --git a/src/lib/krb5/ccache/cc_file.c b/src/lib/krb5/ccache/cc_file.c new file mode 100644 index 000000000000..6789c09e189c --- /dev/null +++ b/src/lib/krb5/ccache/cc_file.c @@ -0,0 +1,1296 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/cc_file.c - File-based credential cache */ +/* + * Copyright 1990,1991,1992,1993,1994,2000,2004,2007 Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Original stdio support copyright 1995 by Cygnus Support. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +/* + * A psuedo-BNF grammar for the FILE credential cache format is: + * + * file ::= + * version (2 bytes; 05 01 for version 1 through 05 04 for version 4) + * header [not present before version 4] + * principal + * credential1 + * credential2 + * ... + * + * header ::= + * headerlen (16 bits) + * header1tag (16 bits) + * header1len (16 bits) + * header1val (header1len bytes) + * + * See ccmarshal.c for the principal and credential formats. Although versions + * 1 and 2 of the FILE format use native byte order for integer representations + * within principals and credentials, the integer fields in the grammar above + * are always in big-endian byte order. + * + * Only one header tag is currently defined. The tag value is 1 + * (FCC_TAG_DELTATIME), and its contents are two 32-bit integers giving the + * seconds and microseconds of the time offset of the KDC relative to the + * client. + * + * Each of the file ccache functions opens and closes the file whenever it + * needs to access it. + * + * This module depends on UNIX-like file descriptors, and UNIX-like behavior + * from the functions: open, close, read, write, lseek. + */ + +#include "k5-int.h" +#include "cc-int.h" + +#include <stdio.h> +#include <errno.h> + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +extern const krb5_cc_ops krb5_cc_file_ops; + +krb5_error_code krb5_change_cache(void); + +static krb5_error_code interpret_errno(krb5_context, int); + +/* The cache format version is a positive integer, represented in the cache + * file as a two-byte big endian number with 0x0500 added to it. */ +#define FVNO_BASE 0x0500 + +#define FCC_TAG_DELTATIME 1 + +#ifndef TKT_ROOT +#ifdef MSDOS_FILESYSTEM +#define TKT_ROOT "\\tkt" +#else +#define TKT_ROOT "/tmp/tkt" +#endif +#endif + +typedef struct fcc_data_st { + k5_cc_mutex lock; + char *filename; +} fcc_data; + +/* Iterator over file caches. */ +struct krb5_fcc_ptcursor_data { + krb5_boolean first; +}; + +/* Iterator over a cache. */ +typedef struct _krb5_fcc_cursor { + FILE *fp; + int version; +} krb5_fcc_cursor; + +k5_cc_mutex krb5int_cc_file_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER; + +/* Add fname to the standard error message for ret. */ +static krb5_error_code +set_errmsg_filename(krb5_context context, krb5_error_code ret, + const char *fname) +{ + if (!ret) + return 0; + k5_setmsg(context, ret, "%s (filename: %s)", error_message(ret), fname); + return ret; +} + +/* Get the size of the cache file as a size_t, or SIZE_MAX if it is too + * large to be represented as a size_t. */ +static krb5_error_code +get_size(krb5_context context, FILE *fp, size_t *size_out) +{ + struct stat sb; + + *size_out = 0; + if (fstat(fileno(fp), &sb) == -1) + return interpret_errno(context, errno); + if (sizeof(off_t) > sizeof(size_t) && sb.st_size > (off_t)SIZE_MAX) + *size_out = SIZE_MAX; + else + *size_out = sb.st_size; + return 0; +} + +/* Read len bytes from fp, storing them in buf. Return KRB5_CC_END + * if not enough bytes are present. */ +static krb5_error_code +read_bytes(krb5_context context, FILE *fp, void *buf, size_t len) +{ + size_t nread; + + nread = fread(buf, 1, len, fp); + if (nread < len) + return ferror(fp) ? errno : KRB5_CC_END; + return 0; +} + +/* Load four bytes from the cache file. Add them to buf (if set) and return + * their value as a 32-bit unsigned integer according to the file format. */ +static krb5_error_code +read32(krb5_context context, FILE *fp, int version, struct k5buf *buf, + uint32_t *out) +{ + krb5_error_code ret; + char bytes[4]; + + ret = read_bytes(context, fp, bytes, 4); + if (ret) + return ret; + if (buf != NULL) + k5_buf_add_len(buf, bytes, 4); + *out = (version < 3) ? load_32_n(bytes) : load_32_be(bytes); + return 0; +} + +/* Load two bytes from the cache file and return their value as a 16-bit + * unsigned integer according to the file format. */ +static krb5_error_code +read16(krb5_context context, FILE *fp, int version, uint16_t *out) +{ + krb5_error_code ret; + char bytes[2]; + + ret = read_bytes(context, fp, bytes, 2); + if (ret) + return ret; + *out = (version < 3) ? load_16_n(bytes) : load_16_be(bytes); + return 0; +} + +/* Read len bytes from the cache file and add them to buf. */ +static krb5_error_code +load_bytes(krb5_context context, FILE *fp, size_t len, struct k5buf *buf) +{ + void *ptr; + + ptr = k5_buf_get_space(buf, len); + return (ptr == NULL) ? KRB5_CC_NOMEM : read_bytes(context, fp, ptr, len); +} + +/* Load a 32-bit length and data from the cache file into buf, but not more + * than maxsize bytes. */ +static krb5_error_code +load_data(krb5_context context, FILE *fp, int version, size_t maxsize, + struct k5buf *buf) +{ + krb5_error_code ret; + uint32_t count; + + ret = read32(context, fp, version, buf, &count); + if (ret) + return ret; + if (count > maxsize) + return KRB5_CC_FORMAT; + return load_bytes(context, fp, count, buf); +} + +/* Load a marshalled principal from the cache file into buf, without + * unmarshalling it. */ +static krb5_error_code +load_principal(krb5_context context, FILE *fp, int version, size_t maxsize, + struct k5buf *buf) +{ + krb5_error_code ret; + uint32_t count; + + if (version > 1) { + ret = load_bytes(context, fp, 4, buf); + if (ret) + return ret; + } + ret = read32(context, fp, version, buf, &count); + if (ret) + return ret; + /* Add one for the realm (except in version 1 which already counts it). */ + if (version != 1) + count++; + while (count-- > 0) { + ret = load_data(context, fp, version, maxsize, buf); + if (ret) + return ret; + } + return 0; +} + +/* Load a marshalled credential from the cache file into buf, without + * unmarshalling it. */ +static krb5_error_code +load_cred(krb5_context context, FILE *fp, int version, size_t maxsize, + struct k5buf *buf) +{ + krb5_error_code ret; + uint32_t count, i; + + /* client and server */ + ret = load_principal(context, fp, version, maxsize, buf); + if (ret) + return ret; + ret = load_principal(context, fp, version, maxsize, buf); + if (ret) + return ret; + + /* keyblock (enctype, enctype again for version 3, length, value) */ + ret = load_bytes(context, fp, (version == 3) ? 4 : 2, buf); + if (ret) + return ret; + ret = load_data(context, fp, version, maxsize, buf); + if (ret) + return ret; + + /* times (4*4 bytes), is_skey (1 byte), ticket flags (4 bytes) */ + ret = load_bytes(context, fp, 4 * 4 + 1 + 4, buf); + if (ret) + return ret; + + /* addresses and authdata, both lists of {type, length, data} */ + for (i = 0; i < 2; i++) { + ret = read32(context, fp, version, buf, &count); + if (ret) + return ret; + while (count-- > 0) { + ret = load_bytes(context, fp, 2, buf); + if (ret) + return ret; + ret = load_data(context, fp, version, maxsize, buf); + if (ret) + return ret; + } + } + + /* ticket and second_ticket */ + ret = load_data(context, fp, version, maxsize, buf); + if (ret) + return ret; + return load_data(context, fp, version, maxsize, buf); +} + +static krb5_error_code +read_principal(krb5_context context, FILE *fp, int version, + krb5_principal *princ) +{ + krb5_error_code ret; + struct k5buf buf; + size_t maxsize; + + *princ = NULL; + k5_buf_init_dynamic(&buf); + + /* Read the principal representation into memory. */ + ret = get_size(context, fp, &maxsize); + if (ret) + goto cleanup; + ret = load_principal(context, fp, version, maxsize, &buf); + if (ret) + goto cleanup; + ret = k5_buf_status(&buf); + if (ret) + goto cleanup; + + /* Unmarshal it from buf into princ. */ + ret = k5_unmarshal_princ(buf.data, buf.len, version, princ); + +cleanup: + k5_buf_free(&buf); + return ret; +} + +/* + * Open and lock an existing cache file. If writable is true, open it for + * writing (with O_APPEND) and get an exclusive lock; otherwise open it for + * reading and get a shared lock. + */ +static krb5_error_code +open_cache_file(krb5_context context, const char *filename, + krb5_boolean writable, FILE **fp_out) +{ + krb5_error_code ret; + int fd, flags, lockmode; + FILE *fp; + + *fp_out = NULL; + + flags = writable ? (O_RDWR | O_APPEND) : O_RDONLY; + fd = open(filename, flags | O_BINARY | O_CLOEXEC, 0600); + if (fd == -1) + return interpret_errno(context, errno); + set_cloexec_fd(fd); + + lockmode = writable ? KRB5_LOCKMODE_EXCLUSIVE : KRB5_LOCKMODE_SHARED; + ret = krb5_lock_file(context, fd, lockmode); + if (ret) { + (void)close(fd); + return ret; + } + + fp = fdopen(fd, writable ? "r+b" : "rb"); + if (fp == NULL) { + (void)krb5_unlock_file(context, fd); + (void)close(fd); + return KRB5_CC_NOMEM; + } + + *fp_out = fp; + return 0; +} + +/* Unlock and close the cache file. Do nothing if fp is NULL. */ +static krb5_error_code +close_cache_file(krb5_context context, FILE *fp) +{ + int st; + krb5_error_code ret; + + if (fp == NULL) + return 0; + ret = krb5_unlock_file(context, fileno(fp)); + st = fclose(fp); + if (ret) + return ret; + return st ? interpret_errno(context, errno) : 0; +} + +/* Read the cache file header. Set time offsets in context from the header if + * appropriate. Set *version_out to the cache file format version. */ +static krb5_error_code +read_header(krb5_context context, FILE *fp, int *version_out) +{ + krb5_error_code ret; + krb5_os_context os_ctx = &context->os_context; + uint16_t fields_len, tag, flen; + uint32_t time_offset, usec_offset; + char i16buf[2]; + int version; + + *version_out = 0; + + /* Get the file format version. */ + ret = read_bytes(context, fp, i16buf, 2); + if (ret) + return KRB5_CC_FORMAT; + version = load_16_be(i16buf) - FVNO_BASE; + if (version < 1 || version > 4) + return KRB5_CCACHE_BADVNO; + *version_out = version; + + /* Tagged header fields begin with version 4. */ + if (version < 4) + return 0; + + if (read16(context, fp, version, &fields_len)) + return KRB5_CC_FORMAT; + while (fields_len) { + if (fields_len < 4 || read16(context, fp, version, &tag) || + read16(context, fp, version, &flen) || flen > fields_len - 4) + return KRB5_CC_FORMAT; + + switch (tag) { + case FCC_TAG_DELTATIME: + if (flen != 8 || + read32(context, fp, version, NULL, &time_offset) || + read32(context, fp, version, NULL, &usec_offset)) + return KRB5_CC_FORMAT; + + if (!(context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) || + (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) + break; + + os_ctx->time_offset = time_offset; + os_ctx->usec_offset = usec_offset; + os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) | + KRB5_OS_TOFFSET_VALID); + break; + + default: + if (flen && fseek(fp, flen, SEEK_CUR) != 0) + return KRB5_CC_FORMAT; + break; + } + fields_len -= (4 + flen); + } + return 0; +} + +/* Create or overwrite the cache file with a header and default principal. */ +static krb5_error_code KRB5_CALLCONV +fcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ) +{ + krb5_error_code ret; + krb5_os_context os_ctx = &context->os_context; + fcc_data *data = id->data; + char i16buf[2], i32buf[4]; + uint16_t fields_len; + ssize_t nwritten; + int st, flags, version, fd = -1; + struct k5buf buf = EMPTY_K5BUF; + krb5_boolean file_locked = FALSE; + + k5_cc_mutex_lock(context, &data->lock); + + unlink(data->filename); + flags = O_CREAT | O_EXCL | O_RDWR | O_BINARY | O_CLOEXEC; + fd = open(data->filename, flags, 0600); + if (fd == -1) { + ret = interpret_errno(context, errno); + goto cleanup; + } + set_cloexec_fd(fd); + +#if defined(HAVE_FCHMOD) || defined(HAVE_CHMOD) +#ifdef HAVE_FCHMOD + st = fchmod(fd, S_IRUSR | S_IWUSR); +#else + st = chmod(data->filename, S_IRUSR | S_IWUSR); +#endif + if (st == -1) { + ret = interpret_errno(context, errno); + goto cleanup; + } +#endif + + ret = krb5_lock_file(context, fd, KRB5_LOCKMODE_EXCLUSIVE); + if (ret) + goto cleanup; + file_locked = TRUE; + + /* Prepare the header and principal in buf. */ + k5_buf_init_dynamic(&buf); + version = context->fcc_default_format - FVNO_BASE; + store_16_be(FVNO_BASE + version, i16buf); + k5_buf_add_len(&buf, i16buf, 2); + if (version >= 4) { + /* Add tagged header fields. */ + fields_len = 0; + if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) + fields_len += 12; + store_16_be(fields_len, i16buf); + k5_buf_add_len(&buf, i16buf, 2); + if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) { + /* Add time offset tag. */ + store_16_be(FCC_TAG_DELTATIME, i16buf); + k5_buf_add_len(&buf, i16buf, 2); + store_16_be(8, i16buf); + k5_buf_add_len(&buf, i16buf, 2); + store_32_be(os_ctx->time_offset, i32buf); + k5_buf_add_len(&buf, i32buf, 4); + store_32_be(os_ctx->usec_offset, i32buf); + k5_buf_add_len(&buf, i32buf, 4); + } + } + k5_marshal_princ(&buf, version, princ); + ret = k5_buf_status(&buf); + if (ret) + goto cleanup; + + /* Write the header and principal. */ + nwritten = write(fd, buf.data, buf.len); + if (nwritten == -1) + ret = interpret_errno(context, errno); + if ((size_t)nwritten != buf.len) + ret = KRB5_CC_IO; + +cleanup: + k5_buf_free(&buf); + if (file_locked) + krb5_unlock_file(context, fd); + if (fd != -1) + close(fd); + k5_cc_mutex_unlock(context, &data->lock); + krb5_change_cache(); + return set_errmsg_filename(context, ret, data->filename); +} + +/* Release an fcc_data object. */ +static void +free_fccdata(krb5_context context, fcc_data *data) +{ + k5_cc_mutex_assert_unlocked(context, &data->lock); + free(data->filename); + k5_cc_mutex_destroy(&data->lock); + free(data); +} + +/* Release the ccache handle. */ +static krb5_error_code KRB5_CALLCONV +fcc_close(krb5_context context, krb5_ccache id) +{ + free_fccdata(context, id->data); + free(id); + return 0; +} + +/* Destroy the cache file and release the handle. */ +static krb5_error_code KRB5_CALLCONV +fcc_destroy(krb5_context context, krb5_ccache id) +{ + krb5_error_code ret = 0; + fcc_data *data = id->data; + int st, fd; + struct stat buf; + unsigned long i, size; + unsigned int wlen; + char zeros[BUFSIZ]; + + k5_cc_mutex_lock(context, &data->lock); + + fd = open(data->filename, O_RDWR | O_BINARY | O_CLOEXEC, 0); + if (fd < 0) { + ret = interpret_errno(context, errno); + goto cleanup; + } + set_cloexec_fd(fd); + +#ifdef MSDOS_FILESYSTEM + /* + * "Disgusting bit of UNIX trivia" - that's how the writers of NFS describe + * the ability of UNIX to still write to a file which has been unlinked. + * Naturally, the PC can't do this. As a result, we have to delete the + * file after we wipe it clean, but that throws off all the error handling + * code. So we have do the work ourselves. + */ + st = fstat(fd, &buf); + if (st == -1) { + ret = interpret_errno(context, errno); + size = 0; /* Nothing to wipe clean */ + } else { + size = (unsigned long)buf.st_size; + } + + memset(zeros, 0, BUFSIZ); + while (size > 0) { + wlen = (int)((size > BUFSIZ) ? BUFSIZ : size); /* How much to write */ + i = write(fd, zeros, wlen); + if (i < 0) { + ret = interpret_errno(context, errno); + /* Don't jump to cleanup--we still want to delete the file. */ + break; + } + size -= i; + } + + (void)close(fd); + + st = unlink(data->filename); + if (st < 0) { + ret = interpret_errno(context, errno); + goto cleanup; + } + +#else /* MSDOS_FILESYSTEM */ + + st = unlink(data->filename); + if (st < 0) { + ret = interpret_errno(context, errno); + (void)close(fd); + goto cleanup; + } + + st = fstat(fd, &buf); + if (st < 0) { + ret = interpret_errno(context, errno); + (void)close(fd); + goto cleanup; + } + + /* XXX This may not be legal XXX */ + size = (unsigned long)buf.st_size; + memset(zeros, 0, BUFSIZ); + for (i = 0; i < size / BUFSIZ; i++) { + if (write(fd, zeros, BUFSIZ) < 0) { + ret = interpret_errno(context, errno); + (void)close(fd); + goto cleanup; + } + } + + wlen = size % BUFSIZ; + if (write(fd, zeros, wlen) < 0) { + ret = interpret_errno(context, errno); + (void)close(fd); + goto cleanup; + } + + st = close(fd); + + if (st) + ret = interpret_errno(context, errno); + +#endif /* MSDOS_FILESYSTEM */ + +cleanup: + (void)set_errmsg_filename(context, ret, data->filename); + k5_cc_mutex_unlock(context, &data->lock); + free_fccdata(context, data); + free(id); + + krb5_change_cache(); + return ret; +} + +extern const krb5_cc_ops krb5_fcc_ops; + +/* Create a file ccache handle for the pathname given by residual. */ +static krb5_error_code KRB5_CALLCONV +fcc_resolve(krb5_context context, krb5_ccache *id, const char *residual) +{ + krb5_ccache lid; + krb5_error_code ret; + fcc_data *data; + + data = malloc(sizeof(fcc_data)); + if (data == NULL) + return KRB5_CC_NOMEM; + data->filename = strdup(residual); + if (data->filename == NULL) { + free(data); + return KRB5_CC_NOMEM; + } + ret = k5_cc_mutex_init(&data->lock); + if (ret) { + free(data->filename); + free(data); + return ret; + } + + lid = malloc(sizeof(struct _krb5_ccache)); + if (lid == NULL) { + free_fccdata(context, data); + return KRB5_CC_NOMEM; + } + + lid->ops = &krb5_fcc_ops; + lid->data = data; + lid->magic = KV5M_CCACHE; + + /* Other routines will get errors on open, and callers must expect them, if + * cache is non-existent/unusable. */ + *id = lid; + return 0; +} + +/* Prepare for a sequential iteration over the cache file. */ +static krb5_error_code KRB5_CALLCONV +fcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor) +{ + krb5_fcc_cursor *fcursor = NULL; + krb5_error_code ret; + krb5_principal princ = NULL; + fcc_data *data = id->data; + FILE *fp = NULL; + int version; + + k5_cc_mutex_lock(context, &data->lock); + + fcursor = malloc(sizeof(krb5_fcc_cursor)); + if (fcursor == NULL) { + ret = KRB5_CC_NOMEM; + goto cleanup; + } + + /* Open the cache file and read the header. */ + ret = open_cache_file(context, data->filename, FALSE, &fp); + if (ret) + goto cleanup; + ret = read_header(context, fp, &version); + if (ret) + goto cleanup; + + /* Read past the default client principal name. */ + ret = read_principal(context, fp, version, &princ); + if (ret) + goto cleanup; + + /* Drop the shared file lock but retain the file handle. */ + (void)krb5_unlock_file(context, fileno(fp)); + fcursor->fp = fp; + fp = NULL; + fcursor->version = version; + *cursor = (krb5_cc_cursor)fcursor; + fcursor = NULL; + +cleanup: + (void)close_cache_file(context, fp); + free(fcursor); + krb5_free_principal(context, princ); + k5_cc_mutex_unlock(context, &data->lock); + return set_errmsg_filename(context, ret, data->filename); +} + +/* Get the next credential from the cache file. */ +static krb5_error_code KRB5_CALLCONV +fcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor, + krb5_creds *creds) +{ + krb5_error_code ret; + krb5_fcc_cursor *fcursor = *cursor; + fcc_data *data = id->data; + struct k5buf buf; + size_t maxsize; + krb5_boolean file_locked = FALSE; + + memset(creds, 0, sizeof(*creds)); + k5_cc_mutex_lock(context, &data->lock); + k5_buf_init_dynamic(&buf); + + ret = krb5_lock_file(context, fileno(fcursor->fp), KRB5_LOCKMODE_SHARED); + if (ret) + goto cleanup; + file_locked = TRUE; + + /* Load a marshalled cred into memory. */ + ret = get_size(context, fcursor->fp, &maxsize); + if (ret) + goto cleanup; + ret = load_cred(context, fcursor->fp, fcursor->version, maxsize, &buf); + if (ret) + goto cleanup; + ret = k5_buf_status(&buf); + if (ret) + goto cleanup; + + /* Unmarshal it from buf into creds. */ + ret = k5_unmarshal_cred(buf.data, buf.len, fcursor->version, creds); + +cleanup: + if (file_locked) + (void)krb5_unlock_file(context, fileno(fcursor->fp)); + k5_cc_mutex_unlock(context, &data->lock); + k5_buf_free(&buf); + return set_errmsg_filename(context, ret, data->filename); +} + +/* Release an iteration cursor. */ +static krb5_error_code KRB5_CALLCONV +fcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor) +{ + krb5_fcc_cursor *fcursor = *cursor; + + (void)fclose(fcursor->fp); + free(fcursor); + *cursor = NULL; + return 0; +} + +/* Generate a unique file ccache using the given template (which will be + * modified to contain the actual name of the file). */ +krb5_error_code +krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id) +{ + krb5_ccache lid; + int fd; + krb5_error_code ret; + fcc_data *data; + char fcc_fvno[2]; + int16_t fcc_flen = 0; + int errsave, cnt; + + fd = mkstemp(template); + if (fd == -1) + return interpret_errno(context, errno); + set_cloexec_fd(fd); + + /* Allocate memory */ + data = malloc(sizeof(fcc_data)); + if (data == NULL) { + close(fd); + unlink(template); + return KRB5_CC_NOMEM; + } + + data->filename = strdup(template); + if (data->filename == NULL) { + free(data); + close(fd); + unlink(template); + return KRB5_CC_NOMEM; + } + + ret = k5_cc_mutex_init(&data->lock); + if (ret) { + free(data->filename); + free(data); + close(fd); + unlink(template); + return ret; + } + k5_cc_mutex_lock(context, &data->lock); + + /* Ignore user's umask, set mode = 0600 */ +#ifndef HAVE_FCHMOD +#ifdef HAVE_CHMOD + chmod(data->filename, S_IRUSR | S_IWUSR); +#endif +#else + fchmod(fd, S_IRUSR | S_IWUSR); +#endif + store_16_be(context->fcc_default_format, fcc_fvno); + cnt = write(fd, &fcc_fvno, 2); + if (cnt != 2) { + errsave = errno; + (void)close(fd); + (void)unlink(data->filename); + ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO; + goto err_out; + } + /* For version 4 we save a length for the rest of the header */ + if (context->fcc_default_format == FVNO_BASE + 4) { + cnt = write(fd, &fcc_flen, sizeof(fcc_flen)); + if (cnt != sizeof(fcc_flen)) { + errsave = errno; + (void)close(fd); + (void)unlink(data->filename); + ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO; + goto err_out; + } + } + if (close(fd) == -1) { + errsave = errno; + (void)unlink(data->filename); + ret = interpret_errno(context, errsave); + goto err_out; + } + + k5_cc_mutex_assert_locked(context, &data->lock); + k5_cc_mutex_unlock(context, &data->lock); + lid = malloc(sizeof(*lid)); + if (lid == NULL) { + free_fccdata(context, data); + return KRB5_CC_NOMEM; + } + + lid->ops = &krb5_fcc_ops; + lid->data = data; + lid->magic = KV5M_CCACHE; + + *id = lid; + + krb5_change_cache(); + return 0; + +err_out: + (void)set_errmsg_filename(context, ret, data->filename); + k5_cc_mutex_unlock(context, &data->lock); + k5_cc_mutex_destroy(&data->lock); + free(data->filename); + free(data); + return ret; +} + +/* + * Create a new file cred cache whose name is guaranteed to be unique. The + * name begins with the string TKT_ROOT (from fcc.h). The cache file is not + * opened, but the new filename is reserved. + */ +static krb5_error_code KRB5_CALLCONV +fcc_generate_new(krb5_context context, krb5_ccache *id) +{ + char scratch[sizeof(TKT_ROOT) + 7]; /* Room for XXXXXX and terminator */ + + (void)snprintf(scratch, sizeof(scratch), "%sXXXXXX", TKT_ROOT); + return krb5int_fcc_new_unique(context, scratch, id); +} + +/* Return an alias to the pathname of the cache file. */ +static const char * KRB5_CALLCONV +fcc_get_name(krb5_context context, krb5_ccache id) +{ + return ((fcc_data *)id->data)->filename; +} + +/* Retrieve a copy of the default principal, if the cache is initialized. */ +static krb5_error_code KRB5_CALLCONV +fcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ) +{ + krb5_error_code ret; + fcc_data *data = id->data; + FILE *fp = NULL; + int version; + + k5_cc_mutex_lock(context, &data->lock); + ret = open_cache_file(context, data->filename, FALSE, &fp); + if (ret) + goto cleanup; + ret = read_header(context, fp, &version); + if (ret) + goto cleanup; + ret = read_principal(context, fp, version, princ); + +cleanup: + (void)close_cache_file(context, fp); + k5_cc_mutex_unlock(context, &data->lock); + return set_errmsg_filename(context, ret, data->filename); +} + +/* Search for a credential within the cache file. */ +static krb5_error_code KRB5_CALLCONV +fcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields, + krb5_creds *mcreds, krb5_creds *creds) +{ + krb5_error_code ret; + + ret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, creds); + return set_errmsg_filename(context, ret, ((fcc_data *)id->data)->filename); +} + +/* Store a credential in the cache file. */ +static krb5_error_code KRB5_CALLCONV +fcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds) +{ + krb5_error_code ret, ret2; + fcc_data *data = id->data; + FILE *fp = NULL; + int version; + struct k5buf buf = EMPTY_K5BUF; + ssize_t nwritten; + + k5_cc_mutex_lock(context, &data->lock); + + /* Open the cache file for O_APPEND writing. */ + ret = open_cache_file(context, data->filename, TRUE, &fp); + if (ret) + goto cleanup; + ret = read_header(context, fp, &version); + if (ret) + goto cleanup; + + /* Marshal the cred and write it to the file with a single append write. */ + k5_buf_init_dynamic(&buf); + k5_marshal_cred(&buf, version, creds); + ret = k5_buf_status(&buf); + if (ret) + goto cleanup; + nwritten = write(fileno(fp), buf.data, buf.len); + if (nwritten == -1) + ret = interpret_errno(context, errno); + if ((size_t)nwritten != buf.len) + ret = KRB5_CC_IO; + + krb5_change_cache(); + +cleanup: + k5_buf_free(&buf); + ret2 = close_cache_file(context, fp); + k5_cc_mutex_unlock(context, &data->lock); + return set_errmsg_filename(context, ret ? ret : ret2, data->filename); +} + +/* Non-functional stub for removing a cred from the cache file. */ +static krb5_error_code KRB5_CALLCONV +fcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags, + krb5_creds *creds) +{ + return KRB5_CC_NOSUPP; +} + +static krb5_error_code KRB5_CALLCONV +fcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags) +{ + return 0; +} + +static krb5_error_code KRB5_CALLCONV +fcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags) +{ + *flags = 0; + return 0; +} + +/* Prepare to iterate over the caches in the per-type collection. */ +static krb5_error_code KRB5_CALLCONV +fcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor) +{ + krb5_cc_ptcursor n = NULL; + struct krb5_fcc_ptcursor_data *cdata = NULL; + + *cursor = NULL; + + n = malloc(sizeof(*n)); + if (n == NULL) + return ENOMEM; + n->ops = &krb5_fcc_ops; + cdata = malloc(sizeof(*cdata)); + if (cdata == NULL) { + free(n); + return ENOMEM; + } + cdata->first = TRUE; + n->data = cdata; + *cursor = n; + return 0; +} + +/* Get the next cache in the per-type collection. The FILE per-type collection + * contains only the context's default cache if it is a file cache. */ +static krb5_error_code KRB5_CALLCONV +fcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, + krb5_ccache *cache_out) +{ + krb5_error_code ret; + struct krb5_fcc_ptcursor_data *cdata = cursor->data; + const char *defname, *residual; + krb5_ccache cache; + struct stat sb; + + *cache_out = NULL; + if (!cdata->first) + return 0; + cdata->first = FALSE; + + defname = krb5_cc_default_name(context); + if (!defname) + return 0; + + /* Check if the default has type FILE or no type; find the residual. */ + if (strncmp(defname, "FILE:", 5) == 0) + residual = defname + 5; + else if (strchr(defname + 2, ':') == NULL) /* Skip drive prefix if any. */ + residual = defname; + else + return 0; + + /* Don't yield a nonexistent default file cache. */ + if (stat(residual, &sb) != 0) + return 0; + + ret = krb5_cc_resolve(context, defname, &cache); + if (ret) + return set_errmsg_filename(context, ret, defname); + *cache_out = cache; + return 0; +} + +/* Release a per-type collection iteration cursor. */ +static krb5_error_code KRB5_CALLCONV +fcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor) +{ + if (*cursor == NULL) + return 0; + free((*cursor)->data); + free(*cursor); + *cursor = NULL; + return 0; +} + +/* Get the cache file's last modification time. */ +static krb5_error_code KRB5_CALLCONV +fcc_last_change_time(krb5_context context, krb5_ccache id, + krb5_timestamp *change_time) +{ + krb5_error_code ret = 0; + fcc_data *data = id->data; + struct stat buf; + + *change_time = 0; + + k5_cc_mutex_lock(context, &data->lock); + + if (stat(data->filename, &buf) == -1) + ret = interpret_errno(context, errno); + else + *change_time = (krb5_timestamp)buf.st_mtime; + + k5_cc_mutex_unlock(context, &data->lock); + + return set_errmsg_filename(context, ret, data->filename); +} + +/* Lock the cache handle against other threads. (This does not lock the cache + * file against other processes.) */ +static krb5_error_code KRB5_CALLCONV +fcc_lock(krb5_context context, krb5_ccache id) +{ + fcc_data *data = id->data; + k5_cc_mutex_lock(context, &data->lock); + return 0; +} + +/* Unlock the cache handle. */ +static krb5_error_code KRB5_CALLCONV +fcc_unlock(krb5_context context, krb5_ccache id) +{ + fcc_data *data = id->data; + k5_cc_mutex_unlock(context, &data->lock); + return 0; +} + +/* Translate a system errno value to a Kerberos com_err code. */ +static krb5_error_code +interpret_errno(krb5_context context, int errnum) +{ + krb5_error_code ret; + + switch (errnum) { + case ENOENT: + case ENOTDIR: +#ifdef ELOOP + case ELOOP: +#endif +#ifdef ENAMETOOLONG + case ENAMETOOLONG: +#endif + ret = KRB5_FCC_NOFILE; + break; + case EPERM: + case EACCES: +#ifdef EISDIR + case EISDIR: /* Mac doesn't have EISDIR */ +#endif + case EROFS: + ret = KRB5_FCC_PERM; + break; + case EINVAL: + case EEXIST: + case EFAULT: + case EBADF: +#ifdef EWOULDBLOCK + case EWOULDBLOCK: +#endif + ret = KRB5_FCC_INTERNAL; + break; + /* + * The rest all map to KRB5_CC_IO. These errnos are listed to + * document that they've been considered explicitly: + * + * - EDQUOT + * - ENOSPC + * - EIO + * - ENFILE + * - EMFILE + * - ENXIO + * - EBUSY + * - ETXTBSY + */ + default: + ret = KRB5_CC_IO; + break; + } + return ret; +} + +const krb5_cc_ops krb5_fcc_ops = { + 0, + "FILE", + fcc_get_name, + fcc_resolve, + fcc_generate_new, + fcc_initialize, + fcc_destroy, + fcc_close, + fcc_store, + fcc_retrieve, + fcc_get_principal, + fcc_start_seq_get, + fcc_next_cred, + fcc_end_seq_get, + fcc_remove_cred, + fcc_set_flags, + fcc_get_flags, + fcc_ptcursor_new, + fcc_ptcursor_next, + fcc_ptcursor_free, + NULL, /* move */ + fcc_last_change_time, + NULL, /* wasdefault */ + fcc_lock, + fcc_unlock, + NULL, /* switch_to */ +}; + +#if defined(_WIN32) +/* + * krb5_change_cache should be called after the cache changes. + * A notification message is is posted out to all top level + * windows so that they may recheck the cache based on the + * changes made. We register a unique message type with which + * we'll communicate to all other processes. + */ + +krb5_error_code +krb5_change_cache(void) +{ + PostMessage(HWND_BROADCAST, krb5_get_notification_message(), 0, 0); + return 0; +} + +unsigned int KRB5_CALLCONV +krb5_get_notification_message(void) +{ + static unsigned int message = 0; + + if (message == 0) + message = RegisterWindowMessage(WM_KERBEROS5_CHANGED); + + return message; +} +#else /* _WIN32 */ + +krb5_error_code +krb5_change_cache(void) +{ + return 0; +} + +unsigned int +krb5_get_notification_message(void) +{ + return 0; +} + +#endif /* _WIN32 */ + +const krb5_cc_ops krb5_cc_file_ops = { + 0, + "FILE", + fcc_get_name, + fcc_resolve, + fcc_generate_new, + fcc_initialize, + fcc_destroy, + fcc_close, + fcc_store, + fcc_retrieve, + fcc_get_principal, + fcc_start_seq_get, + fcc_next_cred, + fcc_end_seq_get, + fcc_remove_cred, + fcc_set_flags, + fcc_get_flags, + fcc_ptcursor_new, + fcc_ptcursor_next, + fcc_ptcursor_free, + NULL, /* move */ + fcc_last_change_time, + NULL, /* wasdefault */ + fcc_lock, + fcc_unlock, + NULL, /* switch_to */ +}; diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c new file mode 100644 index 000000000000..a889e67b4492 --- /dev/null +++ b/src/lib/krb5/ccache/cc_kcm.c @@ -0,0 +1,1074 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/cc_kcm.c - KCM cache type (client side) */ +/* + * Copyright (C) 2014 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 + * COPYRIGHT HOLDER OR CONTRIBUTORS 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. + */ + +/* + * This cache type contacts a daemon for each cache operation, using Heimdal's + * KCM protocol. On OS X, the preferred transport is Mach RPC; on other + * Unix-like platforms or if the daemon is not available via RPC, Unix domain + * sockets are used instead. + */ + +#ifndef _WIN32 +#include "k5-int.h" +#include "k5-input.h" +#include "cc-int.h" +#include "kcm.h" +#include <sys/socket.h> +#include <sys/un.h> +#ifdef __APPLE__ +#include <mach/mach.h> +#include <servers/bootstrap.h> +#include "kcmrpc.h" +#endif + +#define MAX_REPLY_SIZE (10 * 1024 * 1024) + +const krb5_cc_ops krb5_kcm_ops; + +struct uuid_list { + unsigned char *uuidbytes; /* all of the uuids concatenated together */ + size_t count; + size_t pos; +}; + +struct kcmio { + int fd; +#ifdef __APPLE__ + mach_port_t mport; +#endif +}; + +/* This structure bundles together a KCM request and reply, to minimize how + * much we have to declare and clean up in each method. */ +struct kcmreq { + struct k5buf reqbuf; + struct k5input reply; + void *reply_mem; +}; +#define EMPTY_KCMREQ { EMPTY_K5BUF } + +struct kcm_cache_data { + char *residual; /* immutable; may be accessed without lock */ + k5_cc_mutex lock; /* protects io and changetime */ + struct kcmio *io; + krb5_timestamp changetime; +}; + +struct kcm_ptcursor { + char *residual; /* primary or singleton subsidiary */ + struct uuid_list *uuids; /* NULL for singleton subsidiary */ + struct kcmio *io; + krb5_boolean first; +}; + +/* Map EINVAL or KRB5_CC_FORMAT to KRB5_KCM_MALFORMED_REPLY; pass through all + * other codes. */ +static inline krb5_error_code +map_invalid(krb5_error_code code) +{ + return (code == EINVAL || code == KRB5_CC_FORMAT) ? + KRB5_KCM_MALFORMED_REPLY : code; +} + +/* Begin a request for the given opcode. If cache is non-null, supply the + * cache name as a request parameter. */ +static void +kcmreq_init(struct kcmreq *req, kcm_opcode opcode, krb5_ccache cache) +{ + unsigned char bytes[4]; + const char *name; + + memset(req, 0, sizeof(*req)); + + bytes[0] = KCM_PROTOCOL_VERSION_MAJOR; + bytes[1] = KCM_PROTOCOL_VERSION_MINOR; + store_16_be(opcode, bytes + 2); + + k5_buf_init_dynamic(&req->reqbuf); + k5_buf_add_len(&req->reqbuf, bytes, 4); + if (cache != NULL) { + name = ((struct kcm_cache_data *)cache->data)->residual; + k5_buf_add_len(&req->reqbuf, name, strlen(name) + 1); + } +} + +/* Add a 32-bit value to the request in big-endian byte order. */ +static void +kcmreq_put32(struct kcmreq *req, uint32_t val) +{ + unsigned char bytes[4]; + + store_32_be(val, bytes); + k5_buf_add_len(&req->reqbuf, bytes, 4); +} + +#ifdef __APPLE__ + +/* The maximum length of an in-band request or reply as defined by the RPC + * protocol. */ +#define MAX_INBAND_SIZE 2048 + +/* Connect or reconnect to the KCM daemon via Mach RPC, if possible. */ +static krb5_error_code +kcmio_mach_connect(krb5_context context, struct kcmio *io) +{ + krb5_error_code ret; + kern_return_t st; + mach_port_t mport; + char *service; + + ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS, + KRB5_CONF_KCM_MACH_SERVICE, NULL, + DEFAULT_KCM_MACH_SERVICE, &service); + if (ret) + return ret; + if (strcmp(service, "-") == 0) { + profile_release_string(service); + return KRB5_KCM_NO_SERVER; + } + + st = bootstrap_look_up(bootstrap_port, service, &mport); + profile_release_string(service); + if (st) + return KRB5_KCM_NO_SERVER; + if (io->mport != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), io->mport); + io->mport = mport; + return 0; +} + +/* Invoke the Mach RPC to get a reply from the KCM daemon. */ +static krb5_error_code +kcmio_mach_call(krb5_context context, struct kcmio *io, void *data, + size_t len, void **reply_out, size_t *len_out) +{ + krb5_error_code ret; + size_t inband_req_len = 0, outband_req_len = 0, reply_len; + char *inband_req = NULL, *outband_req = NULL, *outband_reply, *copy; + char inband_reply[MAX_INBAND_SIZE]; + mach_msg_type_number_t inband_reply_len, outband_reply_len; + const void *reply; + kern_return_t st; + int code; + + *reply_out = NULL; + *len_out = 0; + + /* Use the in-band or out-of-band request buffer depending on len. */ + if (len <= MAX_INBAND_SIZE) { + inband_req = data; + inband_req_len = len; + } else { + outband_req = data; + outband_req_len = len; + } + + st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req, + outband_req_len, &code, inband_reply, + &inband_reply_len, &outband_reply, &outband_reply_len); + if (st == MACH_SEND_INVALID_DEST) { + /* Get a new port and try again. */ + st = kcmio_mach_connect(context, io); + if (st) + return KRB5_KCM_RPC_ERROR; + st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req, + outband_req_len, &code, inband_reply, + &inband_reply_len, &outband_reply, + &outband_reply_len); + } + if (st) + return KRB5_KCM_RPC_ERROR; + + if (code) { + ret = code; + goto cleanup; + } + + /* The reply could be in the in-band or out-of-band reply buffer. */ + reply = outband_reply_len ? outband_reply : inband_reply; + reply_len = outband_reply_len ? outband_reply_len : inband_reply_len; + copy = k5memdup(reply, reply_len, &ret); + if (copy == NULL) + goto cleanup; + + *reply_out = copy; + *len_out = reply_len; + +cleanup: + if (outband_reply_len) { + vm_deallocate(mach_task_self(), (vm_address_t)outband_reply, + outband_reply_len); + } + return ret; +} + +/* Release any Mach RPC state within io. */ +static void +kcmio_mach_close(struct kcmio *io) +{ + if (io->mport != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), io->mport); +} + +#else /* __APPLE__ */ + +#define kcmio_mach_connect(context, io) EINVAL +#define kcmio_mach_call(context, io, data, len, reply_out, len_out) EINVAL +#define kcmio_mach_close(io) + +#endif + +/* Connect to the KCM daemon via a Unix domain socket. */ +static krb5_error_code +kcmio_unix_socket_connect(krb5_context context, struct kcmio *io) +{ + krb5_error_code ret; + int fd = -1; + struct sockaddr_un addr; + char *path = NULL; + + ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS, + KRB5_CONF_KCM_SOCKET, NULL, + DEFAULT_KCM_SOCKET_PATH, &path); + if (ret) + goto cleanup; + if (strcmp(path, "-") == 0) { + ret = KRB5_KCM_NO_SERVER; + goto cleanup; + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + ret = errno; + goto cleanup; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + ret = (errno == ENOENT) ? KRB5_KCM_NO_SERVER : errno; + goto cleanup; + } + + io->fd = fd; + fd = -1; + +cleanup: + if (fd != -1) + close(fd); + profile_release_string(path); + return ret; +} + +/* Write a KCM request: 4-byte big-endian length, then the marshalled + * request. */ +static krb5_error_code +kcmio_unix_socket_write(krb5_context context, struct kcmio *io, void *request, + size_t len) +{ + char lenbytes[4]; + + store_32_be(len, lenbytes); + if (krb5_net_write(context, io->fd, lenbytes, 4) < 0) + return errno; + if (krb5_net_write(context, io->fd, request, len) < 0) + return errno; + return 0; +} + +/* Read a KCM reply: 4-byte big-endian length, 4-byte big-endian status code, + * then the marshalled reply. */ +static krb5_error_code +kcmio_unix_socket_read(krb5_context context, struct kcmio *io, + void **reply_out, size_t *len_out) +{ + krb5_error_code code; + char lenbytes[4], codebytes[4], *reply; + size_t len; + int st; + + *reply_out = NULL; + *len_out = 0; + + st = krb5_net_read(context, io->fd, lenbytes, 4); + if (st != 4) + return (st == -1) ? errno : KRB5_CC_IO; + len = load_32_be(lenbytes); + if (len > MAX_REPLY_SIZE) + return KRB5_KCM_REPLY_TOO_BIG; + + st = krb5_net_read(context, io->fd, codebytes, 4); + if (st != 4) + return (st == -1) ? errno : KRB5_CC_IO; + code = load_32_be(codebytes); + if (code != 0) + return code; + + reply = malloc(len); + if (reply == NULL) + return ENOMEM; + st = krb5_net_read(context, io->fd, reply, len); + if (st == -1 || (size_t)st != len) { + free(reply); + return (st < 0) ? errno : KRB5_CC_IO; + } + + *reply_out = reply; + *len_out = len; + return 0; +} + +static krb5_error_code +kcmio_connect(krb5_context context, struct kcmio **io_out) +{ + krb5_error_code ret; + struct kcmio *io; + + *io_out = NULL; + io = calloc(1, sizeof(*io)); + if (io == NULL) + return ENOMEM; + io->fd = -1; + + /* Try Mach RPC (OS X only), then fall back to Unix domain sockets */ + ret = kcmio_mach_connect(context, io); + if (ret) + ret = kcmio_unix_socket_connect(context, io); + if (ret) { + free(io); + return ret; + } + + *io_out = io; + return 0; +} + +/* Check req->reqbuf for an error condition and return it. Otherwise, send the + * request to the KCM daemon and get a response. */ +static krb5_error_code +kcmio_call(krb5_context context, struct kcmio *io, struct kcmreq *req) +{ + krb5_error_code ret; + size_t reply_len = 0; + + if (k5_buf_status(&req->reqbuf) != 0) + return ENOMEM; + + if (io->fd != -1) { + ret = kcmio_unix_socket_write(context, io, req->reqbuf.data, + req->reqbuf.len); + if (ret) + return ret; + ret = kcmio_unix_socket_read(context, io, &req->reply_mem, &reply_len); + if (ret) + return ret; + } else { + /* We must be using Mach RPC. */ + ret = kcmio_mach_call(context, io, req->reqbuf.data, req->reqbuf.len, + &req->reply_mem, &reply_len); + if (ret) + return ret; + } + + /* Read the status code from the marshalled reply. */ + k5_input_init(&req->reply, req->reply_mem, reply_len); + ret = k5_input_get_uint32_be(&req->reply); + return req->reply.status ? KRB5_KCM_MALFORMED_REPLY : ret; +} + +static void +kcmio_close(struct kcmio *io) +{ + if (io != NULL) { + kcmio_mach_close(io); + if (io->fd != -1) + close(io->fd); + free(io); + } +} + +/* Fetch a zero-terminated name string from req->reply. The returned pointer + * is an alias and must not be freed by the caller. */ +static krb5_error_code +kcmreq_get_name(struct kcmreq *req, const char **name_out) +{ + const unsigned char *end; + struct k5input *in = &req->reply; + + *name_out = NULL; + end = memchr(in->ptr, '\0', in->len); + if (end == NULL) + return KRB5_KCM_MALFORMED_REPLY; + *name_out = (const char *)in->ptr; + (void)k5_input_get_bytes(in, end + 1 - in->ptr); + return 0; +} + +/* Fetch a UUID list from req->reply. UUID lists are not delimited, so we + * consume the rest of the input. */ +static krb5_error_code +kcmreq_get_uuid_list(struct kcmreq *req, struct uuid_list **uuids_out) +{ + struct uuid_list *uuids; + + *uuids_out = NULL; + + if (req->reply.len % KCM_UUID_LEN != 0) + return KRB5_KCM_MALFORMED_REPLY; + + uuids = malloc(sizeof(*uuids)); + if (uuids == NULL) + return ENOMEM; + uuids->count = req->reply.len / KCM_UUID_LEN; + uuids->pos = 0; + + if (req->reply.len > 0) { + uuids->uuidbytes = malloc(req->reply.len); + if (uuids->uuidbytes == NULL) { + free(uuids); + return ENOMEM; + } + memcpy(uuids->uuidbytes, req->reply.ptr, req->reply.len); + (void)k5_input_get_bytes(&req->reply, req->reply.len); + } else { + uuids->uuidbytes = NULL; + } + + *uuids_out = uuids; + return 0; +} + +static void +free_uuid_list(struct uuid_list *uuids) +{ + if (uuids != NULL) + free(uuids->uuidbytes); + free(uuids); +} + +static void +kcmreq_free(struct kcmreq *req) +{ + k5_buf_free(&req->reqbuf); + free(req->reply_mem); +} + +/* Create a krb5_ccache structure. If io is NULL, make a new connection for + * the cache. Otherwise, always take ownership of io. */ +static krb5_error_code +make_cache(krb5_context context, const char *residual, struct kcmio *io, + krb5_ccache *cache_out) +{ + krb5_error_code ret; + krb5_ccache cache = NULL; + struct kcm_cache_data *data = NULL; + char *residual_copy = NULL; + + *cache_out = NULL; + + if (io == NULL) { + ret = kcmio_connect(context, &io); + if (ret) + return ret; + } + + cache = malloc(sizeof(*cache)); + if (cache == NULL) + goto oom; + data = calloc(1, sizeof(*data)); + if (data == NULL) + goto oom; + residual_copy = strdup(residual); + if (residual_copy == NULL) + goto oom; + if (k5_cc_mutex_init(&data->lock) != 0) + goto oom; + + data->residual = residual_copy; + data->io = io; + data->changetime = 0; + cache->ops = &krb5_kcm_ops; + cache->data = data; + cache->magic = KV5M_CCACHE; + *cache_out = cache; + return 0; + +oom: + free(cache); + free(data); + free(residual_copy); + kcmio_close(io); + return ENOMEM; +} + +/* Lock cache's I/O structure and use it to call the KCM daemon. If modify is + * true, update the last change time. */ +static krb5_error_code +cache_call(krb5_context context, krb5_ccache cache, struct kcmreq *req, + krb5_boolean modify) +{ + krb5_error_code ret; + struct kcm_cache_data *data = cache->data; + + k5_cc_mutex_lock(context, &data->lock); + ret = kcmio_call(context, data->io, req); + if (modify && !ret) + data->changetime = time(NULL); + k5_cc_mutex_unlock(context, &data->lock); + return ret; +} + +/* Try to propagate the KDC time offset from the cache to the krb5 context. */ +static void +get_kdc_offset(krb5_context context, krb5_ccache cache) +{ + struct kcmreq req = EMPTY_KCMREQ; + int32_t time_offset; + + kcmreq_init(&req, KCM_OP_GET_KDC_OFFSET, cache); + if (cache_call(context, cache, &req, FALSE) != 0) + goto cleanup; + time_offset = k5_input_get_uint32_be(&req.reply); + if (!req.reply.status) + goto cleanup; + context->os_context.time_offset = time_offset; + context->os_context.usec_offset = 0; + context->os_context.os_flags &= ~KRB5_OS_TOFFSET_TIME; + context->os_context.os_flags |= KRB5_OS_TOFFSET_VALID; + +cleanup: + kcmreq_free(&req); +} + +/* Try to propagate the KDC offset from the krb5 context to the cache. */ +static void +set_kdc_offset(krb5_context context, krb5_ccache cache) +{ + struct kcmreq req; + + if (context->os_context.os_flags & KRB5_OS_TOFFSET_VALID) { + kcmreq_init(&req, KCM_OP_SET_KDC_OFFSET, cache); + kcmreq_put32(&req, context->os_context.time_offset); + (void)cache_call(context, cache, &req, TRUE); + kcmreq_free(&req); + } +} + +static const char * KRB5_CALLCONV +kcm_get_name(krb5_context context, krb5_ccache cache) +{ + return ((struct kcm_cache_data *)cache->data)->residual; +} + +static krb5_error_code KRB5_CALLCONV +kcm_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual) +{ + krb5_error_code ret; + struct kcmreq req = EMPTY_KCMREQ; + struct kcmio *io = NULL; + const char *defname = NULL; + + *cache_out = NULL; + + ret = kcmio_connect(context, &io); + if (ret) + goto cleanup; + + if (*residual == '\0') { + kcmreq_init(&req, KCM_OP_GET_DEFAULT_CACHE, NULL); + ret = kcmio_call(context, io, &req); + if (ret) + goto cleanup; + ret = kcmreq_get_name(&req, &defname); + if (ret) + goto cleanup; + residual = defname; + } + + ret = make_cache(context, residual, io, cache_out); + io = NULL; + +cleanup: + kcmio_close(io); + kcmreq_free(&req); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +kcm_gen_new(krb5_context context, krb5_ccache *cache_out) +{ + krb5_error_code ret; + struct kcmreq req = EMPTY_KCMREQ; + struct kcmio *io = NULL; + const char *name; + + *cache_out = NULL; + + ret = kcmio_connect(context, &io); + if (ret) + goto cleanup; + kcmreq_init(&req, KCM_OP_GEN_NEW, NULL); + ret = kcmio_call(context, io, &req); + if (ret) + goto cleanup; + ret = kcmreq_get_name(&req, &name); + if (ret) + goto cleanup; + ret = make_cache(context, name, io, cache_out); + io = NULL; + +cleanup: + kcmreq_free(&req); + kcmio_close(io); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +kcm_initialize(krb5_context context, krb5_ccache cache, krb5_principal princ) +{ + krb5_error_code ret; + struct kcmreq req; + + kcmreq_init(&req, KCM_OP_INITIALIZE, cache); + k5_marshal_princ(&req.reqbuf, 4, princ); + ret = cache_call(context, cache, &req, TRUE); + kcmreq_free(&req); + set_kdc_offset(context, cache); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +kcm_close(krb5_context context, krb5_ccache cache) +{ + struct kcm_cache_data *data = cache->data; + + k5_cc_mutex_destroy(&data->lock); + kcmio_close(data->io); + free(data->residual); + free(data); + free(cache); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +kcm_destroy(krb5_context context, krb5_ccache cache) +{ + krb5_error_code ret; + struct kcmreq req; + + kcmreq_init(&req, KCM_OP_DESTROY, cache); + ret = cache_call(context, cache, &req, TRUE); + kcmreq_free(&req); + (void)kcm_close(context, cache); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +kcm_store(krb5_context context, krb5_ccache cache, krb5_creds *cred) +{ + krb5_error_code ret; + struct kcmreq req; + + kcmreq_init(&req, KCM_OP_STORE, cache); + k5_marshal_cred(&req.reqbuf, 4, cred); + ret = cache_call(context, cache, &req, TRUE); + kcmreq_free(&req); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +kcm_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags, + krb5_creds *mcred, krb5_creds *cred_out) +{ + /* There is a KCM opcode for retrieving creds, but Heimdal's client doesn't + * use it. It causes the KCM daemon to actually make a TGS request. */ + return k5_cc_retrieve_cred_default(context, cache, flags, mcred, cred_out); +} + +static krb5_error_code KRB5_CALLCONV +kcm_get_princ(krb5_context context, krb5_ccache cache, + krb5_principal *princ_out) +{ + krb5_error_code ret; + struct kcmreq req; + + kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, cache); + ret = cache_call(context, cache, &req, FALSE); + /* Heimdal KCM can respond with code 0 and no principal. */ + if (!ret && req.reply.len == 0) + ret = KRB5_FCC_NOFILE; + if (!ret) + ret = k5_unmarshal_princ(req.reply.ptr, req.reply.len, 4, princ_out); + kcmreq_free(&req); + return map_invalid(ret); +} + +static krb5_error_code KRB5_CALLCONV +kcm_start_seq_get(krb5_context context, krb5_ccache cache, + krb5_cc_cursor *cursor_out) +{ + krb5_error_code ret; + struct kcmreq req = EMPTY_KCMREQ; + struct uuid_list *uuids; + + *cursor_out = NULL; + + get_kdc_offset(context, cache); + + kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache); + ret = cache_call(context, cache, &req, FALSE); + if (ret) + goto cleanup; + ret = kcmreq_get_uuid_list(&req, &uuids); + if (ret) + goto cleanup; + *cursor_out = (krb5_cc_cursor)uuids; + +cleanup: + kcmreq_free(&req); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor, + krb5_creds *cred_out) +{ + krb5_error_code ret; + struct kcmreq req; + struct uuid_list *uuids = (struct uuid_list *)*cursor; + + memset(cred_out, 0, sizeof(*cred_out)); + + if (uuids->pos >= uuids->count) + return KRB5_CC_END; + + kcmreq_init(&req, KCM_OP_GET_CRED_BY_UUID, cache); + k5_buf_add_len(&req.reqbuf, uuids->uuidbytes + (uuids->pos * KCM_UUID_LEN), + KCM_UUID_LEN); + uuids->pos++; + ret = cache_call(context, cache, &req, FALSE); + if (!ret) + ret = k5_unmarshal_cred(req.reply.ptr, req.reply.len, 4, cred_out); + kcmreq_free(&req); + return map_invalid(ret); +} + +static krb5_error_code KRB5_CALLCONV +kcm_end_seq_get(krb5_context context, krb5_ccache cache, + krb5_cc_cursor *cursor) +{ + free_uuid_list((struct uuid_list *)*cursor); + *cursor = NULL; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +kcm_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags, + krb5_creds *mcred) +{ + krb5_error_code ret; + struct kcmreq req; + + kcmreq_init(&req, KCM_OP_REMOVE_CRED, cache); + kcmreq_put32(&req, flags); + k5_marshal_mcred(&req.reqbuf, mcred); + ret = cache_call(context, cache, &req, TRUE); + kcmreq_free(&req); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +kcm_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags) +{ + /* We don't currently care about any flags for this type. */ + return 0; +} + +static krb5_error_code KRB5_CALLCONV +kcm_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out) +{ + /* We don't currently have any operational flags for this type. */ + *flags_out = 0; + return 0; +} + +/* Construct a per-type cursor, always taking ownership of io and uuids. */ +static krb5_error_code +make_ptcursor(const char *residual, struct uuid_list *uuids, struct kcmio *io, + krb5_cc_ptcursor *cursor_out) +{ + krb5_cc_ptcursor cursor = NULL; + struct kcm_ptcursor *data = NULL; + char *residual_copy = NULL; + + *cursor_out = NULL; + + if (residual != NULL) { + residual_copy = strdup(residual); + if (residual_copy == NULL) + goto oom; + } + cursor = malloc(sizeof(*cursor)); + if (cursor == NULL) + goto oom; + data = malloc(sizeof(*data)); + if (data == NULL) + goto oom; + + data->residual = residual_copy; + data->uuids = uuids; + data->io = io; + data->first = TRUE; + cursor->ops = &krb5_kcm_ops; + cursor->data = data; + *cursor_out = cursor; + return 0; + +oom: + kcmio_close(io); + free_uuid_list(uuids); + free(residual_copy); + free(data); + free(cursor); + return ENOMEM; +} + +static krb5_error_code KRB5_CALLCONV +kcm_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out) +{ + krb5_error_code ret; + struct kcmreq req = EMPTY_KCMREQ; + struct kcmio *io = NULL; + struct uuid_list *uuids = NULL; + const char *defname, *primary; + + *cursor_out = NULL; + + /* Don't try to use KCM for the cache collection unless the default cache + * name has the KCM type. */ + defname = krb5_cc_default_name(context); + if (defname == NULL || strncmp(defname, "KCM:", 4) != 0) + return make_ptcursor(NULL, NULL, NULL, cursor_out); + + ret = kcmio_connect(context, &io); + if (ret) + return ret; + + /* If defname is a subsidiary cache, return a singleton cursor. */ + if (strlen(defname) > 4) + return make_ptcursor(defname + 4, NULL, io, cursor_out); + + kcmreq_init(&req, KCM_OP_GET_CACHE_UUID_LIST, NULL); + ret = kcmio_call(context, io, &req); + if (ret == KRB5_FCC_NOFILE) { + /* There are no accessible caches; return an empty cursor. */ + ret = make_ptcursor(NULL, NULL, NULL, cursor_out); + goto cleanup; + } + if (ret) + goto cleanup; + ret = kcmreq_get_uuid_list(&req, &uuids); + if (ret) + goto cleanup; + + kcmreq_free(&req); + kcmreq_init(&req, KCM_OP_GET_DEFAULT_CACHE, NULL); + ret = kcmio_call(context, io, &req); + if (ret) + goto cleanup; + ret = kcmreq_get_name(&req, &primary); + if (ret) + goto cleanup; + + ret = make_ptcursor(primary, uuids, io, cursor_out); + uuids = NULL; + io = NULL; + +cleanup: + free_uuid_list(uuids); + kcmio_close(io); + kcmreq_free(&req); + return ret; +} + +/* Return true if name is an initialized cache. */ +static krb5_boolean +name_exists(krb5_context context, struct kcmio *io, const char *name) +{ + krb5_error_code ret; + struct kcmreq req; + + kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, NULL); + k5_buf_add_len(&req.reqbuf, name, strlen(name) + 1); + ret = kcmio_call(context, io, &req); + kcmreq_free(&req); + return ret == 0; +} + +static krb5_error_code KRB5_CALLCONV +kcm_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, + krb5_ccache *cache_out) +{ + krb5_error_code ret = 0; + struct kcmreq req = EMPTY_KCMREQ; + struct kcm_ptcursor *data = cursor->data; + struct uuid_list *uuids; + const unsigned char *id; + const char *name; + + *cache_out = NULL; + + /* Return the primary or specified subsidiary cache if we haven't yet. */ + if (data->first && data->residual != NULL) { + data->first = FALSE; + if (name_exists(context, data->io, data->residual)) + return make_cache(context, data->residual, NULL, cache_out); + } + + uuids = data->uuids; + if (uuids == NULL) + return 0; + + while (uuids->pos < uuids->count) { + /* Get the name of the next cache. */ + id = &uuids->uuidbytes[KCM_UUID_LEN * uuids->pos++]; + kcmreq_free(&req); + kcmreq_init(&req, KCM_OP_GET_CACHE_BY_UUID, NULL); + k5_buf_add_len(&req.reqbuf, id, KCM_UUID_LEN); + ret = kcmio_call(context, data->io, &req); + if (ret) + goto cleanup; + ret = kcmreq_get_name(&req, &name); + if (ret) + goto cleanup; + + /* Don't yield the primary cache twice. */ + if (strcmp(name, data->residual) == 0) + continue; + + ret = make_cache(context, name, NULL, cache_out); + break; + } + +cleanup: + kcmreq_free(&req); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +kcm_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor) +{ + struct kcm_ptcursor *data = (*cursor)->data; + + free(data->residual); + free_uuid_list(data->uuids); + kcmio_close(data->io); + free(data); + free(*cursor); + *cursor = NULL; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +kcm_lastchange(krb5_context context, krb5_ccache cache, + krb5_timestamp *time_out) +{ + struct kcm_cache_data *data = cache->data; + + /* + * KCM has no support for retrieving the last change time. Return the time + * of the last change made through this handle, which isn't very useful, + * but is the best we can do for now. + */ + k5_cc_mutex_lock(context, &data->lock); + *time_out = data->changetime; + k5_cc_mutex_unlock(context, &data->lock); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +kcm_lock(krb5_context context, krb5_ccache cache) +{ + k5_cc_mutex_lock(context, &((struct kcm_cache_data *)cache->data)->lock); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +kcm_unlock(krb5_context context, krb5_ccache cache) +{ + k5_cc_mutex_unlock(context, &((struct kcm_cache_data *)cache->data)->lock); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +kcm_switch_to(krb5_context context, krb5_ccache cache) +{ + krb5_error_code ret; + struct kcmreq req; + + kcmreq_init(&req, KCM_OP_SET_DEFAULT_CACHE, cache); + ret = cache_call(context, cache, &req, FALSE); + kcmreq_free(&req); + return ret; +} + +const krb5_cc_ops krb5_kcm_ops = { + 0, + "KCM", + kcm_get_name, + kcm_resolve, + kcm_gen_new, + kcm_initialize, + kcm_destroy, + kcm_close, + kcm_store, + kcm_retrieve, + kcm_get_princ, + kcm_start_seq_get, + kcm_next_cred, + kcm_end_seq_get, + kcm_remove_cred, + kcm_set_flags, + kcm_get_flags, + kcm_ptcursor_new, + kcm_ptcursor_next, + kcm_ptcursor_free, + NULL, /* move */ + kcm_lastchange, + NULL, /* wasdefault */ + kcm_lock, + kcm_unlock, + kcm_switch_to, +}; + +#endif /* not _WIN32 */ diff --git a/src/lib/krb5/ccache/cc_keyring.c b/src/lib/krb5/ccache/cc_keyring.c new file mode 100644 index 000000000000..4fe3f0d6f1f2 --- /dev/null +++ b/src/lib/krb5/ccache/cc_keyring.c @@ -0,0 +1,1755 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/cc_keyring.c */ +/* + * Copyright (c) 2006 + * The Regents of the University of Michigan + * ALL RIGHTS RESERVED + * + * Permission is granted to use, copy, create derivative works + * and redistribute this software and such derivative works + * for any purpose, so long as the name of The University of + * Michigan is not used in any advertising or publicity + * pertaining to the use of distribution of this software + * without specific, written prior authorization. If the + * above copyright notice or any other identification of the + * University of Michigan is included in any copy of any + * portion of this software, then the disclaimer below must + * also be included. + * + * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION + * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY + * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF + * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING + * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE + * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE + * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR + * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING + * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN + * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGES. + */ +/* + * Copyright 1990,1991,1992,1993,1994,2000,2004 Massachusetts Institute of + * Technology. All Rights Reserved. + * + * Original stdio support copyright 1995 by Cygnus Support. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +/* + * This file implements a collection-enabled credential cache type where the + * credentials are stored in the Linux keyring facility. + * + * A residual of this type can have three forms: + * anchor:collection:subsidiary + * anchor:collection + * collection + * + * The anchor name is "process", "thread", or "legacy" and determines where we + * search for keyring collections. In the third form, the anchor name is + * presumed to be "legacy". The anchor keyring for legacy caches is the + * session keyring. + * + * If the subsidiary name is present, the residual identifies a single cache + * within a collection. Otherwise, the residual identifies the collection + * itself. When a residual identifying a collection is resolved, the + * collection's primary key is looked up (or initialized, using the collection + * name as the subsidiary name), and the resulting cache's name will use the + * first name form and will identify the primary cache. + * + * Keyring collections are named "_krb_<collection>" and are linked from the + * anchor keyring. The keys within a keyring collection are links to cache + * keyrings, plus a link to one user key named "krb_ccache:primary" which + * contains a serialized representation of the collection version (currently 1) + * and the primary name of the collection. + * + * Cache keyrings contain one user key per credential which contains a + * serialized representation of the credential. There is also one user key + * named "__krb5_princ__" which contains a serialized representation of the + * cache's default principal. + * + * If the anchor name is "legacy", then the initial primary cache (the one + * named with the collection name) is also linked to the session keyring, and + * we look for a cache in that location when initializing the collection. This + * extra link allows that cache to be visible to old versions of the KEYRING + * cache type, and allows us to see caches created by that code. + */ + +#include "cc-int.h" + +#ifdef USE_KEYRING_CCACHE + +#include <errno.h> +#include <keyutils.h> + +#ifdef DEBUG +#define KRCC_DEBUG 1 +#endif + +#if KRCC_DEBUG +void debug_print(char *fmt, ...); /* prototype to silence warning */ +#include <syslog.h> +#define DEBUG_PRINT(x) debug_print x +void +debug_print(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); +#ifdef DEBUG_STDERR + vfprintf(stderr, fmt, ap); +#else + vsyslog(LOG_ERR, fmt, ap); +#endif + va_end(ap); +} +#else +#define DEBUG_PRINT(x) +#endif + +/* + * We try to use the big_key key type for credentials except in legacy caches. + * We fall back to the user key type if the kernel does not support big_key. + * If the library doesn't support keyctl_get_persistent(), we don't even try + * big_key since the two features were added at the same time. + */ +#ifdef HAVE_PERSISTENT_KEYRING +#define KRCC_CRED_KEY_TYPE "big_key" +#else +#define KRCC_CRED_KEY_TYPE "user" +#endif + +/* + * We use the "user" key type for collection primary names, for cache principal + * names, and for credentials in legacy caches. + */ +#define KRCC_KEY_TYPE_USER "user" + +/* + * We create ccaches as separate keyrings + */ +#define KRCC_KEY_TYPE_KEYRING "keyring" + +/* + * Special name of the key within a ccache keyring + * holding principal information + */ +#define KRCC_SPEC_PRINC_KEYNAME "__krb5_princ__" + +/* + * Special name for the key to communicate the name(s) + * of credentials caches to be used for requests. + * This should currently contain a single name, but + * in the future may contain a list that may be + * intelligently chosen from. + */ +#define KRCC_SPEC_CCACHE_SET_KEYNAME "__krb5_cc_set__" + +/* + * This name identifies the key containing the name of the current primary + * cache within a collection. + */ +#define KRCC_COLLECTION_PRIMARY "krb_ccache:primary" + +/* + * If the library context does not specify a keyring collection, unique ccaches + * will be created within this collection. + */ +#define KRCC_DEFAULT_UNIQUE_COLLECTION "session:__krb5_unique__" + +/* + * Collection keyring names begin with this prefix. We use a prefix so that a + * cache keyring with the collection name itself can be linked directly into + * the anchor, for legacy session keyring compatibility. + */ +#define KRCC_CCCOL_PREFIX "_krb_" + +/* + * For the "persistent" anchor type, we look up or create this fixed keyring + * name within the per-UID persistent keyring. + */ +#define KRCC_PERSISTENT_KEYRING_NAME "_krb" + +/* + * Name of the key holding time offsets for the individual cache + */ +#define KRCC_TIME_OFFSETS "__krb5_time_offsets__" + +/* + * Keyring name prefix and length of random name part + */ +#define KRCC_NAME_PREFIX "krb_ccache_" +#define KRCC_NAME_RAND_CHARS 8 + +#define KRCC_COLLECTION_VERSION 1 + +#define KRCC_PERSISTENT_ANCHOR "persistent" +#define KRCC_PROCESS_ANCHOR "process" +#define KRCC_THREAD_ANCHOR "thread" +#define KRCC_SESSION_ANCHOR "session" +#define KRCC_USER_ANCHOR "user" +#define KRCC_LEGACY_ANCHOR "legacy" + +typedef struct _krcc_cursor +{ + int numkeys; + int currkey; + key_serial_t princ_id; + key_serial_t offsets_id; + key_serial_t *keys; +} *krcc_cursor; + +/* + * This represents a credentials cache "file" + * where cache_id is the keyring serial number for + * this credentials cache "file". Each key + * in the keyring contains a separate key. + */ +typedef struct _krcc_data +{ + char *name; /* Name for this credentials cache */ + k5_cc_mutex lock; /* synchronization */ + key_serial_t collection_id; /* collection containing this cache keyring */ + key_serial_t cache_id; /* keyring representing ccache */ + key_serial_t princ_id; /* key holding principal info */ + krb5_timestamp changetime; + krb5_boolean is_legacy_type; +} krcc_data; + +/* Global mutex */ +k5_cc_mutex krb5int_krcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER; + +extern const krb5_cc_ops krb5_krcc_ops; + +static const char *KRB5_CALLCONV +krcc_get_name(krb5_context context, krb5_ccache id); + +static krb5_error_code KRB5_CALLCONV +krcc_start_seq_get(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor); + +static krb5_error_code KRB5_CALLCONV +krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor, + krb5_creds *creds); + +static krb5_error_code KRB5_CALLCONV +krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor); + +static krb5_error_code KRB5_CALLCONV +krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor); + +static krb5_error_code clear_cache_keyring(krb5_context context, + krb5_ccache id); + +static krb5_error_code make_krcc_data(const char *anchor_name, + const char *collection_name, + const char *subsidiary_name, + key_serial_t cache_id, key_serial_t + collection_id, krcc_data **datapp); + +static krb5_error_code save_principal(krb5_context context, krb5_ccache id, + krb5_principal princ); + +static krb5_error_code save_time_offsets(krb5_context context, krb5_ccache id, + int32_t time_offset, + int32_t usec_offset); + +static krb5_error_code get_time_offsets(krb5_context context, krb5_ccache id, + int32_t *time_offset, + int32_t *usec_offset); + +static void krcc_update_change_time(krcc_data *d); + +/* Note the following is a stub function for Linux */ +extern krb5_error_code krb5_change_cache(void); + +/* + * GET_PERSISTENT(uid) acquires the persistent keyring for uid, or falls back + * to the user keyring if uid matches the current effective uid. + */ + +static key_serial_t +get_persistent_fallback(uid_t uid) +{ + return (uid == geteuid()) ? KEY_SPEC_USER_KEYRING : -1; +} + +#ifdef HAVE_PERSISTENT_KEYRING +#define GET_PERSISTENT get_persistent_real +static key_serial_t +get_persistent_real(uid_t uid) +{ + key_serial_t key; + + key = keyctl_get_persistent(uid, KEY_SPEC_PROCESS_KEYRING); + return (key == -1 && errno == ENOTSUP) ? get_persistent_fallback(uid) : + key; +} +#else +#define GET_PERSISTENT get_persistent_fallback +#endif + +/* + * If a process has no explicitly set session keyring, KEY_SPEC_SESSION_KEYRING + * will resolve to the user session keyring for ID lookup and reading, but in + * some kernel versions, writing to that special keyring will instead create a + * new empty session keyring for the process. We do not want that; the keys we + * create would be invisible to other processes. We can work around that + * behavior by explicitly writing to the user session keyring when it matches + * the session keyring. This function returns the keyring we should write to + * for the session anchor. + */ +static key_serial_t +session_write_anchor() +{ + key_serial_t s, u; + + s = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0); + u = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0); + return (s == u) ? KEY_SPEC_USER_SESSION_KEYRING : KEY_SPEC_SESSION_KEYRING; +} + +/* + * Find or create a keyring within parent with the given name. If possess is + * nonzero, also make sure the key is linked from possess. This is necessary + * to ensure that we have possession rights on the key when the parent is the + * user or persistent keyring. + */ +static krb5_error_code +find_or_create_keyring(key_serial_t parent, key_serial_t possess, + const char *name, key_serial_t *key_out) +{ + key_serial_t key; + + *key_out = -1; + key = keyctl_search(parent, KRCC_KEY_TYPE_KEYRING, name, possess); + if (key == -1) { + if (possess != 0) { + key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, possess); + if (key == -1) + return errno; + if (keyctl_link(key, parent) == -1) + return errno; + } else { + key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent); + if (key == -1) + return errno; + } + } + *key_out = key; + return 0; +} + +/* Parse a residual name into an anchor name, a collection name, and possibly a + * subsidiary name. */ +static krb5_error_code +parse_residual(const char *residual, char **anchor_name_out, + char **collection_name_out, char **subsidiary_name_out) +{ + krb5_error_code ret; + char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL; + const char *sep; + + *anchor_name_out = 0; + *collection_name_out = NULL; + *subsidiary_name_out = NULL; + + /* Parse out the anchor name. Use the legacy anchor if not present. */ + sep = strchr(residual, ':'); + if (sep == NULL) { + anchor_name = strdup(KRCC_LEGACY_ANCHOR); + if (anchor_name == NULL) + goto oom; + } else { + anchor_name = k5memdup0(residual, sep - residual, &ret); + if (anchor_name == NULL) + goto oom; + residual = sep + 1; + } + + /* Parse out the collection and subsidiary name. */ + sep = strchr(residual, ':'); + if (sep == NULL) { + collection_name = strdup(residual); + if (collection_name == NULL) + goto oom; + subsidiary_name = NULL; + } else { + collection_name = k5memdup0(residual, sep - residual, &ret); + if (collection_name == NULL) + goto oom; + subsidiary_name = strdup(sep + 1); + if (subsidiary_name == NULL) + goto oom; + } + + *anchor_name_out = anchor_name; + *collection_name_out = collection_name; + *subsidiary_name_out = subsidiary_name; + return 0; + +oom: + free(anchor_name); + free(collection_name); + free(subsidiary_name); + return ENOMEM; +} + +/* + * Return true if residual identifies a subsidiary cache which should be linked + * into the anchor so it can be visible to old code. This is the case if the + * residual has the legacy anchor and the subsidiary name matches the + * collection name. + */ +static krb5_boolean +is_legacy_cache_name(const char *residual) +{ + const char *sep, *aname, *cname, *sname; + size_t alen, clen, legacy_len = sizeof(KRCC_LEGACY_ANCHOR) - 1; + + /* Get pointers to the anchor, collection, and subsidiary names. */ + aname = residual; + sep = strchr(residual, ':'); + if (sep == NULL) + return FALSE; + alen = sep - aname; + cname = sep + 1; + sep = strchr(cname, ':'); + if (sep == NULL) + return FALSE; + clen = sep - cname; + sname = sep + 1; + + return alen == legacy_len && clen == strlen(sname) && + strncmp(aname, KRCC_LEGACY_ANCHOR, alen) == 0 && + strncmp(cname, sname, clen) == 0; +} + +/* If the default cache name for context is a KEYRING cache, parse its residual + * string. Otherwise set all outputs to NULL. */ +static krb5_error_code +get_default(krb5_context context, char **anchor_name_out, + char **collection_name_out, char **subsidiary_name_out) +{ + const char *defname; + + *anchor_name_out = *collection_name_out = *subsidiary_name_out = NULL; + defname = krb5_cc_default_name(context); + if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0) + return 0; + return parse_residual(defname + 8, anchor_name_out, collection_name_out, + subsidiary_name_out); +} + +/* Create a residual identifying a subsidiary cache. */ +static krb5_error_code +make_subsidiary_residual(const char *anchor_name, const char *collection_name, + const char *subsidiary_name, char **residual_out) +{ + if (asprintf(residual_out, "%s:%s:%s", anchor_name, collection_name, + subsidiary_name) < 0) { + *residual_out = NULL; + return ENOMEM; + } + return 0; +} + +/* Retrieve or create a keyring for collection_name within the anchor, and set + * *collection_id_out to its serial number. */ +static krb5_error_code +get_collection(const char *anchor_name, const char *collection_name, + key_serial_t *collection_id_out) +{ + krb5_error_code ret; + key_serial_t persistent_id, anchor_id, possess_id = 0; + char *ckname, *cnend; + long uidnum; + + *collection_id_out = 0; + + if (strcmp(anchor_name, KRCC_PERSISTENT_ANCHOR) == 0) { + /* + * The collection name is a uid (or empty for the current effective + * uid), and we look up a fixed keyring name within the persistent + * keyring for that uid. We link it to the process keyring to ensure + * that we have possession rights on the collection key. + */ + if (*collection_name != '\0') { + errno = 0; + uidnum = strtol(collection_name, &cnend, 10); + if (errno || *cnend != '\0') + return KRB5_KCC_INVALID_UID; + } else { + uidnum = geteuid(); + } + persistent_id = GET_PERSISTENT(uidnum); + if (persistent_id == -1) + return KRB5_KCC_INVALID_UID; + return find_or_create_keyring(persistent_id, KEY_SPEC_PROCESS_KEYRING, + KRCC_PERSISTENT_KEYRING_NAME, + collection_id_out); + } + + if (strcmp(anchor_name, KRCC_PROCESS_ANCHOR) == 0) { + anchor_id = KEY_SPEC_PROCESS_KEYRING; + } else if (strcmp(anchor_name, KRCC_THREAD_ANCHOR) == 0) { + anchor_id = KEY_SPEC_THREAD_KEYRING; + } else if (strcmp(anchor_name, KRCC_SESSION_ANCHOR) == 0) { + anchor_id = session_write_anchor(); + } else if (strcmp(anchor_name, KRCC_USER_ANCHOR) == 0) { + /* The user keyring does not confer possession, so we need to link the + * collection to the process keyring to maintain possession rights. */ + anchor_id = KEY_SPEC_USER_KEYRING; + possess_id = KEY_SPEC_PROCESS_KEYRING; + } else if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) { + anchor_id = session_write_anchor(); + } else { + return KRB5_KCC_INVALID_ANCHOR; + } + + /* Look up the collection keyring name within the anchor keyring. */ + if (asprintf(&ckname, "%s%s", KRCC_CCCOL_PREFIX, collection_name) == -1) + return ENOMEM; + ret = find_or_create_keyring(anchor_id, possess_id, ckname, + collection_id_out); + free(ckname); + return ret; +} + +/* Store subsidiary_name into the primary index key for collection_id. */ +static krb5_error_code +set_primary_name(krb5_context context, key_serial_t collection_id, + const char *subsidiary_name) +{ + key_serial_t key; + uint32_t len = strlen(subsidiary_name), plen = 8 + len; + unsigned char *payload; + + payload = malloc(plen); + if (payload == NULL) + return ENOMEM; + store_32_be(KRCC_COLLECTION_VERSION, payload); + store_32_be(len, payload + 4); + memcpy(payload + 8, subsidiary_name, len); + key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY, + payload, plen, collection_id); + free(payload); + return (key == -1) ? errno : 0; +} + +static krb5_error_code +parse_index(krb5_context context, int32_t *version, char **primary, + const unsigned char *payload, size_t psize) +{ + krb5_error_code ret; + uint32_t len; + + if (psize < 8) + return KRB5_CC_END; + + *version = load_32_be(payload); + len = load_32_be(payload + 4); + if (len > psize - 8) + return KRB5_CC_END; + *primary = k5memdup0(payload + 8, len, &ret); + return (*primary == NULL) ? ret : 0; +} + +/* + * Get or initialize the primary name within collection_id and set + * *subsidiary_out to its value. If initializing a legacy collection, look + * for a legacy cache and add it to the collection. + */ +static krb5_error_code +get_primary_name(krb5_context context, const char *anchor_name, + const char *collection_name, key_serial_t collection_id, + char **subsidiary_out) +{ + krb5_error_code ret; + key_serial_t primary_id, legacy; + void *payload = NULL; + int payloadlen; + int32_t version; + char *subsidiary_name = NULL; + + *subsidiary_out = NULL; + + primary_id = keyctl_search(collection_id, KRCC_KEY_TYPE_USER, + KRCC_COLLECTION_PRIMARY, 0); + if (primary_id == -1) { + /* Initialize the primary key using the collection name. We can't name + * a key with the empty string, so map that to an arbitrary string. */ + subsidiary_name = strdup((*collection_name == '\0') ? "tkt" : + collection_name); + if (subsidiary_name == NULL) { + ret = ENOMEM; + goto cleanup; + } + ret = set_primary_name(context, collection_id, subsidiary_name); + if (ret) + goto cleanup; + + if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) { + /* Look for a cache created by old code. If we find one, add it to + * the collection. */ + legacy = keyctl_search(KEY_SPEC_SESSION_KEYRING, + KRCC_KEY_TYPE_KEYRING, subsidiary_name, 0); + if (legacy != -1 && keyctl_link(legacy, collection_id) == -1) { + ret = errno; + goto cleanup; + } + } + } else { + /* Read, parse, and free the primary key's payload. */ + payloadlen = keyctl_read_alloc(primary_id, &payload); + if (payloadlen == -1) { + ret = errno; + goto cleanup; + } + ret = parse_index(context, &version, &subsidiary_name, payload, + payloadlen); + if (ret) + goto cleanup; + + if (version != KRCC_COLLECTION_VERSION) { + ret = KRB5_KCC_UNKNOWN_VERSION; + goto cleanup; + } + } + + *subsidiary_out = subsidiary_name; + subsidiary_name = NULL; + +cleanup: + free(payload); + free(subsidiary_name); + return ret; +} + +/* + * Create a keyring with a unique random name within collection_id. Set + * *subsidiary to its name and *cache_id_out to its key serial number. + */ +static krb5_error_code +unique_keyring(krb5_context context, key_serial_t collection_id, + char **subsidiary_out, key_serial_t *cache_id_out) +{ + key_serial_t key; + krb5_error_code ret; + char uniquename[sizeof(KRCC_NAME_PREFIX) + KRCC_NAME_RAND_CHARS]; + int prefixlen = sizeof(KRCC_NAME_PREFIX) - 1; + int tries; + + *subsidiary_out = NULL; + *cache_id_out = 0; + + memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX)); + k5_cc_mutex_lock(context, &krb5int_krcc_mutex); + + /* Loop until we successfully create a new ccache keyring with + * a unique name, or we get an error. Limit to 100 tries. */ + tries = 100; + while (tries-- > 0) { + ret = krb5int_random_string(context, uniquename + prefixlen, + KRCC_NAME_RAND_CHARS); + if (ret) + goto cleanup; + + key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename, + 0); + if (key < 0) { + /* Name does not already exist. Create it to reserve the name. */ + key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0, + collection_id); + if (key < 0) { + ret = errno; + goto cleanup; + } + break; + } + } + + if (tries <= 0) { + ret = KRB5_CC_BADNAME; + goto cleanup; + } + + *subsidiary_out = strdup(uniquename); + if (*subsidiary_out == NULL) { + ret = ENOMEM; + goto cleanup; + } + *cache_id_out = key; + ret = 0; +cleanup: + k5_cc_mutex_unlock(context, &krb5int_krcc_mutex); + return ret; +} + +static krb5_error_code +add_cred_key(const char *name, const void *payload, size_t plen, + key_serial_t cache_id, krb5_boolean legacy_type, + key_serial_t *key_out) +{ + key_serial_t key; + + *key_out = -1; + if (!legacy_type) { + /* Try the preferred cred key type; fall back if no kernel support. */ + key = add_key(KRCC_CRED_KEY_TYPE, name, payload, plen, cache_id); + if (key != -1) { + *key_out = key; + return 0; + } else if (errno != EINVAL && errno != ENODEV) { + return errno; + } + } + /* Use the user key type. */ + key = add_key(KRCC_KEY_TYPE_USER, name, payload, plen, cache_id); + if (key == -1) + return errno; + *key_out = key; + return 0; +} + +static void +update_keyring_expiration(krb5_context context, krb5_ccache id) +{ + krcc_data *data = id->data; + krb5_cc_cursor cursor; + krb5_creds creds; + krb5_timestamp now, endtime = 0; + unsigned int timeout; + + /* + * We have no way to know what is the actual timeout set on the keyring. + * We also cannot keep track of it in a local variable as another process + * can always modify the keyring independently, so just always enumerate + * all keys and find out the highest endtime time. + */ + + /* Find the maximum endtime of all creds in the cache. */ + if (krcc_start_seq_get(context, id, &cursor) != 0) + return; + for (;;) { + if (krcc_next_cred(context, id, &cursor, &creds) != 0) + break; + if (creds.times.endtime > endtime) + endtime = creds.times.endtime; + krb5_free_cred_contents(context, &creds); + } + (void)krcc_end_seq_get(context, id, &cursor); + + if (endtime == 0) /* No creds with end times */ + return; + + if (krb5_timeofday(context, &now) != 0) + return; + + /* Setting the timeout to zero would reset the timeout, so we set it to one + * second instead if creds are already expired. */ + timeout = (endtime > now) ? endtime - now : 1; + (void)keyctl_set_timeout(data->cache_id, timeout); +} + +/* Create or overwrite the cache keyring, and set the default principal. */ +static krb5_error_code KRB5_CALLCONV +krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ) +{ + krcc_data *data = (krcc_data *)id->data; + krb5_os_context os_ctx = &context->os_context; + krb5_error_code ret; + const char *cache_name, *p; + + k5_cc_mutex_lock(context, &data->lock); + + ret = clear_cache_keyring(context, id); + if (ret) + goto out; + + if (!data->cache_id) { + /* The key didn't exist at resolve time. Check again and create the + * key if it still isn't there. */ + p = strrchr(data->name, ':'); + cache_name = (p != NULL) ? p + 1 : data->name; + ret = find_or_create_keyring(data->collection_id, 0, cache_name, + &data->cache_id); + if (ret) + goto out; + } + + /* If this is the legacy cache in a legacy session collection, link it + * directly to the session keyring so that old code can see it. */ + if (is_legacy_cache_name(data->name)) + (void)keyctl_link(data->cache_id, session_write_anchor()); + + ret = save_principal(context, id, princ); + + /* Save time offset if it is valid and this is not a legacy cache. Legacy + * applications would fail to parse the new key in the cache keyring. */ + if (!is_legacy_cache_name(data->name) && + (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) { + ret = save_time_offsets(context, id, os_ctx->time_offset, + os_ctx->usec_offset); + } + + if (ret == 0) + krb5_change_cache(); + +out: + k5_cc_mutex_unlock(context, &data->lock); + return ret; +} + +/* Release the ccache handle. */ +static krb5_error_code KRB5_CALLCONV +krcc_close(krb5_context context, krb5_ccache id) +{ + krcc_data *data = id->data; + + k5_cc_mutex_destroy(&data->lock); + free(data->name); + free(data); + free(id); + return 0; +} + +/* Clear out a ccache keyring, unlinking all keys within it. Call with the + * mutex locked. */ +static krb5_error_code +clear_cache_keyring(krb5_context context, krb5_ccache id) +{ + krcc_data *data = id->data; + int res; + + k5_cc_mutex_assert_locked(context, &data->lock); + + DEBUG_PRINT(("clear_cache_keyring: cache_id %d, princ_id %d\n", + data->cache_id, data->princ_id)); + + if (data->cache_id) { + res = keyctl_clear(data->cache_id); + if (res != 0) + return errno; + } + data->princ_id = 0; + krcc_update_change_time(data); + + return 0; +} + +/* Destroy the cache keyring and release the handle. */ +static krb5_error_code KRB5_CALLCONV +krcc_destroy(krb5_context context, krb5_ccache id) +{ + krb5_error_code ret = 0; + krcc_data *data = id->data; + int res; + + k5_cc_mutex_lock(context, &data->lock); + + clear_cache_keyring(context, id); + if (data->cache_id) { + res = keyctl_unlink(data->cache_id, data->collection_id); + if (res < 0) { + ret = errno; + DEBUG_PRINT(("unlinking key %d from ring %d: %s", data->cache_id, + data->collection_id, error_message(errno))); + } + /* If this is a legacy cache, unlink it from the session anchor. */ + if (is_legacy_cache_name(data->name)) + (void)keyctl_unlink(data->cache_id, session_write_anchor()); + } + + k5_cc_mutex_unlock(context, &data->lock); + k5_cc_mutex_destroy(&data->lock); + free(data->name); + free(data); + free(id); + krb5_change_cache(); + return ret; +} + +/* Create a cache handle for a cache ID. */ +static krb5_error_code +make_cache(krb5_context context, key_serial_t collection_id, + key_serial_t cache_id, const char *anchor_name, + const char *collection_name, const char *subsidiary_name, + krb5_ccache *cache_out) +{ + krb5_error_code ret; + krb5_os_context os_ctx = &context->os_context; + krb5_ccache ccache = NULL; + krcc_data *data; + key_serial_t pkey = 0; + + /* Determine the key containing principal information, if present. */ + pkey = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, + 0); + if (pkey < 0) + pkey = 0; + + ccache = malloc(sizeof(struct _krb5_ccache)); + if (!ccache) + return ENOMEM; + + ret = make_krcc_data(anchor_name, collection_name, subsidiary_name, + cache_id, collection_id, &data); + if (ret) { + free(ccache); + return ret; + } + + data->princ_id = pkey; + ccache->ops = &krb5_krcc_ops; + ccache->data = data; + ccache->magic = KV5M_CCACHE; + *cache_out = ccache; + + /* Look up time offsets if necessary. */ + if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) && + !(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) { + if (get_time_offsets(context, ccache, &os_ctx->time_offset, + &os_ctx->usec_offset) == 0) { + os_ctx->os_flags &= ~KRB5_OS_TOFFSET_TIME; + os_ctx->os_flags |= KRB5_OS_TOFFSET_VALID; + } + } + + return 0; +} + +/* Create a keyring ccache handle for the given residual string. */ +static krb5_error_code KRB5_CALLCONV +krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual) +{ + krb5_error_code ret; + key_serial_t collection_id, cache_id; + char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL; + + ret = parse_residual(residual, &anchor_name, &collection_name, + &subsidiary_name); + if (ret) + goto cleanup; + ret = get_collection(anchor_name, collection_name, &collection_id); + if (ret) + goto cleanup; + + if (subsidiary_name == NULL) { + /* Retrieve or initialize the primary name for the collection. */ + ret = get_primary_name(context, anchor_name, collection_name, + collection_id, &subsidiary_name); + if (ret) + goto cleanup; + } + + /* Look up the cache keyring ID, if the cache is already initialized. */ + cache_id = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, + subsidiary_name, 0); + if (cache_id < 0) + cache_id = 0; + + ret = make_cache(context, collection_id, cache_id, anchor_name, + collection_name, subsidiary_name, id); + if (ret) + goto cleanup; + +cleanup: + free(anchor_name); + free(collection_name); + free(subsidiary_name); + return ret; +} + +/* Prepare for a sequential iteration over the cache keyring. */ +static krb5_error_code KRB5_CALLCONV +krcc_start_seq_get(krb5_context context, krb5_ccache id, + krb5_cc_cursor *cursor) +{ + krcc_cursor krcursor; + krcc_data *data = id->data; + void *keys; + long size; + + k5_cc_mutex_lock(context, &data->lock); + + if (!data->cache_id) { + k5_cc_mutex_unlock(context, &data->lock); + return KRB5_FCC_NOFILE; + } + + size = keyctl_read_alloc(data->cache_id, &keys); + if (size == -1) { + DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno))); + k5_cc_mutex_unlock(context, &data->lock); + return KRB5_CC_IO; + } + + krcursor = calloc(1, sizeof(*krcursor)); + if (krcursor == NULL) { + free(keys); + k5_cc_mutex_unlock(context, &data->lock); + return KRB5_CC_NOMEM; + } + + krcursor->princ_id = data->princ_id; + krcursor->offsets_id = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER, + KRCC_TIME_OFFSETS, 0); + krcursor->numkeys = size / sizeof(key_serial_t); + krcursor->keys = keys; + + k5_cc_mutex_unlock(context, &data->lock); + *cursor = krcursor; + return 0; +} + +/* Get the next credential from the cache keyring. */ +static krb5_error_code KRB5_CALLCONV +krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor, + krb5_creds *creds) +{ + krcc_cursor krcursor; + krb5_error_code ret; + int psize; + void *payload = NULL; + + memset(creds, 0, sizeof(krb5_creds)); + + /* The cursor has the entire list of keys. (Note that we don't support + * remove_cred.) */ + krcursor = *cursor; + if (krcursor == NULL) + return KRB5_CC_END; + + /* If we're pointing past the end of the keys array, there are no more. */ + if (krcursor->currkey >= krcursor->numkeys) + return KRB5_CC_END; + + /* If we're pointing at the entry with the principal, or at the key + * with the time offsets, skip it. */ + while (krcursor->keys[krcursor->currkey] == krcursor->princ_id || + krcursor->keys[krcursor->currkey] == krcursor->offsets_id) { + krcursor->currkey++; + /* Check if we have now reached the end */ + if (krcursor->currkey >= krcursor->numkeys) + return KRB5_CC_END; + } + + /* Read the key; the right size buffer will be allocated and returned. */ + psize = keyctl_read_alloc(krcursor->keys[krcursor->currkey], &payload); + if (psize == -1) { + DEBUG_PRINT(("Error reading key %d: %s\n", + krcursor->keys[krcursor->currkey], + strerror(errno))); + return KRB5_FCC_NOFILE; + } + krcursor->currkey++; + + /* Unmarshal the credential using the file ccache version 4 format. */ + ret = k5_unmarshal_cred(payload, psize, 4, creds); + free(payload); + return ret; +} + +/* Release an iteration cursor. */ +static krb5_error_code KRB5_CALLCONV +krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor) +{ + krcc_cursor krcursor = *cursor; + + if (krcursor != NULL) { + free(krcursor->keys); + free(krcursor); + } + *cursor = NULL; + return 0; +} + +/* Create keyring data for a credential cache. */ +static krb5_error_code +make_krcc_data(const char *anchor_name, const char *collection_name, + const char *subsidiary_name, key_serial_t cache_id, + key_serial_t collection_id, krcc_data **data_out) +{ + krb5_error_code ret; + krcc_data *data; + + *data_out = NULL; + + data = malloc(sizeof(krcc_data)); + if (data == NULL) + return KRB5_CC_NOMEM; + + ret = k5_cc_mutex_init(&data->lock); + if (ret) { + free(data); + return ret; + } + + ret = make_subsidiary_residual(anchor_name, collection_name, + subsidiary_name, &data->name); + if (ret) { + k5_cc_mutex_destroy(&data->lock); + free(data); + return ret; + } + data->princ_id = 0; + data->cache_id = cache_id; + data->collection_id = collection_id; + data->changetime = 0; + data->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0); + krcc_update_change_time(data); + + *data_out = data; + return 0; +} + +/* Create a new keyring cache with a unique name. */ +static krb5_error_code KRB5_CALLCONV +krcc_generate_new(krb5_context context, krb5_ccache *id_out) +{ + krb5_ccache id = NULL; + krb5_error_code ret; + char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL; + char *new_subsidiary_name = NULL, *new_residual = NULL; + krcc_data *data; + key_serial_t collection_id; + key_serial_t cache_id = 0; + + *id_out = NULL; + + /* Determine the collection in which we will create the cache.*/ + ret = get_default(context, &anchor_name, &collection_name, + &subsidiary_name); + if (ret) + return ret; + if (anchor_name == NULL) { + ret = parse_residual(KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name, + &collection_name, &subsidiary_name); + if (ret) + return ret; + } + if (subsidiary_name != NULL) { + k5_setmsg(context, KRB5_DCC_CANNOT_CREATE, + _("Can't create new subsidiary cache because default cache " + "is already a subsidiary")); + ret = KRB5_DCC_CANNOT_CREATE; + goto cleanup; + } + + /* Allocate memory */ + id = malloc(sizeof(struct _krb5_ccache)); + if (id == NULL) { + ret = ENOMEM; + goto cleanup; + } + + id->ops = &krb5_krcc_ops; + + /* Make a unique keyring within the chosen collection. */ + ret = get_collection(anchor_name, collection_name, &collection_id); + if (ret) + goto cleanup; + ret = unique_keyring(context, collection_id, &new_subsidiary_name, + &cache_id); + if (ret) + goto cleanup; + + ret = make_krcc_data(anchor_name, collection_name, new_subsidiary_name, + cache_id, collection_id, &data); + if (ret) + goto cleanup; + + id->data = data; + krb5_change_cache(); + +cleanup: + free(anchor_name); + free(collection_name); + free(subsidiary_name); + free(new_subsidiary_name); + free(new_residual); + if (ret) { + free(id); + return ret; + } + *id_out = id; + return 0; +} + +/* Return an alias to the residual string of the cache. */ +static const char *KRB5_CALLCONV +krcc_get_name(krb5_context context, krb5_ccache id) +{ + return ((krcc_data *)id->data)->name; +} + +/* Retrieve a copy of the default principal, if the cache is initialized. */ +static krb5_error_code KRB5_CALLCONV +krcc_get_principal(krb5_context context, krb5_ccache id, + krb5_principal *princ_out) +{ + krcc_data *data = id->data; + krb5_error_code ret; + void *payload = NULL; + int psize; + + *princ_out = NULL; + k5_cc_mutex_lock(context, &data->lock); + + if (!data->cache_id || !data->princ_id) { + ret = KRB5_FCC_NOFILE; + k5_setmsg(context, ret, _("Credentials cache keyring '%s' not found"), + data->name); + goto errout; + } + + psize = keyctl_read_alloc(data->princ_id, &payload); + if (psize == -1) { + DEBUG_PRINT(("Reading principal key %d: %s\n", + data->princ_id, strerror(errno))); + ret = KRB5_CC_IO; + goto errout; + } + + /* Unmarshal the principal using the file ccache version 4 format. */ + ret = k5_unmarshal_princ(payload, psize, 4, princ_out); + +errout: + free(payload); + k5_cc_mutex_unlock(context, &data->lock); + return ret; +} + +/* Search for a credential within the cache keyring. */ +static krb5_error_code KRB5_CALLCONV +krcc_retrieve(krb5_context context, krb5_ccache id, + krb5_flags whichfields, krb5_creds *mcreds, + krb5_creds *creds) +{ + return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, + creds); +} + +/* Non-functional stub for removing a cred from the cache keyring. */ +static krb5_error_code KRB5_CALLCONV +krcc_remove_cred(krb5_context context, krb5_ccache cache, + krb5_flags flags, krb5_creds *creds) +{ + return KRB5_CC_NOSUPP; +} + +/* Set flags on the cache. (We don't care about any flags.) */ +static krb5_error_code KRB5_CALLCONV +krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags) +{ + return 0; +} + +/* Get the current operational flags (of which we have none) for the cache. */ +static krb5_error_code KRB5_CALLCONV +krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags_out) +{ + *flags_out = 0; + return 0; +} + +/* Store a credential in the cache keyring. */ +static krb5_error_code KRB5_CALLCONV +krcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds) +{ + krb5_error_code ret; + krcc_data *data = id->data; + struct k5buf buf = EMPTY_K5BUF; + char *keyname = NULL; + key_serial_t cred_key; + krb5_timestamp now; + + k5_cc_mutex_lock(context, &data->lock); + + if (!data->cache_id) { + k5_cc_mutex_unlock(context, &data->lock); + return KRB5_FCC_NOFILE; + } + + /* Get the service principal name and use it as the key name */ + ret = krb5_unparse_name(context, creds->server, &keyname); + if (ret) + goto errout; + + /* Serialize credential using the file ccache version 4 format. */ + k5_buf_init_dynamic(&buf); + k5_marshal_cred(&buf, 4, creds); + ret = k5_buf_status(&buf); + if (ret) + goto errout; + + /* Add new key (credentials) into keyring */ + DEBUG_PRINT(("krcc_store: adding new key '%s' to keyring %d\n", + keyname, data->cache_id)); + ret = add_cred_key(keyname, buf.data, buf.len, data->cache_id, + data->is_legacy_type, &cred_key); + if (ret) + goto errout; + + krcc_update_change_time(data); + + /* Set appropriate timeouts on cache keys. */ + ret = krb5_timeofday(context, &now); + if (ret) + goto errout; + + if (creds->times.endtime > now) + (void)keyctl_set_timeout(cred_key, creds->times.endtime - now); + + update_keyring_expiration(context, id); + +errout: + k5_buf_free(&buf); + krb5_free_unparsed_name(context, keyname); + k5_cc_mutex_unlock(context, &data->lock); + return ret; +} + +/* Get the cache's last modification time. (This is currently broken; it + * returns only the last change made using this handle.) */ +static krb5_error_code KRB5_CALLCONV +krcc_last_change_time(krb5_context context, krb5_ccache id, + krb5_timestamp *change_time) +{ + krcc_data *data = id->data; + + k5_cc_mutex_lock(context, &data->lock); + *change_time = data->changetime; + k5_cc_mutex_unlock(context, &data->lock); + return 0; +} + +/* Lock the cache handle against other threads. (This does not lock the cache + * keyring against other processes.) */ +static krb5_error_code KRB5_CALLCONV +krcc_lock(krb5_context context, krb5_ccache id) +{ + krcc_data *data = id->data; + + k5_cc_mutex_lock(context, &data->lock); + return 0; +} + +/* Unlock the cache handle. */ +static krb5_error_code KRB5_CALLCONV +krcc_unlock(krb5_context context, krb5_ccache id) +{ + krcc_data *data = id->data; + + k5_cc_mutex_unlock(context, &data->lock); + return 0; +} + +static krb5_error_code +save_principal(krb5_context context, krb5_ccache id, krb5_principal princ) +{ + krcc_data *data = id->data; + krb5_error_code ret; + struct k5buf buf; + key_serial_t newkey; + + k5_cc_mutex_assert_locked(context, &data->lock); + + /* Serialize princ using the file ccache version 4 format. */ + k5_buf_init_dynamic(&buf); + k5_marshal_princ(&buf, 4, princ); + if (k5_buf_status(&buf) != 0) + return ENOMEM; + + /* Add new key into keyring */ +#ifdef KRCC_DEBUG + { + krb5_error_code rc; + char *princname = NULL; + rc = krb5_unparse_name(context, princ, &princname); + DEBUG_PRINT(("save_principal: adding new key '%s' " + "to keyring %d for principal '%s'\n", + KRCC_SPEC_PRINC_KEYNAME, data->cache_id, + rc ? "<unknown>" : princname)); + if (rc == 0) + krb5_free_unparsed_name(context, princname); + } +#endif + newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, buf.data, + buf.len, data->cache_id); + if (newkey < 0) { + ret = errno; + DEBUG_PRINT(("Error adding principal key: %s\n", strerror(ret))); + } else { + data->princ_id = newkey; + ret = 0; + krcc_update_change_time(data); + } + + k5_buf_free(&buf); + return ret; +} + +/* Add a key to the cache keyring containing the given time offsets. */ +static krb5_error_code +save_time_offsets(krb5_context context, krb5_ccache id, int32_t time_offset, + int32_t usec_offset) +{ + krcc_data *data = id->data; + key_serial_t newkey; + unsigned char payload[8]; + + k5_cc_mutex_assert_locked(context, &data->lock); + + /* Prepare the payload. */ + store_32_be(time_offset, payload); + store_32_be(usec_offset, payload + 4); + + /* Add new key into keyring. */ + newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload, 8, + data->cache_id); + if (newkey == -1) + return errno; + krcc_update_change_time(data); + return 0; +} + +/* Retrieve and parse the key in the cache keyring containing time offsets. */ +static krb5_error_code +get_time_offsets(krb5_context context, krb5_ccache id, int32_t *time_offset, + int32_t *usec_offset) +{ + krcc_data *data = id->data; + krb5_error_code ret = 0; + key_serial_t key; + void *payload = NULL; + int psize; + + k5_cc_mutex_lock(context, &data->lock); + + if (!data->cache_id) { + ret = KRB5_FCC_NOFILE; + goto errout; + } + + key = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, + 0); + if (key == -1) { + ret = ENOENT; + goto errout; + } + + psize = keyctl_read_alloc(key, &payload); + if (psize == -1) { + DEBUG_PRINT(("Reading time offsets key %d: %s\n", + key, strerror(errno))); + ret = KRB5_CC_IO; + goto errout; + } + + if (psize < 8) { + ret = KRB5_CC_END; + goto errout; + } + *time_offset = load_32_be(payload); + *usec_offset = load_32_be((char *)payload + 4); + +errout: + free(payload); + k5_cc_mutex_unlock(context, &data->lock); + return ret; +} + +struct krcc_ptcursor_data { + key_serial_t collection_id; + char *anchor_name; + char *collection_name; + char *subsidiary_name; + char *primary_name; + krb5_boolean first; + long num_keys; + long next_key; + key_serial_t *keys; +}; + +static krb5_error_code KRB5_CALLCONV +krcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out) +{ + struct krcc_ptcursor_data *ptd; + krb5_cc_ptcursor cursor; + krb5_error_code ret; + void *keys; + long size; + + *cursor_out = NULL; + + cursor = k5alloc(sizeof(*cursor), &ret); + if (cursor == NULL) + return ENOMEM; + ptd = k5alloc(sizeof(*ptd), &ret); + if (ptd == NULL) + goto error; + cursor->ops = &krb5_krcc_ops; + cursor->data = ptd; + ptd->first = TRUE; + + ret = get_default(context, &ptd->anchor_name, &ptd->collection_name, + &ptd->subsidiary_name); + if (ret) + goto error; + + /* If there is no default collection, return an empty cursor. */ + if (ptd->anchor_name == NULL) { + *cursor_out = cursor; + return 0; + } + + ret = get_collection(ptd->anchor_name, ptd->collection_name, + &ptd->collection_id); + if (ret) + goto error; + + if (ptd->subsidiary_name == NULL) { + ret = get_primary_name(context, ptd->anchor_name, + ptd->collection_name, ptd->collection_id, + &ptd->primary_name); + if (ret) + goto error; + + size = keyctl_read_alloc(ptd->collection_id, &keys); + if (size == -1) { + ret = errno; + goto error; + } + ptd->keys = keys; + ptd->num_keys = size / sizeof(key_serial_t); + } + + *cursor_out = cursor; + return 0; + +error: + krcc_ptcursor_free(context, &cursor); + return ret; +} + +static krb5_error_code KRB5_CALLCONV +krcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, + krb5_ccache *cache_out) +{ + krb5_error_code ret; + struct krcc_ptcursor_data *ptd = cursor->data; + key_serial_t key, cache_id = 0; + const char *first_name, *keytype, *sep, *subsidiary_name; + size_t keytypelen; + char *description = NULL; + + *cache_out = NULL; + + /* No keyring available */ + if (ptd->collection_id == 0) + return 0; + + if (ptd->first) { + /* Look for the primary cache for a collection cursor, or the + * subsidiary cache for a subsidiary cursor. */ + ptd->first = FALSE; + first_name = (ptd->primary_name != NULL) ? ptd->primary_name : + ptd->subsidiary_name; + cache_id = keyctl_search(ptd->collection_id, KRCC_KEY_TYPE_KEYRING, + first_name, 0); + if (cache_id != -1) { + return make_cache(context, ptd->collection_id, cache_id, + ptd->anchor_name, ptd->collection_name, + first_name, cache_out); + } + } + + /* A subsidiary cursor yields at most the first cache. */ + if (ptd->subsidiary_name != NULL) + return 0; + + keytype = KRCC_KEY_TYPE_KEYRING ";"; + keytypelen = strlen(keytype); + + for (; ptd->next_key < ptd->num_keys; ptd->next_key++) { + /* Free any previously retrieved key description. */ + free(description); + description = NULL; + + /* + * Get the key description, which should have the form: + * typename;UID;GID;permissions;description + */ + key = ptd->keys[ptd->next_key]; + if (keyctl_describe_alloc(key, &description) < 0) + continue; + sep = strrchr(description, ';'); + if (sep == NULL) + continue; + subsidiary_name = sep + 1; + + /* Skip this key if it isn't a keyring. */ + if (strncmp(description, keytype, keytypelen) != 0) + continue; + + /* Don't repeat the primary cache. */ + if (strcmp(subsidiary_name, ptd->primary_name) == 0) + continue; + + /* We found a valid key */ + ptd->next_key++; + ret = make_cache(context, ptd->collection_id, key, ptd->anchor_name, + ptd->collection_name, subsidiary_name, cache_out); + free(description); + return ret; + } + + free(description); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor) +{ + struct krcc_ptcursor_data *ptd = (*cursor)->data; + + if (ptd != NULL) { + free(ptd->anchor_name); + free(ptd->collection_name); + free(ptd->subsidiary_name); + free(ptd->primary_name); + free(ptd->keys); + free(ptd); + } + free(*cursor); + *cursor = NULL; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +krcc_switch_to(krb5_context context, krb5_ccache cache) +{ + krcc_data *data = cache->data; + krb5_error_code ret; + char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL; + key_serial_t collection_id; + + ret = parse_residual(data->name, &anchor_name, &collection_name, + &subsidiary_name); + if (ret) + goto cleanup; + ret = get_collection(anchor_name, collection_name, &collection_id); + if (ret) + goto cleanup; + ret = set_primary_name(context, collection_id, subsidiary_name); + +cleanup: + free(anchor_name); + free(collection_name); + free(subsidiary_name); + return ret; +} + +/* + * Utility routine: called by krcc_* functions to keep + * result of krcc_last_change_time up to date. + * Value monotonically increases -- based on but not guaranteed to be actual + * system time. + */ + +static void +krcc_update_change_time(krcc_data *data) +{ + krb5_timestamp now_time = time(NULL); + data->changetime = (data->changetime >= now_time) ? + data->changetime + 1 : now_time; +} + +/* + * ccache implementation storing credentials in the Linux keyring facility + * The default is to put them at the session keyring level. + * If "KEYRING:process:" or "KEYRING:thread:" is specified, then they will + * be stored at the process or thread level respectively. + */ +const krb5_cc_ops krb5_krcc_ops = { + 0, + "KEYRING", + krcc_get_name, + krcc_resolve, + krcc_generate_new, + krcc_initialize, + krcc_destroy, + krcc_close, + krcc_store, + krcc_retrieve, + krcc_get_principal, + krcc_start_seq_get, + krcc_next_cred, + krcc_end_seq_get, + krcc_remove_cred, + krcc_set_flags, + krcc_get_flags, /* added after 1.4 release */ + krcc_ptcursor_new, + krcc_ptcursor_next, + krcc_ptcursor_free, + NULL, /* move */ + krcc_last_change_time, /* lastchange */ + NULL, /* wasdefault */ + krcc_lock, + krcc_unlock, + krcc_switch_to, +}; + +#else /* !USE_KEYRING_CCACHE */ + +/* + * Export this, but it shouldn't be used. + */ +const krb5_cc_ops krb5_krcc_ops = { + 0, + "KEYRING", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, /* added after 1.4 release */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; +#endif /* USE_KEYRING_CCACHE */ diff --git a/src/lib/krb5/ccache/cc_memory.c b/src/lib/krb5/ccache/cc_memory.c new file mode 100644 index 000000000000..0354575c5c16 --- /dev/null +++ b/src/lib/krb5/ccache/cc_memory.c @@ -0,0 +1,772 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/cc_memory.c - Memory-based credential cache */ +/* + * Copyright 1990,1991,2000,2004,2008 by the Massachusetts Institute of + * Technology. All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include "cc-int.h" +#include "../krb/int-proto.h" +#include <errno.h> + +static krb5_error_code KRB5_CALLCONV krb5_mcc_close +(krb5_context, krb5_ccache id ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_destroy +(krb5_context, krb5_ccache id ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_end_seq_get +(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_generate_new +(krb5_context, krb5_ccache *id ); + +static const char * KRB5_CALLCONV krb5_mcc_get_name +(krb5_context, krb5_ccache id ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_get_principal +(krb5_context, krb5_ccache id , krb5_principal *princ ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_initialize +(krb5_context, krb5_ccache id , krb5_principal princ ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_next_cred +(krb5_context, + krb5_ccache id , + krb5_cc_cursor *cursor , + krb5_creds *creds ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_resolve +(krb5_context, krb5_ccache *id , const char *residual ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_retrieve +(krb5_context, + krb5_ccache id , + krb5_flags whichfields , + krb5_creds *mcreds , + krb5_creds *creds ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_start_seq_get +(krb5_context, krb5_ccache id , krb5_cc_cursor *cursor ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_store +(krb5_context, krb5_ccache id , krb5_creds *creds ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_set_flags +(krb5_context, krb5_ccache id , krb5_flags flags ); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_ptcursor_new +(krb5_context, krb5_cc_ptcursor *); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_ptcursor_next +(krb5_context, krb5_cc_ptcursor, krb5_ccache *); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_ptcursor_free +(krb5_context, krb5_cc_ptcursor *); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_last_change_time +(krb5_context, krb5_ccache, krb5_timestamp *); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_lock +(krb5_context context, krb5_ccache id); + +static krb5_error_code KRB5_CALLCONV krb5_mcc_unlock +(krb5_context context, krb5_ccache id); + + +extern const krb5_cc_ops krb5_mcc_ops; +extern krb5_error_code krb5_change_cache (void); + +#define KRB5_OK 0 + +/* Individual credentials within a cache, in a linked list. */ +typedef struct _krb5_mcc_link { + struct _krb5_mcc_link *next; + krb5_creds *creds; +} krb5_mcc_link, *krb5_mcc_cursor; + +/* Per-cache data header. */ +typedef struct _krb5_mcc_data { + char *name; + k5_cc_mutex lock; + krb5_principal prin; + krb5_mcc_cursor link; + krb5_timestamp changetime; + /* Time offsets for clock-skewed clients. */ + krb5_int32 time_offset; + krb5_int32 usec_offset; +} krb5_mcc_data; + +/* List of memory caches. */ +typedef struct krb5_mcc_list_node { + struct krb5_mcc_list_node *next; + krb5_mcc_data *cache; +} krb5_mcc_list_node; + +/* Iterator over memory caches. */ +struct krb5_mcc_ptcursor_data { + struct krb5_mcc_list_node *cur; +}; + +k5_cc_mutex krb5int_mcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER; +static krb5_mcc_list_node *mcc_head = 0; + +static void update_mcc_change_time(krb5_mcc_data *); + +static void krb5_mcc_free (krb5_context context, krb5_ccache id); + +/* + * Modifies: + * id + * + * Effects: + * Creates/refreshes the memory cred cache id. If the cache exists, its + * contents are destroyed. + * + * Errors: + * system errors + */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ) +{ + krb5_os_context os_ctx = &context->os_context; + krb5_error_code ret; + krb5_mcc_data *d; + + d = (krb5_mcc_data *)id->data; + k5_cc_mutex_lock(context, &d->lock); + + krb5_mcc_free(context, id); + + d = (krb5_mcc_data *)id->data; + ret = krb5_copy_principal(context, princ, + &d->prin); + update_mcc_change_time(d); + + if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) { + /* Store client time offsets in the cache */ + d->time_offset = os_ctx->time_offset; + d->usec_offset = os_ctx->usec_offset; + } + + k5_cc_mutex_unlock(context, &d->lock); + if (ret == KRB5_OK) + krb5_change_cache(); + return ret; +} + +/* + * Modifies: + * id + * + * Effects: + * Invalidates the id, and frees any resources associated with accessing + * the cache. + */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_close(krb5_context context, krb5_ccache id) +{ + free(id); + return KRB5_OK; +} + +static void +krb5_mcc_free(krb5_context context, krb5_ccache id) +{ + krb5_mcc_cursor curr,next; + krb5_mcc_data *d; + + d = (krb5_mcc_data *) id->data; + for (curr = d->link; curr;) { + krb5_free_creds(context, curr->creds); + next = curr->next; + free(curr); + curr = next; + } + d->link = NULL; + krb5_free_principal(context, d->prin); +} + +/* + * Effects: + * Destroys the contents of id. id is invalid after call. + * + * Errors: + * system errors (locks related) + */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_destroy(krb5_context context, krb5_ccache id) +{ + krb5_mcc_list_node **curr, *node; + krb5_mcc_data *d; + + k5_cc_mutex_lock(context, &krb5int_mcc_mutex); + + d = (krb5_mcc_data *)id->data; + for (curr = &mcc_head; *curr; curr = &(*curr)->next) { + if ((*curr)->cache == d) { + node = *curr; + *curr = node->next; + free(node); + break; + } + } + k5_cc_mutex_unlock(context, &krb5int_mcc_mutex); + + k5_cc_mutex_lock(context, &d->lock); + + krb5_mcc_free(context, id); + free(d->name); + k5_cc_mutex_unlock(context, &d->lock); + k5_cc_mutex_destroy(&d->lock); + free(d); + free(id); + + krb5_change_cache (); + return KRB5_OK; +} + +/* + * Requires: + * residual is a legal path name, and a null-terminated string + * + * Modifies: + * id + * + * Effects: + * creates or accesses a memory-based cred cache that is referenced by + * residual. + * + * Returns: + * A filled in krb5_ccache structure "id". + * + * Errors: + * KRB5_CC_NOMEM - there was insufficient memory to allocate the + * krb5_ccache. id is undefined. + * system errors (mutex locks related) + */ +static krb5_error_code new_mcc_data (const char *, krb5_mcc_data **); + +krb5_error_code KRB5_CALLCONV +krb5_mcc_resolve (krb5_context context, krb5_ccache *id, const char *residual) +{ + krb5_os_context os_ctx = &context->os_context; + krb5_ccache lid; + krb5_mcc_list_node *ptr; + krb5_error_code err; + krb5_mcc_data *d; + + k5_cc_mutex_lock(context, &krb5int_mcc_mutex); + for (ptr = mcc_head; ptr; ptr=ptr->next) + if (!strcmp(ptr->cache->name, residual)) + break; + if (ptr) + d = ptr->cache; + else { + err = new_mcc_data(residual, &d); + if (err) { + k5_cc_mutex_unlock(context, &krb5int_mcc_mutex); + return err; + } + } + k5_cc_mutex_unlock(context, &krb5int_mcc_mutex); + + lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache)); + if (lid == NULL) + return KRB5_CC_NOMEM; + + if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) && + !(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) { + /* Use the time offset from the cache entry */ + os_ctx->time_offset = d->time_offset; + os_ctx->usec_offset = d->usec_offset; + os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) | + KRB5_OS_TOFFSET_VALID); + } + + lid->ops = &krb5_mcc_ops; + lid->data = d; + *id = lid; + return KRB5_OK; +} + +/* + * Effects: + * Prepares for a sequential search of the credentials cache. + * Returns a krb5_cc_cursor to be used with krb5_mcc_next_cred and + * krb5_mcc_end_seq_get. + * + * If the cache is modified between the time of this call and the time + * of the final krb5_mcc_end_seq_get, the results are undefined. + * + * Errors: + * KRB5_CC_NOMEM + * system errors + */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_start_seq_get(krb5_context context, krb5_ccache id, + krb5_cc_cursor *cursor) +{ + krb5_mcc_cursor mcursor; + krb5_mcc_data *d; + + d = id->data; + k5_cc_mutex_lock(context, &d->lock); + mcursor = d->link; + k5_cc_mutex_unlock(context, &d->lock); + *cursor = (krb5_cc_cursor) mcursor; + return KRB5_OK; +} + +/* + * Requires: + * cursor is a krb5_cc_cursor originally obtained from + * krb5_mcc_start_seq_get. + * + * Modifes: + * cursor, creds + * + * Effects: + * Fills in creds with the "next" credentals structure from the cache + * id. The actual order the creds are returned in is arbitrary. + * Space is allocated for the variable length fields in the + * credentials structure, so the object returned must be passed to + * krb5_destroy_credential. + * + * The cursor is updated for the next call to krb5_mcc_next_cred. + * + * Errors: + * system errors + */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_next_cred(krb5_context context, krb5_ccache id, + krb5_cc_cursor *cursor, krb5_creds *creds) +{ + krb5_mcc_cursor mcursor; + krb5_error_code retval; + + /* Once the node in the linked list is created, it's never + modified, so we don't need to worry about locking here. (Note + that we don't support _remove_cred.) */ + mcursor = (krb5_mcc_cursor) *cursor; + if (mcursor == NULL) + return KRB5_CC_END; + memset(creds, 0, sizeof(krb5_creds)); + if (mcursor->creds) { + retval = k5_copy_creds_contents(context, mcursor->creds, creds); + if (retval) + return retval; + } + *cursor = (krb5_cc_cursor)mcursor->next; + return KRB5_OK; +} + +/* + * Requires: + * cursor is a krb5_cc_cursor originally obtained from + * krb5_mcc_start_seq_get. + * + * Modifies: + * id, cursor + * + * Effects: + * Finishes sequential processing of the memory credentials ccache id, + * and invalidates the cursor (it must never be used after this call). + */ +/* ARGSUSED */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor) +{ + *cursor = 0L; + return KRB5_OK; +} + +/* Utility routine: Creates the back-end data for a memory cache, and + threads it into the global linked list. + + Call with the global list lock held. */ +static krb5_error_code +new_mcc_data (const char *name, krb5_mcc_data **dataptr) +{ + krb5_error_code err; + krb5_mcc_data *d; + krb5_mcc_list_node *n; + + d = malloc(sizeof(krb5_mcc_data)); + if (d == NULL) + return KRB5_CC_NOMEM; + + err = k5_cc_mutex_init(&d->lock); + if (err) { + free(d); + return err; + } + + d->name = strdup(name); + if (d->name == NULL) { + k5_cc_mutex_destroy(&d->lock); + free(d); + return KRB5_CC_NOMEM; + } + d->link = NULL; + d->prin = NULL; + d->changetime = 0; + d->time_offset = 0; + d->usec_offset = 0; + update_mcc_change_time(d); + + n = malloc(sizeof(krb5_mcc_list_node)); + if (n == NULL) { + free(d->name); + k5_cc_mutex_destroy(&d->lock); + free(d); + return KRB5_CC_NOMEM; + } + + n->cache = d; + n->next = mcc_head; + mcc_head = n; + + *dataptr = d; + return 0; +} + +/* + * Effects: + * Creates a new memory cred cache whose name is guaranteed to be + * unique. The name begins with the string TKT_ROOT (from mcc.h). + * + * Returns: + * The filled in krb5_ccache id. + * + * Errors: + * KRB5_CC_NOMEM - there was insufficient memory to allocate the + * krb5_ccache. id is undefined. + * system errors (from open, mutex locking) + */ + +krb5_error_code KRB5_CALLCONV +krb5_mcc_generate_new (krb5_context context, krb5_ccache *id) +{ + krb5_ccache lid; + char uniquename[8]; + krb5_error_code err; + krb5_mcc_data *d; + + /* Allocate memory */ + lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache)); + if (lid == NULL) + return KRB5_CC_NOMEM; + + lid->ops = &krb5_mcc_ops; + + k5_cc_mutex_lock(context, &krb5int_mcc_mutex); + + /* Check for uniqueness with mutex locked to avoid race conditions */ + while (1) { + krb5_mcc_list_node *ptr; + + err = krb5int_random_string (context, uniquename, sizeof (uniquename)); + if (err) { + k5_cc_mutex_unlock(context, &krb5int_mcc_mutex); + free(lid); + return err; + } + + for (ptr = mcc_head; ptr; ptr=ptr->next) { + if (!strcmp(ptr->cache->name, uniquename)) { + break; /* got a match, loop again */ + } + } + if (!ptr) break; /* got to the end without finding a match */ + } + + err = new_mcc_data(uniquename, &d); + + k5_cc_mutex_unlock(context, &krb5int_mcc_mutex); + if (err) { + free(lid); + return err; + } + lid->data = d; + *id = lid; + krb5_change_cache (); + return KRB5_OK; +} + +/* + * Requires: + * id is a file credential cache + * + * Returns: + * A pointer to the name of the file cred cache id. + */ +const char * KRB5_CALLCONV +krb5_mcc_get_name (krb5_context context, krb5_ccache id) +{ + return (char *) ((krb5_mcc_data *) id->data)->name; +} + +/* + * Modifies: + * id, princ + * + * Effects: + * Retrieves the primary principal from id, as set with + * krb5_mcc_initialize. The principal is returned is allocated + * storage that must be freed by the caller via krb5_free_principal. + * + * Errors: + * system errors + * ENOMEM + */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ) +{ + krb5_mcc_data *ptr = (krb5_mcc_data *)id->data; + if (!ptr->prin) { + *princ = 0L; + return KRB5_FCC_NOFILE; + } + return krb5_copy_principal(context, ptr->prin, princ); +} + +krb5_error_code KRB5_CALLCONV +krb5_mcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields, + krb5_creds *mcreds, krb5_creds *creds) +{ + return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, + creds); +} + +/* + * Non-functional stub implementation for krb5_mcc_remove + * + * Errors: + * KRB5_CC_NOSUPP - not implemented + */ +static krb5_error_code KRB5_CALLCONV +krb5_mcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags, + krb5_creds *creds) +{ + return KRB5_CC_NOSUPP; +} + + +/* + * Requires: + * id is a cred cache returned by krb5_mcc_resolve or + * krb5_mcc_generate_new. + * + * Modifies: + * id + * + * Effects: + * Sets the operational flags of id to flags. + */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags) +{ + return KRB5_OK; +} + +static krb5_error_code KRB5_CALLCONV +krb5_mcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags) +{ + *flags = 0; + return KRB5_OK; +} + +/* + * Modifies: + * the memory cache + * + * Effects: + * Save away creds in the ccache. + * + * Errors: + * system errors (mutex locking) + * ENOMEM + */ +krb5_error_code KRB5_CALLCONV +krb5_mcc_store(krb5_context ctx, krb5_ccache id, krb5_creds *creds) +{ + krb5_error_code err; + krb5_mcc_link *new_node; + krb5_mcc_data *mptr = (krb5_mcc_data *)id->data; + + new_node = malloc(sizeof(krb5_mcc_link)); + if (new_node == NULL) + return ENOMEM; + err = krb5_copy_creds(ctx, creds, &new_node->creds); + if (err) + goto cleanup; + k5_cc_mutex_lock(ctx, &mptr->lock); + new_node->next = mptr->link; + mptr->link = new_node; + update_mcc_change_time(mptr); + k5_cc_mutex_unlock(ctx, &mptr->lock); + return 0; +cleanup: + free(new_node); + return err; +} + +static krb5_error_code KRB5_CALLCONV +krb5_mcc_ptcursor_new( + krb5_context context, + krb5_cc_ptcursor *cursor) +{ + krb5_cc_ptcursor n = NULL; + struct krb5_mcc_ptcursor_data *cdata = NULL; + + *cursor = NULL; + + n = malloc(sizeof(*n)); + if (n == NULL) + return ENOMEM; + n->ops = &krb5_mcc_ops; + cdata = malloc(sizeof(struct krb5_mcc_ptcursor_data)); + if (cdata == NULL) { + free(n); + return ENOMEM; + } + n->data = cdata; + k5_cc_mutex_lock(context, &krb5int_mcc_mutex); + cdata->cur = mcc_head; + k5_cc_mutex_unlock(context, &krb5int_mcc_mutex); + *cursor = n; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +krb5_mcc_ptcursor_next( + krb5_context context, + krb5_cc_ptcursor cursor, + krb5_ccache *ccache) +{ + struct krb5_mcc_ptcursor_data *cdata = NULL; + + *ccache = NULL; + cdata = cursor->data; + if (cdata->cur == NULL) + return 0; + + *ccache = malloc(sizeof(**ccache)); + if (*ccache == NULL) + return ENOMEM; + + (*ccache)->ops = &krb5_mcc_ops; + (*ccache)->data = cdata->cur->cache; + k5_cc_mutex_lock(context, &krb5int_mcc_mutex); + cdata->cur = cdata->cur->next; + k5_cc_mutex_unlock(context, &krb5int_mcc_mutex); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +krb5_mcc_ptcursor_free( + krb5_context context, + krb5_cc_ptcursor *cursor) +{ + if (*cursor == NULL) + return 0; + if ((*cursor)->data != NULL) + free((*cursor)->data); + free(*cursor); + *cursor = NULL; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +krb5_mcc_last_change_time( + krb5_context context, + krb5_ccache id, + krb5_timestamp *change_time) +{ + krb5_mcc_data *data = (krb5_mcc_data *) id->data; + + k5_cc_mutex_lock(context, &data->lock); + *change_time = data->changetime; + k5_cc_mutex_unlock(context, &data->lock); + return 0; +} + +/* + Utility routine: called by krb5_mcc_* functions to keep + result of krb5_mcc_last_change_time up to date +*/ + +static void +update_mcc_change_time(krb5_mcc_data *d) +{ + krb5_timestamp now_time = time(NULL); + d->changetime = (d->changetime >= now_time) ? + d->changetime + 1 : now_time; +} + +static krb5_error_code KRB5_CALLCONV +krb5_mcc_lock(krb5_context context, krb5_ccache id) +{ + krb5_mcc_data *data = (krb5_mcc_data *) id->data; + + k5_cc_mutex_lock(context, &data->lock); + return 0; +} + +static krb5_error_code KRB5_CALLCONV +krb5_mcc_unlock(krb5_context context, krb5_ccache id) +{ + krb5_mcc_data *data = (krb5_mcc_data *) id->data; + + k5_cc_mutex_unlock(context, &data->lock); + return 0; +} + +const krb5_cc_ops krb5_mcc_ops = { + 0, + "MEMORY", + krb5_mcc_get_name, + krb5_mcc_resolve, + krb5_mcc_generate_new, + krb5_mcc_initialize, + krb5_mcc_destroy, + krb5_mcc_close, + krb5_mcc_store, + krb5_mcc_retrieve, + krb5_mcc_get_principal, + krb5_mcc_start_seq_get, + krb5_mcc_next_cred, + krb5_mcc_end_seq_get, + krb5_mcc_remove_cred, + krb5_mcc_set_flags, + krb5_mcc_get_flags, + krb5_mcc_ptcursor_new, + krb5_mcc_ptcursor_next, + krb5_mcc_ptcursor_free, + NULL, /* move */ + krb5_mcc_last_change_time, + NULL, /* wasdefault */ + krb5_mcc_lock, + krb5_mcc_unlock, + NULL, /* switch_to */ +}; diff --git a/src/lib/krb5/ccache/cc_mslsa.c b/src/lib/krb5/ccache/cc_mslsa.c new file mode 100644 index 000000000000..7a8047023716 --- /dev/null +++ b/src/lib/krb5/ccache/cc_mslsa.c @@ -0,0 +1,2209 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/cc_mslsa.c */ +/* + * Copyright 2007 Secure Endpoints Inc. + * + * Copyright 2003,2004 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * Copyright 2000 by Carnegie Mellon University + * + * All Rights Reserved + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation, and that the name of Carnegie Mellon + * University not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE FOR + * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Implementation of microsoft windows lsa credentials cache + */ + +#ifdef _WIN32 +#define UNICODE +#define _UNICODE + +#include <ntstatus.h> +#define WIN32_NO_STATUS +#include "k5-int.h" +#include "com_err.h" +#include "cc-int.h" + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <conio.h> +#include <time.h> + +#define SECURITY_WIN32 +#include <security.h> +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 +#include <ntsecapi.h> + + +#define MAX_MSG_SIZE 256 +#define MAX_MSPRINC_SIZE 1024 + +/* THREAD SAFETY + * The function does_query_ticket_cache_ex2() + * contains static variables to cache the responses of the tests being + * performed. There is no harm in the test being performed more than + * once since the result will always be the same. + */ + +typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); + +static VOID +ShowWinError(LPSTR szAPI, DWORD dwError) +{ + + // TODO - Write errors to event log so that scripts that don't + // check for errors will still get something in the event log + + // This code is completely unsafe for use on non-English systems + // Any call to this function will result in the FormatMessage + // call failing and the program terminating. This might have + // been acceptable when this code was part of ms2mit.exe as + // a standalone executable but it is not appropriate for a library + +#ifdef COMMENT + WCHAR szMsgBuf[MAX_MSG_SIZE]; + DWORD dwRes; + + printf("Error calling function %s: %lu\n", szAPI, dwError); + + dwRes = FormatMessage ( + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + dwError, + MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US), + szMsgBuf, + MAX_MSG_SIZE, + NULL); + if (0 == dwRes) { + printf("FormatMessage failed with %d\n", GetLastError()); + ExitProcess(EXIT_FAILURE); + } + + printf("%S",szMsgBuf); +#endif /* COMMENT */ +} + +static VOID +ShowLsaError(LPSTR szAPI, NTSTATUS Status) +{ + // + // Convert the NTSTATUS to Winerror. Then call ShowWinError(). + // + ShowWinError(szAPI, LsaNtStatusToWinError(Status)); +} + +static BOOL +WINAPI +UnicodeToANSI(LPTSTR lpInputString, LPSTR lpszOutputString, int nOutStringLen) +{ + CPINFO CodePageInfo; + + GetCPInfo(CP_ACP, &CodePageInfo); + + if (CodePageInfo.MaxCharSize > 1) { + // Only supporting non-Unicode strings + int reqLen = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR) lpInputString, -1, + NULL, 0, NULL, NULL); + if ( reqLen > nOutStringLen) + { + return FALSE; + } else { + if (WideCharToMultiByte(CP_ACP, + /* WC_NO_BEST_FIT_CHARS | */ WC_COMPOSITECHECK, + (LPCWSTR) lpInputString, -1, + lpszOutputString, + nOutStringLen, NULL, NULL) == 0) + return FALSE; + } + } + else + { + // Looks like unicode, better translate it + if (WideCharToMultiByte(CP_ACP, + /* WC_NO_BEST_FIT_CHARS | */ WC_COMPOSITECHECK, + (LPCWSTR) lpInputString, -1, + lpszOutputString, + nOutStringLen, NULL, NULL) == 0) + return FALSE; + } + + return TRUE; +} // UnicodeToANSI + +static VOID +WINAPI +ANSIToUnicode(LPCSTR lpInputString, LPWSTR lpszOutputString, int nOutStringLen) +{ + + CPINFO CodePageInfo; + + GetCPInfo(CP_ACP, &CodePageInfo); + + MultiByteToWideChar(CP_ACP, 0, lpInputString, -1, + lpszOutputString, nOutStringLen); +} // ANSIToUnicode + + +static void +MITPrincToMSPrinc(krb5_context context, krb5_principal principal, UNICODE_STRING * msprinc) +{ + char *aname = NULL; + + if (!krb5_unparse_name(context, principal, &aname)) { + msprinc->Length = strlen(aname) * sizeof(WCHAR); + if ( msprinc->Length <= msprinc->MaximumLength ) + ANSIToUnicode(aname, msprinc->Buffer, msprinc->MaximumLength); + else + msprinc->Length = 0; + krb5_free_unparsed_name(context,aname); + } +} + +static BOOL +UnicodeStringToMITPrinc(UNICODE_STRING *service, UNICODE_STRING *realm, + krb5_context context, krb5_principal *principal) +{ + WCHAR princbuf[512]; + WCHAR realmbuf[512]; + char aname[512]; + + /* Convert the realm to a wchar string. */ + realmbuf[0] = '\0'; + wcsncpy(realmbuf, realm->Buffer, realm->Length / sizeof(WCHAR)); + realmbuf[realm->Length / sizeof(WCHAR)] = 0; + /* Convert the principal components to a wchar string. */ + princbuf[0]=0; + wcsncpy(princbuf, service->Buffer, service->Length/sizeof(WCHAR)); + princbuf[service->Length/sizeof(WCHAR)]=0; + wcscat(princbuf, L"@"); + wcscat(princbuf, realmbuf); + if (UnicodeToANSI(princbuf, aname, sizeof(aname))) { + if (krb5_parse_name(context, aname, principal) == 0) + return TRUE; + } + return FALSE; +} + + +static BOOL +KerbExternalNameToMITPrinc(KERB_EXTERNAL_NAME *msprinc, WCHAR *realm, krb5_context context, + krb5_principal *principal) +{ + WCHAR princbuf[512],tmpbuf[128]; + char aname[512]; + USHORT i; + princbuf[0]=0; + for (i=0;i<msprinc->NameCount;i++) { + wcsncpy(tmpbuf, msprinc->Names[i].Buffer, + msprinc->Names[i].Length/sizeof(WCHAR)); + tmpbuf[msprinc->Names[i].Length/sizeof(WCHAR)]=0; + if (princbuf[0]) + wcscat(princbuf, L"/"); + wcscat(princbuf, tmpbuf); + } + wcscat(princbuf, L"@"); + wcscat(princbuf, realm); + if (UnicodeToANSI(princbuf, aname, sizeof(aname))) { + if (krb5_parse_name(context, aname, principal) == 0) + return TRUE; + } + return FALSE; +} + +static time_t +FileTimeToUnixTime(LARGE_INTEGER *ltime) +{ + FILETIME filetime, localfiletime; + SYSTEMTIME systime; + struct tm utime; + filetime.dwLowDateTime=ltime->LowPart; + filetime.dwHighDateTime=ltime->HighPart; + FileTimeToLocalFileTime(&filetime, &localfiletime); + FileTimeToSystemTime(&localfiletime, &systime); + utime.tm_sec=systime.wSecond; + utime.tm_min=systime.wMinute; + utime.tm_hour=systime.wHour; + utime.tm_mday=systime.wDay; + utime.tm_mon=systime.wMonth-1; + utime.tm_year=systime.wYear-1900; + utime.tm_isdst=-1; + return(mktime(&utime)); +} + +static void +MSSessionKeyToMITKeyblock(KERB_CRYPTO_KEY *mskey, krb5_context context, krb5_keyblock *keyblock) +{ + krb5_keyblock tmpblock; + tmpblock.magic=KV5M_KEYBLOCK; + tmpblock.enctype=mskey->KeyType; + tmpblock.length=mskey->Length; + tmpblock.contents=mskey->Value; + krb5_copy_keyblock_contents(context, &tmpblock, keyblock); +} + +static BOOL +IsMSSessionKeyNull(KERB_CRYPTO_KEY *mskey) +{ + DWORD i; + + if (mskey->KeyType == KERB_ETYPE_NULL) + return TRUE; + + for ( i=0; i<mskey->Length; i++ ) { + if (mskey->Value[i]) + return FALSE; + } + + return TRUE; +} + +static void +MSFlagsToMITFlags(ULONG msflags, ULONG *mitflags) +{ + *mitflags=msflags; +} + +static BOOL +MSTicketToMITTicket(KERB_EXTERNAL_TICKET *msticket, krb5_context context, krb5_data *ticket) +{ + krb5_data tmpdata, *newdata = 0; + krb5_error_code rc; + + tmpdata.magic=KV5M_DATA; + tmpdata.length=msticket->EncodedTicketSize; + tmpdata.data=msticket->EncodedTicket; + + // this is ugly and will break krb5_free_data() + // now that this is being done within the library it won't break krb5_free_data() + rc = krb5_copy_data(context, &tmpdata, &newdata); + if (rc) + return FALSE; + + memcpy(ticket, newdata, sizeof(krb5_data)); + free(newdata); + return TRUE; +} + +static BOOL +MSCredToMITCred(KERB_EXTERNAL_TICKET *msticket, UNICODE_STRING ClientRealm, + krb5_context context, krb5_creds *creds) +{ + WCHAR wrealm[128]; + ZeroMemory(creds, sizeof(krb5_creds)); + creds->magic=KV5M_CREDS; + + // construct Client Principal + wcsncpy(wrealm, ClientRealm.Buffer, ClientRealm.Length/sizeof(WCHAR)); + wrealm[ClientRealm.Length/sizeof(WCHAR)]=0; + if (!KerbExternalNameToMITPrinc(msticket->ClientName, wrealm, context, &creds->client)) + return FALSE; + + // construct Service Principal + wcsncpy(wrealm, msticket->DomainName.Buffer, + msticket->DomainName.Length/sizeof(WCHAR)); + wrealm[msticket->DomainName.Length/sizeof(WCHAR)]=0; + if (!KerbExternalNameToMITPrinc(msticket->ServiceName, wrealm, context, &creds->server)) + return FALSE; + MSSessionKeyToMITKeyblock(&msticket->SessionKey, context, + &creds->keyblock); + MSFlagsToMITFlags(msticket->TicketFlags, &creds->ticket_flags); + creds->times.starttime=FileTimeToUnixTime(&msticket->StartTime); + creds->times.endtime=FileTimeToUnixTime(&msticket->EndTime); + creds->times.renew_till=FileTimeToUnixTime(&msticket->RenewUntil); + + creds->addresses = NULL; + + return MSTicketToMITTicket(msticket, context, &creds->ticket); +} + +/* CacheInfoEx2ToMITCred is used when we do not need the real ticket */ +static BOOL +CacheInfoEx2ToMITCred(KERB_TICKET_CACHE_INFO_EX2 *info, + krb5_context context, krb5_creds *creds) +{ + ZeroMemory(creds, sizeof(krb5_creds)); + creds->magic=KV5M_CREDS; + + // construct Client Principal + if (!UnicodeStringToMITPrinc(&info->ClientName, &info->ClientRealm, + context, &creds->client)) + return FALSE; + + // construct Service Principal + if (!UnicodeStringToMITPrinc(&info->ServerName, &info->ServerRealm, + context, &creds->server)) + return FALSE; + + creds->keyblock.magic = KV5M_KEYBLOCK; + creds->keyblock.enctype = info->SessionKeyType; + creds->ticket_flags = info->TicketFlags; + MSFlagsToMITFlags(info->TicketFlags, &creds->ticket_flags); + creds->times.starttime=FileTimeToUnixTime(&info->StartTime); + creds->times.endtime=FileTimeToUnixTime(&info->EndTime); + creds->times.renew_till=FileTimeToUnixTime(&info->RenewTime); + + /* MS Tickets are addressless. MIT requires an empty address + * not a NULL list of addresses. + */ + creds->addresses = (krb5_address **)malloc(sizeof(krb5_address *)); + memset(creds->addresses, 0, sizeof(krb5_address *)); + + return TRUE; +} + +static BOOL +PackageConnectLookup(HANDLE *pLogonHandle, ULONG *pPackageId) +{ + LSA_STRING Name; + NTSTATUS Status; + + Status = LsaConnectUntrusted( + pLogonHandle + ); + + if (FAILED(Status)) + { + ShowLsaError("LsaConnectUntrusted", Status); + return FALSE; + } + + Name.Buffer = MICROSOFT_KERBEROS_NAME_A; + Name.Length = strlen(Name.Buffer); + Name.MaximumLength = Name.Length + 1; + + Status = LsaLookupAuthenticationPackage( + *pLogonHandle, + &Name, + pPackageId + ); + + if (FAILED(Status)) + { + ShowLsaError("LsaLookupAuthenticationPackage", Status); + return FALSE; + } + + return TRUE; + +} + +/* + * This runtime check is only needed on Windows XP and Server 2003. + * It can safely be removed when we no longer wish to support any + * versions of those platforms. + */ +static BOOL +does_query_ticket_cache_ex2 (void) +{ + static BOOL fChecked = FALSE; + static BOOL fEx2Response = FALSE; + + if (!fChecked) + { + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + HANDLE LogonHandle; + ULONG PackageId; + ULONG RequestSize; + PKERB_QUERY_TKT_CACHE_REQUEST pCacheRequest = NULL; + PKERB_QUERY_TKT_CACHE_EX2_RESPONSE pCacheResponse = NULL; + ULONG ResponseSize; + + RequestSize = sizeof(*pCacheRequest) + 1; + + if (!PackageConnectLookup(&LogonHandle, &PackageId)) + return FALSE; + + pCacheRequest = (PKERB_QUERY_TKT_CACHE_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize); + if (!pCacheRequest) { + LsaDeregisterLogonProcess(LogonHandle); + return FALSE; + } + + pCacheRequest->MessageType = KerbQueryTicketCacheEx2Message; + pCacheRequest->LogonId.LowPart = 0; + pCacheRequest->LogonId.HighPart = 0; + + Status = LsaCallAuthenticationPackage( LogonHandle, + PackageId, + pCacheRequest, + RequestSize, + &pCacheResponse, + &ResponseSize, + &SubStatus + ); + + LocalFree(pCacheRequest); + LsaDeregisterLogonProcess(LogonHandle); + + if (!(FAILED(Status) || FAILED(SubStatus))) { + LsaFreeReturnBuffer(pCacheResponse); + fEx2Response = TRUE; + } + fChecked = TRUE; + } + + return fEx2Response; +} + +static DWORD +ConcatenateUnicodeStrings(UNICODE_STRING *pTarget, UNICODE_STRING Source1, UNICODE_STRING Source2) +{ + // + // The buffers for Source1 and Source2 cannot overlap pTarget's + // buffer. Source1.Length + Source2.Length must be <= 0xFFFF, + // otherwise we overflow... + // + + USHORT TotalSize = Source1.Length + Source2.Length; + PBYTE buffer = (PBYTE) pTarget->Buffer; + + if (TotalSize > pTarget->MaximumLength) + return ERROR_INSUFFICIENT_BUFFER; + + if ( pTarget->Buffer != Source1.Buffer ) + memcpy(buffer, Source1.Buffer, Source1.Length); + memcpy(buffer + Source1.Length, Source2.Buffer, Source2.Length); + + pTarget->Length = TotalSize; + return ERROR_SUCCESS; +} + +static BOOL +get_STRING_from_registry(HKEY hBaseKey, char * key, char * value, char * outbuf, DWORD outlen) +{ + HKEY hKey; + DWORD dwCount; + LONG rc; + + if (!outbuf || outlen == 0) + return FALSE; + + rc = RegOpenKeyExA(hBaseKey, key, 0, KEY_QUERY_VALUE, &hKey); + if (rc) + return FALSE; + + dwCount = outlen; + rc = RegQueryValueExA(hKey, value, 0, 0, (LPBYTE) outbuf, &dwCount); + RegCloseKey(hKey); + + return rc?FALSE:TRUE; +} + +static BOOL +GetSecurityLogonSessionData(PSECURITY_LOGON_SESSION_DATA * ppSessionData) +{ + NTSTATUS Status = 0; + HANDLE TokenHandle; + TOKEN_STATISTICS Stats; + DWORD ReqLen; + BOOL Success; + + if (!ppSessionData) + return FALSE; + *ppSessionData = NULL; + + Success = OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &TokenHandle ); + if ( !Success ) + return FALSE; + + Success = GetTokenInformation( TokenHandle, TokenStatistics, &Stats, sizeof(TOKEN_STATISTICS), &ReqLen ); + CloseHandle( TokenHandle ); + if ( !Success ) + return FALSE; + + Status = LsaGetLogonSessionData( &Stats.AuthenticationId, ppSessionData ); + if ( FAILED(Status) || !ppSessionData ) + return FALSE; + + return TRUE; +} + +static DWORD +ConstructTicketRequest(UNICODE_STRING DomainName, PKERB_RETRIEVE_TKT_REQUEST * outRequest, ULONG * outSize) +{ + DWORD Error; + UNICODE_STRING TargetPrefix; + USHORT TargetSize; + ULONG RequestSize; + PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL; + + *outRequest = NULL; + *outSize = 0; + + // + // Set up the "krbtgt/" target prefix into a UNICODE_STRING so we + // can easily concatenate it later. + // + + TargetPrefix.Buffer = L"krbtgt/"; + TargetPrefix.Length = wcslen(TargetPrefix.Buffer) * sizeof(WCHAR); + TargetPrefix.MaximumLength = TargetPrefix.Length; + + // + // We will need to concatenate the "krbtgt/" prefix and the + // Logon Session's DnsDomainName into our request's target name. + // + // Therefore, first compute the necessary buffer size for that. + // + // Note that we might theoretically have integer overflow. + // + + TargetSize = TargetPrefix.Length + DomainName.Length; + + // + // The ticket request buffer needs to be a single buffer. That buffer + // needs to include the buffer for the target name. + // + + RequestSize = sizeof(*pTicketRequest) + TargetSize; + + // + // Allocate the request buffer and make sure it's zero-filled. + // + + pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize); + if (!pTicketRequest) + return GetLastError(); + + // + // Concatenate the target prefix with the previous reponse's + // target domain. + // + + pTicketRequest->TargetName.Length = 0; + pTicketRequest->TargetName.MaximumLength = TargetSize; + pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1); + Error = ConcatenateUnicodeStrings(&(pTicketRequest->TargetName), + TargetPrefix, + DomainName); + *outRequest = pTicketRequest; + *outSize = RequestSize; + return Error; +} + +static BOOL +PurgeAllTickets(HANDLE LogonHandle, ULONG PackageId) +{ + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + KERB_PURGE_TKT_CACHE_REQUEST PurgeRequest; + + PurgeRequest.MessageType = KerbPurgeTicketCacheMessage; + PurgeRequest.LogonId.LowPart = 0; + PurgeRequest.LogonId.HighPart = 0; + PurgeRequest.ServerName.Buffer = L""; + PurgeRequest.ServerName.Length = 0; + PurgeRequest.ServerName.MaximumLength = 0; + PurgeRequest.RealmName.Buffer = L""; + PurgeRequest.RealmName.Length = 0; + PurgeRequest.RealmName.MaximumLength = 0; + Status = LsaCallAuthenticationPackage(LogonHandle, + PackageId, + &PurgeRequest, + sizeof(PurgeRequest), + NULL, + NULL, + &SubStatus + ); + if (FAILED(Status) || FAILED(SubStatus)) + return FALSE; + return TRUE; +} + +static BOOL +PurgeTicketEx(HANDLE LogonHandle, ULONG PackageId, + krb5_context context, krb5_flags flags, krb5_creds *cred) +{ + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + KERB_PURGE_TKT_CACHE_EX_REQUEST * pPurgeRequest; + DWORD dwRequestLen = sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 4096; + char * cname = NULL, * crealm = NULL; + char * sname = NULL, * srealm = NULL; + + if (krb5_unparse_name(context, cred->client, &cname)) + return FALSE; + + if (krb5_unparse_name(context, cred->server, &sname)) { + krb5_free_unparsed_name(context, cname); + return FALSE; + } + + pPurgeRequest = malloc(dwRequestLen); + if ( pPurgeRequest == NULL ) + return FALSE; + memset(pPurgeRequest, 0, dwRequestLen); + + crealm = strrchr(cname, '@'); + *crealm = '\0'; + crealm++; + + srealm = strrchr(sname, '@'); + *srealm = '\0'; + srealm++; + + pPurgeRequest->MessageType = KerbPurgeTicketCacheExMessage; + pPurgeRequest->LogonId.LowPart = 0; + pPurgeRequest->LogonId.HighPart = 0; + pPurgeRequest->Flags = 0; + pPurgeRequest->TicketTemplate.ClientName.Buffer = (PWSTR)((CHAR *)pPurgeRequest + sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST)); + pPurgeRequest->TicketTemplate.ClientName.Length = strlen(cname)*sizeof(WCHAR); + pPurgeRequest->TicketTemplate.ClientName.MaximumLength = 256; + ANSIToUnicode(cname, pPurgeRequest->TicketTemplate.ClientName.Buffer, + pPurgeRequest->TicketTemplate.ClientName.MaximumLength); + + pPurgeRequest->TicketTemplate.ClientRealm.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 512); + pPurgeRequest->TicketTemplate.ClientRealm.Length = strlen(crealm)*sizeof(WCHAR); + pPurgeRequest->TicketTemplate.ClientRealm.MaximumLength = 256; + ANSIToUnicode(crealm, pPurgeRequest->TicketTemplate.ClientRealm.Buffer, + pPurgeRequest->TicketTemplate.ClientRealm.MaximumLength); + + pPurgeRequest->TicketTemplate.ServerName.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 1024); + pPurgeRequest->TicketTemplate.ServerName.Length = strlen(sname)*sizeof(WCHAR); + pPurgeRequest->TicketTemplate.ServerName.MaximumLength = 256; + ANSIToUnicode(sname, pPurgeRequest->TicketTemplate.ServerName.Buffer, + pPurgeRequest->TicketTemplate.ServerName.MaximumLength); + + pPurgeRequest->TicketTemplate.ServerRealm.Buffer = (PWSTR)(((CHAR *)pPurgeRequest)+sizeof(KERB_PURGE_TKT_CACHE_EX_REQUEST) + 1536); + pPurgeRequest->TicketTemplate.ServerRealm.Length = strlen(srealm)*sizeof(WCHAR); + pPurgeRequest->TicketTemplate.ServerRealm.MaximumLength = 256; + ANSIToUnicode(srealm, pPurgeRequest->TicketTemplate.ServerRealm.Buffer, + pPurgeRequest->TicketTemplate.ServerRealm.MaximumLength); + + pPurgeRequest->TicketTemplate.StartTime; + pPurgeRequest->TicketTemplate.EndTime; + pPurgeRequest->TicketTemplate.RenewTime; + pPurgeRequest->TicketTemplate.EncryptionType = cred->keyblock.enctype; + pPurgeRequest->TicketTemplate.TicketFlags = flags; + + Status = LsaCallAuthenticationPackage( LogonHandle, + PackageId, + pPurgeRequest, + dwRequestLen, + NULL, + NULL, + &SubStatus + ); + free(pPurgeRequest); + krb5_free_unparsed_name(context,cname); + krb5_free_unparsed_name(context,sname); + + if (FAILED(Status) || FAILED(SubStatus)) + return FALSE; + return TRUE; +} + +static BOOL +KerbSubmitTicket( HANDLE LogonHandle, ULONG PackageId, + krb5_context context, krb5_creds *cred) +{ + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + KERB_SUBMIT_TKT_REQUEST * pSubmitRequest; + DWORD dwRequestLen; + krb5_auth_context auth_context; + krb5_keyblock * keyblock = 0; + krb5_replay_data replaydata; + krb5_data * krb_cred = 0; + krb5_error_code rc; + + if (krb5_auth_con_init(context, &auth_context)) { + return FALSE; + } + + if (krb5_auth_con_setflags(context, auth_context, + KRB5_AUTH_CONTEXT_RET_TIME)) { + return FALSE; + } + + krb5_auth_con_getsendsubkey(context, auth_context, &keyblock); + if (keyblock == NULL) + krb5_auth_con_getkey(context, auth_context, &keyblock); + + /* make up a key, any key, that can be used to generate the + * encrypted KRB_CRED pdu. The Vista release LSA requires + * that an enctype other than NULL be used. */ + if (keyblock == NULL) { + keyblock = (krb5_keyblock *)malloc(sizeof(krb5_keyblock)); + keyblock->enctype = ENCTYPE_ARCFOUR_HMAC; + keyblock->length = 16; + keyblock->contents = (krb5_octet *)malloc(16); + keyblock->contents[0] = 0xde; + keyblock->contents[1] = 0xad; + keyblock->contents[2] = 0xbe; + keyblock->contents[3] = 0xef; + keyblock->contents[4] = 0xfe; + keyblock->contents[5] = 0xed; + keyblock->contents[6] = 0xf0; + keyblock->contents[7] = 0xd; + keyblock->contents[8] = 0xde; + keyblock->contents[9] = 0xad; + keyblock->contents[10] = 0xbe; + keyblock->contents[11] = 0xef; + keyblock->contents[12] = 0xfe; + keyblock->contents[13] = 0xed; + keyblock->contents[14] = 0xf0; + keyblock->contents[15] = 0xd; + krb5_auth_con_setsendsubkey(context, auth_context, keyblock); + } + rc = krb5_mk_1cred(context, auth_context, cred, &krb_cred, &replaydata); + if (rc) { + krb5_auth_con_free(context, auth_context); + if (keyblock) + krb5_free_keyblock(context, keyblock); + if (krb_cred) + krb5_free_data(context, krb_cred); + return FALSE; + } + + dwRequestLen = sizeof(KERB_SUBMIT_TKT_REQUEST) + krb_cred->length + (keyblock ? keyblock->length : 0); + + pSubmitRequest = (PKERB_SUBMIT_TKT_REQUEST)malloc(dwRequestLen); + memset(pSubmitRequest, 0, dwRequestLen); + + pSubmitRequest->MessageType = KerbSubmitTicketMessage; + pSubmitRequest->LogonId.LowPart = 0; + pSubmitRequest->LogonId.HighPart = 0; + pSubmitRequest->Flags = 0; + + if (keyblock) { + pSubmitRequest->Key.KeyType = keyblock->enctype; + pSubmitRequest->Key.Length = keyblock->length; + pSubmitRequest->Key.Offset = sizeof(KERB_SUBMIT_TKT_REQUEST)+krb_cred->length; + } else { + pSubmitRequest->Key.KeyType = ENCTYPE_NULL; + pSubmitRequest->Key.Length = 0; + pSubmitRequest->Key.Offset = 0; + } + pSubmitRequest->KerbCredSize = krb_cred->length; + pSubmitRequest->KerbCredOffset = sizeof(KERB_SUBMIT_TKT_REQUEST); + memcpy(((CHAR *)pSubmitRequest)+sizeof(KERB_SUBMIT_TKT_REQUEST), + krb_cred->data, krb_cred->length); + if (keyblock) + memcpy(((CHAR *)pSubmitRequest)+sizeof(KERB_SUBMIT_TKT_REQUEST)+krb_cred->length, + keyblock->contents, keyblock->length); + krb5_free_data(context, krb_cred); + + Status = LsaCallAuthenticationPackage( LogonHandle, + PackageId, + pSubmitRequest, + dwRequestLen, + NULL, + NULL, + &SubStatus + ); + free(pSubmitRequest); + if (keyblock) + krb5_free_keyblock(context, keyblock); + krb5_auth_con_free(context, auth_context); + + if (FAILED(Status) || FAILED(SubStatus)) { + return FALSE; + } + return TRUE; +} + +/* + * A simple function to determine if there is an exact match between two tickets + * We rely on the fact that the external tickets contain the raw Kerberos ticket. + * If the EncodedTicket fields match, the KERB_EXTERNAL_TICKETs must be the same. + */ +static BOOL +KerbExternalTicketMatch( PKERB_EXTERNAL_TICKET one, PKERB_EXTERNAL_TICKET two ) +{ + if ( one->EncodedTicketSize != two->EncodedTicketSize ) + return FALSE; + + if ( memcmp(one->EncodedTicket, two->EncodedTicket, one->EncodedTicketSize) ) + return FALSE; + + return TRUE; +} + +krb5_boolean +krb5_is_permitted_tgs_enctype(krb5_context context, krb5_const_principal princ, krb5_enctype etype) +{ + krb5_enctype *list, *ptr; + krb5_boolean ret; + + if (krb5_get_tgs_ktypes(context, princ, &list)) + return(0); + + ret = 0; + + for (ptr = list; *ptr; ptr++) + if (*ptr == etype) + ret = 1; + + krb5_free_enctypes(context, list); + + return(ret); +} + +// to allow the purging of expired tickets from LSA cache. This is necessary +// to force the retrieval of new TGTs. Microsoft does not appear to retrieve +// new tickets when they expire. Instead they continue to accept the expired +// tickets. This is safe to do because the LSA purges its cache when it +// retrieves a new TGT (ms calls this renew) but not when it renews the TGT +// (ms calls this refresh). +// UAC-limited processes are not allowed to obtain a copy of the MSTGT +// session key. We used to check for UAC-limited processes and refuse all +// access to the TGT, but this makes the MSLSA ccache completely unusable. +// Instead we ought to just flag that the tgt session key is not valid. + +static BOOL +GetMSTGT(krb5_context context, HANDLE LogonHandle, ULONG PackageId, KERB_EXTERNAL_TICKET **ticket, BOOL enforce_tgs_enctypes) +{ + // + // INVARIANTS: + // + // (FAILED(Status) || FAILED(SubStatus)) ==> error + // bIsLsaError ==> LsaCallAuthenticationPackage() error + // + + BOOL bIsLsaError = FALSE; + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + DWORD Error; + + KERB_QUERY_TKT_CACHE_REQUEST CacheRequest; + PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL; + PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL; + ULONG RequestSize; + ULONG ResponseSize; + int purge_cache = 0; + int ignore_cache = 0; + krb5_enctype *etype_list = NULL, *ptr = NULL, etype = 0; + + memset(&CacheRequest, 0, sizeof(KERB_QUERY_TKT_CACHE_REQUEST)); + CacheRequest.MessageType = KerbRetrieveTicketMessage; + CacheRequest.LogonId.LowPart = 0; + CacheRequest.LogonId.HighPart = 0; + + Status = LsaCallAuthenticationPackage( + LogonHandle, + PackageId, + &CacheRequest, + sizeof(CacheRequest), + &pTicketResponse, + &ResponseSize, + &SubStatus + ); + + if (FAILED(Status)) + { + // if the call to LsaCallAuthenticationPackage failed we cannot + // perform any queries most likely because the Kerberos package + // is not available or we do not have access + bIsLsaError = TRUE; + goto cleanup; + } + + if (FAILED(SubStatus)) { + PSECURITY_LOGON_SESSION_DATA pSessionData = NULL; + BOOL Success = FALSE; + OSVERSIONINFOEX verinfo; + int supported = 0; + + // SubStatus 0x8009030E is not documented. However, it appears + // to mean there is no TGT + if (SubStatus != 0x8009030E) { + bIsLsaError = TRUE; + goto cleanup; + } + + verinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + GetVersionEx((OSVERSIONINFO *)&verinfo); + supported = (verinfo.dwMajorVersion > 5) || + (verinfo.dwMajorVersion == 5 && verinfo.dwMinorVersion >= 1); + + // If we could not get a TGT from the cache we won't know what the + // Kerberos Domain should have been. On Windows XP and 2003 Server + // we can extract it from the Security Logon Session Data. However, + // the required fields are not supported on Windows 2000. :( + if ( supported && GetSecurityLogonSessionData(&pSessionData) ) { + if ( pSessionData->DnsDomainName.Buffer ) { + Error = ConstructTicketRequest(pSessionData->DnsDomainName, + &pTicketRequest, &RequestSize); + LsaFreeReturnBuffer(pSessionData); + if ( Error ) + goto cleanup; + } else { + LsaFreeReturnBuffer(pSessionData); + bIsLsaError = TRUE; + goto cleanup; + } + } else { + CHAR UserDnsDomain[256]; + WCHAR UnicodeUserDnsDomain[256]; + UNICODE_STRING wrapper; + if ( !get_STRING_from_registry(HKEY_CURRENT_USER, + "Volatile Environment", + "USERDNSDOMAIN", + UserDnsDomain, + sizeof(UserDnsDomain) + ) ) + { + goto cleanup; + } + + ANSIToUnicode(UserDnsDomain,UnicodeUserDnsDomain,256); + wrapper.Buffer = UnicodeUserDnsDomain; + wrapper.Length = wcslen(UnicodeUserDnsDomain) * sizeof(WCHAR); + wrapper.MaximumLength = 256; + + Error = ConstructTicketRequest(wrapper, + &pTicketRequest, &RequestSize); + if ( Error ) + goto cleanup; + } + } else { + /* We have succeeded in obtaining a credential from the cache. + * Assuming the enctype is one that we support and the ticket + * has not expired and is not marked invalid we will use it. + * Otherwise, we must create a new ticket request and obtain + * a credential we can use. + */ + + /* Check Supported Enctypes */ + if ( !enforce_tgs_enctypes || + IsMSSessionKeyNull(&pTicketResponse->Ticket.SessionKey) || + krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType) ) { + FILETIME Now, MinLife, EndTime, LocalEndTime; + __int64 temp; + // FILETIME is in units of 100 nano-seconds + // If obtained tickets are either expired or have a lifetime + // less than 20 minutes, retry ... + GetSystemTimeAsFileTime(&Now); + EndTime.dwLowDateTime=pTicketResponse->Ticket.EndTime.LowPart; + EndTime.dwHighDateTime=pTicketResponse->Ticket.EndTime.HighPart; + FileTimeToLocalFileTime(&EndTime, &LocalEndTime); + temp = Now.dwHighDateTime; + temp <<= 32; + temp = Now.dwLowDateTime; + temp += 1200 * 10000; + MinLife.dwHighDateTime = (DWORD)((temp >> 32) & 0xFFFFFFFF); + MinLife.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); + if (CompareFileTime(&MinLife, &LocalEndTime) >= 0) { + purge_cache = 1; + } + if (pTicketResponse->Ticket.TicketFlags & KERB_TICKET_FLAGS_invalid) { + ignore_cache = 1; // invalid, need to attempt a TGT request + } + goto cleanup; // we have a valid ticket, all done + } else { + // not supported + ignore_cache = 1; + } + + Error = ConstructTicketRequest(pTicketResponse->Ticket.TargetDomainName, + &pTicketRequest, &RequestSize); + if ( Error ) { + goto cleanup; + } + + // + // Free the previous response buffer so we can get the new response. + // + + if ( pTicketResponse ) { + memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE)); + LsaFreeReturnBuffer(pTicketResponse); + pTicketResponse = NULL; + } + + if ( purge_cache ) { + // + // Purge the existing tickets which we cannot use so new ones can + // be requested. It is not possible to purge just the TGT. All + // service tickets must be purged. + // + PurgeAllTickets(LogonHandle, PackageId); + } + } + + // + // Intialize the request of the request. + // + + pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage; + pTicketRequest->LogonId.LowPart = 0; + pTicketRequest->LogonId.HighPart = 0; + // Note: pTicketRequest->TargetName set up above + pTicketRequest->CacheOptions = ((ignore_cache || !purge_cache) ? + KERB_RETRIEVE_TICKET_DONT_USE_CACHE : 0L); + pTicketRequest->TicketFlags = 0L; + pTicketRequest->EncryptionType = 0L; + + Status = LsaCallAuthenticationPackage( LogonHandle, + PackageId, + pTicketRequest, + RequestSize, + &pTicketResponse, + &ResponseSize, + &SubStatus + ); + + if (FAILED(Status) || FAILED(SubStatus)) + { + bIsLsaError = TRUE; + goto cleanup; + } + + // + // Check to make sure the new tickets we received are of a type we support + // + + /* Check Supported Enctypes */ + if ( !enforce_tgs_enctypes || + krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType) ) { + goto cleanup; // we have a valid ticket, all done + } + + if (krb5_get_tgs_ktypes(context, NULL, &etype_list)) { + ptr = etype_list = NULL; + etype = ENCTYPE_DES_CBC_CRC; + } else { + ptr = etype_list + 1; + etype = *etype_list; + } + + while ( etype ) { + // Try once more but this time specify the Encryption Type + // (This will not store the retrieved tickets in the LSA cache unless + // 0 is supported.) + pTicketRequest->EncryptionType = etype; + pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET; + + if ( pTicketResponse ) { + memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE)); + LsaFreeReturnBuffer(pTicketResponse); + pTicketResponse = NULL; + } + + Status = LsaCallAuthenticationPackage( LogonHandle, + PackageId, + pTicketRequest, + RequestSize, + &pTicketResponse, + &ResponseSize, + &SubStatus + ); + + if (FAILED(Status) || FAILED(SubStatus)) + { + bIsLsaError = TRUE; + goto cleanup; + } + + if ( pTicketResponse->Ticket.SessionKey.KeyType == etype && + (!enforce_tgs_enctypes || + krb5_is_permitted_tgs_enctype(context, NULL, pTicketResponse->Ticket.SessionKey.KeyType)) ) { + goto cleanup; // we have a valid ticket, all done + } + + if ( ptr ) { + etype = *ptr++; + } else { + etype = 0; + } + } + +cleanup: + if ( etype_list ) + krb5_free_enctypes(context, etype_list); + + if ( pTicketRequest ) + LocalFree(pTicketRequest); + + if (FAILED(Status) || FAILED(SubStatus)) + { + if (bIsLsaError) + { + // XXX - Will be fixed later + if (FAILED(Status)) + ShowLsaError("LsaCallAuthenticationPackage", Status); + if (FAILED(SubStatus)) + ShowLsaError("LsaCallAuthenticationPackage", SubStatus); + } + else + { + ShowWinError("GetMSTGT", Status); + } + + if (pTicketResponse) { + memset(pTicketResponse,0,sizeof(KERB_RETRIEVE_TKT_RESPONSE)); + LsaFreeReturnBuffer(pTicketResponse); + pTicketResponse = NULL; + } + return(FALSE); + } + + *ticket = &(pTicketResponse->Ticket); + return(TRUE); +} + +static BOOL +GetQueryTktCacheResponseEx(HANDLE LogonHandle, ULONG PackageId, + PKERB_QUERY_TKT_CACHE_EX_RESPONSE * ppResponse) +{ + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + + KERB_QUERY_TKT_CACHE_REQUEST CacheRequest; + PKERB_QUERY_TKT_CACHE_EX_RESPONSE pQueryResponse = NULL; + ULONG ResponseSize; + + CacheRequest.MessageType = KerbQueryTicketCacheExMessage; + CacheRequest.LogonId.LowPart = 0; + CacheRequest.LogonId.HighPart = 0; + + Status = LsaCallAuthenticationPackage( + LogonHandle, + PackageId, + &CacheRequest, + sizeof(CacheRequest), + &pQueryResponse, + &ResponseSize, + &SubStatus + ); + + if ( !(FAILED(Status) || FAILED(SubStatus)) ) { + *ppResponse = pQueryResponse; + return TRUE; + } + + return FALSE; +} + +static BOOL +GetQueryTktCacheResponseEx2(HANDLE LogonHandle, ULONG PackageId, + PKERB_QUERY_TKT_CACHE_EX2_RESPONSE * ppResponse) +{ + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + + KERB_QUERY_TKT_CACHE_REQUEST CacheRequest; + PKERB_QUERY_TKT_CACHE_EX2_RESPONSE pQueryResponse = NULL; + ULONG ResponseSize; + + CacheRequest.MessageType = KerbQueryTicketCacheEx2Message; + CacheRequest.LogonId.LowPart = 0; + CacheRequest.LogonId.HighPart = 0; + + Status = LsaCallAuthenticationPackage( + LogonHandle, + PackageId, + &CacheRequest, + sizeof(CacheRequest), + &pQueryResponse, + &ResponseSize, + &SubStatus + ); + + if ( !(FAILED(Status) || FAILED(SubStatus)) ) { + *ppResponse = pQueryResponse; + return TRUE; + } + + return FALSE; +} + +static BOOL +GetMSCacheTicketFromMITCred( HANDLE LogonHandle, ULONG PackageId, + krb5_context context, krb5_creds *creds, + PKERB_EXTERNAL_TICKET *ticket) +{ + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + ULONG RequestSize; + PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL; + PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL; + ULONG ResponseSize; + + RequestSize = sizeof(*pTicketRequest) + MAX_MSPRINC_SIZE; + + pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize); + if (!pTicketRequest) + return FALSE; + + pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage; + pTicketRequest->LogonId.LowPart = 0; + pTicketRequest->LogonId.HighPart = 0; + + pTicketRequest->TargetName.Length = 0; + pTicketRequest->TargetName.MaximumLength = MAX_MSPRINC_SIZE; + pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1); + MITPrincToMSPrinc(context, creds->server, &pTicketRequest->TargetName); + pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET; + pTicketRequest->TicketFlags = creds->ticket_flags; + pTicketRequest->EncryptionType = creds->keyblock.enctype; + + Status = LsaCallAuthenticationPackage( LogonHandle, + PackageId, + pTicketRequest, + RequestSize, + &pTicketResponse, + &ResponseSize, + &SubStatus + ); + + LocalFree(pTicketRequest); + + if (FAILED(Status) || FAILED(SubStatus)) + return(FALSE); + + /* otherwise return ticket */ + *ticket = &(pTicketResponse->Ticket); + return(TRUE); +} + +static BOOL +GetMSCacheTicketFromCacheInfoEx(HANDLE LogonHandle, ULONG PackageId, + PKERB_TICKET_CACHE_INFO_EX tktinfo, + PKERB_EXTERNAL_TICKET *ticket) +{ + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + ULONG RequestSize; + PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL; + PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL; + ULONG ResponseSize; + + RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length; + + pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize); + if (!pTicketRequest) + return FALSE; + + pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage; + pTicketRequest->LogonId.LowPart = 0; + pTicketRequest->LogonId.HighPart = 0; + pTicketRequest->TargetName.Length = tktinfo->ServerName.Length; + pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length; + pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1); + memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length); + pTicketRequest->CacheOptions = 0; + pTicketRequest->EncryptionType = tktinfo->EncryptionType; + pTicketRequest->TicketFlags = 0; + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable ) + pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE; + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded ) + pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED; + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable ) + pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE; + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable ) + pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE; + + Status = LsaCallAuthenticationPackage( + LogonHandle, + PackageId, + pTicketRequest, + RequestSize, + &pTicketResponse, + &ResponseSize, + &SubStatus + ); + + LocalFree(pTicketRequest); + + if (FAILED(Status) || FAILED(SubStatus)) + return(FALSE); + + /* otherwise return ticket */ + *ticket = &(pTicketResponse->Ticket); + + /* set the initial flag if we were attempting to retrieve one + * because Windows won't necessarily return the initial ticket + * to us. + */ + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_initial ) + (*ticket)->TicketFlags |= KERB_TICKET_FLAGS_initial; + + return(TRUE); +} + +static BOOL +GetMSCacheTicketFromCacheInfoEx2(HANDLE LogonHandle, ULONG PackageId, + PKERB_TICKET_CACHE_INFO_EX2 tktinfo, + PKERB_EXTERNAL_TICKET *ticket) +{ + NTSTATUS Status = 0; + NTSTATUS SubStatus = 0; + ULONG RequestSize; + PKERB_RETRIEVE_TKT_REQUEST pTicketRequest = NULL; + PKERB_RETRIEVE_TKT_RESPONSE pTicketResponse = NULL; + ULONG ResponseSize; + + RequestSize = sizeof(*pTicketRequest) + tktinfo->ServerName.Length; + + pTicketRequest = (PKERB_RETRIEVE_TKT_REQUEST) LocalAlloc(LMEM_ZEROINIT, RequestSize); + if (!pTicketRequest) + return FALSE; + + pTicketRequest->MessageType = KerbRetrieveEncodedTicketMessage; + pTicketRequest->LogonId.LowPart = 0; + pTicketRequest->LogonId.HighPart = 0; + pTicketRequest->TargetName.Length = tktinfo->ServerName.Length; + pTicketRequest->TargetName.MaximumLength = tktinfo->ServerName.Length; + pTicketRequest->TargetName.Buffer = (PWSTR) (pTicketRequest + 1); + memcpy(pTicketRequest->TargetName.Buffer,tktinfo->ServerName.Buffer, tktinfo->ServerName.Length); + pTicketRequest->CacheOptions = KERB_RETRIEVE_TICKET_CACHE_TICKET; + pTicketRequest->EncryptionType = tktinfo->SessionKeyType; + pTicketRequest->TicketFlags = 0; + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwardable ) + pTicketRequest->TicketFlags |= KDC_OPT_FORWARDABLE; + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_forwarded ) + pTicketRequest->TicketFlags |= KDC_OPT_FORWARDED; + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_proxiable ) + pTicketRequest->TicketFlags |= KDC_OPT_PROXIABLE; + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_renewable ) + pTicketRequest->TicketFlags |= KDC_OPT_RENEWABLE; + + Status = LsaCallAuthenticationPackage( + LogonHandle, + PackageId, + pTicketRequest, + RequestSize, + &pTicketResponse, + &ResponseSize, + &SubStatus + ); + + LocalFree(pTicketRequest); + + if (FAILED(Status) || FAILED(SubStatus)) + return(FALSE); + + /* otherwise return ticket */ + *ticket = &(pTicketResponse->Ticket); + + + /* set the initial flag if we were attempting to retrieve one + * because Windows won't necessarily return the initial ticket + * to us. + */ + if ( tktinfo->TicketFlags & KERB_TICKET_FLAGS_initial ) + (*ticket)->TicketFlags |= KERB_TICKET_FLAGS_initial; + + return(TRUE); +} + +static krb5_error_code KRB5_CALLCONV krb5_lcc_close +(krb5_context, krb5_ccache id); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_destroy +(krb5_context, krb5_ccache id); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_end_seq_get +(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_generate_new +(krb5_context, krb5_ccache *id); + +static const char * KRB5_CALLCONV krb5_lcc_get_name +(krb5_context, krb5_ccache id); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_get_principal +(krb5_context, krb5_ccache id, krb5_principal *princ); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_initialize +(krb5_context, krb5_ccache id, krb5_principal princ); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_next_cred +(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor, + krb5_creds *creds); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_resolve +(krb5_context, krb5_ccache *id, const char *residual); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_retrieve +(krb5_context, krb5_ccache id, krb5_flags whichfields, + krb5_creds *mcreds, krb5_creds *creds); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_start_seq_get +(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_store +(krb5_context, krb5_ccache id, krb5_creds *creds); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_set_flags +(krb5_context, krb5_ccache id, krb5_flags flags); + +static krb5_error_code KRB5_CALLCONV krb5_lcc_get_flags +(krb5_context, krb5_ccache id, krb5_flags *flags); + +extern const krb5_cc_ops krb5_lcc_ops; + +krb5_error_code krb5_change_cache (void); + +krb5_boolean +krb5int_cc_creds_match_request(krb5_context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds); + +#define KRB5_OK 0 + +typedef struct _krb5_lcc_data { + HANDLE LogonHandle; + ULONG PackageId; + char * cc_name; + krb5_principal princ; + krb5_flags flags; +} krb5_lcc_data; + +typedef struct _krb5_lcc_cursor { + union { + PKERB_QUERY_TKT_CACHE_RESPONSE w2k; + PKERB_QUERY_TKT_CACHE_EX_RESPONSE xp; + PKERB_QUERY_TKT_CACHE_EX2_RESPONSE ex2; + } response; + unsigned int index; + PKERB_EXTERNAL_TICKET mstgt; +} krb5_lcc_cursor; + + +/* + * Requires: + * residual is ignored + * + * Modifies: + * id + * + * Effects: + * Acccess the MS Kerberos LSA cache in the current logon session + * Ignore the residual. + * + * Returns: + * A filled in krb5_ccache structure "id". + * + * Errors: + * KRB5_CC_NOMEM - there was insufficient memory to allocate the + * + * krb5_ccache. id is undefined. + * permission errors + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_resolve (krb5_context context, krb5_ccache *id, const char *residual) +{ + krb5_ccache lid; + krb5_lcc_data *data; + HANDLE LogonHandle; + ULONG PackageId, i; + PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse; + + if (!PackageConnectLookup(&LogonHandle, &PackageId)) + return KRB5_FCC_NOFILE; + + lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache)); + if (lid == NULL) { + LsaDeregisterLogonProcess(LogonHandle); + return KRB5_CC_NOMEM; + } + + lid->ops = &krb5_lcc_ops; + + lid->data = (krb5_pointer) malloc(sizeof(krb5_lcc_data)); + if (lid->data == NULL) { + free(lid); + LsaDeregisterLogonProcess(LogonHandle); + return KRB5_CC_NOMEM; + } + + lid->magic = KV5M_CCACHE; + data = (krb5_lcc_data *)lid->data; + data->LogonHandle = LogonHandle; + data->PackageId = PackageId; + data->princ = NULL; + + data->cc_name = (char *)malloc(strlen(residual)+1); + if (data->cc_name == NULL) { + free(lid->data); + free(lid); + LsaDeregisterLogonProcess(LogonHandle); + return KRB5_CC_NOMEM; + } + strcpy(data->cc_name, residual); + + /* If there are already tickets present, grab a client principal name. */ + if (GetQueryTktCacheResponseEx(LogonHandle, PackageId, &pResponse)) { + /* Take the first client principal we find; they should all be the + * same anyway. */ + for (i = 0; i < pResponse->CountOfTickets; i++) { + if (UnicodeStringToMITPrinc(&pResponse->Tickets[i].ClientName, + &pResponse->Tickets[i].ClientRealm, + context, &data->princ)) + break; + + } + LsaFreeReturnBuffer(pResponse); + } + + /* + * other routines will get errors on open, and callers must expect them, + * if cache is non-existent/unusable + */ + *id = lid; + return KRB5_OK; +} + +/* + * return success although we do not do anything + * We should delete all tickets belonging to the specified principal + */ + +static krb5_error_code KRB5_CALLCONV +krb5_lcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags, + krb5_creds *creds); + +static krb5_error_code KRB5_CALLCONV +krb5_lcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ) +{ + krb5_cc_cursor cursor; + krb5_error_code code; + krb5_creds cred; + + code = krb5_cc_start_seq_get(context, id, &cursor); + if (code) { + if (code == KRB5_CC_NOTFOUND) + return KRB5_OK; + return code; + } + + while ( !(code = krb5_cc_next_cred(context, id, &cursor, &cred)) ) + { + if ( krb5_principal_compare(context, princ, cred.client) ) { + code = krb5_lcc_remove_cred(context, id, 0, &cred); + } + krb5_free_cred_contents(context, &cred); + } + + if (code == KRB5_CC_END || code == KRB5_CC_NOTFOUND) + { + krb5_cc_end_seq_get(context, id, &cursor); + return KRB5_OK; + } + return code; +} + +/* + * Modifies: + * id + * + * Effects: + * Closes the microsoft lsa cache, invalidates the id, and frees any resources + * associated with the cache. + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_close(krb5_context context, krb5_ccache id) +{ + register int closeval = KRB5_OK; + register krb5_lcc_data *data; + + if (id) { + data = (krb5_lcc_data *) id->data; + + if (data) { + LsaDeregisterLogonProcess(data->LogonHandle); + if (data->cc_name) + free(data->cc_name); + free(data); + } + free(id); + } + return closeval; +} + +/* + * Effects: + * Destroys the contents of id. + * + * Errors: + * system errors + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_destroy(krb5_context context, krb5_ccache id) +{ + register krb5_lcc_data *data; + + if (id) { + data = (krb5_lcc_data *) id->data; + + return PurgeAllTickets(data->LogonHandle, data->PackageId) ? KRB5_OK : KRB5_FCC_INTERNAL; + } + return KRB5_FCC_INTERNAL; +} + +/* + * Effects: + * Prepares for a sequential search of the credentials cache. + * Returns a krb5_cc_cursor to be used with krb5_lcc_next_cred and + * krb5_lcc_end_seq_get. + * + * If the cache is modified between the time of this call and the time + * of the final krb5_lcc_end_seq_get, the results are undefined. + * + * Errors: + * KRB5_CC_NOMEM + * KRB5_FCC_INTERNAL - system errors + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor) +{ + krb5_lcc_cursor *lcursor; + krb5_lcc_data *data = (krb5_lcc_data *)id->data; + + lcursor = (krb5_lcc_cursor *) malloc(sizeof(krb5_lcc_cursor)); + if (lcursor == NULL) { + *cursor = 0; + return KRB5_CC_NOMEM; + } + + /* + * obtain a tgt to refresh the ccache in case the ticket is expired + */ + if (!GetMSTGT(context, data->LogonHandle, data->PackageId, &lcursor->mstgt, TRUE)) { + free(lcursor); + *cursor = 0; + return KRB5_CC_NOTFOUND; + } + + if ( does_query_ticket_cache_ex2() ) { + if (!GetQueryTktCacheResponseEx2(data->LogonHandle, data->PackageId, + &lcursor->response.ex2)) { + LsaFreeReturnBuffer(lcursor->mstgt); + free(lcursor); + *cursor = 0; + return KRB5_FCC_INTERNAL; + } + } else + if (!GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId, + &lcursor->response.xp)) { + LsaFreeReturnBuffer(lcursor->mstgt); + free(lcursor); + *cursor = 0; + return KRB5_FCC_INTERNAL; + } + lcursor->index = 0; + *cursor = (krb5_cc_cursor) lcursor; + return KRB5_OK; +} + + +/* + * Requires: + * cursor is a krb5_cc_cursor originally obtained from + * krb5_lcc_start_seq_get. + * + * Modifes: + * cursor + * + * Effects: + * Fills in creds with the TGT obtained from the MS LSA + * + * The cursor is updated to indicate TGT retrieval + * + * Errors: + * KRB5_CC_END + * KRB5_FCC_INTERNAL - system errors + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor, krb5_creds *creds) +{ + krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor; + krb5_lcc_data *data; + KERB_EXTERNAL_TICKET *msticket; + krb5_error_code retval = KRB5_OK; + + data = (krb5_lcc_data *)id->data; + +next_cred: + if ( does_query_ticket_cache_ex2() ) { + if ( lcursor->index >= lcursor->response.ex2->CountOfTickets ) { + if (retval == KRB5_OK) + return KRB5_CC_END; + else { + LsaFreeReturnBuffer(lcursor->mstgt); + LsaFreeReturnBuffer(lcursor->response.ex2); + free(*cursor); + *cursor = 0; + return retval; + } + } + + if ( data->flags & KRB5_TC_NOTICKET ) { + if (!CacheInfoEx2ToMITCred( &lcursor->response.ex2->Tickets[lcursor->index++], + context, creds)) { + retval = KRB5_FCC_INTERNAL; + goto next_cred; + } + return KRB5_OK; + } else { + if (!GetMSCacheTicketFromCacheInfoEx2(data->LogonHandle, + data->PackageId, + &lcursor->response.ex2->Tickets[lcursor->index++],&msticket)) { + retval = KRB5_FCC_INTERNAL; + goto next_cred; + } + } + } else { + if (lcursor->index >= lcursor->response.xp->CountOfTickets) { + if (retval == KRB5_OK) { + return KRB5_CC_END; + } else { + LsaFreeReturnBuffer(lcursor->mstgt); + LsaFreeReturnBuffer(lcursor->response.xp); + free(*cursor); + *cursor = 0; + return retval; + } + } + + if (!GetMSCacheTicketFromCacheInfoEx(data->LogonHandle, + data->PackageId, + &lcursor->response.xp->Tickets[lcursor->index++], + &msticket)) { + retval = KRB5_FCC_INTERNAL; + goto next_cred; + } + } + + /* Don't return tickets with NULL Session Keys */ + if ( IsMSSessionKeyNull(&msticket->SessionKey) ) { + LsaFreeReturnBuffer(msticket); + goto next_cred; + } + + /* convert the ticket */ + if ( does_query_ticket_cache_ex2() ) { + if (!MSCredToMITCred(msticket, lcursor->response.ex2->Tickets[lcursor->index-1].ClientRealm, context, creds)) + retval = KRB5_FCC_INTERNAL; + } else { + if (!MSCredToMITCred(msticket, + lcursor->response.xp->Tickets[lcursor->index - + 1].ClientRealm, + context, creds)) + retval = KRB5_FCC_INTERNAL; + } + LsaFreeReturnBuffer(msticket); + return retval; +} + +/* + * Requires: + * cursor is a krb5_cc_cursor originally obtained from + * krb5_lcc_start_seq_get. + * + * Modifies: + * id, cursor + * + * Effects: + * Finishes sequential processing of the file credentials ccache id, + * and invalidates the cursor (it must never be used after this call). + */ +/* ARGSUSED */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor) +{ + krb5_lcc_cursor *lcursor = (krb5_lcc_cursor *) *cursor; + + if ( lcursor ) { + LsaFreeReturnBuffer(lcursor->mstgt); + if ( does_query_ticket_cache_ex2() ) + LsaFreeReturnBuffer(lcursor->response.ex2); + else + LsaFreeReturnBuffer(lcursor->response.xp); + free(*cursor); + } + *cursor = 0; + + return KRB5_OK; +} + + +/* + * Errors: + * KRB5_CC_READONLY - not supported + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_generate_new (krb5_context context, krb5_ccache *id) +{ + return KRB5_CC_READONLY; +} + +/* + * Requires: + * id is a ms lsa credential cache + * + * Returns: + * The ccname specified during the krb5_lcc_resolve call + */ +static const char * KRB5_CALLCONV +krb5_lcc_get_name (krb5_context context, krb5_ccache id) +{ + + if ( !id ) + return ""; + + return (char *) ((krb5_lcc_data *) id->data)->cc_name; +} + +/* + * Modifies: + * id, princ + * + * Effects: + * Retrieves the primary principal from id, as set with + * krb5_lcc_initialize. The principal is returned is allocated + * storage that must be freed by the caller via krb5_free_principal. + * + * Errors: + * system errors + * KRB5_CC_NOT_KTYPE + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ) +{ + PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse; + krb5_lcc_data *data = (krb5_lcc_data *)id->data; + ULONG i; + + /* obtain principal */ + if (data->princ) + return krb5_copy_principal(context, data->princ, princ); + else { + if (GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId, + &pResponse)) { + /* Take the first client principal we find; they should all be the + * same anyway. */ + for (i = 0; i < pResponse->CountOfTickets; i++) { + if (UnicodeStringToMITPrinc(&pResponse->Tickets[i].ClientName, + &pResponse->Tickets[i].ClientRealm, + context, &data->princ)) + break; + } + LsaFreeReturnBuffer(pResponse); + if (data->princ) + return krb5_copy_principal(context, data->princ, princ); + } + } + return KRB5_CC_NOTFOUND; +} + + +static krb5_error_code KRB5_CALLCONV +krb5_lcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields, + krb5_creds *mcreds, krb5_creds *creds) +{ + krb5_error_code kret = KRB5_OK; + krb5_lcc_data *data = (krb5_lcc_data *)id->data; + KERB_EXTERNAL_TICKET *msticket = 0, *mstgt = 0, *mstmp = 0; + krb5_creds * mcreds_noflags = 0; + krb5_creds fetchcreds; + PKERB_QUERY_TKT_CACHE_EX_RESPONSE pResponse = 0; + unsigned int i; + + memset(&fetchcreds, 0, sizeof(krb5_creds)); + + /* first try to find out if we have an existing ticket which meets the requirements */ + kret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, + creds); + /* This sometimes returns a zero-length ticket; work around it. */ + if ( !kret && creds->ticket.length > 0 ) + return KRB5_OK; + + /* if not, we must try to get a ticket without specifying any flags or etypes */ + kret = krb5_copy_creds(context, mcreds, &mcreds_noflags); + if (kret) + goto cleanup; + mcreds_noflags->ticket_flags = 0; + mcreds_noflags->keyblock.enctype = 0; + + if (!GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, mcreds_noflags, &msticket)) { + kret = KRB5_CC_NOTFOUND; + goto cleanup; + } + + /* try again to find out if we have an existing ticket which meets the requirements */ + kret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, + creds); + /* This sometimes returns a zero-length ticket; work around it. */ + if ( !kret && creds->ticket.length > 0 ) + goto cleanup; + + /* if not, obtain a ticket using the request flags and enctype even though it may not + * be stored in the LSA cache for future use. + */ + if ( msticket ) { + LsaFreeReturnBuffer(msticket); + msticket = 0; + } + + if (!GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, mcreds, &msticket)) { + kret = KRB5_CC_NOTFOUND; + goto cleanup; + } + + /* convert the ticket */ + /* + * We can obtain the correct client realm for a ticket by walking the + * cache contents until we find the matching service ticket. + */ + + if (!GetQueryTktCacheResponseEx(data->LogonHandle, data->PackageId, + &pResponse)) { + kret = KRB5_FCC_INTERNAL; + goto cleanup; + } + + for (i = 0; i < pResponse->CountOfTickets; i++) { + if (!GetMSCacheTicketFromCacheInfoEx(data->LogonHandle, + data->PackageId, + &pResponse->Tickets[i], &mstmp)) { + continue; + } + + if (KerbExternalTicketMatch(msticket,mstmp)) + break; + + LsaFreeReturnBuffer(mstmp); + mstmp = 0; + } + + if (!MSCredToMITCred(msticket, mstmp ? + pResponse->Tickets[i].ClientRealm : + msticket->DomainName, context, &fetchcreds)) { + LsaFreeReturnBuffer(pResponse); + kret = KRB5_FCC_INTERNAL; + goto cleanup; + } + LsaFreeReturnBuffer(pResponse); + + + /* check to see if this ticket matches the request using logic from + * k5_cc_retrieve_cred_default() + */ + if ( krb5int_cc_creds_match_request(context, whichfields, mcreds, &fetchcreds) ) { + *creds = fetchcreds; + } else { + krb5_free_cred_contents(context, &fetchcreds); + kret = KRB5_CC_NOTFOUND; + } + +cleanup: + if ( mstmp ) + LsaFreeReturnBuffer(mstmp); + if ( mstgt ) + LsaFreeReturnBuffer(mstgt); + if ( msticket ) + LsaFreeReturnBuffer(msticket); + if ( mcreds_noflags ) + krb5_free_creds(context, mcreds_noflags); + return kret; +} + + +/* + * We can't write to the MS LSA cache. So we request the cache to obtain a ticket for the same + * principal in the hope that next time the application requires a ticket for the service it + * is attempt to store, the retrieved ticket will be good enough. + * + * Errors: + * KRB5_CC_READONLY - not supported + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds) +{ + krb5_error_code kret = KRB5_OK; + krb5_lcc_data *data = (krb5_lcc_data *)id->data; + KERB_EXTERNAL_TICKET *msticket = 0, *msticket2 = 0; + krb5_creds * creds_noflags = 0; + + if (krb5_is_config_principal(context, creds->server)) { + /* mslsa cannot store config creds, so we have to bail. + * The 'right' thing to do would be to return an appropriate error, + * but that would require modifying the calling code to check + * for that error and ignore it. + */ + return KRB5_OK; + } + + if (KerbSubmitTicket( data->LogonHandle, data->PackageId, context, creds )) + return KRB5_OK; + + /* If not, lets try to obtain a matching ticket from the KDC */ + if ( creds->ticket_flags != 0 && creds->keyblock.enctype != 0 ) { + /* if not, we must try to get a ticket without specifying any flags or etypes */ + kret = krb5_copy_creds(context, creds, &creds_noflags); + if (kret == 0) { + creds_noflags->ticket_flags = 0; + creds_noflags->keyblock.enctype = 0; + + GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, creds_noflags, &msticket2); + krb5_free_creds(context, creds_noflags); + } + } + + GetMSCacheTicketFromMITCred(data->LogonHandle, data->PackageId, context, creds, &msticket); + if (msticket || msticket2) { + if (msticket) + LsaFreeReturnBuffer(msticket); + if (msticket2) + LsaFreeReturnBuffer(msticket2); + return KRB5_OK; + } + return KRB5_CC_READONLY; +} + +/* + * Individual credentials can be implemented differently depending + * on the operating system version. (undocumented.) + * + * Errors: + * KRB5_CC_READONLY: + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags, + krb5_creds *creds) +{ + krb5_lcc_data *data = (krb5_lcc_data *)id->data; + + if (PurgeTicketEx(data->LogonHandle, data->PackageId, context, flags, + creds)) + return KRB5_OK; + + return KRB5_CC_READONLY; +} + + +/* + * Effects: + * Set + */ +static krb5_error_code KRB5_CALLCONV +krb5_lcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags) +{ + krb5_lcc_data *data = (krb5_lcc_data *)id->data; + + data->flags = flags; + return KRB5_OK; +} + +static krb5_error_code KRB5_CALLCONV +krb5_lcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags) +{ + krb5_lcc_data *data = (krb5_lcc_data *)id->data; + + *flags = data->flags; + return KRB5_OK; +} + +static krb5_error_code KRB5_CALLCONV +krb5_lcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor) +{ + krb5_cc_ptcursor new_cursor = (krb5_cc_ptcursor )malloc(sizeof(*new_cursor)); + if (!new_cursor) + return ENOMEM; + new_cursor->ops = &krb5_lcc_ops; + new_cursor->data = (krb5_pointer)(1); + *cursor = new_cursor; + new_cursor = NULL; + return 0; +} + +static krb5_error_code KRB5_CALLCONV +krb5_lcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, krb5_ccache *ccache) +{ + krb5_error_code code = 0; + *ccache = 0; + if (cursor->data == NULL) + return 0; + + cursor->data = NULL; + if ((code = krb5_lcc_resolve(context, ccache, ""))) { + if (code != KRB5_FCC_NOFILE) + /* Note that we only want to return serious errors. + * Any non-zero return code will prevent the cccol iterator + * from advancing to the next ccache collection. */ + return code; + } + return 0; +} + +static krb5_error_code KRB5_CALLCONV +krb5_lcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor) +{ + if (*cursor) { + free(*cursor); + *cursor = NULL; + } + return 0; +} + +const krb5_cc_ops krb5_lcc_ops = { + 0, + "MSLSA", + krb5_lcc_get_name, + krb5_lcc_resolve, + krb5_lcc_generate_new, + krb5_lcc_initialize, + krb5_lcc_destroy, + krb5_lcc_close, + krb5_lcc_store, + krb5_lcc_retrieve, + krb5_lcc_get_principal, + krb5_lcc_start_seq_get, + krb5_lcc_next_cred, + krb5_lcc_end_seq_get, + krb5_lcc_remove_cred, + krb5_lcc_set_flags, + krb5_lcc_get_flags, + krb5_lcc_ptcursor_new, + krb5_lcc_ptcursor_next, + krb5_lcc_ptcursor_free, + NULL, /* move */ + NULL, /* lastchange */ + NULL, /* wasdefault */ + NULL, /* lock */ + NULL, /* unlock */ + NULL, /* switch_to */ +}; +#endif /* _WIN32 */ diff --git a/src/lib/krb5/ccache/cc_retr.c b/src/lib/krb5/ccache/cc_retr.c new file mode 100644 index 000000000000..1314d24bd68d --- /dev/null +++ b/src/lib/krb5/ccache/cc_retr.c @@ -0,0 +1,280 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/cc_retr.c */ +/* + * Copyright 1990,1991,1999,2007,2008 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#include "k5-int.h" +#include "cc-int.h" +#include "../krb/int-proto.h" + +#define KRB5_OK 0 + +#define set(bits) (whichfields & bits) +#define flags_match(a,b) (((a) & (b)) == (a)) + +static int +times_match_exact(const krb5_ticket_times *t1, const krb5_ticket_times *t2) +{ + return (t1->authtime == t2->authtime && + t1->starttime == t2->starttime && + t1->endtime == t2->endtime && + t1->renew_till == t2->renew_till); +} + +static krb5_boolean +times_match(const krb5_ticket_times *t1, const krb5_ticket_times *t2) +{ + if (t1->renew_till) { + if (t1->renew_till > t2->renew_till) + return FALSE; /* this one expires too late */ + } + if (t1->endtime) { + if (t1->endtime > t2->endtime) + return FALSE; /* this one expires too late */ + } + /* only care about expiration on a times_match */ + return TRUE; +} + +static krb5_boolean +standard_fields_match(krb5_context context, const krb5_creds *mcreds, const krb5_creds *creds) +{ + return (krb5_principal_compare(context, mcreds->client,creds->client) + && krb5_principal_compare(context, mcreds->server,creds->server)); +} + +/* only match the server name portion, not the server realm portion */ + +static krb5_boolean +srvname_match(krb5_context context, const krb5_creds *mcreds, const krb5_creds *creds) +{ + krb5_boolean retval; + krb5_principal_data p1, p2; + + retval = krb5_principal_compare(context, mcreds->client,creds->client); + if (retval != TRUE) + return retval; + /* + * Hack to ignore the server realm for the purposes of the compare. + */ + p1 = *mcreds->server; + p2 = *creds->server; + p1.realm = p2.realm; + return krb5_principal_compare(context, &p1, &p2); +} + +static krb5_boolean +authdata_match(krb5_authdata *const *mdata, krb5_authdata *const *data) +{ + const krb5_authdata *mdatap, *datap; + + if (mdata == data) + return TRUE; + + if (mdata == NULL) + return *data == NULL; + + if (data == NULL) + return *mdata == NULL; + + while ((mdatap = *mdata) && (datap = *data)) { + if ((mdatap->ad_type != datap->ad_type) || + (mdatap->length != datap->length) || + (memcmp ((char *)mdatap->contents, + (char *)datap->contents, (unsigned) mdatap->length) != 0)) + return FALSE; + mdata++; + data++; + } + return (*mdata == NULL) && (*data == NULL); +} + +static krb5_boolean +data_match(const krb5_data *data1, const krb5_data *data2) +{ + if (!data1) { + if (!data2) + return TRUE; + else + return FALSE; + } + if (!data2) return FALSE; + + return data_eq(*data1, *data2) ? TRUE : FALSE; +} + +static int +pref (krb5_enctype my_ktype, int nktypes, krb5_enctype *ktypes) +{ + int i; + for (i = 0; i < nktypes; i++) + if (my_ktype == ktypes[i]) + return i; + return -1; +} + +/* + * Effects: + * Searches the credentials cache for a credential matching mcreds, + * with the fields specified by whichfields. If one if found, it is + * returned in creds, which should be freed by the caller with + * krb5_free_credentials(). + * + * The fields are interpreted in the following way (all constants are + * preceded by KRB5_TC_). MATCH_IS_SKEY requires the is_skey field to + * match exactly. MATCH_TIMES requires the requested lifetime to be + * at least as great as that specified; MATCH_TIMES_EXACT requires the + * requested lifetime to be exactly that specified. MATCH_FLAGS + * requires only the set bits in mcreds be set in creds; + * MATCH_FLAGS_EXACT requires all bits to match. + * + * Flag SUPPORTED_KTYPES means check all matching entries that have + * any supported enctype (according to tgs_enctypes) and return the one + * with the enctype listed earliest. Return CC_NOT_KTYPE if a match + * is found *except* for having a supported enctype. + * + * Errors: + * system errors + * permission errors + * KRB5_CC_NOMEM + * KRB5_CC_NOT_KTYPE + */ + +krb5_boolean +krb5int_cc_creds_match_request(krb5_context context, krb5_flags whichfields, krb5_creds *mcreds, krb5_creds *creds) +{ + if (((set(KRB5_TC_MATCH_SRV_NAMEONLY) && + srvname_match(context, mcreds, creds)) || + standard_fields_match(context, mcreds, creds)) + && + (! set(KRB5_TC_MATCH_IS_SKEY) || + mcreds->is_skey == creds->is_skey) + && + (! set(KRB5_TC_MATCH_FLAGS_EXACT) || + mcreds->ticket_flags == creds->ticket_flags) + && + (! set(KRB5_TC_MATCH_FLAGS) || + flags_match(mcreds->ticket_flags, creds->ticket_flags)) + && + (! set(KRB5_TC_MATCH_TIMES_EXACT) || + times_match_exact(&mcreds->times, &creds->times)) + && + (! set(KRB5_TC_MATCH_TIMES) || + times_match(&mcreds->times, &creds->times)) + && + ( ! set(KRB5_TC_MATCH_AUTHDATA) || + authdata_match(mcreds->authdata, creds->authdata)) + && + (! set(KRB5_TC_MATCH_2ND_TKT) || + data_match (&mcreds->second_ticket, &creds->second_ticket)) + && + ((! set(KRB5_TC_MATCH_KTYPE))|| + (mcreds->keyblock.enctype == creds->keyblock.enctype))) + return TRUE; + return FALSE; +} + +static krb5_error_code +krb5_cc_retrieve_cred_seq (krb5_context context, krb5_ccache id, + krb5_flags whichfields, krb5_creds *mcreds, + krb5_creds *creds, int nktypes, krb5_enctype *ktypes) +{ + /* This function could be considerably faster if it kept indexing */ + /* information.. sounds like a "next version" idea to me. :-) */ + + krb5_cc_cursor cursor; + krb5_error_code kret; + krb5_error_code nomatch_err = KRB5_CC_NOTFOUND; + struct { + krb5_creds creds; + int pref; + } fetched, best; + int have_creds = 0; + krb5_flags oflags = 0; +#define fetchcreds (fetched.creds) + + kret = krb5_cc_start_seq_get(context, id, &cursor); + if (kret != KRB5_OK) + return kret; + + while (krb5_cc_next_cred(context, id, &cursor, &fetchcreds) == KRB5_OK) { + if (krb5int_cc_creds_match_request(context, whichfields, mcreds, &fetchcreds)) + { + if (ktypes) { + fetched.pref = pref (fetchcreds.keyblock.enctype, + nktypes, ktypes); + if (fetched.pref < 0) + nomatch_err = KRB5_CC_NOT_KTYPE; + else if (!have_creds || fetched.pref < best.pref) { + if (have_creds) + krb5_free_cred_contents (context, &best.creds); + else + have_creds = 1; + best = fetched; + continue; + } + } else { + krb5_cc_end_seq_get(context, id, &cursor); + *creds = fetchcreds; + return KRB5_OK; + } + } + + /* This one doesn't match */ + krb5_free_cred_contents(context, &fetchcreds); + } + + /* If we get here, a match wasn't found */ + krb5_cc_end_seq_get(context, id, &cursor); + if (have_creds) { + *creds = best.creds; + return KRB5_OK; + } else + return nomatch_err; +} + +krb5_error_code +k5_cc_retrieve_cred_default(krb5_context context, krb5_ccache id, + krb5_flags flags, krb5_creds *mcreds, + krb5_creds *creds) +{ + krb5_enctype *ktypes; + int nktypes; + krb5_error_code ret; + + if (flags & KRB5_TC_SUPPORTED_KTYPES) { + ret = krb5_get_tgs_ktypes (context, mcreds->server, &ktypes); + if (ret) + return ret; + nktypes = k5_count_etypes (ktypes); + + ret = krb5_cc_retrieve_cred_seq (context, id, flags, mcreds, creds, + nktypes, ktypes); + free (ktypes); + return ret; + } else { + return krb5_cc_retrieve_cred_seq (context, id, flags, mcreds, creds, + 0, 0); + } +} diff --git a/src/lib/krb5/ccache/ccapi/Makefile.in b/src/lib/krb5/ccache/ccapi/Makefile.in new file mode 100644 index 000000000000..73657378603f --- /dev/null +++ b/src/lib/krb5/ccache/ccapi/Makefile.in @@ -0,0 +1,26 @@ +mydir=lib$(S)krb5$(S)ccache$(S)ccapi +BUILDTOP=$(REL)..$(S)..$(S)..$(S).. +LOCALINCLUDES = $(WIN_INCLUDES) +DEFINES= -DUSE_CCAPI -DUSE_CCAPI_V3 + +##DOS##WIN_INCLUDES = -I$(top_srcdir)\windows\lib + +##DOS##BUILDTOP = ..\..\..\.. +##DOS##PREFIXDIR = ccache\file +##DOS##OBJFILE = $(OUTPRE)file.lst + +STLIBOBJS = \ + stdcc.o \ + stdcc_util.o \ + winccld.o + +OBJS = $(OUTPRE)stdcc.$(OBJEXT) $(OUTPRE)stdcc_util.$(OBJEXT) $(OUTPRE)winccld.$(OBJEXT) + +SRCS = $(srcdir)/stdcc.c $(srcdir)/stdcc_util.c $(srcdir)/winccld.c + +##DOS##LIBOBJS = $(OBJS) + +all-unix: all-libobjs +clean-unix:: clean-libobjs + +@libobj_frag@ diff --git a/src/lib/krb5/ccache/ccapi/deps b/src/lib/krb5/ccache/ccapi/deps new file mode 100644 index 000000000000..7df6d68520fe --- /dev/null +++ b/src/lib/krb5/ccache/ccapi/deps @@ -0,0 +1,18 @@ +# +# Generated makefile dependencies follow. +# +stdcc.so stdcc.po $(OUTPRE)stdcc.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/CredentialsCache.h \ + $(top_srcdir)/include/k5-buf.h $(top_srcdir)/include/k5-err.h \ + $(top_srcdir)/include/k5-gmt_mktime.h $(top_srcdir)/include/k5-int-pkinit.h \ + $(top_srcdir)/include/k5-int.h $(top_srcdir)/include/k5-platform.h \ + $(top_srcdir)/include/k5-plugin.h $(top_srcdir)/include/k5-thread.h \ + $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/locate_plugin.h \ + $(top_srcdir)/include/krb5/preauth_plugin.h $(top_srcdir)/include/port-sockets.h \ + $(top_srcdir)/include/socket-utils.h stdcc.c stdcc.h stdcc_util.h +stdcc_util.so stdcc_util.po $(OUTPRE)stdcc_util.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(COM_ERR_DEPS) $(top_srcdir)/include/CredentialsCache.h \ + $(top_srcdir)/include/krb5.h stdcc_util.c stdcc_util.h +winccld.so winccld.po $(OUTPRE)winccld.$(OBJEXT): winccld.c diff --git a/src/lib/krb5/ccache/ccapi/stdcc.c b/src/lib/krb5/ccache/ccapi/stdcc.c new file mode 100644 index 000000000000..0256a0a5d887 --- /dev/null +++ b/src/lib/krb5/ccache/ccapi/stdcc.c @@ -0,0 +1,1730 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/ccapi/stdcc.c - ccache API support functions */ +/* + * Copyright 1998, 1999, 2006, 2008 by the Massachusetts Institute of + * Technology. All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +/* + * Written by Frank Dabek July 1998 + * Updated by Jeffrey Altman June 2006 + */ + +#if defined(_WIN32) || defined(USE_CCAPI) + +#include "k5-int.h" +#include "../cc-int.h" +#include "stdcc.h" +#include "stdcc_util.h" +#include "string.h" +#include <stdio.h> + +#if defined(_WIN32) +#include "winccld.h" +#endif + +#ifndef CC_API_VER2 +#define CC_API_VER2 +#endif + +#ifdef DEBUG +#if defined(_WIN32) +#include <io.h> +#define SHOW_DEBUG(buf) MessageBox((HWND)NULL, (buf), "ccapi debug", MB_OK) +#endif +/* XXX need macintosh debugging statement if we want to debug */ +/* on the mac */ +#else +#define SHOW_DEBUG(buf) +#endif + +#ifdef USE_CCAPI_V3 +cc_context_t gCntrlBlock = NULL; +cc_int32 gCCVersion = 0; +#else +apiCB *gCntrlBlock = NULL; +#endif + +/* + * declare our global object wanna-be + * must be installed in ccdefops.c + */ + +krb5_cc_ops krb5_cc_stdcc_ops = { + 0, + "API", +#ifdef USE_CCAPI_V3 + krb5_stdccv3_get_name, + krb5_stdccv3_resolve, + krb5_stdccv3_generate_new, + krb5_stdccv3_initialize, + krb5_stdccv3_destroy, + krb5_stdccv3_close, + krb5_stdccv3_store, + krb5_stdccv3_retrieve, + krb5_stdccv3_get_principal, + krb5_stdccv3_start_seq_get, + krb5_stdccv3_next_cred, + krb5_stdccv3_end_seq_get, + krb5_stdccv3_remove, + krb5_stdccv3_set_flags, + krb5_stdccv3_get_flags, + krb5_stdccv3_ptcursor_new, + krb5_stdccv3_ptcursor_next, + krb5_stdccv3_ptcursor_free, + NULL, /* move */ + krb5_stdccv3_last_change_time, /* lastchange */ + NULL, /* wasdefault */ + krb5_stdccv3_lock, + krb5_stdccv3_unlock, + krb5_stdccv3_switch_to, +#else + krb5_stdcc_get_name, + krb5_stdcc_resolve, + krb5_stdcc_generate_new, + krb5_stdcc_initialize, + krb5_stdcc_destroy, + krb5_stdcc_close, + krb5_stdcc_store, + krb5_stdcc_retrieve, + krb5_stdcc_get_principal, + krb5_stdcc_start_seq_get, + krb5_stdcc_next_cred, + krb5_stdcc_end_seq_get, + krb5_stdcc_remove, + krb5_stdcc_set_flags, + krb5_stdcc_get_flags, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +#endif +}; + +#if defined(_WIN32) +/* + * cache_changed be called after the cache changes. + * A notification message is is posted out to all top level + * windows so that they may recheck the cache based on the + * changes made. We register a unique message type with which + * we'll communicate to all other processes. + */ +static void cache_changed() +{ + static unsigned int message = 0; + + if (message == 0) + message = RegisterWindowMessage(WM_KERBEROS5_CHANGED); + + PostMessage(HWND_BROADCAST, message, 0, 0); +} +#else /* _WIN32 */ + +static void cache_changed() +{ + return; +} +#endif /* _WIN32 */ + +struct err_xlate +{ + int cc_err; + krb5_error_code krb5_err; +}; + +static const struct err_xlate err_xlate_table[] = +{ +#ifdef USE_CCAPI_V3 + { ccIteratorEnd, KRB5_CC_END }, + { ccErrBadParam, KRB5_FCC_INTERNAL }, + { ccErrNoMem, KRB5_CC_NOMEM }, + { ccErrInvalidContext, KRB5_FCC_NOFILE }, + { ccErrInvalidCCache, KRB5_FCC_NOFILE }, + { ccErrInvalidString, KRB5_FCC_INTERNAL }, + { ccErrInvalidCredentials, KRB5_FCC_INTERNAL }, + { ccErrInvalidCCacheIterator, KRB5_FCC_INTERNAL }, + { ccErrInvalidCredentialsIterator, KRB5_FCC_INTERNAL }, + { ccErrInvalidLock, KRB5_FCC_INTERNAL }, + { ccErrBadName, KRB5_CC_BADNAME }, + { ccErrBadCredentialsVersion, KRB5_FCC_INTERNAL }, + { ccErrBadAPIVersion, KRB5_FCC_INTERNAL }, + { ccErrContextLocked, KRB5_FCC_INTERNAL }, + { ccErrContextUnlocked, KRB5_FCC_INTERNAL }, + { ccErrCCacheLocked, KRB5_FCC_INTERNAL }, + { ccErrCCacheUnlocked, KRB5_FCC_INTERNAL }, + { ccErrBadLockType, KRB5_FCC_INTERNAL }, + { ccErrNeverDefault, KRB5_FCC_INTERNAL }, + { ccErrCredentialsNotFound, KRB5_CC_NOTFOUND }, + { ccErrCCacheNotFound, KRB5_FCC_NOFILE }, + { ccErrContextNotFound, KRB5_FCC_NOFILE }, + { ccErrServerUnavailable, KRB5_CC_IO }, + { ccErrServerInsecure, KRB5_CC_IO }, + { ccErrServerCantBecomeUID, KRB5_CC_IO }, + { ccErrTimeOffsetNotSet, KRB5_FCC_INTERNAL }, + { ccErrBadInternalMessage, KRB5_FCC_INTERNAL }, + { ccErrNotImplemented, KRB5_FCC_INTERNAL }, +#else + { CC_BADNAME, KRB5_CC_BADNAME }, + { CC_NOTFOUND, KRB5_CC_NOTFOUND }, + { CC_END, KRB5_CC_END }, + { CC_IO, KRB5_CC_IO }, + { CC_WRITE, KRB5_CC_WRITE }, + { CC_NOMEM, KRB5_CC_NOMEM }, + { CC_FORMAT, KRB5_CC_FORMAT }, + { CC_WRITE, KRB5_CC_WRITE }, + { CC_LOCKED, KRB5_FCC_INTERNAL /* XXX */ }, + { CC_BAD_API_VERSION, KRB5_FCC_INTERNAL /* XXX */ }, + { CC_NO_EXIST, KRB5_FCC_NOFILE }, + { CC_NOT_SUPP, KRB5_FCC_INTERNAL /* XXX */ }, + { CC_BAD_PARM, KRB5_FCC_INTERNAL /* XXX */ }, + { CC_ERR_CACHE_ATTACH, KRB5_FCC_INTERNAL /* XXX */ }, + { CC_ERR_CACHE_RELEASE, KRB5_FCC_INTERNAL /* XXX */ }, + { CC_ERR_CACHE_FULL, KRB5_FCC_INTERNAL /* XXX */ }, + { CC_ERR_CRED_VERSION, KRB5_FCC_INTERNAL /* XXX */ }, +#endif + { 0, 0 } +}; + +/* Note: cc_err_xlate is NOT idempotent. Don't call it multiple times. */ +static krb5_error_code cc_err_xlate(int err) +{ + const struct err_xlate *p; + +#ifdef USE_CCAPI_V3 + if (err == ccNoError) + return 0; +#else + if (err == CC_NOERROR) + return 0; +#endif + + for (p = err_xlate_table; p->cc_err; p++) { + if (err == p->cc_err) + return p->krb5_err; + } + + return KRB5_FCC_INTERNAL; +} + + +#ifdef USE_CCAPI_V3 + +static krb5_error_code stdccv3_get_timeoffset (krb5_context in_context, + cc_ccache_t in_ccache) +{ + krb5_error_code err = 0; + + if (gCCVersion >= ccapi_version_5) { + krb5_os_context os_ctx = (krb5_os_context) &in_context->os_context; + cc_time_t time_offset = 0; + + err = cc_ccache_get_kdc_time_offset (in_ccache, cc_credentials_v5, + &time_offset); + + if (!err) { + os_ctx->time_offset = time_offset; + os_ctx->usec_offset = 0; + os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) | + KRB5_OS_TOFFSET_VALID); + } + + if (err == ccErrTimeOffsetNotSet) { + err = 0; /* okay if there is no time offset */ + } + } + + return err; /* Don't translate. Callers will translate for us */ +} + +static krb5_error_code stdccv3_set_timeoffset (krb5_context in_context, + cc_ccache_t in_ccache) +{ + krb5_error_code err = 0; + + if (gCCVersion >= ccapi_version_5) { + krb5_os_context os_ctx = (krb5_os_context) &in_context->os_context; + + if (!err && os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) { + err = cc_ccache_set_kdc_time_offset (in_ccache, + cc_credentials_v5, + os_ctx->time_offset); + } + } + + return err; /* Don't translate. Callers will translate for us */ +} + +static krb5_error_code stdccv3_setup (krb5_context context, + stdccCacheDataPtr ccapi_data) +{ + krb5_error_code err = 0; + + if (!err && !gCntrlBlock) { + err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL); + } + + if (!err && ccapi_data && !ccapi_data->NamedCache) { + /* ccache has not been opened yet. open it. */ + err = cc_context_open_ccache (gCntrlBlock, ccapi_data->cache_name, + &ccapi_data->NamedCache); + } + + if (!err && ccapi_data && ccapi_data->NamedCache) { + err = stdccv3_get_timeoffset (context, ccapi_data->NamedCache); + } + + return err; /* Don't translate. Callers will translate for us */ +} + +/* krb5_stdcc_shutdown is exported; use the old name */ +void krb5_stdcc_shutdown() +{ + if (gCntrlBlock) { cc_context_release(gCntrlBlock); } + gCntrlBlock = NULL; + gCCVersion = 0; +} + +/* + * -- generate_new -------------------------------- + * + * create a new cache with a unique name, corresponds to creating a + * named cache initialize the API here if we have to. + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_generate_new (krb5_context context, krb5_ccache *id ) +{ + krb5_error_code err = 0; + krb5_ccache newCache = NULL; + stdccCacheDataPtr ccapi_data = NULL; + cc_ccache_t ccache = NULL; + cc_string_t ccstring = NULL; + char *name = NULL; + + if (!err) { + err = stdccv3_setup(context, NULL); + } + + if (!err) { + newCache = (krb5_ccache) malloc (sizeof (*newCache)); + if (!newCache) { err = KRB5_CC_NOMEM; } + } + + if (!err) { + ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data)); + if (!ccapi_data) { err = KRB5_CC_NOMEM; } + } + + if (!err) { + err = cc_context_create_new_ccache (gCntrlBlock, cc_credentials_v5, "", + &ccache); + } + + if (!err) { + err = stdccv3_set_timeoffset (context, ccache); + } + + if (!err) { + err = cc_ccache_get_name (ccache, &ccstring); + } + + if (!err) { + name = strdup (ccstring->data); + if (!name) { err = KRB5_CC_NOMEM; } + } + + if (!err) { + ccapi_data->cache_name = name; + name = NULL; /* take ownership */ + + ccapi_data->NamedCache = ccache; + ccache = NULL; /* take ownership */ + + newCache->ops = &krb5_cc_stdcc_ops; + newCache->data = ccapi_data; + ccapi_data = NULL; /* take ownership */ + + /* return a pointer to the new cache */ + *id = newCache; + newCache = NULL; + } + + if (ccstring) { cc_string_release (ccstring); } + if (name) { free (name); } + if (ccache) { cc_ccache_release (ccache); } + if (ccapi_data) { free (ccapi_data); } + if (newCache) { free (newCache); } + + return cc_err_xlate (err); +} + +/* + * resolve + * + * create a new cache with the name stored in residual + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_resolve (krb5_context context, krb5_ccache *id , const char *residual ) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = NULL; + krb5_ccache ccache = NULL; + char *name = NULL; + cc_string_t defname = NULL; + + if (id == NULL) { err = KRB5_CC_NOMEM; } + + if (!err) { + err = stdccv3_setup (context, NULL); + } + + if (!err) { + ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data)); + if (!ccapi_data) { err = KRB5_CC_NOMEM; } + } + + if (!err) { + ccache = (krb5_ccache ) malloc (sizeof (*ccache)); + if (!ccache) { err = KRB5_CC_NOMEM; } + } + + if (!err) { + if ((residual == NULL) || (strlen(residual) == 0)) { + err = cc_context_get_default_ccache_name(gCntrlBlock, &defname); + if (defname) + residual = defname->data; + } + } + + if (!err) { + name = strdup (residual); + if (!name) { err = KRB5_CC_NOMEM; } + } + + if (!err) { + err = cc_context_open_ccache (gCntrlBlock, residual, + &ccapi_data->NamedCache); + if (err == ccErrCCacheNotFound) { + ccapi_data->NamedCache = NULL; + err = 0; /* ccache just doesn't exist yet */ + } + } + + if (!err) { + ccapi_data->cache_name = name; + name = NULL; /* take ownership */ + + ccache->ops = &krb5_cc_stdcc_ops; + ccache->data = ccapi_data; + ccapi_data = NULL; /* take ownership */ + + *id = ccache; + ccache = NULL; /* take ownership */ + } + + if (ccache) { free (ccache); } + if (ccapi_data) { free (ccapi_data); } + if (name) { free (name); } + if (defname) { cc_string_release(defname); } + + return cc_err_xlate (err); +} + +/* + * initialize + * + * initialize the cache, check to see if one already exists for this + * principal if not set our principal to this principal. This + * searching enables ticket sharing + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_initialize (krb5_context context, + krb5_ccache id, + krb5_principal princ) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + char *name = NULL; + cc_ccache_t ccache = NULL; + + if (id == NULL) { err = KRB5_CC_NOMEM; } + + if (!err) { + err = stdccv3_setup (context, NULL); + } + + if (!err) { + err = krb5_unparse_name(context, princ, &name); + } + + if (!err) { + err = cc_context_create_ccache (gCntrlBlock, ccapi_data->cache_name, + cc_credentials_v5, name, + &ccache); + } + + if (!err) { + err = stdccv3_set_timeoffset (context, ccache); + } + + if (!err) { + if (ccapi_data->NamedCache) { + err = cc_ccache_release (ccapi_data->NamedCache); + } + ccapi_data->NamedCache = ccache; + ccache = NULL; /* take ownership */ + cache_changed (); + } + + if (ccache) { cc_ccache_release (ccache); } + if (name ) { krb5_free_unparsed_name(context, name); } + + return cc_err_xlate(err); +} + +/* + * store + * + * store some credentials in our cache + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_store (krb5_context context, krb5_ccache id, krb5_creds *creds ) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + cc_credentials_union *cred_union = NULL; + + if (!err) { + err = stdccv3_setup (context, ccapi_data); + } + + if (!err) { + /* copy the fields from the almost identical structures */ + err = copy_krb5_creds_to_cc_cred_union (context, creds, &cred_union); + } + + if (!err) { + err = cc_ccache_store_credentials (ccapi_data->NamedCache, cred_union); + } + + if (!err) { + cache_changed(); + } + + if (cred_union) { cred_union_release (cred_union); } + + return cc_err_xlate (err); +} + +/* + * start_seq_get + * + * begin an iterator call to get all of the credentials in the cache + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_start_seq_get (krb5_context context, + krb5_ccache id, + krb5_cc_cursor *cursor ) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + cc_credentials_iterator_t iterator = NULL; + + if (!err) { + err = stdccv3_setup (context, ccapi_data); + } + + if (!err) { + err = cc_ccache_new_credentials_iterator(ccapi_data->NamedCache, + &iterator); + } + + if (!err) { + *cursor = iterator; + } + + return cc_err_xlate (err); +} + +/* + * next cred + * + * - get the next credential in the cache as part of an iterator call + * - this maps to call to cc_seq_fetch_creds + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_next_cred (krb5_context context, + krb5_ccache id, + krb5_cc_cursor *cursor, + krb5_creds *creds) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + cc_credentials_t credentials = NULL; + cc_credentials_iterator_t iterator = *cursor; + + if (!iterator) { err = KRB5_CC_END; } + + if (!err) { + err = stdccv3_setup (context, ccapi_data); + } + + /* Note: CCAPI v3 ccaches can contain both v4 and v5 creds */ + while (!err) { + err = cc_credentials_iterator_next (iterator, &credentials); + + if (!err && (credentials->data->version == cc_credentials_v5)) { + copy_cc_cred_union_to_krb5_creds(context, credentials->data, creds); + break; + } + } + + if (credentials) { cc_credentials_release (credentials); } + if (err == ccIteratorEnd) { + cc_credentials_iterator_release (iterator); + *cursor = 0; + } + + return cc_err_xlate (err); +} + + +/* + * retrieve + * + * - try to find a matching credential in the cache + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_retrieve (krb5_context context, + krb5_ccache id, + krb5_flags whichfields, + krb5_creds *mcreds, + krb5_creds *creds) +{ + return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, + creds); +} + +/* + * end seq + * + * just free up the storage assoicated with the cursor (if we can) + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_end_seq_get (krb5_context context, + krb5_ccache id, + krb5_cc_cursor *cursor) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + cc_credentials_iterator_t iterator = *cursor; + + if (!iterator) { return 0; } + + if (!err) { + err = stdccv3_setup (context, ccapi_data); + } + + if (!err) { + err = cc_credentials_iterator_release(iterator); + } + + return cc_err_xlate(err); +} + +/* + * close + * + * - free our pointers to the NC + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_close(krb5_context context, + krb5_ccache id) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + + if (!err) { + err = stdccv3_setup (context, NULL); + } + + if (!err) { + if (ccapi_data) { + if (ccapi_data->cache_name) { + free (ccapi_data->cache_name); + } + if (ccapi_data->NamedCache) { + err = cc_ccache_release (ccapi_data->NamedCache); + } + free (ccapi_data); + id->data = NULL; + } + free (id); + } + + return cc_err_xlate(err); +} + +/* + * destroy + * + * - free our storage and the cache + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_destroy (krb5_context context, + krb5_ccache id) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + + if (!err) { + err = stdccv3_setup(context, ccapi_data); + } + + if (!err) { + if (ccapi_data) { + if (ccapi_data->cache_name) { + free(ccapi_data->cache_name); + } + if (ccapi_data->NamedCache) { + /* destroy the named cache */ + err = cc_ccache_destroy(ccapi_data->NamedCache); + if (err == ccErrCCacheNotFound) { + err = 0; /* ccache maybe already destroyed */ + } + cache_changed(); + } + free(ccapi_data); + id->data = NULL; + } + free(id); + } + + return cc_err_xlate(err); +} + +/* + * getname + * + * - return the name of the named cache + */ +const char * KRB5_CALLCONV +krb5_stdccv3_get_name (krb5_context context, + krb5_ccache id ) +{ + stdccCacheDataPtr ccapi_data = id->data; + + if (!ccapi_data) { + return NULL; + } else { + return (ccapi_data->cache_name); + } +} + + +/* get_principal + * + * - return the principal associated with the named cache + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_get_principal (krb5_context context, + krb5_ccache id , + krb5_principal *princ) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + cc_string_t name = NULL; + + if (!err) { + err = stdccv3_setup(context, ccapi_data); + } + + if (!err) { + err = cc_ccache_get_principal (ccapi_data->NamedCache, cc_credentials_v5, &name); + } + + if (!err) { + err = krb5_parse_name (context, name->data, princ); + } else { + err = cc_err_xlate (err); + } + + if (name) { cc_string_release (name); } + + return err; +} + +/* + * set_flags + * + * - currently a NOP since we don't store any flags in the NC + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_set_flags (krb5_context context, + krb5_ccache id, + krb5_flags flags) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + + err = stdccv3_setup (context, ccapi_data); + + return cc_err_xlate (err); +} + +/* + * get_flags + * + * - currently a NOP since we don't store any flags in the NC + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_get_flags (krb5_context context, + krb5_ccache id, + krb5_flags *flags) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + + err = stdccv3_setup (context, ccapi_data); + + return cc_err_xlate (err); +} + +/* + * remove + * + * - remove the specified credentials from the NC + */ +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_remove (krb5_context context, + krb5_ccache id, + krb5_flags whichfields, + krb5_creds *in_creds) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + cc_credentials_iterator_t iterator = NULL; + int found = 0; + + if (!err) { + err = stdccv3_setup(context, ccapi_data); + } + + + if (!err) { + err = cc_ccache_new_credentials_iterator(ccapi_data->NamedCache, + &iterator); + } + + /* Note: CCAPI v3 ccaches can contain both v4 and v5 creds */ + while (!err && !found) { + cc_credentials_t credentials = NULL; + + err = cc_credentials_iterator_next (iterator, &credentials); + + if (!err && (credentials->data->version == cc_credentials_v5)) { + krb5_creds creds; + + err = copy_cc_cred_union_to_krb5_creds(context, + credentials->data, &creds); + + if (!err) { + found = krb5int_cc_creds_match_request(context, + whichfields, + in_creds, + &creds); + krb5_free_cred_contents (context, &creds); + } + + if (!err && found) { + err = cc_ccache_remove_credentials (ccapi_data->NamedCache, credentials); + } + } + + if (credentials) { cc_credentials_release (credentials); } + } + if (err == ccIteratorEnd) { err = ccErrCredentialsNotFound; } + + if (iterator) { + err = cc_credentials_iterator_release(iterator); + } + + if (!err) { + cache_changed (); + } + + return cc_err_xlate (err); +} + +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_ptcursor_new(krb5_context context, + krb5_cc_ptcursor *cursor) +{ + krb5_error_code err = 0; + krb5_cc_ptcursor ptcursor = NULL; + cc_ccache_iterator_t iterator = NULL; + + ptcursor = malloc(sizeof(*ptcursor)); + if (ptcursor == NULL) { + err = ccErrNoMem; + } + else { + memset(ptcursor, 0, sizeof(*ptcursor)); + } + + if (!err) { + err = stdccv3_setup(context, NULL); + } + if (!err) { + ptcursor->ops = &krb5_cc_stdcc_ops; + err = cc_context_new_ccache_iterator(gCntrlBlock, &iterator); + } + + if (!err) { + ptcursor->data = iterator; + } + + if (err) { + if (ptcursor) { krb5_stdccv3_ptcursor_free(context, &ptcursor); } + // krb5_stdccv3_ptcursor_free sets ptcursor to NULL for us + } + + *cursor = ptcursor; + + return cc_err_xlate(err); +} + +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_ptcursor_next( + krb5_context context, + krb5_cc_ptcursor cursor, + krb5_ccache *ccache) +{ + krb5_error_code err = 0; + cc_ccache_iterator_t iterator = NULL; + + krb5_ccache newCache = NULL; + stdccCacheDataPtr ccapi_data = NULL; + cc_ccache_t ccCache = NULL; + cc_string_t ccstring = NULL; + char *name = NULL; + + if (!cursor || !cursor->data) { + err = ccErrInvalidContext; + } + + *ccache = NULL; + + if (!err) { + newCache = (krb5_ccache) malloc (sizeof (*newCache)); + if (!newCache) { err = ccErrNoMem; } + } + + if (!err) { + ccapi_data = (stdccCacheDataPtr) malloc (sizeof (*ccapi_data)); + if (!ccapi_data) { err = ccErrNoMem; } + } + + if (!err) { + iterator = cursor->data; + err = cc_ccache_iterator_next(iterator, &ccCache); + } + + if (!err) { + err = cc_ccache_get_name (ccCache, &ccstring); + } + + if (!err) { + name = strdup (ccstring->data); + if (!name) { err = ccErrNoMem; } + } + + if (!err) { + ccapi_data->cache_name = name; + name = NULL; /* take ownership */ + + ccapi_data->NamedCache = ccCache; + ccCache = NULL; /* take ownership */ + + newCache->ops = &krb5_cc_stdcc_ops; + newCache->data = ccapi_data; + ccapi_data = NULL; /* take ownership */ + + /* return a pointer to the new cache */ + *ccache = newCache; + newCache = NULL; + } + + if (name) { free (name); } + if (ccstring) { cc_string_release (ccstring); } + if (ccCache) { cc_ccache_release (ccCache); } + if (ccapi_data) { free (ccapi_data); } + if (newCache) { free (newCache); } + + if (err == ccIteratorEnd) { + err = ccNoError; + } + + return cc_err_xlate(err); +} + +krb5_error_code KRB5_CALLCONV +krb5_stdccv3_ptcursor_free( + krb5_context context, + krb5_cc_ptcursor *cursor) +{ + if (*cursor != NULL) { + if ((*cursor)->data != NULL) { + cc_ccache_iterator_release((cc_ccache_iterator_t)((*cursor)->data)); + } + free(*cursor); + *cursor = NULL; + } + return 0; +} + +krb5_error_code KRB5_CALLCONV krb5_stdccv3_last_change_time +(krb5_context context, krb5_ccache id, + krb5_timestamp *change_time) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + cc_time_t ccapi_change_time = 0; + + *change_time = 0; + + if (!err) { + err = stdccv3_setup(context, ccapi_data); + } + if (!err) { + err = cc_ccache_get_change_time (ccapi_data->NamedCache, &ccapi_change_time); + } + if (!err) { + *change_time = ccapi_change_time; + } + + return cc_err_xlate (err); +} + +krb5_error_code KRB5_CALLCONV krb5_stdccv3_lock +(krb5_context context, krb5_ccache id) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + + if (!err) { + err = stdccv3_setup(context, ccapi_data); + } + if (!err) { + err = cc_ccache_lock(ccapi_data->NamedCache, cc_lock_write, cc_lock_block); + } + return cc_err_xlate(err); +} + +krb5_error_code KRB5_CALLCONV krb5_stdccv3_unlock +(krb5_context context, krb5_ccache id) +{ + krb5_error_code err = 0; + stdccCacheDataPtr ccapi_data = id->data; + + if (!err) { + err = stdccv3_setup(context, ccapi_data); + } + if (!err) { + err = cc_ccache_unlock(ccapi_data->NamedCache); + } + return cc_err_xlate(err); +} + +krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_lock +(krb5_context context) +{ + krb5_error_code err = 0; + + if (!err && !gCntrlBlock) { + err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL); + } + if (!err) { + err = cc_context_lock(gCntrlBlock, cc_lock_write, cc_lock_block); + } + return cc_err_xlate(err); +} + +krb5_error_code KRB5_CALLCONV krb5_stdccv3_context_unlock +(krb5_context context) +{ + krb5_error_code err = 0; + + if (!err && !gCntrlBlock) { + err = cc_initialize (&gCntrlBlock, ccapi_version_max, &gCCVersion, NULL); + } + if (!err) { + err = cc_context_unlock(gCntrlBlock); + } + return cc_err_xlate(err); +} + +krb5_error_code KRB5_CALLCONV krb5_stdccv3_switch_to +(krb5_context context, krb5_ccache id) +{ + krb5_error_code retval; + stdccCacheDataPtr ccapi_data = id->data; + int err; + + retval = stdccv3_setup(context, ccapi_data); + if (retval) + return cc_err_xlate(retval); + + err = cc_ccache_set_default(ccapi_data->NamedCache); + return cc_err_xlate(err); +} + +#else /* !USE_CCAPI_V3 */ + +static krb5_error_code stdcc_setup(krb5_context context, + stdccCacheDataPtr ccapi_data) +{ + int err; + + /* make sure the API has been intialized */ + if (gCntrlBlock == NULL) { +#ifdef CC_API_VER2 + err = cc_initialize(&gCntrlBlock, CC_API_VER_2, NULL, NULL); +#else + err = cc_initialize(&gCntrlBlock, CC_API_VER_1, NULL, NULL); +#endif + if (err != CC_NOERROR) + return cc_err_xlate(err); + } + + /* + * No ccapi_data structure, so we don't need to make sure the + * ccache exists. + */ + if (!ccapi_data) + return 0; + + /* + * The ccache already exists + */ + if (ccapi_data->NamedCache) + return 0; + + err = cc_open(gCntrlBlock, ccapi_data->cache_name, + CC_CRED_V5, 0L, &ccapi_data->NamedCache); + if (err == CC_NOTFOUND) + err = CC_NO_EXIST; + if (err == CC_NOERROR) + return 0; + + ccapi_data->NamedCache = NULL; + return cc_err_xlate(err); +} + +void krb5_stdcc_shutdown() +{ + if (gCntrlBlock) + cc_shutdown(&gCntrlBlock); + gCntrlBlock = NULL; +} + +/* + * -- generate_new -------------------------------- + * + * create a new cache with a unique name, corresponds to creating a + * named cache iniitialize the API here if we have to. + */ +krb5_error_code KRB5_CALLCONV krb5_stdcc_generate_new +(krb5_context context, krb5_ccache *id ) +{ + krb5_ccache newCache = NULL; + krb5_error_code retval; + stdccCacheDataPtr ccapi_data = NULL; + char *name = NULL; + cc_time_t change_time; + int err; + + if ((retval = stdcc_setup(context, NULL))) + return retval; + + retval = KRB5_CC_NOMEM; + if (!(newCache = (krb5_ccache) malloc(sizeof(struct _krb5_ccache)))) + goto errout; + if (!(c |