content: /*
   * © 2016 AgNO3 Gmbh & Co. KG
   * 
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   * 
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   * 
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  package jcifs.smb;
  
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import jcifs.CIFSContext;
  import jcifs.CIFSException;
  import jcifs.SidResolver;
  import jcifs.dcerpc.DcerpcHandle;
  import jcifs.dcerpc.UnicodeString;
  import jcifs.dcerpc.rpc;
  import jcifs.dcerpc.rpc.sid_t;
  import jcifs.dcerpc.msrpc.LsaPolicyHandle;
  import jcifs.dcerpc.msrpc.MsrpcEnumerateAliasesInDomain;
  import jcifs.dcerpc.msrpc.MsrpcGetMembersInAlias;
  import jcifs.dcerpc.msrpc.MsrpcLookupSids;
  import jcifs.dcerpc.msrpc.MsrpcQueryInformationPolicy;
  import jcifs.dcerpc.msrpc.SamrAliasHandle;
  import jcifs.dcerpc.msrpc.SamrDomainHandle;
  import jcifs.dcerpc.msrpc.SamrPolicyHandle;
  import jcifs.dcerpc.msrpc.lsarpc;
  import jcifs.dcerpc.msrpc.lsarpc.LsarTranslatedName;
  import jcifs.dcerpc.msrpc.samr;
  
  
  /**
   * Internal use only: SID resolver cache
   * 
   * @author mbechler
   * @internal
   */
  public class SIDCacheImpl implements SidResolver {
  
      private Map<SID, SID> sidCache = new HashMap<>();
  
  
      /**
       * @param baseContext
       */
      public SIDCacheImpl ( CIFSContext baseContext ) {}
  
  
      void resolveSids ( DcerpcHandle handle, LsaPolicyHandle policyHandle, jcifs.SID[] sids ) throws IOException {
          MsrpcLookupSids rpc = new MsrpcLookupSids(policyHandle, sids);
          handle.sendrecv(rpc);
          switch ( rpc.retval ) {
          case 0:
          case NtStatus.NT_STATUS_NONE_MAPPED:
          case 0x00000107: // NT_STATUS_SOME_NOT_MAPPED
              break;
          default:
              throw new SmbException(rpc.retval, false);
          }
  
          for ( int si = 0; si < sids.length; si++ ) {
              SID out = sids[ si ].unwrap(SID.class);
              LsarTranslatedName resp = rpc.names.names[ si ];
              out.domainName = null;
              switch ( resp.sid_type ) {
              case jcifs.SID.SID_TYPE_USER:
              case jcifs.SID.SID_TYPE_DOM_GRP:
              case jcifs.SID.SID_TYPE_DOMAIN:
              case jcifs.SID.SID_TYPE_ALIAS:
              case jcifs.SID.SID_TYPE_WKN_GRP:
                  rpc.unicode_string ustr = rpc.domains.domains[ resp.sid_index ].name;
                  out.domainName = ( new UnicodeString(ustr, false) ).toString();
                  break;
              }
  
              UnicodeString ucstr = new UnicodeString(resp.name, false);
              out.acctName = ucstr.toString();
              out.type = resp.sid_type;
              out.origin_server = null;
              out.origin_ctx = null;
          }
      }
  
  
      void resolveSids0 ( String authorityServerName, CIFSContext tc, jcifs.SID[] sids ) throws CIFSException {
          synchronized ( this.sidCache ) {
              try ( DcerpcHandle handle = DcerpcHandle.getHandle("ncacn_np:" + authorityServerName + "[\\PIPE\\lsarpc]", tc) ) {
                  String server = authorityServerName;
                  int dot = server.indexOf('.');
                  if ( dot > 0 && Character.isDigit(server.charAt(0)) == false )
                      server = server.substring(0, dot);
                  try ( LsaPolicyHandle policyHandle = new LsaPolicyHandle(handle, "\\\\" + server, 0x00000800) ) {
                      resolveSids(handle, policyHandle, sids);
                  }
              }
              catch ( IOException e ) {
                  throw new CIFSException("Failed to resolve SIDs", e);
              }
          }
      }
  
  
      @Override
      public void resolveSids ( CIFSContext tc, String authorityServerName, jcifs.SID[] sids, int offset, int length ) throws CIFSException {
          ArrayList<SID> list = new ArrayList<>(sids.length);
          int si;
  
          synchronized ( this.sidCache ) {
              for ( si = 0; si < length; si++ ) {
                  SID s = sids[ offset + si ].unwrap(SID.class);
                  SID sid = this.sidCache.get(s);
                  if ( sid != null ) {
                      s.type = sid.type;
                      s.domainName = sid.domainName;
                      s.acctName = sid.acctName;
                  }
                  else {
                      list.add(s);
                  }
              }
  
              if ( list.size() > 0 ) {
                  SID[] resolved = list.toArray(new SID[list.size()]);
                  resolveSids0(authorityServerName, tc, resolved);
                  for ( si = 0; si < resolved.length; si++ ) {
                      this.sidCache.put(resolved[ si ], resolved[ si ]);
                  }
              }
          }
      }
  
  
      /**
       * Resolve an array of SIDs using a cache and at most one MSRPC request.
       * <p>
       * This method will attempt
       * to resolve SIDs using a cache and cache the results of any SIDs that
       * required resolving with the authority. SID cache entries are currently not
       * expired because under normal circumstances SID information never changes.
       *
       * @param authorityServerName
       *            The hostname of the server that should be queried. For maximum efficiency this should be the hostname
       *            of a domain controller however a member server will work as well and a domain controller may not
       *            return names for SIDs corresponding to local accounts for which the domain controller is not an
       *            authority.
       * @param tc
       *            The context that should be used to communicate with the named server.
       * @param sids
       *            The SIDs that should be resolved. After this function is called, the names associated with the SIDs
       *            may be queried with the <tt>toDisplayString</tt>, <tt>getDomainName</tt>, and <tt>getAccountName</tt>
       *            methods.
       */
      @Override
      public void resolveSids ( CIFSContext tc, String authorityServerName, jcifs.SID[] sids ) throws CIFSException {
          ArrayList<SID> list = new ArrayList<>(sids.length);
          int si;
  
          synchronized ( this.sidCache ) {
              for ( si = 0; si < sids.length; si++ ) {
                  SID s = sids[ si ].unwrap(SID.class);
                  SID sid = this.sidCache.get(s);
                  if ( sid != null ) {
                      s.type = sid.type;
                      s.domainName = sid.domainName;
                      s.acctName = sid.acctName;
                  }
                  else {
                      list.add(s);
                  }
              }
  
              if ( list.size() > 0 ) {
                  SID[] resolved = list.toArray(new SID[list.size()]);
                  resolveSids0(authorityServerName, tc, resolved);
                  for ( si = 0; si < resolved.length; si++ ) {
                      this.sidCache.put(resolved[ si ], resolved[ si ]);
                  }
              }
          }
      }
  
  
      @Override
      public SID getServerSid ( CIFSContext tc, String server ) throws CIFSException {
          lsarpc.LsarDomainInfo info = new lsarpc.LsarDomainInfo();
          MsrpcQueryInformationPolicy rpc;
  
          synchronized ( this.sidCache ) {
              try ( DcerpcHandle handle = DcerpcHandle.getHandle("ncacn_np:" + server + "[\\PIPE\\lsarpc]", tc) ) {
                  // NetApp doesn't like the 'generic' access mask values
                  try ( LsaPolicyHandle policyHandle = new LsaPolicyHandle(handle, null, 0x00000001) ) {
                      rpc = new MsrpcQueryInformationPolicy(policyHandle, (short) lsarpc.POLICY_INFO_ACCOUNT_DOMAIN, info);
                      handle.sendrecv(rpc);
                      if ( rpc.retval != 0 )
                          throw new SmbException(rpc.retval, false);
                  }
  
                  return new SID(info.sid, jcifs.SID.SID_TYPE_DOMAIN, ( new UnicodeString(info.name, false) ).toString(), null, false);
              }
              catch ( IOException e ) {
                  throw new CIFSException("Failed to get SID from server", e);
              }
          }
      }
  
  
      @Override
      public SID[] getGroupMemberSids ( CIFSContext tc, String authorityServerName, jcifs.SID domsid, int rid, int flags ) throws CIFSException {
          lsarpc.LsarSidArray sidarray = new lsarpc.LsarSidArray();
          MsrpcGetMembersInAlias rpc = null;
  
          synchronized ( this.sidCache ) {
              try ( DcerpcHandle handle = DcerpcHandle.getHandle("ncacn_np:" + authorityServerName + "[\\PIPE\\samr]", tc) ) {
                  SamrPolicyHandle policyHandle = new SamrPolicyHandle(handle, authorityServerName, 0x00000030);
                  SamrDomainHandle domainHandle = new SamrDomainHandle(handle, policyHandle, 0x00000200, domsid.unwrap(sid_t.class));
                  try ( SamrAliasHandle aliasHandle = new SamrAliasHandle(handle, domainHandle, 0x0002000c, rid) ) {
                      rpc = new MsrpcGetMembersInAlias(aliasHandle, sidarray);
                      handle.sendrecv(rpc);
                      if ( rpc.retval != 0 )
                          throw new SmbException(rpc.retval, false);
                      SID[] sids = new SID[rpc.sids.num_sids];
  
                      String origin_server = handle.getServer();
                      CIFSContext origin_ctx = handle.getTransportContext();
  
                      for ( int i = 0; i < sids.length; i++ ) {
                          sids[ i ] = new SID(rpc.sids.sids[ i ].sid, 0, null, null, false);
                          sids[ i ].origin_server = origin_server;
                          sids[ i ].origin_ctx = origin_ctx;
                      }
                      if ( sids.length > 0 && ( flags & SID.SID_FLAG_RESOLVE_SIDS ) != 0 ) {
                          resolveSids(origin_ctx, origin_server, sids);
                      }
                      return sids;
                  }
              }
              catch ( IOException e ) {
                  throw new CIFSException("Failed to get group member SIDs", e);
              }
          }
  
      }
  
  
      /**
       * 
       * {@inheritDoc}
       *
       * @see jcifs.SidResolver#getLocalGroupsMap(jcifs.CIFSContext, java.lang.String, int)
       */
      @Override
      public Map<jcifs.SID, List<jcifs.SID>> getLocalGroupsMap ( CIFSContext tc, String authorityServerName, int flags ) throws CIFSException {
          SID domSid = getServerSid(tc, authorityServerName);
          synchronized ( this.sidCache ) {
              try ( DcerpcHandle handle = DcerpcHandle.getHandle("ncacn_np:" + authorityServerName + "[\\PIPE\\samr]", tc) ) {
                  samr.SamrSamArray sam = new samr.SamrSamArray();
                  try ( SamrPolicyHandle policyHandle = new SamrPolicyHandle(handle, authorityServerName, 0x02000000);
                        SamrDomainHandle domainHandle = new SamrDomainHandle(handle, policyHandle, 0x02000000, domSid) ) {
                      MsrpcEnumerateAliasesInDomain rpc = new MsrpcEnumerateAliasesInDomain(domainHandle, 0xFFFF, sam);
                      handle.sendrecv(rpc);
                      if ( rpc.retval != 0 ) {
                          throw new SmbException(rpc.retval, false);
                      }
  
                      Map<jcifs.SID, List<jcifs.SID>> map = new HashMap<>();
  
                      for ( int ei = 0; ei < rpc.sam.count; ei++ ) {
                          samr.SamrSamEntry entry = rpc.sam.entries[ ei ];
  
                          SID[] mems = getGroupMemberSids(tc, authorityServerName, domSid, entry.idx, flags);
                          SID groupSid = new SID(domSid, entry.idx);
                          groupSid.type = jcifs.SID.SID_TYPE_ALIAS;
                          groupSid.domainName = domSid.getDomainName();
                          groupSid.acctName = ( new UnicodeString(entry.name, false) ).toString();
  
                          for ( int mi = 0; mi < mems.length; mi++ ) {
                              List<jcifs.SID> groups = map.get(mems[ mi ]);
                              if ( groups == null ) {
                                  groups = new ArrayList<>();
                                  map.put(mems[ mi ], groups);
                              }
                              if ( !groups.contains(groupSid) )
                                  groups.add(groupSid);
                          }
                      }
  
                      return map;
                  }
              }
              catch ( IOException e ) {
                  throw new CIFSException("Failed to resolve groups", e);
              }
          }
      }
  }
  