DCE/RPC over SMB - nt login, code walk-through.

Luke Kenneth Casson Leighton lkcl at samba.org
Mon Feb 14 04:54:38 GMT 2000

stage 2.  what happens when rpcclient -S . -U% -l log is used and an
lsaquery command issued.  server-side.

rrpcclient -S . means connection on loop-back.  so we're bypassing SMB,
for now.

inside lsarpcd, firs thing is that open_sockets() waits for an msrpc
connection (msrpcd.c).  the next critical function to be reached is

msrpcd_init() calls get_user_creds().  this reads in s vuser_key, pid +
vuid.  strictly speaking, this stage is now redundant (i keep telling
myself that, and i get it wrong, every time :-) :-)  however, i'll cover
this in the SMB section.  damn damn why am i so stupid :)

*sigh*.  ok.  so msrpcd_init() gets user credentials, and then looks up
the pid+vuid with get_valid_user_strcut() and does a boecome_user(),
setting the vuser context and the unix context to that contained in
user_struct.  as this is a call from rpcclient as root, this enables us to
do an "su" if we specify -Usomeotheruser.  but let's leave that aside for

the next critical call is msrpcd_process() which goes into a while-loop on
receive_message_or_msrpc and then calling process_msrpc on it.
receive_message_or_msrpc calls receive_msrpc, which is the counter-part of
msrpc_send(), client-side.

process_msrpc() is where the fun starts.  rpc_local() is called, which
either adds the received PDU to the amounting stack of data or calls
rpc_redir_local().  rpc_redir_local() is where the down-and-dirty happens,
so we'll skip that for now.  back out to process_msrpc().  the next
function to be called is msrpc_send() and there then follows a series of
loops on rpc_local() followed by msrpc_send().

rpc_local() is responsible for picking up PDUs and assembling them, and
processing them one-at-a-time in rpc_redir_local().  as a *consequence* of
calling rpc_redir_local(), data may be produced in the form of PDUs to be
send back with msrpc_send().

so this complex loop, here, is a state-machine version of the logic in
rpc_api_pipe_req() which can send one-or-more PDUs and receive one-or-more
PDUs.  except that this logic _also_ has to cope with receiving
one-or-more PDUs and *not* sending a PDU back (the PDU_3WAY_AUTH for
challenge/response NTLMSSP sequences).

glad that's out the way.  on with the fun.  rpc_redir_local().

rpc_redir_local() accepts a data stream of pre-assembled marshalled data
with the DCE/RPC headers stripped out.  *one* of the eaders is preserved
intact so that the PDU type can be identified.

in the example we are following, we will be expecting to receive a bind
request.  tracing the function calls, we come to
srv_pipe_bind_and_alt_req().  there are some critical noteworthy things
here, aside from the "authentication" process being handed off to
higher-order-functions.  i picked this example as an anonymous connection
so that we won't have to go through these, for now.  the critical things
to note are that assoc_gid is initialised from the vuser key that was
created from the incoming loop-back connection.  if the DCE/RPC bind
request contains a non-NULL assoc_gid, then this is used instead.

why?  because this is the first part of the vuser_key.  remember, in the
bind_request from the client, we send the smbd pid in the assoc_gid, and
the smbd vuid in the context_id.  well, here we're picking it up again.
rreconstruction of this info is critical, as it is used to form the right
user context, picked up from the get_valid_user_struct() database
(implemented with tdb, and the pid+vuid as the database key).

in this example, the key is assembled but nothing is actually done with it
-- right now.  it just contains the same vuser_key info that rpcclient
created for us.

the call to api_auth_gen (a higher-order-function) creates the
bind-response for us, just like api_auth_check() decoded the bund-request
for us.  back out of srv_pipe_bind_and_alt_req(). send the bind response.
if anyone's interested, the default for non-authenticated bind / bind ack
processing is done in srv_pipe_noauth.c.

back to rpc_redir_local().  ignoring all the fragment reassembly, the
first next call we will receive is an actual dce/rpc request: an
lsa_open_policy().  wow!  so, first we obtain the context_id (see switch
RPC_REQUEST).  the context_id contains the smbd vuid, therefore we use
this to create a vuser_key and do a become_vuser() call.  if this fails,
we do a become_guest() instead.  following this, an api_pipe_request() is

api_pipe_request() jumps us to a higher-order-function table which takes
us through to lsarpcd/srv_lsa.c  this is an lsa_open_policy() call from
the client, so api_lsa_open_policy() is called, here.  _now_ we're getting
somewhere.  note the similarity between this function and
lsa_open_policy() in cli_lsarpc.c?  we've come _full_ damn circle just to
call a function with exactly the same arguments as lsa_open_policy()...



hey,  we're calling exactly the same function, except it's over a network.
that's so cool!  except, like, it's got an underscore in front of it.
anyway, in to _lsa_open_policy().

_lsa_open_policy() is actually very simple.  it ... well... opens a policy
handle.  this does a create_policy_hnd() call and then calls

hey, this is deja-vu.  we've been here before.  didn't we _already_ call
register_policy_hnd()  NO -  we didn't... but lsa_open_policy() did, back
in rpcclient.  so efffectively what we are doing here is to register the
same info as what the client is doing: matching up info into policy

the thing to note here is that _unfortunately, the call to
ope_policy_hnd() had to be made with a get_sec_ctx() function.  this picks
up the vuser_key from the last become_vuser() call that was made.
threading is therefore going to be a damn nuisance.  i _can_ think of a
way round this, it will require another sed script mod, passing the
vuser_key down the the chain into srv_lsa.c and all other similar files.

next important event is _lsa_query_info_policy().  other than receiving
the info level and sending back the structure, the only noteworthy DCE/RPC
activity carried out by this function is to check that the policy handle
is valid.  we _cannot_ just hand out info to any old POLICY_HND, as in a
"real" implementation, we would obtain the vuser_key, obtain the user's
SID from the user_struct, and call check_security_permissions() here
inside _lsa_query_info_policy().

however, this information is available anonymously, so we don't need to do
that!  but really, it's not the proper way to do this.  i'm getting
side-tracked by implementation issues, here, and this is about DCE/RPC
itself, not DCE/RPC services.

next important event is the _lsa_close() call, which receives a policy
handle.  again, we call close_policy_hnd() which calls, if it existed in
this example, the free_fn associated with the data.  in _this_ example, in
the _lsa_open_policy() implementation we _didn't_ associate any data with
the policy handle, therefore there is nothing to do.  however, this is
generally not the case: there useually is data associated with policy
handles in server-side implementation of dce/rpc services.

the one significant thing i am missing from the
associate-data-with-policy-handle "thing" is a type field.  i ran into
difficulties with this in the samrtdbd implementation, over the
_samr_query_sec_object function.  it';s possible to query the security
descriptor of *any *POLICY_HND, not just users, not just groups.  so i
either have to deal with this, or set a "type" in the API, not just a
free-fn and a pointer-to-data.

this is actually quite important. because if someone does this:

samr_open_user(    &pol_hnd)
samr_query_group_info(pol_hnd , ...)

this will result in the pointer-t--data associated with the open user
being typecast to a *group* data structure, which is _seriously_ bad

anyway, i think i've covered all the DCE/RPC-related issues, here, i don't
want to get too into service-related implementation issues at this point.

i will think about what to write up, next.


More information about the samba-technical mailing list