Related
I'm trying to make a https client by openssl 1.0.1u that can visit websites with ssl protocol.
When visiting most of https websites (like google.com, yahoo.com, facebook.com, ...), it works well and the home page content is returned. However, there are certain websites (relatively small websites), the server returns me 0 bytes, here are some details:
I use SSLv23_method() to create my openssl context:
this->_sslContext = SSL_CTX_new(SSLv23_method()); // SSLv23_method: Negotiate highest available SSL/TLS version
Then I found that in the following calling sequence (listed forwardly):
(ssl_lib.c) SSL_read(SSL *s, void *buf, int num) ---->
(s3_lib.c) ssl3_read(SSL *s, void *buf, int len) ---->
(s3_lib.c) ssl3_read_internal(SSL *s, void *buf, int len, int peek) ---->
(s3_pkt.c) int ssl3_read_bytes(SSL *s, int type, unsigned char *buf, int len, int peek)
With some website (failed case), the function SSL_read() return 0 bytes because inside the function ssl3_read_bytes(), I got a alert_descr set to SSL_AD_CLOSE_NOTIFY then the function simply return 0, here is the source code:
...
if (alert_level == SSL3_AL_WARNING)
{
s->s3->warn_alert = alert_descr;
if (alert_descr == SSL_AD_CLOSE_NOTIFY) {
s->shutdown |= SSL_RECEIVED_SHUTDOWN;
return (0);
}
Anyone can give me any hint to fix this problem? Thanks.
=== UPDATE ===
Upon Steffen Ullrich's suggestion, I post source code that sends request / gets respone. My small experimental https client is composed of Socket and SSLSocket classes and a helper WebpageFetcher class. The function WebpageFetcher::fetchPage is used to send the https request and get the respond from private function WebpageFetcher::_getResponse():
wchar_t * WebpageFetcher::fetchPage(wchar_t * url, int port, bool useSSL)
{
wchar_t * response = NULL;
Socket * socket = Socket::createSocket(false, useSSL);
if (socket == nullptr)
{
response = String(L"Connection failed. Unable to create a SSLSocket!\n").toCharArray();
return response;
}
if (!socket->connect(url, port))//Connection failed
{
response = String(L"Connection failed. Possible reason: Wrong server URL or port.\n").toCharArray();
}
else //Connection succeeded
{
//Send request to server socket
static const char * REQUEST = "GET / \r\n\r\n";
static const int REQUEST_LEN = (const int)strlen(REQUEST);
socket->send((void *)REQUEST, REQUEST_LEN);
//Get the response from server
response = _getResponse(socket);
socket->shutDown();
socket->close();
}
delete socket;
return response;
}
// ============================================================================
wchar_t * WebpageFetcher::_getResponse(Socket * socket)
{
static const int READSIZE = 1024; //Reading buffer size, the larger the better performance
int responseBufferSize = READSIZE + 1;
char * readBuf = new char[READSIZE];
char * responseBuf = new char[responseBufferSize];
int bytesReceived;
int totalBytesReceived = 0;
while ((bytesReceived = socket->recv(readBuf, READSIZE)) > 0)
{
// Check if need to expand responseBuf size
if (totalBytesReceived + bytesReceived >= responseBufferSize)//No enough capacity, expand the response buffer
{
responseBufferSize += READSIZE;
char * tempBuf = new char[responseBufferSize];
memcpy(tempBuf, responseBuf, totalBytesReceived);
delete[] responseBuf;
responseBuf = tempBuf; //Response buffer expanded
}
// Append data from readBuf
memcpy(responseBuf + totalBytesReceived, readBuf, bytesReceived);
totalBytesReceived += bytesReceived;
responseBuf[totalBytesReceived] = '\0';
}
wchar_t * response = (wchar_t *)(totalBytesReceived == 0 ? //Generate the response as a C wide string
String(L"Received nothing from server. Possible reason: Wrong port.\n").toCharArray() :
StringUtil::charsToWchars(responseBuf));
delete[] readBuf;
delete[] responseBuf;
return response;
}
I passed argument useSSL with true when call factory function Socket::createSocket() so that the socket I got is a SSLSocket instance, which overrides the default functions connect(), _send() and _recv() to let openssl to do the actual job. Here is the constructor of my SSLSocket class, which derives from class Socket:
SSLSocket::SSLSocket(bool isServerSocket, int port, int socketType, int socketProtocol, int uOptions, wchar_t * strBindingAddress, wchar_t * cerPath, wchar_t * keyPath, wchar_t * keyPass) :
Socket(isServerSocket, port, socketType, socketProtocol, uOptions, strBindingAddress)
{
// Register the error strings
SSL_load_error_strings();
// Register the available ciphers and digests
SSL_library_init();
// Create an SSL_CTX structure by choosing a SSL/TLS protocol version
this->_sslContext = SSL_CTX_new(SSLv23_method()); // Use SSL 2 or SSL 3
// Create an SSL struct (client only, server does not need one)
this->_sslHandle = (this->_isServer ? NULL : SSL_new(this->_sslContext));
bool success = false;
if (!this->_isServer) // is Client socket
{
success = (this->_sslHandle != NULL);
}
else if (cerPath != NULL && keyPath != NULL) // is Server socket
{
success = ......
}
if (!success)
this->close();
}
And the followings are the functions override the virtual functions in parent class Socket, which lets openssl to do the relevant job:
bool SSLSocket::connect(wchar_t * strDestination, int port, int timeout)
{
SocketAddress socketAddress(strDestination, port);
return this->connect(&socketAddress, timeout);
}
bool SSLSocket::connect(SocketAddress * sockAddress, int timeout)
{
bool success =
(this->_sslHandle != NULL &&
Socket::connect(sockAddress, timeout) && // Regular TCP connection
SSL_set_fd(this->_sslHandle, (int)this->_hSocket) == 1 && // Connect the SSL struct to our connection
SSL_connect(this->_sslHandle) == 1); // Initiate SSL handshake
if (!success)
this->close();
return success;
}
int SSLSocket::_recv(void * lpBuffer, int size, int flags)
{
MonitorLock cs(&_mutex);
return SSL_read(this->_sslHandle, lpBuffer, size);
}
int SSLSocket::_send(const void * lpBuffer, int size, int flags)
{
return SSL_write(this->_sslHandle, lpBuffer, size);
}
I'm using Dovecot v2.3.9.3. By looking to the dovecot docs I see that specification for Redis is the following:
redis:param=value:param2=value2:...
Here one of the parameters is host: Redis server host (default: 127.0.0.1).
In my configuration I have specified uri = redis:host=redis:port=6379:
Feb 23 20:48:32 auth: Fatal: dict /etc/dovecot/dovecot-dict-auth.conf.ext: Failed to init dict: dict redis: Invalid IP: redis
redis hostname on my server resolves to the IP without any problem:
# getent hosts redis
192.168.48.2 redis redis
Is there a way to use hostname (maybe some hidden setting which enables the resolution), or they just bluntly did not implement a support for that? :/
TL;DR
Dovecot redis dict driver accepts undocumented parameter path in which you can specify the unix socket. You may then create a proxy to the hostname redis serving on the tcp port 6379 via the unix socket /run/redis.soc:
socat unix-listen:/run/redis.soc,reuseaddr,fork,perm=0644,user=dovecot tcp:redis:6379 &
Dovecot config becomes:
# Dictionary URI
uri = redis:path=/run/redis.soc
The following is my analysis of the problem. I don't code in C, so my understanding was limited.
Code which covers my error (Invalid IP: redis) is the following:
} else if (str_begins(*args, "host=")) {
if (net_addr2ip(*args+5, &ip) < 0) {
*error_r = t_strdup_printf("Invalid IP: %s",
*args+5);
ret = -1;
}
}
It relies on the net_addr2ip function which depends on the net_addr2ip_inet4_fast. Both of these functions do not seem to do, what their names are suggesting (they do not turn addr to ip):
static bool net_addr2ip_inet4_fast(const char *addr, struct ip_addr *ip)
{
uint8_t *saddr = (void *)&ip->u.ip4.s_addr;
unsigned int i, num;
if (str_parse_uint(addr, &num, &addr) < 0)
return FALSE;
if (*addr == '\0' && num <= 0xffffffff) {
/* single-number IPv4 address */
ip->u.ip4.s_addr = htonl(num);
ip->family = AF_INET;
return TRUE;
}
/* try to parse as a.b.c.d */
i = 0;
for (;;) {
if (num >= 256)
return FALSE;
saddr[i] = num;
if (i == 3)
break;
i++;
if (*addr != '.')
return FALSE;
addr++;
if (str_parse_uint(addr, &num, &addr) < 0)
return FALSE;
}
if (*addr != '\0')
return FALSE;
ip->family = AF_INET;
return TRUE;
}
int net_addr2ip(const char *addr, struct ip_addr *ip)
{
int ret;
if (net_addr2ip_inet4_fast(addr, ip))
return 0;
if (strchr(addr, ':') != NULL) {
/* IPv6 */
T_BEGIN {
if (addr[0] == '[') {
/* allow [ipv6 addr] */
size_t len = strlen(addr);
if (addr[len-1] == ']')
addr = t_strndup(addr+1, len-2);
}
ret = inet_pton(AF_INET6, addr, &ip->u.ip6);
} T_END;
if (ret == 0)
return -1;
ip->family = AF_INET6;
} else {
/* IPv4 */
if (inet_aton(addr, &ip->u.ip4) == 0)
return -1;
ip->family = AF_INET;
}
return 0;
}
Therefore host parameter in dovecot's redis dict driver cannot be anything else but an IP address ¯_(ツ)_/¯
I must use the RFC5077 TLS session resumption. My Client use Windows SChannel and server usually uses OpenSSL. In my test, following result.
OpenSSL 1.1.0 (or later) and SChannel: Always session reused, SChannel send previous Session Ticket.
OpenSSL 1.0.2 (any revision) and Schannel: Always new session, SChannel does not send Session Ticket.
OpenSSL and OpenSSL: Always session reused.
So I want to know that
Why Schannel don't use TLS session resumption only for OpenSSL 1.0.2?
The difference between 1.0.2 and 1.1.0.
How to use TLS session resumption at OpenSSL 1.0.2 and SChannel?
Server code: Simple TLS Server
Client code: Windows C++
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define SECURITY_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h>
#include <sspi.h>
#include <schannel.h>
#include <stdio.h>
#include <vector>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Secur32.lib")
struct WSA {
WSA() {
WSADATA wsaData;
if (auto result = WSAStartup(WINSOCK_VERSION, &wsaData))
throw result;
}
~WSA() {
WSACleanup();
}
};
struct Credential : CredHandle {
Credential() {
SCHANNEL_CRED cred = { .dwVersion = SCHANNEL_CRED_VERSION, .dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION };
if (auto ss = AcquireCredentialsHandleW(nullptr, UNISP_NAME_W, SECPKG_CRED_OUTBOUND, nullptr, &cred, nullptr, nullptr, this, nullptr); ss != SEC_E_OK)
throw ss;
}
~Credential() {
FreeCredentialsHandle(this);
}
};
struct Socket {
SOCKET s;
Socket(const char* target, int port) {
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(target);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
throw WSAGetLastError();
if (connect(s, reinterpret_cast<const SOCKADDR*>(&addr), sizeof addr))
throw WSAGetLastError();
u_long val = 1;
ioctlsocket(s, FIONBIO, &val);
}
~Socket() {
closesocket(s);
}
auto Read() {
for (std::vector<unsigned char> result;;) {
char buffer[2048];
if (auto read = recv(s, buffer, sizeof buffer, 0); read == 0)
return result;
else if (read == SOCKET_ERROR) {
if (auto lastError = WSAGetLastError(); lastError != WSAEWOULDBLOCK)
throw lastError;
if (!empty(result))
return result;
Sleep(0);
} else
result.insert(end(result), buffer, buffer + read);
}
}
void Write(void* data, int length) {
for (auto p = reinterpret_cast<const char*>(data); 0 < length;) {
auto sent = send(s, p, length, 0);
if (sent == 0)
throw 0;
else if (sent == SOCKET_ERROR)
throw WSAGetLastError();
p += sent;
length -= sent;
}
}
};
int main() {
WSA wsa;
Credential credential;
for (int i = 0; i < 5; i++) {
Socket socket{ "127.0.0.1", 4433 };
std::vector<unsigned char> read;
auto first = true;
CtxtHandle context;
for (SECURITY_STATUS ss = SEC_I_CONTINUE_NEEDED; ss == SEC_I_CONTINUE_NEEDED;) {
SecBuffer inbuf[] = {
{ .BufferType = SECBUFFER_EMPTY },
{ .BufferType = SECBUFFER_EMPTY },
};
if (!first) {
auto data = socket.Read();
read.insert(end(read), begin(data), end(data));
inbuf[0] = { static_cast<unsigned long>(read.size()), SECBUFFER_TOKEN, read.data() };
}
SecBufferDesc indesc = { SECBUFFER_VERSION, 2, inbuf };
SecBuffer outbuf = { .BufferType = SECBUFFER_TOKEN };
SecBufferDesc outdesc = { SECBUFFER_VERSION, 1, &outbuf };
unsigned long attr = 0;
ss = InitializeSecurityContextW(&credential, first ? nullptr : &context, L"localhost", ISC_REQ_ALLOCATE_MEMORY, 0, SECURITY_NETWORK_DREP, &indesc, 0, &context, &outdesc, &attr, nullptr);
if (FAILED(ss))
throw ss;
first = false;
read.erase(begin(read), end(read) - (inbuf[1].BufferType == SECBUFFER_EXTRA ? inbuf[1].cbBuffer : 0));
if (outbuf.cbBuffer != 0) {
socket.Write(outbuf.pvBuffer, outbuf.cbBuffer);
FreeContextBuffer(outbuf.pvBuffer);
}
}
for (;;) {
SecBuffer buffer[] = {
{ static_cast<unsigned long>(read.size()), SECBUFFER_DATA, read.data() },
{ .BufferType = SECBUFFER_EMPTY },
{ .BufferType = SECBUFFER_EMPTY },
{ .BufferType = SECBUFFER_EMPTY },
};
SecBufferDesc desc{ SECBUFFER_VERSION, 4, buffer };
if (auto ss = DecryptMessage(&context, &desc, 0, nullptr); ss == SEC_I_CONTEXT_EXPIRED)
break;
else if (ss == SEC_E_OK) {
if (buffer[1].BufferType == SECBUFFER_DATA && 0 < buffer[1].cbBuffer && buffer[1].pvBuffer)
printf("%.*s", buffer[1].cbBuffer, reinterpret_cast<const char*>(buffer[1].pvBuffer));
read.erase(begin(read), end(read) - (buffer[3].BufferType == SECBUFFER_EXTRA ? buffer[3].cbBuffer : 0));
} else if (ss != SEC_E_INCOMPLETE_MESSAGE)
throw ss;
if (auto data = socket.Read(); empty(data))
break;
else
read.insert(end(read), begin(data), end(data));
}
if (auto ss = DeleteSecurityContext(&context); ss != SEC_E_OK)
throw ss;
}
}
I'm a maintainer of ftp client. Some ftps server requires that DATA connection must reuse the TLS session of CONTROL connection for security.
At Windows Update 2019/10, RFC7627 Extended Master Secret was enabled. SChannel requires RFC7627 EMS support when RFC5077 TLS Session Resumption.
OpenSSL suport RFC7627 extended master secret from 1.1.0. So SChannel cannot reuse TLS session with OpenSSL 1.0.2.
In OpenSSL documentation it says:
All these functions are implemented as macros. Those containing a 1 increment the reference count of the supplied certificate or chain so it must be freed at some point after the operation. Those containing a 0 do not increment reference counts and the supplied certificate or chain MUST NOT be freed after the operation.
But when I tried to look at examples of cases about which one should be used where I'm confused.
First OpenSSL:
It uses SSL_add0_chain_cert itself in the SSL_CTX_use_certificate_chain_file function of ssl_rsa.c. Here is the source:
static int use_certificate_chain_file(SSL_CTX *ctx, SSL *ssl, const char *file) {
if (ctx)
ret = SSL_CTX_use_certificate(ctx, x);
else
ret = SSL_use_certificate(ssl, x);
......
while ((ca = PEM_read_bio_X509(in, NULL, passwd_callback,
passwd_callback_userdata))
!= NULL) {
if (ctx)
r = SSL_CTX_add0_chain_cert(ctx, ca);
else
r = SSL_add0_chain_cert(ssl, ca);
......
}
Second usage I see is OpenResty Lua:
It uses SSL_add0_chain_cert in one way of setting certificate (ngx_http_lua_ffi_ssl_set_der_certificate), see here:
int ngx_http_lua_ffi_ssl_set_der_certificate(ngx_http_request_t *r,
const char *data, size_t len, char **err) {
......
if (SSL_use_certificate(ssl_conn, x509) == 0) {
*err = "SSL_use_certificate() failed";
goto failed;
}
......
while (!BIO_eof(bio)) {
x509 = d2i_X509_bio(bio, NULL);
if (x509 == NULL) {
*err = "d2i_X509_bio() failed";
goto failed;
}
if (SSL_add0_chain_cert(ssl_conn, x509) == 0) {
*err = "SSL_add0_chain_cert() failed";
goto failed;
}
}
BIO_free(bio);
*err = NULL;
return NGX_OK;
failed:
.......
}
Yet uses SSL_add1_chain_cert in another way (ngx_http_lua_ffi_set_cert), see here:
int ngx_http_lua_ffi_set_cert(ngx_http_request_t *r,
void *cdata, char **err) {
......
if (SSL_use_certificate(ssl_conn, x509) == 0) {
*err = "SSL_use_certificate() failed";
goto failed;
}
x509 = NULL;
/* read rest of the chain */
for (i = 1; i < sk_X509_num(chain); i++) {
x509 = sk_X509_value(chain, i);
if (x509 == NULL) {
*err = "sk_X509_value() failed";
goto failed;
}
if (SSL_add1_chain_cert(ssl_conn, x509) == 0) {
*err = "SSL_add1_chain_cert() failed";
goto failed;
}
}
*err = NULL;
return NGX_OK; /* No free of x509 here */
failed:
......
}
Yet I don't see a clear difference of what changes when calling these two in Lua, and it doesn't seem like the cert X509, when set successfully, gets freed in either case. According to my understanding of the OpenSSL doc, I should expect X509_free(x509) gets called somewhere after SSL_add1_chain_cert called on that x509. Is that the correct understanding?
Last, the Openssl implementation of ssl_cert_add1_chain_cert (what boils down from SSL_add1_chain_cert macro) does indeed show it's just a wrapper of ssl_cert_add0_chain_cert with reference count incremented on the cert, but how should that be reflected in the calling process?
int ssl_cert_add1_chain_cert(SSL *s, SSL_CTX *ctx, X509 *x)
{
if (!ssl_cert_add0_chain_cert(s, ctx, x))
return 0;
X509_up_ref(x);
return 1;
}
Now Nginx only deals with another function SSL_CTX_add_extra_chain_cert which leaves the burden of such choice behind, as it does not deal with switching cert per SSL connection basis. In my case I need to patch Nginx with this capability, switching cert per connection (but without using Lua).
So I'm not sure which one I should be using, SSL_add0_chain_cert or SSL_add1_chain_cert? And what's the freeing practice here?
I have a client that connects to LDAP server using TLS. For this connection, I want to enable CRL check and reject the connection only if any server/client certificates are revoked.
In special cases (like CRL missing, CRL expired) I want to ignore the error and establish the connection.
So I though to overwrite the default SSL verify call back to ignore the specific errors.
But the call back is not called at all. Always only default call-back is called.
Here is my call back:
static int verify_callback(int ok, X509_STORE_CTX *ctx)
{
X509* cert = X509_STORE_CTX_get_current_cert(ctx);
if (ok)
return ok;
int sslRet = X509_STORE_CTX_get_error(ctx);
const char* err = NULL;
switch (sslRet)
{
case X509_V_ERR_UNABLE_TO_GET_CRL:
case X509_V_ERR_CRL_HAS_EXPIRED:
case X509_V_ERR_CRL_NOT_YET_VALID:
printf( "CRL: Verification failed... but ignored : %d\n", sslRet);
return 1;
default:
err = X509_verify_cert_error_string(sslRet);
if (err)
printf( "CRL: Failed to verify : %s\n",err);
return 0;
}
return sslRet;
}
Default verify call-back is overwritten using the ldap call-back set option:
void ldap_tls_cb(LDAP * ld, SSL * ssl, SSL_CTX * ctx, void * arg)
{
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER , verify_callback);
printf("verify call back is set...\n");
return;
}
Main Program:
int main( int argc, char **argv )
{
LDAP *ldap;
int auth_method = LDAP_AUTH_SIMPLE; //LDAP_AUTH_SASL
int ldap_version = LDAP_VERSION3;
char *ldap_host = "10.104.40.35";
int ldap_port = 389;
if ( (ldap = ldap_init(ldap_host, ldap_port)) == NULL ) {
perror( "ldap_init failed" );
return( EXIT_FAILURE );
}
int result = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
if (result != LDAP_OPT_SUCCESS ) {
ldap_perror(ldap, "ldap_set_option failed!");
return(EXIT_FAILURE);
}
int requireCert = LDAP_OPT_X_TLS_DEMAND;
result = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &requireCert);
if (result != LDAP_OPT_SUCCESS ) {
ldap_perror(ldap, "ldap_set_option - req cert -failed!");
return(EXIT_FAILURE);
}
result = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, "/etc/certs/Cert.pem");
if (result != LDAP_OPT_SUCCESS ) {
ldap_perror(ldap, "ldap_set_option - cert file - failed!");
return(EXIT_FAILURE);
}
int crlvalue = LDAP_OPT_X_TLS_CRL_ALL;
result =ldap_set_option(NULL, LDAP_OPT_X_TLS_CRLCHECK, &crlvalue);
if (result != LDAP_OPT_SUCCESS ) {
ldap_perror(ldap, "ldap_set_option failed!");
return(EXIT_FAILURE);
}
int debug = 7;
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug);
result = ldap_set_option(ldap, LDAP_OPT_X_TLS_CONNECT_CB, (void *)ldap_tls_cb);
if (result != LDAP_SUCCESS) {
fprintf(stderr, "ldap_set_option(LDAP_OPT_X_TLS_CONNECT_CB): %s\n", ldap_err2string(result));
return(1);
}
int msgidp = 0;
result = ldap_start_tls(ldap,NULL,NULL,&msgidp);
if (result != LDAP_OPT_SUCCESS ) {
ldap_perror(ldap, "start tls failed!");
return result;
} else {
printf("Start tls success.\n");
}
LDAPMessage *resultm;
struct timeval timeout;
result = ldap_result(ldap, msgidp, 0, &timeout, &resultm );
if ( result == -1 || result == 0 ) {
printf("ldap_result failed;retC=%d \n", result);
return result;
}
result = ldap_parse_extended_result(ldap, resultm, NULL, NULL, 0 );
if ( result == LDAP_SUCCESS ) {
result = ldap_install_tls (ldap);
printf("installing tls... %s\n", ldap_err2string(result));
}
int request_id = 0;
result = ldap_sasl_bind(ldap, "", LDAP_SASL_SIMPLE, NULL, 0, 0, &request_id);
if ( result != LDAP_SUCCESS ) {
fprintf(stderr, "ldap_x_bind_s: %s\n", ldap_err2string(result));
printf("LDAP bind error .. %d\n", result);
return(EXIT_FAILURE);
} else {
printf("LDAP connection successful.\n");
}
ldap_unbind(ldap);
return(EXIT_SUCCESS);
}
can someone help to check why my verify call-back is not called?
I think you need to set the callback on the SSL object directly instead of the context, so
void ldap_tls_cb(LDAP * ld, SSL * ssl, SSL_CTX * ctx, void * arg)
{
SSL_set_verify(ssl, SSL_VERIFY_PEER, verify_callback);
printf("verify call back is set...\n");
return;
}
The reason for this is that the SSL handle has already been initialised by the time your connect callback is called (see the OpenLDAP code), and
it's too late to set this callback through the context at that point:
If no special callback was set before, the default callback for the underlying ctx is used, that was valid at the time ssl was created with SSL_new(3).
OpenLDAP can be built with GnuTLS, so you may need to check that it's using OpenSSL before setting the callback. The LDAP_OPT_X_TLS_PACKAGE option could be used for this (note that I haven't tested this code):
char* package = NULL;
int result = ldap_get_option(NULL, LDAP_OPT_X_TLS_PACKAGE, (void *)&package);
if (result != LDAP_OPT_SUCCESS) {
ldap_perror(ldap, "ldap_get_option failed!");
return(EXIT_FAILURE);
} else {
if (strcmp(package, "OpenSSL") == 0) {
// Set your callback
}
ldap_memfree(package);
}