package org.ovirt.engine.core.bll.adbroker;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.regex.Pattern;
import org.ovirt.engine.core.common.businessentities.AdUser;
import org.ovirt.engine.core.common.businessentities.ad_groups;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.LogCompat;
import org.ovirt.engine.core.compat.LogFactoryCompat;
import org.ovirt.engine.core.compat.Regex;
import org.ovirt.engine.core.compat.RegexOptions;
import org.ovirt.engine.core.utils.jwin32.ConvertSidException;
import org.ovirt.engine.core.utils.jwin32.LOCAL_GROUP_INFO_0;
import org.ovirt.engine.core.utils.jwin32.USER_INFO_20;
import org.ovirt.engine.core.utils.jwin32.jwin32;
import com.sun.jna.WString;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
public abstract class LUBrokerCommandBase extends BrokerCommandBase {
private static LogCompat log = LogFactoryCompat.getLog(LUBrokerCommandBase.class);
@Override
protected String getPROTOCOL() {
return "LDAP://";
}
protected LUBrokerCommandBase(LdapBrokerBaseParameters parameters) {
super(parameters);
}
@Override
public LdapReturnValueBase Execute() {
try {
ExecuteQuery();
} catch (RuntimeException e) {
log.errorFormat("Error in executing LU broker command. Exception is {0} ", e.getMessage());
_ldapReturnValue.setSucceeded(false);
_ldapReturnValue.setReturnValue(null);
}
return _ldapReturnValue;
}
protected abstract void ExecuteQuery();
/**
* This method convert a single condition from the ldap query string into a
* regex. The pattern describe the condition to be converted. If the pattern
* does not exist in the query it will return a null.
*
* @param query
* @param pattern
* @return
*/
protected Regex queryToRegex(String query, String pattern) {
Regex retval = null;
if (query.contains(pattern)) {
String value = query.substring(query.indexOf(pattern)).split("[()=]")[1].trim();
log.debug("queryToRegex, value=" + value);
retval = new Regex(value, RegexOptions.IgnoreCase);
}
return retval;
}
protected Pattern queryToPattern(String query, String pattern) {
Pattern retval = null;
if (query.contains(pattern)) {
String value = query.substring(query.indexOf(pattern)).split("[()=]")[1].trim();
log.debug("queryToPattern, value=" + value.replace("*", ".*"));
retval = Pattern.compile(value.replace("*", ".*"), Pattern.CASE_INSENSITIVE);
}
return retval;
}
/**
* This method return an array list of local users with their info. I had to
* hack around quite a few bits here, so I guess it worth some through
* explaining. But first please follow the most important rule of WIN32 APIs
* - get into MSDN and read the remarks section about NetUserEnum. Ok, so as
* you can see, NetUserEnum allocate memory area by itself. this memory area
* is later on populated with an array of structures (pointer to pointer)
* which hold the wanted information. This required some trickery. which I
* couldnt get working any other way, so please test this throughly if you
* think of any cleaner solution. I was mapping the needed structure into
* java class according to JNA instructions. adding it a ByReference class
* (again, according to instructions). Then (here come the tricky part) I've
* created another class which have a member of that previous ByReference
* type and is overriding the toArray method so that it will actually call
* the toArray of the original structure mapping class. Doing the same with
* PointerByRefernce and useMemory failed! (you can try again if you like, I
* might have gotten something wrong there.) Ok, so we've got that part
* working, now, we need the following info about each user: full name, SID
* and flags (expired, locked, etc.) it seems very apropriate to use
* USER_INFO_20 but!!! it seems that calling NetUserEnum with level=23 fails
* with error 124 or 1722, another trickery is needed! So I'm calling
* NetUserEnum with level=0 which gives only the username, then calling
* NetUserGetInfo (again, highly advasiable, no required, to read the
* remarks section) with level=23 which miraculously works ;)
*
* Update: made it working by useMemory and PointerByReference. but had to
* drop the sid for that so now, this is used only with USER_INFO_20 (no
* need anymore for 0 and 23) and a special Implementation, using
* LookupAccountName to get the sid, which is shared with LOCAL_GROUP class
*/
protected ArrayList<AdUser> getAdUsers() {
int nStatus;
ArrayList<AdUser> retVal = new ArrayList<AdUser>();
IntByReference nEntriesRead = new IntByReference();
IntByReference nTotalEntries = new IntByReference();
PointerByReference pUI20 = new PointerByReference();
nStatus = jwin32.netapi32.NetUserEnum(new WString(""), 20, jwin32.FILTER_NORMAL_ACCOUNT, pUI20,
jwin32.MAX_PREFERRED_LENGTH, // This
// have
// high
// potential
// of
// breaking
// on
// systems
// with
// a
// large
// number
// of
// users!
nEntriesRead,
nTotalEntries,
null);
if (nStatus == jwin32.NERR_Success || nStatus == jwin32.ERROR_MORE_DATA) {
// Allocates a USER_INFO_0 object that maps to a USER_INFO_0
// struct that is located in the address that is held by the
// value of pUI0.
// This address is the beginning of a sequence of structures of
// USER_INFO_0, so retrieving array is possible.
USER_INFO_20 userTemp = new USER_INFO_20(pUI20.getValue());
USER_INFO_20 users[] = (USER_INFO_20[]) userTemp.toArray(nTotalEntries.getValue());
for (USER_INFO_20 user : users) {
retVal.add(populateUser(user));
}
if (nEntriesRead.getValue() > 0) {
jwin32.netapi32.NetApiBufferFree(pUI20.getValue());
}
}
return (retVal);
}
protected ArrayList<ad_groups> getAdGroups() {
log.debug("getAdGroups Entry");
int nStatus;
PointerByReference pGI0 = new PointerByReference();
IntByReference nEntriesRead = new IntByReference();
IntByReference nTotalEntries = new IntByReference();
ArrayList<ad_groups> retVal = new ArrayList<ad_groups>();
nStatus = jwin32.netapi32.NetLocalGroupEnum(
new WString(""),
0,
pGI0,
jwin32.MAX_PREFERRED_LENGTH,
nEntriesRead,
nTotalEntries,
null
);
if (nStatus == jwin32.NERR_Success ||
nStatus == jwin32.ERROR_MORE_DATA) {
log.debug("NetLocalGroupEnum returned " + nEntriesRead.getValue() + " entries");
LOCAL_GROUP_INFO_0 groupsArr = new LOCAL_GROUP_INFO_0(pGI0.getValue());
LOCAL_GROUP_INFO_0[] groups = (LOCAL_GROUP_INFO_0[]) groupsArr.toArray(nEntriesRead.getValue());
for (LOCAL_GROUP_INFO_0 group : groups) {
String retSID;
PointerByReference group_sid;
PointerByReference stringSID = new PointerByReference();
IntByReference nSIDSize = new IntByReference();
log.debug("populating group name=" + group.lgrpi0_name);
retVal.add(populateGroup(group));
}
if (nEntriesRead.getValue() > 0) {
jwin32.netapi32.NetApiBufferFree(pGI0.getValue());
}
} else {
log.error("getAdGroups, NetLocalGroupEnum error (" + jwin32.kernel32.GetLastError() + ") return=" + nStatus);
}
log.debug("getAdGroups, Return size=" + retVal.size());
return (retVal);
}
protected AdUser populateUser(USER_INFO_20 child) {
log.debug("populateUser Entry");
try {
AdUser user = new AdUser();
user.setUserId(sidToGuid(child.getSID()));
user.setName(child.usri20_full_name.toString());
user.setUserName(child.usri20_name.toString());
/*
* According to MSDN documentation for C# Environment.MachineName it
* seems the class is getting the machine name from environment
* variable named COMPUTERNAME
*/
user.setDomainControler(System.getenv("COMPUTERNAME"));
user.setPasswordExpired((child.usri20_flags & jwin32.UF_PASSWORD_EXPIRED) == jwin32.UF_PASSWORD_EXPIRED);
// debug print all properties
// List<string> test = new List<string>();
// foreach (string key in child.Properties.PropertyNames)
// {
// test.Add(key + ":" + child.Properties[key].Value);
// }
return user;
} catch (ConvertSidException ex) {
log.error("convert sid failed");
return null;
}
}
protected ad_groups populateGroup(LOCAL_GROUP_INFO_0 child) {
log.debug("populateGroup, Entry");
try {
ad_groups group = new ad_groups();
group.setid(sidToGuid(child.getSID()));
group.setname(child.lgrpi0_name.toString()); // todo: getName()
group.setdomain(System.getenv("COMPUTERNAME"));
log.debug("populateGroup, Return");
return group;
} catch (ConvertSidException ex) {
log.error("convert sid failed");
return null;
}
}
/* do we really need a byte[] format ? */
protected static Guid sidToGuid(byte[] sid) {
if (sid == null) {
return (null);
}
byte[] barray = new byte[16];
if (sid.length == 16) {
barray = sid;
} else if (sid.length > 16) {
int offset = sid.length - 16;
for (int i = 0; i < 15; i++) {
barray[i] = sid[i + offset];
}
}
return new Guid(barray, true);
}
private static Guid sidToGuid(String sid) throws ConvertSidException {
log.debug("sidToGuid Entry, sid=" + sid + " length=" + sid.length());
try {
ByteBuffer bb = ByteBuffer.allocate(16);
bb.order(ByteOrder.LITTLE_ENDIAN);
String[] arrSidParts = sid.split("-");
for (int i = 4; i < arrSidParts.length; i++) {
bb.putInt((int) Long.parseLong(arrSidParts[i]));
}
return new Guid(bb.array(), false);
} catch (java.lang.ArrayIndexOutOfBoundsException aioobe) {
throw new ConvertSidException("Given sid has a wrong length. Please validate it: " + sid);
} catch (java.nio.BufferOverflowException boe) {
throw new ConvertSidException("Given sid length is too long. Please validate it: " + sid);
}
}
}