Repository: lldap/lldap (language: rust)

Issue:
[BUG] Misleading error code when trying to bind without authentication
**Describe the bug**

When trying to bind to the default minimal configured lldap without providing (or with incorrect) authentication data, the response says "Missing DN value" which is certainly helpful for humans, but it also contains the "Naming Violation" message with the error code 64.

In an automatic evaluation of the response, code 64 does not indicate that authentication is missing or incorrect.

The RFC (https://www.rfc-editor.org/rfc/rfc4511#appendix-A.2) describes it like:
```
namingViolation (64)
         Indicates that the entry's name violates naming restrictions.
```

Technically it's not completely wrong, but it's misleading and there are better options :)


**To Reproduce**
Steps to reproduce the behavior:
1. Setup lldap following the docker installation tutorial
  - just insert the required secrets, comment out uid/gid, publish the port 3890
  - run docker compose up
2. Execute the example script or try to bind without user/password with any other client

This ruby snippet can be executed (`ruby snippet.rb`) to reproduce the error with a fresh lldap docker container.
```ruby
require 'net/ldap'
require 'net/ldap/entry'

ldap = Net::LDAP.new(
  host: '10.88.193.1',
  port: '3890',
)

### first try without authentication

ldap.bind

p ldap.get_operation_result
# prints: #<OpenStruct extended_response=nil, code=64, error_message="Missing DN value", matched_dn="", message="Naming Violation">


### second try with incorrect authentication

ldap.auth "foobert", "password"
ldap.bind

p ldap.get_operation_result
# prints: #<OpenStruct extended_response=nil, code=64, error_message="Missing DN value", matched_dn="", message="Naming Violation">


### third try with authentication

ldap.auth "cn=admin,ou=people,dc=example,dc=com", "password"
ldap.bind

p ldap.get_operation_result
# prints: #<OpenStruct extended_response=nil, code=0, error_message="", matched_dn="", message="Success">
```

**Expected behavior**
Getting the correct errors for missing and wrong authentication data when binding to the ldap server.

I would recommend to use these:
```
inappropriateAuthentication (48)
         Indicates the server requires the client that had attempted
         to bind anonymously or without supplying credentials to
         provide some form of credentials.

invalidCredentials (49)
         Indicates that the provided credentials (e.g., the user's name
         and password) are invalid.
```

**Logs**
The provided log contains all 3 authentication requests from the example code above.
```
lldap-1  | 2024-09-11T14:53:28.397938371+00:00  INFO     LDAP session [ 207µs | 50.96% / 100.00% ]
lldap-1  | 2024-09-11T14:53:28.398005035+00:00  INFO     ┕━ LDAP request [ 101µs | 46.71% / 49.04% ]
lldap-1  | 2024-09-11T14:53:28.398029730+00:00  DEBUG       ┝━ 🐛 [debug]:  | msg: LdapMsg { msgid: 1, op: BindRequest(LdapBindRequest { dn: "", cred: LdapBindCred::Simple }), ctrl: [] }
lldap-1  | 2024-09-11T14:53:28.398032586+00:00  DEBUG       ┝━ do_bind [ 4.81µs | 2.32% ] dn: 
lldap-1  | 2024-09-11T14:53:28.398047093+00:00  DEBUG       ┕━ 🐛 [debug]:  | response: BindResponse(LdapBindResponse { res: LdapResult { code: NamingViolation, matcheddn: "", message: "Missing DN value", referral: [] }, saslcreds: None })
lldap-1  | 2024-09-11T14:53:28.399175650+00:00  INFO     LDAP session [ 76.8µs | 62.71% / 100.00% ]
lldap-1  | 2024-09-11T14:53:28.399192422+00:00  INFO     ┕━ LDAP request [ 28.6µs | 36.13% / 37.29% ]
lldap-1  | 2024-09-11T14:53:28.399196599+00:00  DEBUG       ┝━ 🐛 [debug]:  | msg: LdapMsg { msgid: 1, op: BindRequest(LdapBindRequest { dn: "foobert", cred: LdapBindCred::Simple }), ctrl: [] }
lldap-1  | 2024-09-11T14:53:28.399197461+00:00  DEBUG       ┝━ do_bind [ 892ns | 1.16% ] dn: foobert
lldap-1  | 2024-09-11T14:53:28.399201328+00:00  DEBUG       ┕━ 🐛 [debug]:  | response: BindResponse(LdapBindResponse { res: LdapResult { code: NamingViolation, matcheddn: "", message: "Missing DN value", referral: [] }, saslcreds: None })
lldap-1  | 2024-09-11T14:53:28.399567018+00:00  INFO     LDAP session [ 53.4ms | 0.09% / 100.00% ]
lldap-1  | 2024-09-11T14:53:28.400278420+00:00  INFO     ┕━ LDAP request [ 53.4ms | 0.15% / 99.91% ]
lldap-1  | 2024-09-11T14:53:28.400282248+00:00  DEBUG       ┝━ 🐛 [debug]:  | msg: LdapMsg { msgid: 1, op: BindRequest(LdapBindRequest { dn: "cn=admin,ou=people,dc=example,dc=com", cred: LdapBindCred::Simple }), ctrl: [] }
lldap-1  | 2024-09-11T14:53:28.400282959+00:00  DEBUG       ┝━ do_bind [ 53.3ms | 0.07% / 99.76% ] dn: cn=admin,ou=people,dc=example,dc=com
lldap-1  | 2024-09-11T14:53:28.400300501+00:00  DEBUG       │  ┝━ bind [ 53.0ms | 0.02% / 99.33% ]
lldap-1  | 2024-09-11T14:53:28.400303948+00:00  DEBUG       │  │  ┝━ get_password_file_for_user [ 84.1µs | 0.16% ] user_id: UserId("admin")
lldap-1  | 2024-09-11T14:53:28.400532994+00:00  DEBUG       │  │  ┕━ passwords_match [ 53.0ms | 99.15% ] username: admin
lldap-1  | 2024-09-11T14:53:28.453496590+00:00  DEBUG       │  ┝━ get_user_groups [ 190µs | 0.36% ] user_id: "admin"
lldap-1  | 2024-09-11T14:53:28.454138994+00:00  DEBUG       │  │  ┕━ 🐛 [debug]:  | return: {GroupDetails { group_id: GroupId(1), display_name: "lldap_admin", creation_date: 2024-09-11T13:28:34.083304822, uuid: Uuid("521401e4-cabc-3386-b987-8aa27f1c816b"), attributes: [] }}
lldap-1  | 2024-09-11T14:53:28.454147230+00:00  DEBUG       │  ┕━ 🐛 [debug]: Success!
lldap-1  | 2024-09-11T14:53:28.454155034+00:00  DEBUG       ┕━ 🐛 [debug]:  | response: BindResponse(LdapBindResponse { res: LdapResult { code: Success, matcheddn: "", message: "", referral: [] }, saslcreds: None })
```



Discussion / hints:

Fix patch:
diff --git a/server/src/infra/ldap_handler.rs b/server/src/infra/ldap_handler.rs
index f2729a77..f9f2ea17 100644
--- a/server/src/infra/ldap_handler.rs
+++ b/server/src/infra/ldap_handler.rs
@@ -263,6 +263,12 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
 
     #[instrument(skip_all, level = "debug", fields(dn = %request.dn))]
     pub async fn do_bind(&mut self, request: &LdapBindRequest) -> (LdapResultCode, String) {
+        if request.dn.is_empty() {
+            return (
+                LdapResultCode::InappropriateAuthentication,
+                "Anonymous bind not allowed".to_string(),
+            );
+        }
         let user_id = match get_user_id_from_distinguished_name(
             &request.dn.to_ascii_lowercase(),
             &self.ldap_info.base_dn,


Test patch: