<div dir="ltr"><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Dec 3, 2019 at 2:29 PM Seth Grover <<a href="mailto:Seth.D.Grover@gmail.com">Seth.D.Grover@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>...</div></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>Back in 2013 Bart Dopheide submitted a patch to the mailing list to add <span class="gmail-il">LDAP</span>
 StartTLS (elevate connection to TLS after initial connection is 
initiated) support to the list of supported protocols in protocol.c (<a href="https://www.stunnel.org/pipermail/stunnel-users/2013-November/004437.html" target="_blank">https://www.stunnel.org/pipermail/stunnel-users/2013-November/004437.html</a>). It doesn't look like this patch was ever accepted into stunnel.<br><br>I've
 run into a similar requirement and have updated the patch to work 
against stunnel 5.56. In addition, there are a few other minor changes, 
the most significant being as follows. It would appear that Windows 
Active Directory servers do not implement the <span class="gmail-il">ldap</span> extended response PDU in the same way as OpenLDAP (see this thread: <a href="https://www.openldap.org/lists/openldap-software/200401/msg00800.html" target="_blank">https://www.openldap.org/lists/openldap-software/200401/msg00800.html</a>).
 With this patch you can specify either "protocol = winldap" or 
"protocol = openldap" and have it work either way. I haven't modified 
the logic of Bart's original patch as far as OpenLDAP goes, but I have 
split the code path where applicable to handle the Windows case.<br>... <br></div></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr">The patch is at the end of this message.<br>...<br></div></blockquote><div><br></div><div>I apologize, but I had a stupid bug in the OpenLDAP portion of my patch which I hadn't been able to test as I didn't have an OpenLDAP server instance set up. On line 108 of my patch, this code:</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>resp_len = buffer_8;</div></blockquote><div><br></div><div>should be changed to:<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>resp_len = buffer_8[0];</div></blockquote><div><br></div><div>I set up an openldap instance in docker (<a href="https://github.com/osixia/docker-openldap">https://github.com/osixia/docker-openldap</a>) and am now getting correct results against both Active Directory and OpenLDAP. For completeness' sake, I am including the full (corrected) patch again here:</div><div><br></div><div>diff -Nurp a/src/protocol.c b/src/protocol.c<br>--- a/src/protocol.c  2019-05-15 13:35:16.000000000 -0600<br>+++ b/src/protocol.c       2019-12-03 13:54:47.536940900 -0700<br>@@ -64,6 +64,8 @@ NOEXPORT char *pop3_server(CLI *, SERVIC<br> NOEXPORT char *imap_client(CLI *, SERVICE_OPTIONS *, const PHASE);<br> NOEXPORT char *imap_server(CLI *, SERVICE_OPTIONS *, const PHASE);<br> NOEXPORT char *nntp_client(CLI *, SERVICE_OPTIONS *, const PHASE);<br>+NOEXPORT char *openldap_client(CLI *, SERVICE_OPTIONS *, const PHASE);<br>+NOEXPORT char *winldap_client(CLI *, SERVICE_OPTIONS *, const PHASE);<br> NOEXPORT char *connect_server(CLI *, SERVICE_OPTIONS *, const PHASE);<br> NOEXPORT char *connect_client(CLI *, SERVICE_OPTIONS *, const PHASE);<br> #ifndef OPENSSL_NO_MD4<br>@@ -113,6 +115,14 @@ char *protocol(CLI *c, SERVICE_OPTIONS *<br>         return opt->option.client ?<br>             nntp_client(c, opt, phase) :<br>             "The 'nntp' protocol is not supported in the server mode";<br>+    if(!strcasecmp(opt->protocol, "openldap"))<br>+        return opt->option.client ?<br>+            openldap_client(c, opt, phase) :<br>+            "The 'openldap' protocol is not supported in the server mode";<br>+    if(!strcasecmp(opt->protocol, "winldap"))<br>+        return opt->option.client ?<br>+            winldap_client(c, opt, phase) :<br>+            "The 'winldap' protocol is not supported in the server mode";<br>     if(!strcasecmp(opt->protocol, "connect"))<br>         return opt->option.client ?<br>             connect_client(c, opt, phase) :<br>@@ -1119,6 +1129,182 @@ NOEXPORT char *nntp_client(CLI *c, SERVI<br>     return NULL;<br> }<br><br>+/**************************************** LDAP, RFC 2830 */<br>+uint8_t ldap_startssl_message[0x1d + 2] =<br>+{<br>+  0x30,        /* tag = UNIVERSAL SEQUENCE */<br>+  0x1d,        /* len = 29 (the remaining number of bytes in this message) */<br>+  0x02,        /*   messageID */<br>+  0x01,        /*   len = 1 */<br>+  0x01,        /*   value = 1 (this is messageID 1) */<br>+               /*   --- */<br>+  0x77,        /*   protocolOp = APPLICATION (23) (=ExtendedRequest)<br>+                 *     0b01xxxxxx => APPLICATION<br>+                 *     0bxx1xxxxx => ?<br>+                 *     0xxxx10111 => 23<br>+               */<br>+  0x18,        /*   len = 24 */<br>+  0x80,        /*   type = requstName? */<br>+  0x16,        /*   len = 22 */<br>+  /* OID: 1.3.6.1.4.1.1466.20037 (=LDAP_START_TLS_OID)*/<br>+  '1', '.',<br>+  '3', '.',<br>+  '6', '.',<br>+  '1', '.',<br>+  '4', '.',<br>+  '1', '.',<br>+  '1', '4', '6', '6', '.',<br>+  '2', '0', '0', '3', '7'<br>+  /* No requestValue, as per RFC2830 (in 2.1: "The requestValue field is absent") */<br>+};<br>+<br>+typedef enum {<br>+    LDAP_OPENLDAP,<br>+    LDAP_WINLDAP<br>+} LDAP_MODE;<br>+<br>+#define LDAP_UNIVERSAL_SEQUENCE                0x30<br>+#define LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG        0x84<br>+#define LDAP_RESPONSE_MSG_ID_TYPE_INT          0x02<br>+#define LDAP_RESPONSE_EXPECTED_MSG_ID_LEN      0x01<br>+#define LDAP_RESPONSE_EXPECTED_MSG_ID          0x01<br>+#define LDAP_RESPONSE_EXT_RESP                 0x0a<br>+#define LDAP_RESPONSE_EXT_RESP_APPLICATION     0x78<br>+#define LDAP_RESPONSE_EXPECTED_ERR_LEN         0x01<br>+#define LDAP_RESPONSE_SUCCESS                  0x00<br>+<br>+NOEXPORT char *ldap_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase, const LDAP_MODE ldap_mode) {<br>+<br>+    /* thanks to these threads for help with these PDUs<br>+         <a href="https://www.stunnel.org/pipermail/stunnel-users/2013-November/004437.html">https://www.stunnel.org/pipermail/stunnel-users/2013-November/004437.html</a><br>+         <a href="https://www.openldap.org/lists/openldap-software/200401/msg00800.html">https://www.openldap.org/lists/openldap-software/200401/msg00800.html</a> */<br>+<br>+    uint8_t buffer_8[1];<br>+    uint32_t buffer_32[1];<br>+    uint32_t resp_len;<br>+    uint8_t ldap_response[256];<br>+    uint8_t *resp_ptr;<br>+<br>+    (void)opt; /* squash the unused parameter warning */<br>+<br>+    if(phase!=PROTOCOL_MIDDLE)<br>+        return NULL;<br>+<br>+    /* send "Start TLS" request to AD server */<br>+    s_log(LOG_DEBUG, "Requesting LDAP Start TLS");<br>+    s_write(c, c->remote_fd.fd, ldap_startssl_message, (size_t)ldap_startssl_message[1] + 2);<br>+<br>+    /* LDAP_UNIVERSAL_SEQUENCE (1 byte) */<br>+    s_read(c, c->remote_fd.fd, buffer_8, 1);<br>+    if(buffer_8[0] != LDAP_UNIVERSAL_SEQUENCE) {<br>+        s_log(LOG_ERR, "start tag is not UNIVERSAL SEQUENCE");<br>+        throw_exception(c, 1);<br>+    }<br>+<br>+    if(ldap_mode == LDAP_OPENLDAP) {<br>+      /* OpenLDAP - response length (1 byte) */<br>+      s_log(LOG_DEBUG, "Reading OpenLDAP message size (1 byte)");<br>+      s_read(c, c->remote_fd.fd, buffer_8, 1);<br>+      resp_len = buffer_8[0];<br>+<br>+    } else if(ldap_mode == LDAP_WINLDAP) {<br>+<br>+      /* WinLDAP - "response length is 4 bytes" flag - LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG (1-byte) */<br>+      s_read(c, c->remote_fd.fd, buffer_8, 1);<br>+      if(buffer_8[0] != LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG) {<br>+          s_log(LOG_ERR, "LDAP message length flag is an unexpected value");<br>+          throw_exception(c, 1);<br>+      }<br>+<br>+      /* WinLDAP - response length (4 bytes, network byte order) */<br>+      s_log(LOG_DEBUG, "Reading WinLDAP message size (4 bytes)");<br>+      s_read(c, c->remote_fd.fd, buffer_32, 4);<br>+      resp_len = ntohl(buffer_32[0]);<br>+<br>+    } else {<br>+      s_log(LOG_ERR, "Unsupported LDAP mode");<br>+      throw_exception(c, 1);<br>+    }<br>+<br>+    /* LDAP response message */<br>+    s_log(LOG_DEBUG, "Reading LDAP message (%u byte(s))", resp_len);<br>+    s_read(c, c->remote_fd.fd, ldap_response, resp_len);<br>+<br>+    resp_ptr = &ldap_response[0];<br>+<br>+    /* LDAP_RESPONSE_MSG_ID_TYPE_INT - 1 byte */<br>+    if(*resp_ptr != LDAP_RESPONSE_MSG_ID_TYPE_INT) {<br>+        s_log(LOG_ERR, "LDAP response has an incorrect message ID type");<br>+        throw_exception(c, 1);<br>+    }<br>+    resp_ptr++;<br>+<br>+    /* LDAP_RESPONSE_EXPECTED_MSG_ID_LEN - 1 byte */<br>+    if(*resp_ptr != LDAP_RESPONSE_EXPECTED_MSG_ID_LEN) {<br>+        s_log(LOG_ERR, "LDAP response has an unexpected message ID length");<br>+        throw_exception(c, 1);<br>+    }<br>+    resp_ptr++;<br>+<br>+    /* LDAP_RESPONSE_EXPECTED_MSG_ID - 1 byte */<br>+    if(*resp_ptr != LDAP_RESPONSE_EXPECTED_MSG_ID) {<br>+        s_log(LOG_ERR, "LDAP response has an unexpected message ID");<br>+        throw_exception(c, 1);<br>+    }<br>+    resp_ptr++;<br>+<br>+    /* LDAP_RESPONSE_EXT_RESP_APPLICATION - 1 byte */<br>+    if(*resp_ptr != LDAP_RESPONSE_EXT_RESP_APPLICATION) {<br>+        s_log(LOG_ERR, "LDAP response protocolOp is not APPLICATION");<br>+        throw_exception(c, 1);<br>+    }<br>+    resp_ptr++;<br>+<br>+    if(ldap_mode == LDAP_WINLDAP) {<br>+      /* WinLDAP - "response length is 4 bytes" flag - LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG (1-byte) */<br>+      if(*resp_ptr != LDAP_WINLDAP_FOUR_BYTE_LEN_FLAG) {<br>+          s_log(LOG_ERR, "LDAP extendedResp length flag is an unexpected value");<br>+          throw_exception(c, 1);<br>+      }<br>+      /* WinLDAP - extended response message length (4-bytes) */<br>+      resp_ptr += 5;<br>+<br>+    } else {<br>+      /* OpenLDAP - extended response message length (1-byte) */<br>+      resp_ptr++;<br>+    }<br>+<br>+    /* LDAP_RESPONSE_EXT_RESP - 1 byte */<br>+    if(*resp_ptr != LDAP_RESPONSE_EXT_RESP) {<br>+        s_log(LOG_ERR, "LDAP response type is not EXT_RESP");<br>+        throw_exception(c, 1);<br>+    }<br>+    resp_ptr++;<br>+<br>+    /* LDAP_RESPONSE_EXT_RESP - 1 byte */<br>+    if(*resp_ptr != LDAP_RESPONSE_EXPECTED_ERR_LEN) {<br>+        s_log(LOG_ERR, "LDAP response has an unexpected error code length");<br>+        throw_exception(c, 1);<br>+    }<br>+    resp_ptr++;<br>+<br>+    if(*resp_ptr != LDAP_RESPONSE_SUCCESS) {<br>+        s_log(LOG_ERR, "LDAP response has indicated an error (%u)", *resp_ptr);<br>+        throw_exception(c, 1);<br>+    }<br>+<br>+    return NULL;<br>+}<br>+<br>+<br>+NOEXPORT char *openldap_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {<br>+  return ldap_client(c, opt, phase, LDAP_OPENLDAP);<br>+}<br>+<br>+NOEXPORT char *winldap_client(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {<br>+  return ldap_client(c, opt, phase, LDAP_WINLDAP);<br>+}<br>+<br> /**************************************** connect */<br><br> NOEXPORT char *connect_server(CLI *c, SERVICE_OPTIONS *opt, const PHASE phase) {<br></div><div><br></div></div></div>