/* jcifs smb client library in Java * Copyright (C) 2006 "Michael B. Allen" <jcifs at samba dot org> * "Eric Glass" <jcifs at samba dot org> * * 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.util.*; import java.io.IOException; import jcifs.util.Hexdump; import jcifs.dcerpc.*; import jcifs.dcerpc.msrpc.*; /** * A Windows SID is a numeric identifier used to represent Windows * accounts. SIDs are commonly represented using a textual format such as * <tt>S-1-5-21-1496946806-2192648263-3843101252-1029</tt> but they may * also be resolved to yield the name of the associated Windows account * such as <tt>Administrators</tt> or <tt>MYDOM\alice</tt>. * <p> * Consider the following output of <tt>examples/SidLookup.java</tt>: * <pre> * toString: S-1-5-21-4133388617-793952518-2001621813-512 * toDisplayString: WNET\Domain Admins * getType: 2 * getTypeText: Domain group * getDomainName: WNET * getAccountName: Domain Admins * </pre> */ public class SID extends rpc.sid_t { public static final int SID_TYPE_USE_NONE = lsarpc.SID_NAME_USE_NONE; public static final int SID_TYPE_USER = lsarpc.SID_NAME_USER; public static final int SID_TYPE_DOM_GRP = lsarpc.SID_NAME_DOM_GRP; public static final int SID_TYPE_DOMAIN = lsarpc.SID_NAME_DOMAIN; public static final int SID_TYPE_ALIAS = lsarpc.SID_NAME_ALIAS; public static final int SID_TYPE_WKN_GRP = lsarpc.SID_NAME_WKN_GRP; public static final int SID_TYPE_DELETED = lsarpc.SID_NAME_DELETED; public static final int SID_TYPE_INVALID = lsarpc.SID_NAME_INVALID; public static final int SID_TYPE_UNKNOWN = lsarpc.SID_NAME_UNKNOWN; static final String[] SID_TYPE_NAMES = { "0", "User", "Domain group", "Domain", "Local group", "Builtin group", "Deleted", "Invalid", "Unknown" }; public static final int SID_FLAG_RESOLVE_SIDS = 0x0001; public static SID EVERYONE = null; public static SID CREATOR_OWNER = null; public static SID SYSTEM = null; static { try { EVERYONE = new SID("S-1-1-0"); CREATOR_OWNER = new SID("S-1-3-0"); SYSTEM = new SID("S-1-5-18"); } catch (SmbException se) { } } static Map sid_cache = new HashMap(); static void resolveSids(DcerpcHandle handle, LsaPolicyHandle policyHandle, 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++) { sids[si].type = rpc.names.names[si].sid_type; sids[si].domainName = null; switch (sids[si].type) { case SID_TYPE_USER: case SID_TYPE_DOM_GRP: case SID_TYPE_DOMAIN: case SID_TYPE_ALIAS: case SID_TYPE_WKN_GRP: int sid_index = rpc.names.names[si].sid_index; rpc.unicode_string ustr = rpc.domains.domains[sid_index].name; sids[si].domainName = (new UnicodeString(ustr, false)).toString(); break; } sids[si].acctName = (new UnicodeString(rpc.names.names[si].name, false)).toString(); sids[si].origin_server = null; sids[si].origin_auth = null; } } static void resolveSids0(String authorityServerName, NtlmPasswordAuthentication auth, SID[] sids) throws IOException { DcerpcHandle handle = null; LsaPolicyHandle policyHandle = null; synchronized (sid_cache) { try { handle = DcerpcHandle.getHandle("ncacn_np:" + authorityServerName + "[\\PIPE\\lsarpc]", auth); String server = authorityServerName; int dot = server.indexOf('.'); if (dot > 0 && Character.isDigit(server.charAt(0)) == false) server = server.substring(0, dot); policyHandle = new LsaPolicyHandle(handle, "\\\\" + server, 0x00000800); SID.resolveSids(handle, policyHandle, sids); } finally { if (handle != null) { if (policyHandle != null) { policyHandle.close(); } handle.close(); } } } } static public void resolveSids(String authorityServerName, NtlmPasswordAuthentication auth, SID[] sids, int offset, int length) throws IOException { ArrayList list = new ArrayList(sids.length); int si; synchronized (sid_cache) { for (si = 0; si < length; si++) { SID sid = (SID)sid_cache.get(sids[offset + si]); if (sid != null) { sids[offset + si].type = sid.type; sids[offset + si].domainName = sid.domainName; sids[offset + si].acctName = sid.acctName; } else { list.add(sids[offset + si]); } } if (list.size() > 0) { sids = (SID[])list.toArray(new SID[0]); SID.resolveSids0(authorityServerName, auth, sids); for (si = 0; si < sids.length; si++) { sid_cache.put(sids[si], sids[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 auth The credentials that should be used to communicate with the named server. As usual, <tt>null</tt> indicates that default credentials should be used. * @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. */ static public void resolveSids(String authorityServerName, NtlmPasswordAuthentication auth, SID[] sids) throws IOException { ArrayList list = new ArrayList(sids.length); int si; synchronized (sid_cache) { for (si = 0; si < sids.length; si++) { SID sid = (SID)sid_cache.get(sids[si]); if (sid != null) { sids[si].type = sid.type; sids[si].domainName = sid.domainName; sids[si].acctName = sid.acctName; } else { list.add(sids[si]); } } if (list.size() > 0) { sids = (SID[])list.toArray(new SID[0]); SID.resolveSids0(authorityServerName, auth, sids); for (si = 0; si < sids.length; si++) { sid_cache.put(sids[si], sids[si]); } } } } public static SID getServerSid(String server, NtlmPasswordAuthentication auth) throws IOException { DcerpcHandle handle = null; LsaPolicyHandle policyHandle = null; lsarpc.LsarDomainInfo info = new lsarpc.LsarDomainInfo(); MsrpcQueryInformationPolicy rpc; synchronized (sid_cache) { try { handle = DcerpcHandle.getHandle("ncacn_np:" + server + "[\\PIPE\\lsarpc]", auth); // NetApp doesn't like the 'generic' access mask values 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, SID.SID_TYPE_DOMAIN, (new UnicodeString(info.name, false)).toString(), null, false); } finally { if (handle != null) { if (policyHandle != null) { policyHandle.close(); } handle.close(); } } } } public static byte[] toByteArray(rpc.sid_t sid) { byte[] dst = new byte[1 + 1 + 6 + sid.sub_authority_count * 4]; int di = 0; dst[di++] = sid.revision; dst[di++] = sid.sub_authority_count; System.arraycopy(sid.identifier_authority, 0, dst, di, 6); di += 6; for (int ii = 0; ii < sid.sub_authority_count; ii++) { jcifs.util.Encdec.enc_uint32le(sid.sub_authority[ii], dst, di); di += 4; } return dst; } int type; String domainName = null; String acctName = null; String origin_server = null; NtlmPasswordAuthentication origin_auth = null; /* * Construct a SID from it's binary representation. */ public SID(byte[] src, int si) { revision = src[si++]; sub_authority_count = src[si++]; identifier_authority = new byte[6]; System.arraycopy(src, si, identifier_authority, 0, 6); si += 6; if (sub_authority_count > 100) throw new RuntimeException( "Invalid SID sub_authority_count" ); sub_authority = new int[sub_authority_count]; for (int i = 0; i < sub_authority_count; i++) { sub_authority[i] = ServerMessageBlock.readInt4( src, si ); si += 4; } } /** * Construct a SID from it's textual representation such as * <tt>S-1-5-21-1496946806-2192648263-3843101252-1029</tt>. */ public SID(String textual) throws SmbException { StringTokenizer st = new StringTokenizer(textual, "-"); if (st.countTokens() < 3 || !st.nextToken().equals("S")) // need S-N-M throw new SmbException("Bad textual SID format: " + textual); this.revision = Byte.parseByte(st.nextToken()); String tmp = st.nextToken(); long id = 0; if (tmp.startsWith("0x")) id = Long.parseLong(tmp.substring(2), 16); else id = Long.parseLong(tmp); this.identifier_authority = new byte[6]; for (int i = 5; id > 0; i--) { this.identifier_authority[i] = (byte) (id % 256); id >>= 8; } this.sub_authority_count = (byte) st.countTokens(); if (this.sub_authority_count > 0) { this.sub_authority = new int[this.sub_authority_count]; for (int i = 0; i < this.sub_authority_count; i++) this.sub_authority[i] = (int)(Long.parseLong(st.nextToken()) & 0xFFFFFFFFL); } } /** * Construct a SID from a domain SID and an RID * (relative identifier). For example, a domain SID * <tt>S-1-5-21-1496946806-2192648263-3843101252</tt> and RID <tt>1029</tt> would * yield the SID <tt>S-1-5-21-1496946806-2192648263-3843101252-1029</tt>. */ public SID(SID domsid, int rid) { this.revision = domsid.revision; this.identifier_authority = domsid.identifier_authority; this.sub_authority_count = (byte)(domsid.sub_authority_count + 1); this.sub_authority = new int[this.sub_authority_count]; int i; for (i = 0; i < domsid.sub_authority_count; i++) { this.sub_authority[i] = domsid.sub_authority[i]; } this.sub_authority[i] = rid; } public SID(rpc.sid_t sid, int type, String domainName, String acctName, boolean decrementAuthority) { this.revision = sid.revision; this.sub_authority_count = sid.sub_authority_count; this.identifier_authority = sid.identifier_authority; this.sub_authority = sid.sub_authority; this.type = type; this.domainName = domainName; this.acctName = acctName; if (decrementAuthority) { this.sub_authority_count--; this.sub_authority = new int[sub_authority_count]; for (int i = 0; i < this.sub_authority_count; i++) { this.sub_authority[i] = sid.sub_authority[i]; } } } public SID getDomainSid() { return new SID(this, SID_TYPE_DOMAIN, this.domainName, null, getType() != SID_TYPE_DOMAIN); } public int getRid() { if (getType() == SID_TYPE_DOMAIN) throw new IllegalArgumentException("This SID is a domain sid"); return sub_authority[sub_authority_count - 1]; } /** * Returns the type of this SID indicating the state or type of account. * <p> * SID types are described in the following table. * <tt> * <table> * <tr><th>Type</th><th>Name</th></tr> * <tr><td>SID_TYPE_USE_NONE</td><td>0</td></tr> * <tr><td>SID_TYPE_USER</td><td>User</td></tr> * <tr><td>SID_TYPE_DOM_GRP</td><td>Domain group</td></tr> * <tr><td>SID_TYPE_DOMAIN</td><td>Domain</td></tr> * <tr><td>SID_TYPE_ALIAS</td><td>Local group</td></tr> * <tr><td>SID_TYPE_WKN_GRP</td><td>Builtin group</td></tr> * <tr><td>SID_TYPE_DELETED</td><td>Deleted</td></tr> * <tr><td>SID_TYPE_INVALID</td><td>Invalid</td></tr> * <tr><td>SID_TYPE_UNKNOWN</td><td>Unknown</td></tr> * </table> * </tt> */ public int getType() { if (origin_server != null) resolveWeak(); return type; } /** * Return text represeting the SID type suitable for display to * users. Text includes 'User', 'Domain group', 'Local group', etc. */ public String getTypeText() { if (origin_server != null) resolveWeak(); return SID_TYPE_NAMES[type]; } /** * Return the domain name of this SID unless it could not be * resolved in which case the numeric representation is returned. */ public String getDomainName() { if (origin_server != null) resolveWeak(); if (type == SID_TYPE_UNKNOWN) { String full = toString(); return full.substring(0, full.length() - getAccountName().length() - 1); } return domainName; } /** * Return the sAMAccountName of this SID unless it could not * be resolved in which case the numeric RID is returned. If this * SID is a domain SID, this method will return an empty String. */ public String getAccountName() { if (origin_server != null) resolveWeak(); if (type == SID_TYPE_UNKNOWN) return "" + sub_authority[sub_authority_count - 1]; if (type == SID_TYPE_DOMAIN) return ""; return acctName; } public int hashCode() { int hcode = identifier_authority[5]; for (int i = 0; i < sub_authority_count; i++) { hcode += 65599 * sub_authority[i]; } return hcode; } public boolean equals(Object obj) { if (obj instanceof SID) { SID sid = (SID)obj; if (sid == this) return true; if (sid.sub_authority_count == sub_authority_count) { int i = sub_authority_count; while (i-- > 0) { if (sid.sub_authority[i] != sub_authority[i]) { return false; } } for (i = 0; i < 6; i++) { if (sid.identifier_authority[i] != identifier_authority[i]) { return false; } } return sid.revision == revision; } } return false; } /** * Return the numeric representation of this sid such as * <tt>S-1-5-21-1496946806-2192648263-3843101252-1029</tt>. */ public String toString() { String ret = "S-" + (revision & 0xFF) + "-"; if (identifier_authority[0] != (byte)0 || identifier_authority[1] != (byte)0) { ret += "0x"; ret += Hexdump.toHexString(identifier_authority, 0, 6); } else { long shift = 0; long id = 0; for (int i = 5; i > 1; i--) { id += (identifier_authority[i] & 0xFFL) << shift; shift += 8; } ret += id; } for (int i = 0; i < sub_authority_count ; i++) ret += "-" + (sub_authority[i] & 0xFFFFFFFFL); return ret; } /** * Return a String representing this SID ideal for display to * users. This method should return the same text that the ACL * editor in Windows would display. * <p> * Specifically, if the SID has * been resolved and it is not a domain SID or builtin account, * the full DOMAIN\name form of the account will be * returned (e.g. MYDOM\alice or MYDOM\Domain Users). * If the SID has been resolved but it is is a domain SID, * only the domain name will be returned (e.g. MYDOM). * If the SID has been resolved but it is a builtin account, * only the name component will be returned (e.g. SYSTEM). * If the sid cannot be resolved the numeric representation from * toString() is returned. */ public String toDisplayString() { if (origin_server != null) resolveWeak(); if (domainName != null) { String str; if (type == SID_TYPE_DOMAIN) { str = domainName; } else if (type == SID_TYPE_WKN_GRP || domainName.equals("BUILTIN")) { if (type == SID_TYPE_UNKNOWN) { str = toString(); } else { str = acctName; } } else { str = domainName + "\\" + acctName; } return str; } return toString(); } /** * Manually resolve this SID. Normally SIDs are automatically * resolved. However, if a SID is constructed explicitly using a SID * constructor, JCIFS will have no knowledge of the server that created the * SID and therefore cannot possibly resolve it automatically. In this case, * this method will be necessary. * * @param authorityServerName The FQDN of the server that is an authority for the SID. * @param auth Credentials suitable for accessing the SID's information. */ public void resolve(String authorityServerName, NtlmPasswordAuthentication auth) throws IOException { SID[] sids = new SID[1]; sids[0] = this; SID.resolveSids(authorityServerName, auth, sids); } void resolveWeak() { if (origin_server != null) { try { resolve(origin_server, origin_auth); } catch(IOException ioe) { } finally { origin_server = null; origin_auth = null; } } } static SID[] getGroupMemberSids0(DcerpcHandle handle, SamrDomainHandle domainHandle, SID domsid, int rid, int flags) throws IOException { SamrAliasHandle aliasHandle = null; lsarpc.LsarSidArray sidarray = new lsarpc.LsarSidArray(); MsrpcGetMembersInAlias rpc = null; try { 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(); NtlmPasswordAuthentication origin_auth = (NtlmPasswordAuthentication)handle.getPrincipal(); 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_auth = origin_auth; } if (sids.length > 0 && (flags & SID_FLAG_RESOLVE_SIDS) != 0) { SID.resolveSids(origin_server, origin_auth, sids); } return sids; } finally { if (aliasHandle != null) { aliasHandle.close(); } } } public SID[] getGroupMemberSids(String authorityServerName, NtlmPasswordAuthentication auth, int flags) throws IOException { if (type != SID_TYPE_DOM_GRP && type != SID_TYPE_ALIAS) return new SID[0]; DcerpcHandle handle = null; SamrPolicyHandle policyHandle = null; SamrDomainHandle domainHandle = null; SID domsid = getDomainSid(); synchronized (sid_cache) { try { handle = DcerpcHandle.getHandle("ncacn_np:" + authorityServerName + "[\\PIPE\\samr]", auth); policyHandle = new SamrPolicyHandle(handle, authorityServerName, 0x00000030); domainHandle = new SamrDomainHandle(handle, policyHandle, 0x00000200, domsid); return SID.getGroupMemberSids0(handle, domainHandle, domsid, getRid(), flags); } finally { if (handle != null) { if (policyHandle != null) { if (domainHandle != null) { domainHandle.close(); } policyHandle.close(); } handle.close(); } } } } /** * This specialized method returns a Map of users and local groups for the * target server where keys are SIDs representing an account and each value * is an ArrayList of SIDs represents the local groups that the account is * a member of. * <p/> * This method is designed to assist with computing access control for a * given user when the target object's ACL has local groups. Local groups * are not listed in a user's group membership (e.g. as represented by the * tokenGroups constructed attribute retrived via LDAP). * <p/> * Domain groups nested inside a local group are currently not expanded. In * this case the key (SID) type will be SID_TYPE_DOM_GRP rather than * SID_TYPE_USER. * * @param authorityServerName The server from which the local groups will be queried. * @param auth The credentials required to query groups and group members. * @param flags Flags that control the behavior of the operation. When all * name associated with SIDs will be required, the SID_FLAG_RESOLVE_SIDS * flag should be used which causes all group member SIDs to be resolved * together in a single more efficient operation. */ static Map getLocalGroupsMap(String authorityServerName, NtlmPasswordAuthentication auth, int flags) throws IOException { SID domsid = SID.getServerSid(authorityServerName, auth); DcerpcHandle handle = null; SamrPolicyHandle policyHandle = null; SamrDomainHandle domainHandle = null; samr.SamrSamArray sam = new samr.SamrSamArray(); MsrpcEnumerateAliasesInDomain rpc; synchronized (sid_cache) { try { handle = DcerpcHandle.getHandle("ncacn_np:" + authorityServerName + "[\\PIPE\\samr]", auth); policyHandle = new SamrPolicyHandle(handle, authorityServerName, 0x02000000); domainHandle = new SamrDomainHandle(handle, policyHandle, 0x02000000, domsid); rpc = new MsrpcEnumerateAliasesInDomain(domainHandle, 0xFFFF, sam); handle.sendrecv(rpc); if (rpc.retval != 0) throw new SmbException(rpc.retval, false); Map map = new HashMap(); for (int ei = 0; ei < rpc.sam.count; ei++) { samr.SamrSamEntry entry = rpc.sam.entries[ei]; SID[] mems = SID.getGroupMemberSids0(handle, domainHandle, domsid, entry.idx, flags); SID groupSid = new SID(domsid, entry.idx); groupSid.type = SID_TYPE_ALIAS; groupSid.domainName = domsid.getDomainName(); groupSid.acctName = (new UnicodeString(entry.name, false)).toString(); for (int mi = 0; mi < mems.length; mi++) { ArrayList groups = (ArrayList)map.get(mems[mi]); if (groups == null) { groups = new ArrayList(); map.put(mems[mi], groups); } if (!groups.contains(groupSid)) groups.add(groupSid); } } return map; } finally { if (handle != null) { if (policyHandle != null) { if (domainHandle != null) { domainHandle.close(); } policyHandle.close(); } handle.close(); } } } } }