aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/krb5
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2017-07-07 17:03:42 +0000
committerCy Schubert <cy@FreeBSD.org>2017-07-07 17:03:42 +0000
commit33a9b234e7087f573ef08cd7318c6497ba08b439 (patch)
treed0ea40ad3bf5463a3c55795977c71bcb7d781b4b /src/lib/krb5
downloadsrc-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')
-rw-r--r--src/lib/krb5/Makefile.in74
-rw-r--r--src/lib/krb5/asn.1/KRB5-asn.py436
-rw-r--r--src/lib/krb5/asn.1/Makefile.in35
-rw-r--r--src/lib/krb5/asn.1/README.asn1577
-rw-r--r--src/lib/krb5/asn.1/TODO.asn175
-rw-r--r--src/lib/krb5/asn.1/asn1_encode.c1636
-rw-r--r--src/lib/krb5/asn.1/asn1_encode.h588
-rw-r--r--src/lib/krb5/asn.1/asn1_k_encode.c1817
-rw-r--r--src/lib/krb5/asn.1/asn1buf.c209
-rw-r--r--src/lib/krb5/asn.1/asn1buf.h147
-rw-r--r--src/lib/krb5/asn.1/deps48
-rw-r--r--src/lib/krb5/asn.1/krbasn1.h74
-rw-r--r--src/lib/krb5/asn.1/ldap_key_seq.c127
-rw-r--r--src/lib/krb5/ccache/Makefile.in159
-rw-r--r--src/lib/krb5/ccache/cc-int.h210
-rw-r--r--src/lib/krb5/ccache/cc_dir.c772
-rw-r--r--src/lib/krb5/ccache/cc_file.c1296
-rw-r--r--src/lib/krb5/ccache/cc_kcm.c1074
-rw-r--r--src/lib/krb5/ccache/cc_keyring.c1755
-rw-r--r--src/lib/krb5/ccache/cc_memory.c772
-rw-r--r--src/lib/krb5/ccache/cc_mslsa.c2209
-rw-r--r--src/lib/krb5/ccache/cc_retr.c280
-rw-r--r--src/lib/krb5/ccache/ccapi/Makefile.in26
-rw-r--r--src/lib/krb5/ccache/ccapi/deps18
-rw-r--r--src/lib/krb5/ccache/ccapi/stdcc.c1730
-rw-r--r--src/lib/krb5/ccache/ccapi/stdcc.h180
-rw-r--r--src/lib/krb5/ccache/ccapi/stdcc_util.c1071
-rw-r--r--src/lib/krb5/ccache/ccapi/stdcc_util.h49
-rw-r--r--src/lib/krb5/ccache/ccapi/winccld.c101
-rw-r--r--src/lib/krb5/ccache/ccapi/winccld.h204
-rw-r--r--src/lib/krb5/ccache/ccbase.c579
-rw-r--r--src/lib/krb5/ccache/cccopy.c37
-rw-r--r--src/lib/krb5/ccache/cccursor.c295
-rw-r--r--src/lib/krb5/ccache/ccdefault.c97
-rw-r--r--src/lib/krb5/ccache/ccdefops.c58
-rw-r--r--src/lib/krb5/ccache/ccfns.c331
-rw-r--r--src/lib/krb5/ccache/ccmarshal.c517
-rw-r--r--src/lib/krb5/ccache/ccselect.c179
-rw-r--r--src/lib/krb5/ccache/ccselect_k5identity.c210
-rw-r--r--src/lib/krb5/ccache/ccselect_realm.c95
-rw-r--r--src/lib/krb5/ccache/deps221
-rw-r--r--src/lib/krb5/ccache/fcc.h41
-rw-r--r--src/lib/krb5/ccache/kcmrpc.defs56
-rw-r--r--src/lib/krb5/ccache/kcmrpc_types.h39
-rw-r--r--src/lib/krb5/ccache/scc.h88
-rw-r--r--src/lib/krb5/ccache/ser_cc.c215
-rw-r--r--src/lib/krb5/ccache/t_cc.c439
-rw-r--r--src/lib/krb5/ccache/t_cccol.c363
-rwxr-xr-xsrc/lib/krb5/ccache/t_cccol.py119
-rw-r--r--src/lib/krb5/ccache/t_cccursor.c81
-rw-r--r--src/lib/krb5/ccache/t_marshal.c407
-rw-r--r--src/lib/krb5/ccache/t_memory.c138
-rw-r--r--src/lib/krb5/ccache/t_stdio.c168
-rw-r--r--src/lib/krb5/deps16
-rw-r--r--src/lib/krb5/error_tables/Makefile.in63
-rw-r--r--src/lib/krb5/error_tables/asn1_err.et16
-rw-r--r--src/lib/krb5/error_tables/deps15
-rw-r--r--src/lib/krb5/error_tables/init_ets.c48
-rw-r--r--src/lib/krb5/error_tables/k5e1_err.et45
-rw-r--r--src/lib/krb5/error_tables/kdb5_err.et89
-rw-r--r--src/lib/krb5/error_tables/krb524_err.et34
-rw-r--r--src/lib/krb5/error_tables/krb5_err.et356
-rw-r--r--src/lib/krb5/error_tables/kv5m_err.et93
-rw-r--r--src/lib/krb5/keytab/Makefile.in72
-rw-r--r--src/lib/krb5/keytab/deps122
-rw-r--r--src/lib/krb5/keytab/kt-int.h47
-rw-r--r--src/lib/krb5/keytab/kt_file.c1665
-rw-r--r--src/lib/krb5/keytab/kt_memory.c638
-rw-r--r--src/lib/krb5/keytab/kt_srvtab.c435
-rw-r--r--src/lib/krb5/keytab/ktadd.c39
-rw-r--r--src/lib/krb5/keytab/ktbase.c301
-rw-r--r--src/lib/krb5/keytab/ktdefault.c62
-rw-r--r--src/lib/krb5/keytab/ktfns.c165
-rw-r--r--src/lib/krb5/keytab/ktfr_entry.c50
-rw-r--r--src/lib/krb5/keytab/ktremove.c39
-rw-r--r--src/lib/krb5/keytab/read_servi.c84
-rw-r--r--src/lib/krb5/keytab/t_keytab.c456
-rw-r--r--src/lib/krb5/krb/Makefile.in531
-rw-r--r--src/lib/krb5/krb/addr_comp.c45
-rw-r--r--src/lib/krb5/krb/addr_order.c60
-rw-r--r--src/lib/krb5/krb/addr_srch.c65
-rw-r--r--src/lib/krb5/krb/ai_authdata.c341
-rw-r--r--src/lib/krb5/krb/allow_weak.c34
-rw-r--r--src/lib/krb5/krb/appdefault.c167
-rw-r--r--src/lib/krb5/krb/auth_con.c463
-rw-r--r--src/lib/krb5/krb/auth_con.h39
-rw-r--r--src/lib/krb5/krb/authdata.c1311
-rw-r--r--src/lib/krb5/krb/authdata.h107
-rw-r--r--src/lib/krb5/krb/authdata_dec.c296
-rw-r--r--src/lib/krb5/krb/authdata_enc.c146
-rw-r--r--src/lib/krb5/krb/authdata_exp.c95
-rw-r--r--src/lib/krb5/krb/bld_pr_ext.c97
-rw-r--r--src/lib/krb5/krb/bld_princ.c166
-rw-r--r--src/lib/krb5/krb/brand.c72
-rw-r--r--src/lib/krb5/krb/cammac_util.c86
-rw-r--r--src/lib/krb5/krb/chk_trans.c438
-rw-r--r--src/lib/krb5/krb/chpw.c511
-rw-r--r--src/lib/krb5/krb/cleanup.h30
-rw-r--r--src/lib/krb5/krb/conv_creds.c61
-rw-r--r--src/lib/krb5/krb/conv_princ.c358
-rw-r--r--src/lib/krb5/krb/copy_addrs.c77
-rw-r--r--src/lib/krb5/krb/copy_athctr.c88
-rw-r--r--src/lib/krb5/krb/copy_auth.c117
-rw-r--r--src/lib/krb5/krb/copy_cksum.c46
-rw-r--r--src/lib/krb5/krb/copy_creds.c114
-rw-r--r--src/lib/krb5/krb/copy_ctx.c129
-rw-r--r--src/lib/krb5/krb/copy_data.c98
-rw-r--r--src/lib/krb5/krb/copy_key.c35
-rw-r--r--src/lib/krb5/krb/copy_princ.c74
-rw-r--r--src/lib/krb5/krb/copy_tick.c122
-rw-r--r--src/lib/krb5/krb/cp_key_cnt.c36
-rw-r--r--src/lib/krb5/krb/decode_kdc.c86
-rw-r--r--src/lib/krb5/krb/decrypt_tk.c70
-rw-r--r--src/lib/krb5/krb/deltat.c1696
-rw-r--r--src/lib/krb5/krb/deps1402
-rw-r--r--src/lib/krb5/krb/enc_helper.c53
-rw-r--r--src/lib/krb5/krb/enc_keyhelper.c56
-rw-r--r--src/lib/krb5/krb/encode_kdc.c125
-rw-r--r--src/lib/krb5/krb/encrypt_tk.c64
-rw-r--r--src/lib/krb5/krb/etype_list.c70
-rw-r--r--src/lib/krb5/krb/fast.c706
-rw-r--r--src/lib/krb5/krb/fast.h114
-rw-r--r--src/lib/krb5/krb/fwd_tgt.c186
-rw-r--r--src/lib/krb5/krb/gc_via_tkt.c425
-rw-r--r--src/lib/krb5/krb/gen_save_subkey.c70
-rw-r--r--src/lib/krb5/krb/gen_seqnum.c77
-rw-r--r--src/lib/krb5/krb/gen_subkey.c75
-rw-r--r--src/lib/krb5/krb/get_creds.c1293
-rw-r--r--src/lib/krb5/krb/get_in_tkt.c1849
-rw-r--r--src/lib/krb5/krb/gic_keytab.c381
-rw-r--r--src/lib/krb5/krb/gic_opt.c500
-rw-r--r--src/lib/krb5/krb/gic_pwd.c584
-rw-r--r--src/lib/krb5/krb/in_tkt_sky.c120
-rw-r--r--src/lib/krb5/krb/init_creds_ctx.h84
-rw-r--r--src/lib/krb5/krb/init_ctx.c605
-rw-r--r--src/lib/krb5/krb/init_keyblock.c35
-rw-r--r--src/lib/krb5/krb/int-proto.h330
-rw-r--r--src/lib/krb5/krb/kdc_rep_dc.c77
-rw-r--r--src/lib/krb5/krb/kerrs.c252
-rw-r--r--src/lib/krb5/krb/kfree.c892
-rw-r--r--src/lib/krb5/krb/libdef_parse.c152
-rw-r--r--src/lib/krb5/krb/mk_cred.c308
-rw-r--r--src/lib/krb5/krb/mk_error.c49
-rw-r--r--src/lib/krb5/krb/mk_priv.c225
-rw-r--r--src/lib/krb5/krb/mk_rep.c152
-rw-r--r--src/lib/krb5/krb/mk_req.c88
-rw-r--r--src/lib/krb5/krb/mk_req_ext.c430
-rw-r--r--src/lib/krb5/krb/mk_safe.c258
-rw-r--r--src/lib/krb5/krb/pac.c1252
-rw-r--r--src/lib/krb5/krb/pac_sign.c269
-rw-r--r--src/lib/krb5/krb/parse.c238
-rw-r--r--src/lib/krb5/krb/parse_host_string.c124
-rw-r--r--src/lib/krb5/krb/plugin.c494
-rw-r--r--src/lib/krb5/krb/pr_to_salt.c83
-rw-r--r--src/lib/krb5/krb/preauth2.c1034
-rw-r--r--src/lib/krb5/krb/preauth_ec.c166
-rw-r--r--src/lib/krb5/krb/preauth_encts.c137
-rw-r--r--src/lib/krb5/krb/preauth_otp.c1263
-rw-r--r--src/lib/krb5/krb/preauth_pkinit.c204
-rw-r--r--src/lib/krb5/krb/preauth_sam2.c433
-rw-r--r--src/lib/krb5/krb/princ_comp.c167
-rw-r--r--src/lib/krb5/krb/privsafe.c270
-rw-r--r--src/lib/krb5/krb/random_str.c68
-rw-r--r--src/lib/krb5/krb/rd_cred.c216
-rw-r--r--src/lib/krb5/krb/rd_error.c44
-rw-r--r--src/lib/krb5/krb/rd_priv.c188
-rw-r--r--src/lib/krb5/krb/rd_rep.c204
-rw-r--r--src/lib/krb5/krb/rd_req.c109
-rw-r--r--src/lib/krb5/krb/rd_req_dec.c990
-rw-r--r--src/lib/krb5/krb/rd_safe.c210
-rw-r--r--src/lib/krb5/krb/recvauth.c251
-rw-r--r--src/lib/krb5/krb/response_items.c224
-rw-r--r--src/lib/krb5/krb/s4u_authdata.c601
-rw-r--r--src/lib/krb5/krb/s4u_creds.c833
-rw-r--r--src/lib/krb5/krb/send_tgs.c293
-rw-r--r--src/lib/krb5/krb/sendauth.c230
-rw-r--r--src/lib/krb5/krb/ser_actx.c544
-rw-r--r--src/lib/krb5/krb/ser_adata.c194
-rw-r--r--src/lib/krb5/krb/ser_addr.c198
-rw-r--r--src/lib/krb5/krb/ser_auth.c344
-rw-r--r--src/lib/krb5/krb/ser_cksum.c196
-rw-r--r--src/lib/krb5/krb/ser_ctx.c638
-rw-r--r--src/lib/krb5/krb/ser_key.c194
-rw-r--r--src/lib/krb5/krb/ser_princ.c177
-rw-r--r--src/lib/krb5/krb/serialize.c269
-rw-r--r--src/lib/krb5/krb/set_realm.c48
-rw-r--r--src/lib/krb5/krb/sname_match.c57
-rw-r--r--src/lib/krb5/krb/srv_dec_tkt.c140
-rw-r--r--src/lib/krb5/krb/srv_rcache.c87
-rw-r--r--src/lib/krb5/krb/str_conv.c352
-rw-r--r--src/lib/krb5/krb/strftime.c416
-rw-r--r--src/lib/krb5/krb/strptime.c386
-rw-r--r--src/lib/krb5/krb/t_ad_fx_armor.c37
-rw-r--r--src/lib/krb5/krb/t_authdata.c113
-rw-r--r--src/lib/krb5/krb/t_cc_config.c163
-rw-r--r--src/lib/krb5/krb/t_copy_context.c166
-rw-r--r--src/lib/krb5/krb/t_deltat.c157
-rw-r--r--src/lib/krb5/krb/t_etypes.c277
-rw-r--r--src/lib/krb5/krb/t_expand.c3
-rw-r--r--src/lib/krb5/krb/t_expire_warn.c87
-rwxr-xr-xsrc/lib/krb5/krb/t_expire_warn.py65
-rw-r--r--src/lib/krb5/krb/t_in_ccache.c149
-rwxr-xr-xsrc/lib/krb5/krb/t_in_ccache_patypes.py86
-rw-r--r--src/lib/krb5/krb/t_kerb.c234
-rw-r--r--src/lib/krb5/krb/t_krb5.conf53
-rw-r--r--src/lib/krb5/krb/t_pac.c322
-rw-r--r--src/lib/krb5/krb/t_parse_host_string.c251
-rw-r--r--src/lib/krb5/krb/t_princ.c404
-rw-r--r--src/lib/krb5/krb/t_ref_kerb.out19
-rw-r--r--src/lib/krb5/krb/t_response_items.c94
-rw-r--r--src/lib/krb5/krb/t_ser.c712
-rw-r--r--src/lib/krb5/krb/t_sname_match.c117
-rw-r--r--src/lib/krb5/krb/t_vfy_increds.c79
-rwxr-xr-xsrc/lib/krb5/krb/t_vfy_increds.py100
-rw-r--r--src/lib/krb5/krb/t_walk_rtree.c58
-rw-r--r--src/lib/krb5/krb/tgtname.c48
-rwxr-xr-xsrc/lib/krb5/krb/transit-tests54
-rw-r--r--src/lib/krb5/krb/unparse.c257
-rw-r--r--src/lib/krb5/krb/val_renew.c197
-rw-r--r--src/lib/krb5/krb/valid_times.c57
-rw-r--r--src/lib/krb5/krb/vfy_increds.c321
-rw-r--r--src/lib/krb5/krb/vic_opt.c15
-rw-r--r--src/lib/krb5/krb/walk_rtree.c620
-rw-r--r--src/lib/krb5/krb/walktree-tests79
-rw-r--r--src/lib/krb5/krb/x-deltat.y241
-rw-r--r--src/lib/krb5/krb5_libinit.c111
-rw-r--r--src/lib/krb5/krb5_libinit.h10
-rw-r--r--src/lib/krb5/libkrb5.exports670
-rw-r--r--src/lib/krb5/os/Makefile.in263
-rw-r--r--src/lib/krb5/os/accessor.c117
-rw-r--r--src/lib/krb5/os/c_ustime.c126
-rw-r--r--src/lib/krb5/os/ccdefname.c320
-rw-r--r--src/lib/krb5/os/changepw.c398
-rw-r--r--src/lib/krb5/os/deps571
-rw-r--r--src/lib/krb5/os/dnsglue.c432
-rw-r--r--src/lib/krb5/os/dnsglue.h181
-rw-r--r--src/lib/krb5/os/dnssrv.c250
-rw-r--r--src/lib/krb5/os/expand_path.c541
-rw-r--r--src/lib/krb5/os/full_ipadr.c83
-rw-r--r--src/lib/krb5/os/gen_port.c46
-rw-r--r--src/lib/krb5/os/gen_rname.c53
-rw-r--r--src/lib/krb5/os/genaddrs.c125
-rw-r--r--src/lib/krb5/os/hostaddr.c128
-rw-r--r--src/lib/krb5/os/hostrealm.c537
-rw-r--r--src/lib/krb5/os/hostrealm_dns.c143
-rw-r--r--src/lib/krb5/os/hostrealm_domain.c128
-rw-r--r--src/lib/krb5/os/hostrealm_profile.c117
-rw-r--r--src/lib/krb5/os/hostrealm_registry.c135
-rw-r--r--src/lib/krb5/os/init_os_ctx.c519
-rw-r--r--src/lib/krb5/os/krbfileio.c99
-rw-r--r--src/lib/krb5/os/ktdefname.c94
-rw-r--r--src/lib/krb5/os/localaddr.c1560
-rw-r--r--src/lib/krb5/os/localauth.c443
-rw-r--r--src/lib/krb5/os/localauth_an2ln.c59
-rw-r--r--src/lib/krb5/os/localauth_k5login.c183
-rw-r--r--src/lib/krb5/os/localauth_names.c102
-rw-r--r--src/lib/krb5/os/localauth_rule.c336
-rw-r--r--src/lib/krb5/os/locate_kdc.c853
-rw-r--r--src/lib/krb5/os/lock_file.c182
-rw-r--r--src/lib/krb5/os/mk_faddr.c82
-rw-r--r--src/lib/krb5/os/net_read.c64
-rw-r--r--src/lib/krb5/os/net_write.c81
-rw-r--r--src/lib/krb5/os/os-proto.h195
-rw-r--r--src/lib/krb5/os/port2ip.c81
-rw-r--r--src/lib/krb5/os/prompter.c331
-rw-r--r--src/lib/krb5/os/read_msg.c62
-rw-r--r--src/lib/krb5/os/read_pwd.c296
-rw-r--r--src/lib/krb5/os/realm_dom.c61
-rw-r--r--src/lib/krb5/os/ref_std_conf.out11
-rw-r--r--src/lib/krb5/os/sendto_kdc.c1600
-rw-r--r--src/lib/krb5/os/sn2princ.c206
-rw-r--r--src/lib/krb5/os/t_an_to_ln.c42
-rw-r--r--src/lib/krb5/os/t_discover_uri.py47
-rw-r--r--src/lib/krb5/os/t_expand_path.c18
-rw-r--r--src/lib/krb5/os/t_gifconf.c137
-rw-r--r--src/lib/krb5/os/t_kuserok.c54
-rw-r--r--src/lib/krb5/os/t_locate_kdc.c143
-rw-r--r--src/lib/krb5/os/t_std_conf.c224
-rw-r--r--src/lib/krb5/os/t_trace.c245
-rw-r--r--src/lib/krb5/os/t_trace.ref48
-rw-r--r--src/lib/krb5/os/td_krb5.conf19
-rw-r--r--src/lib/krb5/os/thread_safe.c37
-rw-r--r--src/lib/krb5/os/timeofday.c67
-rw-r--r--src/lib/krb5/os/toffset.c118
-rw-r--r--src/lib/krb5/os/trace.c426
-rw-r--r--src/lib/krb5/os/unlck_file.c34
-rw-r--r--src/lib/krb5/os/ustime.c81
-rw-r--r--src/lib/krb5/os/write_msg.c76
-rw-r--r--src/lib/krb5/posix/Makefile.in15
-rw-r--r--src/lib/krb5/posix/syslog.c11
-rw-r--r--src/lib/krb5/rcache/Makefile.in50
-rw-r--r--src/lib/krb5/rcache/README82
-rw-r--r--src/lib/krb5/rcache/RELEASE17
-rw-r--r--src/lib/krb5/rcache/deps98
-rw-r--r--src/lib/krb5/rcache/rc-int.h91
-rw-r--r--src/lib/krb5/rcache/rc_base.c181
-rw-r--r--src/lib/krb5/rcache/rc_base.h15
-rw-r--r--src/lib/krb5/rcache/rc_conv.c76
-rw-r--r--src/lib/krb5/rcache/rc_dfl.c851
-rw-r--r--src/lib/krb5/rcache/rc_dfl.h48
-rw-r--r--src/lib/krb5/rcache/rc_io.c522
-rw-r--r--src/lib/krb5/rcache/rc_io.h60
-rw-r--r--src/lib/krb5/rcache/rc_none.c96
-rw-r--r--src/lib/krb5/rcache/rcdef.c45
-rw-r--r--src/lib/krb5/rcache/rcfns.c95
-rw-r--r--src/lib/krb5/rcache/ser_rc.c212
-rw-r--r--src/lib/krb5/rcache/t_replay.c265
-rw-r--r--src/lib/krb5/unicode/CompositionExclusions.txt176
-rw-r--r--src/lib/krb5/unicode/Makefile.in72
-rw-r--r--src/lib/krb5/unicode/UCD-Terms29
-rw-r--r--src/lib/krb5/unicode/UnicodeData.txt13874
-rw-r--r--src/lib/krb5/unicode/deps15
-rw-r--r--src/lib/krb5/unicode/ucdata/MUTTUCData.txt303
-rw-r--r--src/lib/krb5/unicode/ucdata/README313
-rw-r--r--src/lib/krb5/unicode/ucdata/api.txt401
-rw-r--r--src/lib/krb5/unicode/ucdata/bidiapi.txt84
-rw-r--r--src/lib/krb5/unicode/ucdata/format.txt267
-rw-r--r--src/lib/krb5/unicode/ucdata/ucdata.c1497
-rw-r--r--src/lib/krb5/unicode/ucdata/ucdata.h354
-rw-r--r--src/lib/krb5/unicode/ucdata/ucdata.man504
-rw-r--r--src/lib/krb5/unicode/ucdata/ucgendat.c1945
-rw-r--r--src/lib/krb5/unicode/ucdata/ucpgba.c755
-rw-r--r--src/lib/krb5/unicode/ucdata/ucpgba.h166
-rw-r--r--src/lib/krb5/unicode/ucdata/ucpgba.man97
-rw-r--r--src/lib/krb5/unicode/ucdata/uctable.h14305
-rw-r--r--src/lib/krb5/unicode/ucstr.c444
-rw-r--r--src/lib/krb5/unicode/ure/README212
-rw-r--r--src/lib/krb5/unicode/ure/ure.c2139
-rw-r--r--src/lib/krb5/unicode/ure/ure.h152
-rw-r--r--src/lib/krb5/unicode/ure/urestubs.c125
-rw-r--r--src/lib/krb5/unicode/utbm/README121
-rw-r--r--src/lib/krb5/unicode/utbm/utbm.c475
-rw-r--r--src/lib/krb5/unicode/utbm/utbm.h110
-rw-r--r--src/lib/krb5/unicode/utbm/utbmstub.c108
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, &gtimebuf) != 0)
+ return ASN1_BAD_GMTIME;
+#else
+ if (gmtime_r(&gmt_time, &gtimebuf) == NULL)
+ return ASN1_BAD_GMTIME;
+#endif
+#else /* HAVE_GMTIME_R */
+ gtime = gmtime(&gmt_time);
+ if (gtime == NULL)
+ return ASN1_BAD_GMTIME;
+ memcpy(&gtimebuf, gtime, sizeof(gtimebuf));
+#endif /* HAVE_GMTIME_R */
+ gtime = &gtimebuf;
+
+ 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