50

In our PHP application, we need the PHP curl-extension built in a way, that it supports the following features:

  • WinSSL (Access to the Windows certificate store)
  • NTLM, Basic and Digest authentication
  • HTTP/2 support
  • SSH2 support
  • IPv6 support

I've tried to build curl in a way to achieve this:

  • Link it against WinSSL
  • Link it against nghttp2
  • Link it against libssh2
  • Enable IPv6

I did so with the command line:

nmake /f Makefile.vc mode=dll VC=15 ENABLE_WINSSL=yes DEBUG=no MACHINE=x64 ENABLE_SSPI=no WITH_NGHTTP2=dll WITH_ZLIB=static WITH_SSH2=static WITH_DEVEL=C:\curl\deps-x64

In curls winbuild/ subfolder. Then I compiled the PHP curl extension against the result.

With the result, I have the following incorrect behavior when doing an HTTP request against a web service which offers Basic, Digest, NTLM and Negotiate authentication (an Exchange webservice):

  • If curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM); is used, everything works fine.

  • If curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); is used, everything works fine, too.

  • If curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM | CURLAUTH_BASIC); is used, authentication fails.

The failing request contains an NTLM token which is way too short (seems to be cut off at some point). Some googling indicated that this may be due to curl being compiled to use SSPI. However, I cannot disable SSPI, because WinSSL requires it.

Does anyone know a solution to this? How to get a php-curl extension that fulfills all the above requirements?

Nmk
  • 1,281
  • 2
  • 14
  • 25
Jost
  • 5,948
  • 8
  • 42
  • 72
  • 2
    either an auth negotiation issue or a libcurl bug? can you post a CURLOPT_VERBOSE log for `CURLAUTH_NTLM | CURLAUTH_BASIC` ? – hanshenrik Jan 11 '19 at 15:18
  • Why don't you create two compiled version one for NTLM and another for WinSSL ? – Mason.Chase Jan 12 '19 at 23:40
  • 2
    TFM says you need SSPI to do NTLM on Windows so I don't think that would be it. Following on @Mason.Chase 's comment, i'd solve it by having wrapper functions and do a doNTLM() || doBasicHTTP() vs. passing the two options to curl. – ivanivan Jan 13 '19 at 17:13
  • 1
    @ivanivan The failing call is done by an external library, so we cannot really split NTLM and Basic authentication in our code. As a last resort, we could fork the library and change the code, but that would add additional maintainance overhead to our software. I'd rather have a solution that makes that exact call work :( – Jost Jan 14 '19 at 08:14
  • 1
    I'm not sure if CURL can do both NTLM and Basic in the same connection so ignoring any SSL, NTLM requires encryption & Base64 encoding before sending it to the server and the server will give Base64 encoded data when you try to connect, this might be your problem try opening a connection first and reading the headers to see what method it needs first basic or NTLM. http://davenport.sourceforge.net/ntlm.html#ntlmHttpAuthentication – Barkermn01 Jan 14 '19 at 16:05
  • 1
    @MartinBarker: The Server sends multiple, different WWW-Authenticate headers, and curl should choose the appropriate one and do that authentication. The ones curl may choose the appropriate one from are determined by the option from the question, so in the failing case curl should choose either NTLM or Basic auth, and chooses NTLM. But that authentication fails, because the Authorization header generated by curl is broken – Jost Jan 15 '19 at 21:53
  • 1
    Ok we need to see your code because generally usage of CURL does not get any information from the server before you construct your request. – Barkermn01 Jan 17 '19 at 13:42
  • It dies - first curl sends the request unauthenticated, gehts a `401 Unauthorized` response with WWW-Authenticate headers, chooses an authentication mechanism depending on the headers from that response and from the configured allowed authentication methods, and then sends the request again, authenticated using the chosen method (NTLM in this case) – Jost Jan 18 '19 at 18:22
  • 1
    Have you tried `CURLAUTH_ANY`? Would be interesting to see the response headers from the server as well. – miken32 Mar 14 '19 at 20:36
  • 1
    My 2 cents: Do you set `curl_setopt($ch, CURLOPT_USERPWD, "USER:PWD");`? Would be interesting, if the first auth-negotiation request is then no longer `401`, but authed. Also, if there is forwarding/follow-location involved, then `curl_setopt($ch, CURLOPT_UNRESTRICTED_AUTH, true);` comes into play. All in all, hard to say, without looking at the code or request logs. More info needed. – Jens A. Koch Mar 06 '20 at 12:32

1 Answers1

1

I believe the LibCurl here satisfies all that:

https://curl.haxx.se/windows

Using this file:

#include <curl/curl.h>
#include <stdio.h>
int main() {
   curl_version_info_data *o = curl_version_info(CURLVERSION_NOW); 
   printf("SSL: %s\n", o->ssl_version);
   printf("NTLM: %d\n", o->features & CURL_VERSION_NTLM);
   printf("HTTP/2: %d\n", o->features & CURL_VERSION_HTTP2);
   printf("SSH2: %s\n", o->libssh_version);
   printf("IPv6: %d\n", o->features & CURL_VERSION_IPV6);
}

Build:

cc https.c `
'-Icurl-7.70.0-win64-mingw\include' `
'-Lcurl-7.70.0-win64-mingw\lib' `
-lcrypt32 `
-lcurl `
-lwldap32 `
-lws2_32

Result:

SSL: OpenSSL/1.1.1g (Schannel)
NTLM: 16
HTTP/2: 65536
SSH2: libssh2/1.9.0
IPv6: 1

I did not build with Visual Studio, but Clang:

https://github.com/mstorsjo/llvm-mingw

Also I am not sure what exactly OpenSSL/1.1.1g (Schannel), I guess it means OpenSSL and WinSSL both? If that's not the case, you can build LibCurl yourself with CFG=-winssl:

https://github.com/nu8/gulf/blob/master/c-https/1-curl.ps1

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Zombo
  • 1
  • 62
  • 391
  • 407