Repository: cobbler/cobbler

Issue:
Bad file descriptor when attempting to use SSL with LDAP auth
Whenever I attempt to configure a Cobbler server to authenticate using LDAPS, I get the following stack trace in cobbler.log:

```
Fri Jun 22 16:53:03 2012 - INFO | Exception occured: exceptions.IOError
Fri Jun 22 16:53:03 2012 - INFO | Exception value: [Errno 9] Bad file descriptor
Fri Jun 22 16:53:03 2012 - INFO | Exception Info:
  File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 1944, in _dispatch
    return method_handle(*params)
   File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 1745, in login
    if self.__validate_user(login_user,login_password):
   File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 1649, in __validate_user
    return self.api.authenticate(input_user,input_password)
   File "/usr/lib/python2.4/site-packages/cobbler/api.py", line 922, in authenticate
    rc = self.authn.authenticate(self,user,password)
   File "/usr/lib/python2.4/site-packages/cobbler/modules/authn_ldap.py", line 91, in authenticate
    traceback.print_exc()
   File "/usr/lib64/python2.4/traceback.py", line 212, in print_exc
    print_exception(etype, value, tb, limit, file)
   File "/usr/lib64/python2.4/traceback.py", line 124, in print_exception
    _print(file, 'Traceback (most recent call last):')
   File "/usr/lib64/python2.4/traceback.py", line 13, in _print
    file.write(str+terminator)
```

Non-SSL LDAP works fine.



Discussion:
Have you confirmed LDAP is working over SSL outside of cobbler? Also, is it running on port 636? It looks like the code in the authn_ldap.py module is somewhat hard-coded to base the LDAP URL on the port (it only uses ldaps:// if the port == 636). If the port is neither 389/636, it uses ldap:// regardless.

My guess is that your ldap port is set to 389 but you have ldap_tls=1, so it's trying to do SSL over a non-SSL connection.

LDAP via SSL definitely works outside of Cobbler on the standard port of 636; it's all these servers will accept (heavily firewalled infrastructure).  Following is relevant configs from /etc/cobbler/settings:

```
ldap_server: "my.ldap.server"
ldap_base_dn: "o=my.ldap.server"
ldap_port: 636
ldap_tls: 1
ldap_anonymous_bind: 1
ldap_search_bind_dn: ''
ldap_search_passwd: ''
ldap_search_prefix: 'uid='
```

I'm having trouble replicating this issue, have you done any further debugging? 

I haven't; I'm unsure of where to start.  I will attempt to read through the authn_ldap module and/or try connecting to our LDAP infrastructure with an interactive Python session to see if I can learn more.  I strongly suspect it is a configuration problem on my side; but I have no idea where to look.

My issue is getting TLS up and running at all. Did you verify it with
TLS enabled via ldapsearch or some other client utility? On my side,
it's definitely my LDAP config but I just haven't had a chance to get
it configured correctly yet.

SSL is definitely working; I wouldn't be able to connect without it as port 389 is firewalled to this system (internal NOAA LDAP server).  ldapsearch works fine from the same system running cobbler.

Is this still an issue with current cobbler versions?

Please re-open if this is still an issue.

It appears that this issue has been resolved.  I'm no longer getting the specified error when attempting to use LDAPS for authentication.

Now I'm getting a proxy timeout error whenever I attempt to log in.  But, that's an issue for another ticket.

Actually I lied.  Testing on a different server shows the same issue.

```
Tue May 27 14:31:18 2014 - INFO | Exception occured: exceptions.IOError
Tue May 27 14:31:18 2014 - INFO | Exception value: [Errno 9] Bad file descriptor
Tue May 27 14:31:18 2014 - INFO | Exception Info:
  File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 2058, in _dispatch
    return method_handle(*params)
   File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 1849, in login
    if self.__validate_user(login_user,login_password):
   File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 1746, in __validate_user
    return self.api.authenticate(input_user,input_password)
   File "/usr/lib/python2.4/site-packages/cobbler/api.py", line 1013, in authenticate
    rc = self.authn.authenticate(self,user,password)
   File "/usr/lib/python2.4/site-packages/cobbler/modules/authn_ldap.py", line 104, in authenticate
    traceback.print_exc()
   File "/usr/lib64/python2.4/traceback.py", line 212, in print_exc
    print_exception(etype, value, tb, limit, file)
   File "/usr/lib64/python2.4/traceback.py", line 124, in print_exception
    _print(file, 'Traceback (most recent call last):')
   File "/usr/lib64/python2.4/traceback.py", line 13, in _print
    file.write(str+terminator)
```

Also, it appears I don't have sufficient privileges to re-open this issue.

I did some more digging.  With the following settings:

```
ldap_server: "my.ldap.server"
ldap_base_dn: "o=my.ldap.server"
ldap_port: 636
ldap_tls: 0
ldap_anonymous_bind: 1
ldap_search_bind_dn: ''
ldap_search_passwd: ''
ldap_search_prefix: 'uid='
```

I get a different error:

```
Mon Jun 16 15:45:40 2014 - INFO | Exception occured: ldap.SERVER_DOWN
Mon Jun 16 15:45:40 2014 - INFO | Exception value: {'desc': "Can't contact LDAP server"}
Mon Jun 16 15:45:40 2014 - INFO | Exception Info:
  File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 2058, in _dispatch
    return method_handle(*params)
   File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 1849, in login
    if self.__validate_user(login_user,login_password):
   File "/usr/lib/python2.4/site-packages/cobbler/remote.py", line 1746, in __validate_user
    return self.api.authenticate(input_user,input_password)
   File "/usr/lib/python2.4/site-packages/cobbler/api.py", line 1013, in authenticate
    rc = self.authn.authenticate(self,user,password)
   File "/usr/lib/python2.4/site-packages/cobbler/modules/authn_ldap.py", line 126, in authenticate
    result = dir.search_s(basedn, ldap.SCOPE_SUBTREE, filter, [])
   File "/usr/lib64/python2.4/site-packages/ldap/ldapobject.py", line 481, in search_s
    return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout)
   File "/usr/lib64/python2.4/site-packages/ldap/ldapobject.py", line 475, in search_ext_s
    return self.result(msgid,all=1,timeout=timeout)[1]
   File "/usr/lib64/python2.4/site-packages/ldap/ldapobject.py", line 405, in result
    res_type,res_data,res_msgid = self.result2(msgid,all,timeout)
   File "/usr/lib64/python2.4/site-packages/ldap/ldapobject.py", line 409, in result2
    res_type, res_data, res_msgid, srv_ctrls = self.result3(msgid,all,timeout)
   File "/usr/lib64/python2.4/site-packages/ldap/ldapobject.py", line 415, in result3
    rtype, rdata, rmsgid, serverctrls = self._ldap_call(self._l.result3,msgid,all,timeout)
   File "/usr/lib64/python2.4/site-packages/ldap/ldapobject.py", line 94, in _ldap_call
    result = func(*args,**kwargs)
```

And get a strange error on my LDAP server:

```
539f1124 conn=505318 fd=45 ACCEPT from IP=my.ip.add.ress:36212 (IP=my.ip.add.ress:636)
TLS: can't accept: error:140760FC:SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol.
```

If i set ldap_tls to 1, I get the previous error.

I could have a simple configuration issue but I SEEM to have a similar issue.

My configurations are as follows:

<pre>
  ldap_server: 'ldap.test.example.com'
  ldap_base_dn: 'dc=test,dc=example,dc=com'
  ldap_port: 389
  ldap_tls: 1
  ldap_anonymous_bind: 0
  ldap_search_bind_dn: 'cn=some_binddn,ou=People,dc=test,dc=example,dc=com'
  ldap_search_passwd: 'my_ldap_password'
  ldap_search_prefix: 'uid='
  ldap_tls_cacertfile: '/etc/pki/cacerts'
  ldap_tls_keyfile: '/etc/pki/private/my_server.pem'
  ldap_tls_certfile: '/etc/pki/public/my_server.pub'
</pre>

I was able to make ldapsearches with using tls with no problems. So I can't see any issues with my ldap.conf or any other outside sources.

<pre>
ldapsearch -D cn=some_binddn,ou=People,dc=test,dc=example,dc=com-x -LLL -Z -W -h ldap://ldap.test.example.com -p 389
</pre>

<pre>
Tue Jun 24 21:25:32 2014 - INFO | Exception occured: <type 'exceptions.IOError'>
Tue Jun 24 21:25:32 2014 - INFO | Exception value: [Errno 9] Bad file descriptor
Tue Jun 24 21:25:32 2014 - INFO | Exception Info:
  File "/usr/lib/python2.6/site-packages/cobbler/remote.py", line 2049, in _dispatch
    return method_handle(*params)
   File "/usr/lib/python2.6/site-packages/cobbler/remote.py", line 1840, in login
    if self.__validate_user(login_user,login_password):
   File "/usr/lib/python2.6/site-packages/cobbler/remote.py", line 1737, in __validate_user
    return self.api.authenticate(input_user,input_password)
   File "/usr/lib/python2.6/site-packages/cobbler/api.py", line 1012, in authenticate
    rc = self.authn.authenticate(self,user,password)
   File "/usr/lib/python2.6/site-packages/cobbler/modules/authn_ldap.py", line 105, in authenticate
    traceback.print_exc()
   File "/usr/lib64/python2.6/traceback.py", line 233, in print_exc
    print_exception(etype, value, tb, limit, file)
   File "/usr/lib64/python2.6/traceback.py", line 124, in print_exception
    _print(file, 'Traceback (most recent call last):')
   File "/usr/lib64/python2.6/traceback.py", line 13, in _print
    file.write(str+terminator)
</pre>

The stack trace wasn't useful so I modified authn_ldap.py it to log the stack trace from the start_tls_s method.

I got the following..

<pre>
Traceback (most recent call last):
  File "/usr/lib/python2.6/site-packages/cobbler/modules/authn_ldap.py", line 114, in authenticate
    dir.start_tls_s()
  File "/usr/lib64/python2.6/site-packages/ldap/ldapobject.py", line 540, in start_tls_s
    return self._ldap_call(self._l.start_tls_s)
  File "/usr/lib64/python2.6/site-packages/ldap/ldapobject.py", line 96, in _ldap_call
    result = func(*args,**kwargs)
CONNECT_ERROR: {'desc': 'Connect error'}
</pre>

Not very useful at all.

Disclaimer: I am not a python developer or have any experience with python.

Despite not being a python developer.. I noticed if is the ldap_port is set as 389 in plan text (without quotes) like..

<pre>
ldap_port : 389
</pre>

The logic fails and always defaults to the else clause as the port is never type casted to an integer similar to the way the tls variable is done.

<pre>
        if port == '389':
            uri += 'ldap://' + server
        elif port == '636':
            uri += 'ldaps://' + server
        else:
            uri += 'ldap://' + "%s:%s" % (server, port)
        uri += ' '
</pre>

Can you try this:

```
diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py
index 3950800..cf6243a 100644
--- a/cobbler/modules/authn_ldap.py
+++ b/cobbler/modules/authn_ldap.py
@@ -52,7 +52,7 @@ def authenticate(api_handle, username, password):
     server = api_handle.settings().ldap_server
     basedn = api_handle.settings().ldap_base_dn
-    port = api_handle.settings().ldap_port
+    port = str(api_handle.settings().ldap_port)
     tls = api_handle.settings().ldap_tls
     anon_bind = api_handle.settings().ldap_anonymous_bind
     prefix = api_handle.settings().ldap_search_prefix
```

As expected, the patch worked without an issue.

<pre>
ldap_port: 389
</pre>

Quick logger I setup to print the uri.

<pre>
INFO:root:ldap://my.host.com
</pre>

Also, I figured out it was a misconfiguration issue on my part. I thought I could specify the cacert DIRECTORY. Turns out it was just the actual FILE (hashed of course) instead. Completely my fault for not RTFM. Sorry for the misconfiguration fail. Thanks for the help!

I can also confirm that the patch works perfectly with the following config in /etc/cobbler/settings:

```
ldap_server: "my.ldap.server"
ldap_base_dn: "o=my.ldap"
ldap_port: 636
ldap_tls: 0
ldap_anonymous_bind: 1
ldap_search_bind_dn: ''
ldap_search_passwd: ''
ldap_search_prefix: 'uid='
```

Thanks a bunch!


Resolving patch:
diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py
--- a/cobbler/modules/authn_ldap.py
+++ b/cobbler/modules/authn_ldap.py
@@ -52,7 +52,7 @@ def authenticate(api_handle, username, password):
 
     server = api_handle.settings().ldap_server
     basedn = api_handle.settings().ldap_base_dn
-    port = api_handle.settings().ldap_port
+    port = str(api_handle.settings().ldap_port)
     tls = api_handle.settings().ldap_tls
     anon_bind = api_handle.settings().ldap_anonymous_bind
     prefix = api_handle.settings().ldap_search_prefix


Test patch: