unexpected behavior using the DirectorySearcher "server is not operational" - c#

I'm experiencing a very weird issue using the DirectorySearcher class when trying to query groups over LDAP.
using(var directoryEntry = new DirectoryEntry(thePath,theUserName,ThePassword)
{
var ONLY_GROUPS = "(objectClass=group)"
var filter = string.format("(&{0}({1}=*{2}*))",ONLY_GROUPS,"Name","theGroupName");
using(var searcher = new DirectorySearcher(directoryEntry,filter))
{
...
searcher.FindAll();
...
}
In some cases on our production code when calling the FindAll function some customers are getting a ComException "server is not operational".
which means that the machine trying to connect to the LDAP server has no connection to it.
but as part of our code flow we are calling on the same LDAP a different query for retrieving Domain Controllers, which always works.
When calling the LDAP query for retrieving groups we get the ComException.
another notes
We have C++ code that runs the groups query over the same LDAP which works.
In addition I've created an executable that runs the same production code and it works for the failing customers (so I guess we've excluded the option this is connectivity issue)
I am running out with ideas when can cause this issue.

Since you're using LDAPS, it's likely a problem with the SSL certificate. If the cert is not trusted on the computer that is initiating the connection, the exception you get is exactly the same as if the server could not be contacted at all.
On a problem computer, download the certificate from the server using this PowerShell script:
$webRequest = [Net.WebRequest]::Create("https://example.com:636")
try { $webRequest.GetResponse() } catch {}
$cert = $webRequest.ServicePoint.Certificate
$bytes = $cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert)
set-content -value $bytes -encoding byte -path "certificate.cer"
Replace example.com in the first line with your domain name. Leave the https:// and the :636 (unless you're running LDAPS from a non-standard port).
After running that script, there will be a certificate.cer file in the current directory. Open it to view it. You will see a warning if the certificate is not trusted. If it's not, then the root certificate needs to be added to the Trusted Root Certificates on the current computer.

here is the code for getting ldap groups
using (DirectoryEntry entry = new DirectoryEntry("LDAP://thedomain.com:636/dc=thedomain,dc=com", directory.LdapBindUser, directory.LdapBindPassword))
{
string filter = "(&(objectClass=group))";
using (DirectorySearcher searcher = new DirectorySearcher(entry, filter))
{
searcher.FindAll();
}
}
here is the code for getting ldap domain controllers
using (DirectoryEntry entry = new DirectoryEntry("LDAP://thedomain.com:636/dc=thedomain,dc=com", directory.LdapBindUser, directory.LdapBindPassword))
{
string filter = "(&(objectCategory=computer)(|(primaryGroupID=516)(primaryGroupID=521)))";
using (DirectorySearcher searcher = new DirectorySearcher(entry, filter))
{
searcher.FindAll();
}
}

would like to add another important update about this issue.
it is seems that the first attempt to run the ldap query works ! .
meaning after iisrest , the ldap group query works
but then i wait for 120 seconds things stop working and ldap query is stuck/hand on the Bind stage
after another iisrest , again ldap group is working , wait for 120 seconds same result

i now have another step with understanding my issue
after calling the code mentioned above
i could see after running netstat on the ldap machine
netstat -nat | findstr my_ip_address | findstr :389
i could see the connection stays established even when the using section is done
TCP ldap_ip_address:389 my_ip_address:24730 ESTABLISHED InHost
i could see there is another parameter authentication type , by default Secure
when using my code this way the connection is disposed after the using section
using (var directoryEntry = new DirectoryEntry(
directoryPath,
ConfigurationManager.AppSettings["ldapUsername"],
ConfigurationManager.AppSettings["ldapPassword"],
AuthenticationTypes.Anonymous))
{
}

Related

Connecting to an SQL database over the internet using C#

I'm currently doing an assignment where I want my program to be able to read and update a database. The database itself runs on oracle and was provided by my university (I have my own schema I believe?)
Right now I can connect via SSH using programs such as teraterm or putty, once I log in it takes me to an interactive menu which allows me to select a few various options. One of which is shell. Once I select that I am able to use bash commands to log into the SQL section and use these:
bash$ export ORACLE_HOME=/opt/oracle/product/client/11.2.0
bash$ export TWO_TASK=SSID
bash$ $ORACLE_HOME/bin/sqlplus
to connect to the SQL database. Easy.
However, I want to be able to do that through my program and it is proving difficult for me. I am using SSH.NET and can connect via SSH seemingly well. The problem is I cannot access the SQL section. When I run the commands the first two work correctly I believe, but the last one does not. It seems to not be able to see the anything past $ORACLE_HOME. When I "echo $ORACLE_HOME /*" it even tells me that /bin is a folder:
/bin /boot /dev /etc /export /hey.php /home /lib /lib64 /local /lost+found /media /misc
/mnt /opt /proc /root /sbin /selinux /srv /stage /sys /tmp /usr /var
But instead, when I run the last line of code I get the error message:
Error = "bash: /bin/sqlplus: No such file or directory\n"
I'm not sure whether there is an easier way of accessing the SQL stuff... But I am very close using SSH.NET but I just can't see why I can't open the SQL section like I can in putty or teraterm...
Any help would be greatly appreciated, thank you for your time.
My actual C# code is this:
//Connection information
string user = "SSHusername";
string pass = "password";
string host = "address";
//Set up the SSH connection
using (var client = new SshClient(host, user, pass))
{
//Start the connection
client.Connect();
var output = client.RunCommand("export ORACLE_HOME=/opt/oracle/product/client/11.2.0");
Console.WriteLine(output.Result);
output = client.RunCommand("export TWO_TASK=SSID");
Console.WriteLine(output.Result);
output = client.RunCommand("$ORACLE_HOME/bin/sqlplus");
Console.WriteLine(output.Result);
output = client.RunCommand("username");
Console.WriteLine(output.Result);
output = client.RunCommand("password");
Console.WriteLine(output.Result);
output = client.RunCommand("SELECT * FROM users;");
Console.WriteLine(output.Result);
client.Disconnect();
Console.WriteLine(output.Result);
}
1.I suggest you use native C# package for connect Oracle. You will get wrong format of output.
I see your variable is not work. Because SQLPLUS client should be under
$ORACLE_HOME/bin/sqlplus. But your code show /bin/sqlplus. Means $ORACE_HOME not work.
You can directly change code and run directly sqlplus like /opt/oracle/product/client/11.2.0/bin/sqlplus user/pass#SSID
You can set some script on remote oracle server and get result over that or upload script from C# host to remote each time.
If you're using the SSH.NET library, using a Shell instead of separate Commands should work - something like (untested):
using (var client = new SshClient(host, user, pass)) {
client.Connect();
client.CreateCommand("export ORACLE_HOME=/opt/oracle/product/client/11.2.0").Execute();
client.CreateCommand("export TWO_TASK=SSID").Execute();
client.CreateCommand("$ORACLE_HOME/bin/sqlplus").Execute());
...
client.Disconnect();
}
Original source code found at SSH.NET example

How to get BaseDN of Directory Server from IP or serverName

I am working on LDAP, I have installed OpenLDAP on my local machine and created a RootDSE as dc=wave,dc=com.
and I am trying to fetch this baseDN using this code:
DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}", server), "CN=Manager,dc=wave,dc=com", "secret");
string dnPath;
try
{
dnPath = directoryEntry.Properties["distinguishedName"].Value.ToString();
}
catch
{
dnPath = "";
}
but it always fall in exception, also why do I need to pass whole dn for username?
same thing if I try with Active Directory server it works perfectly, also I don't need to pass the whole dn for username.
any Idea?
The System.DirectoryServices namespace really only works with Active Directory. You should switch over to using System.DirectoryServices.Protocols when working with any other directory. You'll probably find that its even better when working with AD as well.

Mail enabling an AD account too soon after creation

I'm using the System.DirectoryServices.AccountManagement library to create an AD user account, then soon after using a PowerShell runspace to run the Enable-Mailbox command.
When I run this, it is sometimes failing on the Mail-Enable with the error "Active Directory account must be logon-enabled for the user's mailbox."
If rerun the same code, but just try to Mail-Enable the account only, it works fine. And again, other times it's able to create the AD account and Mail-Enable.
This link suggests that AD is still configuring the account when Exchange tries to mail-enable it:
http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/d53d91fd-c479-40e4-9791-32cb5da24721?prof=required
Here is the runspace code:
var connectionInfo = new WSManConnectionInfo(new Uri(ConfigurationManager.AppSettings["PSExchangeURI"]), ConfigurationManager.AppSettings["PSExchangeShellURI"], new PSCredential(ConfigurationManager.AppSettings["Username"], ConfigurationManager.AppSettings["Password"].ToSecureString()));
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
var command = new Command("Enable-Mailbox");
command.Parameters.Add("Identity", userPrincipal.UserPrincipalName);
command.Parameters.Add("Alias", userPrincipal.SamAccountName);
command.Parameters.Add("DisplayName", userPrincipal.DisplayName);
command.Parameters.Add("Database", ConfigurationManager.AppSettings["ExchangeDatabase"]);
using (var runspace = RunspaceFactory.CreateRunspace(connectionInfo)) {
using (var pipeline = runspace.CreatePipeline()) {
runspace.Open();
pipeline.Commands.Add(command);
var results = pipeline.Invoke();
}
}
Is there something else I can do to avoid this error (besides introducing a thread sleep)?
What you are seeing is likely to be down to replication time lag and the exchange server talking to a different DC then the AD user creation code.
What you should do is to line up exchange and your AD creation code to talk to the same DC.
From the PrincipalContext object under S.DS.AM read the DC's FQDN from the ConnectedServer property. Then pass in that value to the -DomainController parameter to the enable-mailbox cmdlet.
So I solve the "hard coding" issue of the DC by declaring a $dchostname variable in my code at run time. It queries the domain to find a suitable DC and then all processes in my script use that domain. This way even if I replace all my DCs, I don't have to update my code.
#Domain Controller Information
$dcs = (Get-ADDomainController -Filter *)
$dc = $dcs | Where {$_.OperationMasterRoles -like "*RIDMaster*"}
$dchostname = $dc.HostName

Connecting to LDAP from C# using DirectoryServices

I am trying to connect to an edirectory 8.8 server running LDAP. How would I go about doing that in .Net? Can I still use the classes in System.DirectoryService such as DirectoryEntry and DirectorySearcher or are they AD specific? Do I need to specify the "Connection String" any differently?
I am trying something like the code below but it doesn't seem to work...
DirectoryEntry de = new DirectoryEntry ("LDAP://novellBox.sample.com","admin","password",AuthenticationTypes.None);
DirectorySearcher ds = new DirectorySearcher(de);
var test = ds.FindAll();
Any ideas?
Well, I think your connection string is missing a bit - specifying just the server name isn't good enough - you also need to specify a "starting point" for your search.
In AD, this would typically be something like the "Users" container in your domain, which you'd specify like this in LDAP parlance:
LDAP://novellBox.sample.com/cn=Users,dc=YourCompany,dc=com
Not sure how LDAP compliant the newer versions of eDirectory are - but that should work since in theory, it's standard LDAP regardless of the implementation :-)
But then again: only in theory, there's no difference between theory and practice.....
There's also a System.DirectoryServices.Protocols namespace which offers low-level LDAP calls directly - and that's definitely not tied to AD at all, but it's really quite low-level.....
There's also a Novell C# LDAP library but I've never tried it and can't say how complete or capable it is. It might give you some clues, though!
Also see this other Stackoverflow question about Novell, LDAP and C# - it might give you additional info.
I had a hard time figuring this out but you could use something like the following, it worked sweet for me:
Domain domain = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, "novellBox.sample.com");
DirectorySearcher ds = new DirectorySearcher(domain.GetDirectoryEntry(), searchQuery);
using (SearchResultCollection src = ds.FindAll())
{....}
I think you need to use LDAP syntax for the host.
Make sure you don't forget to release the connection with using - if you don't dispose of the directory entries they hang around forever until the pool runs out and your app breaks.
using (DirectoryEntry de = new DirectoryEntry ("LDAP://CN=server,DC=domain,DC=com","admin","password",AuthenticationTypes.Secure))
{
...
}
Depending ont he directory server configuration, you might actually need to use the System.DirectoryServices.Protocols namespace. I wrote up a post on connecting to OpenLDAP with it.
http://mikemstech.blogspot.com/2013/03/searching-non-microsoft-ldap.html
If the external LDAP require authentication with DN try this: first retrieve the DN of user, then try the authentication with DN and user credentials. I've tested it on Domino LDAP.
// Autheticate in external LDAP
string ldapserver = "10.1.1.1:389";
string ldapbasedn = "o=mycompany";
string ldapuser = "cn=Administrator,o=mycompany";
string ldappassword = "adminpassword";
string ldapfilter = "(&(objectclass=person)(cn={0}))";
string user = "usertest";
string password = "userpassword";
try
{
string DN = "";
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + ldapserver + "/" + ldapbasedn, ldapuser, ldappassword, AuthenticationTypes.None))
{
DirectorySearcher ds = new DirectorySearcher(entry);
ds.SearchScope = SearchScope.Subtree;
ds.Filter = string.Format(ldapfilter, user);
SearchResult result = ds.FindOne();
if (result != null )
{
DN = result.Path.Replace("LDAP://" + ldapserver + "/" , "");
}
}
// try logon
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + ldapserver + "/" + ldapbasedn, DN, password, AuthenticationTypes.None))
{
DirectorySearcher ds = new DirectorySearcher(entry);
ds.SearchScope = SearchScope.Subtree;
SearchResult result = ds.FindOne();
}
} catch (Exception) { }
I am trying to connect to an edirectory 8.8 server running LDAP. How would I go about doing that in .Net? Can I still use the classes in System.DirectoryService such as DirectoryEntry and DirectorySearcher or are they AD specific?
We are using System.DirectoryServices for Microsoft Active Directory, OpenLDAP running on Linux and eDirectiry without any problem. So the answer is yes, you can use these classes to access eDir.
Do I need to specify the "Connection String" any differently?
Yes you are. When passing to DirectoryEntry a string starting with "LDAP://" you need to conform to the LDAP syntax which is very different than URI syntax.
I recommend you to use an LDAP browser (google it, there are many free downloads) in order to get the correct path to the root object otherwise you will spend time on trying to figure out the correct object types.

Determine the LocalSystem account name using C#

We have an application that installs SQL Server Express from the command line and specifies the service account as the LocalSystem account via the parameter SQLACCOUNT="NT AUTHORITY\SYSTEM".
This doesn't work with different languages because the account name for LocalSystem is different. There's a table listing the differences here:
http://forums.microsoft.com/MSR/ShowPost.aspx?PostID=685354&SiteID=37
This doesn't seem to be complete (the Swedish version isn't listed). So I'd like to be able to determine the name programmatically, perhaps using the SID?
I've found some VB Script to do this:
Set objWMI = GetObject("winmgmts:root\cimv2")
Set objSid = objWMI.Get("Win32_SID.SID='S-1-5-18'")
MsgBox objSid.ReferencedDomainName & "\" & objSid.AccountName
Does anyone know the equivalent code that can be used in C#?
You can use .NET's built-in System.Security.Principal.SecurityIdentifier class for this purpose: by translating it into an instance of NtAccount you can obtain the account name:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);
Later edit, in response to question in comments: you do not need any special privileges to do SID-to-name lookups on the local machine -- for example, even if the user account you're running under is only in the Guests group, this code should work. Things are a little bit different if the SID resolves to a domain account, but even that should work correctly in most cases, as long as you're logged on to the domain (and a domain controller is available at the time of the lookup).
Or you can use:
string localSystem = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null).Translate(typeof(NTAccount)).Value;
With WellKnownSidType you can look for other accounts, as NetworkService for example.
This should do something similar to what you posted. I'm not sure how to get specific properties of WMI objects offhand, but this will get you started with the syntax:
ManagementObject m = new ManagementObject("winmgmts:root\cimv2");
m.Get();
MessageBox.Show(m["Win32_SID.SID='S-1-5-18'"].ToString());
The problem with the accepted answer is that the account name must be resolvable by the local machine running the code.
If you are reading the ACLs on a remote machine you may well not be able to resolve Domain SIDs / local SIDs on the remote box. The following uses WMI and takes the parameter of the remote machine and the SID you want the remote machine to resolve.
/// <summary>
/// Returns the Account name for the specified SID
// using WMI against the specified remote machine
/// </summary>
private string RemoteSID2AccountName(String MachineName, String SIDString)
{
ManagementScope oScope = new ManagementScope(#"\\" + MachineName +
#"\root\cimv2");
ManagementPath oPath = new ManagementPath("Win32_SID.SID='" + SIDString + "'");
ManagementObject oObject = new ManagementObject(oScope, oPath, null);
return oObject["AccountName"].ToString();
}

Categories

Resources