%% kerlberos %% %% Copyright 2020 Alex Wilson %% The University of Queensland %% All rights reserved. %% %% Redistribution and use in source and binary forms, with or without %% modification, are permitted provided that the following conditions %% are met: %% 1. Redistributions of source code must retain the above copyright %% notice, this list of conditions and the following disclaimer. %% 2. Redistributions in binary form must reproduce the above copyright %% notice, this list of conditions and the following disclaimer in the %% documentation and/or other materials provided with the distribution. %% %% THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR %% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES %% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. %% IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, %% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT %% NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, %% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY %% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT %% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF %% THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. %% %% @private -module(ms_rpce). -export([start/1, finish/1, read/2, get_ptr/2]). -include("ms_pac.hrl"). -export_type([rpce_ptr/0, rpce_state/0]). -record(rpce_ptr, {tag :: binary()}). -record(rpce_state, { r :: binary(), off = 0 :: integer(), deferred = gb_sets:empty() :: gb_sets:set(#rpce_ptr{}), ptrtype = #{} :: #{#rpce_ptr{} => atom()}, ptrdata = #{} :: #{#rpce_ptr{} => term()} }). -opaque rpce_ptr() :: #rpce_ptr{}. -opaque rpce_state() :: #rpce_state{}. -spec start(binary()) -> rpce_state(). start(Rem0) -> <> = Rem0, Version = 1, Endian = 16#10, CHLenRem = CHLen - 4, <<_CHPad:CHLenRem/binary, ObjBufLen:32/little, _:4/binary, Rem2:(ObjBufLen)/binary>> = Rem1, <<_:32, Rem3/binary>> = Rem2, #rpce_state{r = Rem3, off = 0}. -spec attrs_to_atoms(integer()) -> [krb_ms_pac:sid_attr()]. attrs_to_atoms(N) -> <<_:2, E:1, _:25, D:1, C:1, B:1, A:1>> = <>, case A of 1 -> [mandatory]; _ -> [] end ++ case B of 1 -> [default]; _ -> [] end ++ case C of 1 -> [enabled]; _ -> [] end ++ case D of 1 -> [owner]; _ -> [] end ++ case E of 1 -> [resource]; _ -> [] end. -spec padding_size(integer(), integer()) -> integer(). padding_size(PrimSize, Off) -> Rem = Off rem PrimSize, case Rem of 0 -> 0; _ -> %io:format("padding ~p\n", [PrimSize - Rem]), PrimSize - Rem end. -spec read(term(), rpce_state()) -> {term(), rpce_state()}. read(filetime, S0 = #rpce_state{r = Rem0, off = Off}) -> Padding = padding_size(4, Off), <<_:Padding/binary, V:64/little, Rem1/binary>> = Rem0, S1 = S0#rpce_state{r = Rem1, off = Off + Padding + 8}, case V of 16#7fffffffffffffff -> {never, S1}; 0 -> {null, S1}; _ -> USec = (V - 116444736000000000) div 10, {{USec, microsecond}, S1} end; read({unicode_string, Len, _MaxLen}, S0 = #rpce_state{r = Rem0, off = Off}) -> Padding = padding_size(4, Off), <<_:Padding/binary, _MaxLength:32/little, 0:32/little, Length:32/little, Rem1/binary>> = Rem0, ActualLen = Length * 2, <> = Rem1, <> = Data, S1 = S0#rpce_state{r = Rem2, off = Off + Padding + 12 + ActualLen}, Str = unicode:characters_to_binary(StringData, {utf16, little}, utf8), {Str, S1}; read(rpc_unicode_string, S0 = #rpce_state{off = Off}) -> Padding = padding_size(4, Off), #rpce_state{r = Rem0, deferred = Def0, ptrtype = Typ0} = S0, <<_:Padding/binary, Len:16/little, MaxLen:16/little, Referent:4/binary, Rem1/binary>> = Rem0, Ptr = #rpce_ptr{tag = Referent}, S1 = case Typ0 of #{Ptr := {unicode_string, Len, MaxLen}} -> S0#rpce_state{r = Rem1, off = Off + Padding + 8}; #{Ptr := _} -> error(type_confusion); _ -> Def1 = gb_sets:add(Ptr, Def0), Typ1 = Typ0#{Ptr => {unicode_string, Len, MaxLen}}, S0#rpce_state{ r = Rem1, deferred = Def1, ptrtype = Typ1, off = Off + Padding + 8 } end, {Ptr, S1}; read(sid, S0 = #rpce_state{r = Rem0, off = Off}) -> Padding = padding_size(4, Off), case Rem0 of <<_:Padding/binary, SubAuthCount:32/little, 1, SubAuthCount, IdentifierAuth:48/big, Rem1/binary>> -> ok; <<_:Padding/binary, 1, SubAuthCount, IdentifierAuth:48/big, Rem1/binary>> -> ok end, SubAuthLen = SubAuthCount * 4, <> = Rem1, S1 = S0#rpce_state{r = Rem2, off = Off + Padding + 12 + SubAuthLen}, {SubAuths, _} = lists:foldl(fun (_I, {Acc0, ARem0}) -> <> = ARem0, {[Next | Acc0], ARem1} end, {[], SubAuthsBin}, lists:seq(1, SubAuthCount)), Sid = #sid{revision = 1, identifier_auth = IdentifierAuth, sub_auths = lists:reverse(SubAuths)}, {Sid, S1}; read(kerb_sid_and_attributes, S0 = #rpce_state{}) -> {SidPtr, S1} = read({pointer, sid}, S0), {Attrs, S2} = read(ulong, S1), {#sid_and_attributes{sid_ptr = SidPtr, attrs = attrs_to_atoms(Attrs)}, S2}; read(ushort, S0 = #rpce_state{r = Rem0, off = Off}) -> Padding = padding_size(2, Off), <<_:Padding/binary, V:16/little, Rem1/binary>> = Rem0, {V, S0#rpce_state{r = Rem1, off = Off + Padding + 2}}; read(ulong, S0 = #rpce_state{r = Rem0, off = Off}) -> Padding = padding_size(4, Off), <<_:Padding/binary, V:32/little, Rem1/binary>> = Rem0, {V, S0#rpce_state{r = Rem1, off = Off + Padding + 4}}; read({array, ulong}, S0 = #rpce_state{}) -> {Bins, S1} = read({array, conformant, 4}, S0), {[V || <> <- Bins], S1}; read(group_membership, S0 = #rpce_state{}) -> {Rid, S1} = read(ulong, S0), {Attrs, S2} = read(ulong, S1), {#group_membership{rid = Rid, attrs = attrs_to_atoms(Attrs)}, S2}; read({array, Type}, S0 = #rpce_state{}) -> {Count, S1} = read(ulong, S0), {Res, S2} = lists:foldl(fun (_I, {Acc0, SS0}) -> {SAA, SS1} = read(Type, SS0), {[SAA | Acc0], SS1} end, {[], S1}, lists:seq(1, Count)), {lists:reverse(Res), S2}; read({array, conformant, MemberSize}, S0 = #rpce_state{r = Rem0, off = Off}) -> Padding = padding_size(4, Off), <<_:Padding/binary, Count:32/little, Rem1/binary>> = Rem0, Length = Count * MemberSize, <> = Rem1, {Datas, <<>>} = lists:foldl(fun (_I, {Acc0, ARem0}) -> <> = ARem0, {[Next | Acc0], ARem1} end, {[], Data}, lists:seq(1, Count)), S1 = S0#rpce_state{r = Rem2, off = Off + Padding + 12 + Length}, {lists:reverse(Datas), S1}; read({pointer, UnderType}, S0 = #rpce_state{off = Off}) -> #rpce_state{r = Rem0, deferred = Def0, ptrtype = Typ0} = S0, Padding = padding_size(4, Off), <<_:Padding/binary, Referent:4/binary, Rem1/binary>> = Rem0, Ptr = #rpce_ptr{tag = Referent}, S1 = case {Referent, Typ0} of {<<0,0,0,0>>, _} -> S0#rpce_state{r = Rem1, off = Off + Padding + 4}; {_, #{Ptr := UnderType}} -> S0#rpce_state{r = Rem1, off = Off + Padding + 4}; {_, #{Ptr := Other}} -> error({type_confusion, Ptr, Other, UnderType}); {_, _} -> Def1 = gb_sets:add(Ptr, Def0), Typ1 = Typ0#{Ptr => UnderType}, S0#rpce_state{ r = Rem1, deferred = Def1, ptrtype = Typ1, off = Off + Padding + 4 } end, {Ptr, S1}; read(user_session_key, S0 = #rpce_state{r = Rem0, off = Off}) -> Padding = padding_size(8, Off), <<_:Padding/binary, V:16/binary, Rem1/binary>> = Rem0, {V, S0#rpce_state{r = Rem1, off = Off + Padding + 16}}. finish(S0 = #rpce_state{deferred = Defs0, ptrtype = Types}) -> case gb_sets:is_empty(Defs0) of true -> #rpce_state{r = Rem} = S0, RemSize = bit_size(Rem), <<0:RemSize>> = Rem, S0; false -> {Ptr, Defs1} = gb_sets:take_smallest(Defs0), S1 = S0#rpce_state{deferred = Defs1}, #{Ptr := Type} = Types, %io:format("reading deferred ~p, ref ~p\n", [Type, Ptr]), %io:format(" @ data ~p\n", [S1#rpce_state.r]), {V, S2} = read(Type, S1), %io:format(" => ~p\n", [V]), #rpce_state{ptrdata = Data0} = S2, Data1 = Data0#{Ptr => V}, S3 = S2#rpce_state{ptrdata = Data1}, finish(S3) end. get_ptr(#rpce_ptr{tag = <<0,0,0,0>>}, _) -> null; get_ptr(Ptr = #rpce_ptr{}, #rpce_state{ptrdata = PtrData}) -> #{Ptr := Data} = PtrData, Data.