9

Further to my previous Question, which I managed to answer myself with help from the Oracle forums, I now have another issue which follows on from the earlier one (provided for background).

I wish to query LDAP directly from my C# code to perform an LDAP lookup of an Oracle TNS hostname in order to get the connection string. This is normally stored in tnsnames.ora, and my organisation uses LDAP (via ldap.ora) to resolve hostnames from an LDAP server using Active Directory.

However, I am using ODP.NET, Managed Driver Beta (Oracle.ManagedDataAccess.dll) in my C# application which doesn't support LDAP as mentioned in the release notes pointed to by the Oracle forum reply I mentioned earlier. This is why I wish to query LDAP directly from C#.

I found a way to do this here using DirectoryEntry and DirectorySearcher, but I have no idea what to put as the parameters to DirectorySearcher. I have access to ldap.ora which is in the following format:

# LDAP.ORA Configuration
# Generated by Oracle configuration tools.
DEFAULT_ADMIN_CONTEXT = "dc=xx,dc=mycompany,dc=com"
DIRECTORY_SERVERS = (ldap_server1.mycompany.com:389:636,ldap_server2.mycompany.com:389:636, ...) DIRECTORY_SERVER_TYPE = OID

But, how do I map this to setting up the LDAP query in my C# code?

Csa77
  • 649
  • 13
  • 19
Neo
  • 4,145
  • 6
  • 53
  • 76
  • what have you tried? In C#, you'll probably setup a DirectorySearcher http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher%28v=vs.100%29.aspx – tbone Jan 28 '13 at 17:13
  • Yes, as I mentioned in the question, I have used `DirectorySearcher`, but I have no idea what to put as the parameters to `DirectorySearcher` based on the **ldap.ora** file. – Neo Jan 28 '13 at 17:42

4 Answers4

5

Further to my second comment in the accepted Answer, this is the code for performing an LDAP lookup which improves the original version I found here. And it also handles server lists in the ldap.ora file that includes multiple delimited port numbers.

private static string ResolveServiceNameLdap(string serviceName)
{
    string tnsAdminPath = Path.Combine(@"C:\Apps\oracle\network\admin", "ldap.ora");
    string connectionString = string.Empty;

    // ldap.ora can contain many LDAP servers
    IEnumerable<string> directoryServers = null;

    if (File.Exists(tnsAdminPath))
    {
        string defaultAdminContext = string.Empty;

        using (var sr = File.OpenText(tnsAdminPath))
        {
            string line;

            while ((line = sr.ReadLine()) != null)
            {
                // Ignore commetns
                if (line.StartsWith("#"))
                {
                    continue;
                }

                // Ignore empty lines
                if (line == string.Empty)
                {
                    continue;
                }

                // If line starts with DEFAULT_ADMIN_CONTEXT then get its value
                if (line.StartsWith("DEFAULT_ADMIN_CONTEXT"))
                {
                    defaultAdminContext = line.Substring(line.IndexOf('=') + 1).Trim(new[] {'\"', ' '});
                }

                // If line starts with DIRECTORY_SERVERS then get its value
                if (line.StartsWith("DIRECTORY_SERVERS"))
                {
                    string[] serversPorts = line.Substring(line.IndexOf('=') + 1).Trim(new[] {'(', ')', ' '}).Split(',');
                    directoryServers = serversPorts.SelectMany(x =>
                    {
                        // If the server includes multiple port numbers, this needs to be handled
                        string[] serverPorts = x.Split(':');
                        if (serverPorts.Count() > 1)
                        {
                            return serverPorts.Skip(1).Select(y => string.Format("{0}:{1}", serverPorts.First(), y));
                        }

                        return new[] {x};
                    });
                }
            }
        }

        // Iterate through each LDAP server, and try to connect
        foreach (string directoryServer in directoryServers)
        {
            // Try to connect to LDAP server with using default admin contact
            try
            {
                var directoryEntry = new DirectoryEntry("LDAP://" + directoryServer + "/" + defaultAdminContext, null, null, AuthenticationTypes.Anonymous);
                var directorySearcher = new DirectorySearcher(directoryEntry, "(&(objectclass=orclNetService)(cn=" + serviceName + "))", new[] { "orclnetdescstring" }, SearchScope.Subtree);

                SearchResult searchResult = directorySearcher.FindOne();

                var value = searchResult.Properties["orclnetdescstring"][0] as byte[];

                if (value != null)
                {
                    connectionString = Encoding.Default.GetString(value);
                }

                // If the connection was successful, then not necessary to try other LDAP servers
                break;
            }
            catch
            {
                // If the connection to LDAP server not successful, try to connect to the next LDAP server
                continue;
            }
        }

        // If casting was not successful, or not found any TNS value, then result is an error message
        if (string.IsNullOrEmpty(connectionString))
        {
            connectionString = "TNS value not found in LDAP";
        }
    }
    else
    {
        // If ldap.ora doesn't exist, then return error message
        connectionString = "ldap.ora not found";
    }

    return connectionString;
}
Community
  • 1
  • 1
Neo
  • 4,145
  • 6
  • 53
  • 76
1

To get you started, try:

using System.DirectoryServices;
...

DirectoryEntry de = new DirectoryEntry();
de.Username = "username@mysite.com";
de.Password = "password";
de.Path = "LDAP://DC=mysite,DC=com";
de.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher des = new DirectorySearcher(de);

Now you should be able to hit ldap using the DirectorySearcher (filter, findone, etc) described here

tbone
  • 15,107
  • 3
  • 33
  • 40
  • OK, but my problem is more to do with how to map the **ldap.ora** file to this C# code. What goes in 'mysite'? Do I use my Windows username and password in 'username' and 'password'? From my **ldap.ora** file, would I use "dc=xx,dc=mycompany,dc=com" where you have put "DC=mysite,DC=com"? Do I put "ldap_server1.mycompany.com" where you have put "mysite.com"? What about the port? When I call `FindAll()`, I currently get an error - "Logon failure: unknown user name or bad password." But, this happens whatever I put in place of 'mysite', so it's not clear what I have wrong. – Neo Jan 28 '13 at 18:27
1

Judging by what I found in Oracle Database Name Resolution with OpenLDAP, the code should look something like this:

string directoryServer = "ldap_server1.mycompany.com:389";
string defaultAdminContext = "dc=xx,dc=mycompany,dc=com";
string oracleHostEntryPath = string.Format("LDAP://{0}/cn=OracleContext,{1}", directoryServer, defaultAdminContext);

var directoryEntry = new DirectoryEntry(oracleHostEntryPath) {AuthenticationType = AuthenticationTypes.None};
var directorySearcher = new DirectorySearcher(directoryEntry, "(&(objectclass=orclNetService)(cn=ABCDEFG1))", new[] { "orclnetdescstring" }, SearchScope.Subtree);

string oracleNetDescription = Encoding.Default.GetString(des.FindOne().Properties["orclnetdescstring"][0] as byte[]);
Neo
  • 4,145
  • 6
  • 53
  • 76
JamieSee
  • 12,696
  • 2
  • 31
  • 47
  • This got me a little further, but no cigar just yet. I can't use `DirectoryEntry.Exists()` due to reasons outlined here: http://stackoverflow.com/questions/4284253. `new DirectoryEntry("LDAP://ldap_server1.mycompany.com:389/cn=OracleContext,dc=xx,dc=mycompany,dc=com", "domain\\username", "password")` successfully finds the LDAP server (introducing a deliberate typo makes it fail). However, it throws a DirectoryServicesCOMException complaining about account/password. This happens regardless of the username I enter (even non-existent ones). So, what domain/username/password should I specify? – Neo Jan 29 '13 at 12:27
  • SOLUTION! I made some edits to your code. I did some searching and found this: http://www.codeproject.com/Articles/472540/Alternative-tnsping-utility. The `resolveldap` function on that article is exactly what I was looking for. The LDAP server supports anonymous access, so this needs to be specified on `DirectoryEntry` via `AuthenticationType`. The rest of that method is useful as it gets the LDAP servers from the **ldap.ora** file itself by parsing. So, now I have a working solution. no connection strings hardcoded and no LDAP servers hardcoded. I'll post the modified code in another Answer. – Neo Jan 29 '13 at 15:39
  • 1
    Excellent! I'm glad I was able to get you going in the right direction. Thanks for updating this with your final solution. I'm sure others will find this useful. – JamieSee Jan 29 '13 at 19:18
1

Here a more complete sample based on the other answers:

using System;
using System.Data;
using System.DirectoryServices;
using System.Text;
using Oracle.ManagedDataAccess.Client;

namespace BAT
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            string directoryServer = "myServer:389";
            string defaultAdminContext = "cn=OracleContext,dc=world";
            string serviceName = "MYDB";
            string userId = "MYUSER";
            string password = "MYPW";

            using (IDbConnection connection = GetConnection(directoryServer, defaultAdminContext, serviceName, userId,
                    password))
            {
                connection.Open();

                connection.Close();
            }

        }

        private static IDbConnection GetConnection(string directoryServer, string defaultAdminContext,
            string serviceName, string userId, string password)
        {
            string descriptor = ConnectionDescriptor(directoryServer, defaultAdminContext, serviceName);
            // Connect to Oracle
            string connectionString = $"user id={userId};password={password};data source={descriptor}";

            OracleConnection con = new OracleConnection(connectionString);
            return con;
        } 

        private static string ConnectionDescriptor(string directoryServer, string defaultAdminContext,
            string serviceName)
        {
            string ldapAdress = $"LDAP://{directoryServer}/{defaultAdminContext}";
            string query = $"(&(objectclass=orclNetService)(cn={serviceName}))";
            string orclnetdescstring = "orclnetdescstring";

            DirectoryEntry directoryEntry = new DirectoryEntry(ldapAdress, null, null, AuthenticationTypes.Anonymous);
            DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry, query, new[] { orclnetdescstring },
                SearchScope.Subtree);

            SearchResult searchResult = directorySearcher.FindOne();
            byte[] value = searchResult.Properties[orclnetdescstring][0] as byte[];

            if (value != null)
            {
                string descriptor = Encoding.Default.GetString(value);
                return descriptor;
            }

            throw new Exception("Error querying LDAP");
        }
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
yonexbat
  • 2,902
  • 2
  • 32
  • 43
  • It's hard to believe I'm the first person to use this after 4 years. Thanks, it helped a lot! – Mmm Aug 17 '20 at 18:25