Hi, I am working on adding NTLM single-sign-on support by using Samba's 'winbind' helper ntlm_auth.
When using Samba's 'winbind' daemon, NTLM single-sign-on is supported by delegating the NTLM challenge/response protocol to a helper in /usr/bin/ntlm_auth. And there's a really simple test hack at http://david.woodhou.se/ntlm_auth_v2.c. This is work-in-progress patch. My question is how do I make it fall back to asking a password (if it needs one) when ntlm_auth failed unexpectedly? And I am also appreciate feedbacks on finishing it. Thanks in advance. diff --git a/configure.ac b/configure.ac index c37ab3d..9e5e6da 100644 --- a/configure.ac +++ b/configure.ac @@ -2687,6 +2687,26 @@ then USE_MANUAL="no"; fi +dnl ********************************************************** +dnl path of NTLM single-sign-on helper ntlm_auth +dnl +if test "$ac_cv_native_windows" != "yes"; then + AC_ARG_WITH(ntlm-auth, + AC_HELP_STRING([--with-ntlm-auth=PATH],[Where to look for ntlm_auth, path points to ntlm_auth installation (default: /usr/bin/ntlm_auth);]) + AC_HELP_STRING([--without-ntlm-auth], [disable ntlm single-sign-on by using ntlm_auth]), + ntlm_auth="$withval", + ntlm_auth="/usr/bin/ntlm_auth") + if test "$ntlm_auth" != "no"; then + AC_DEFINE(USE_NTLM_AUTH, 1, [Whether or not use Samba's 'winbind' daemon helper 'ntlm_auth' for NTLM single-sign-on]) + if test "$ntlm_auth" = "yes"; then + dnl --with-ntlm-auth (without path) used, use default path + ntlm_auth="/usr/bin/ntlm_auth" + fi + fi + AC_SUBST(ntlm_auth) + AC_DEFINE_UNQUOTED(NTLM_AUTH, "$ntlm_auth", [Samba's 'winbind' daemon helper 'ntlm_auth' which can be used for NTLM single-sign-on]) +fi + dnl ************************************************************************* dnl If the manual variable still is set, then we go with providing a built-in dnl manual diff --git a/lib/http.c b/lib/http.c index 3d9f2f2..1f7a970 100644 --- a/lib/http.c +++ b/lib/http.c @@ -450,14 +450,22 @@ CURLcode Curl_http_auth_act(struct connectdata *conn) if(data->state.authproblem) return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK; +#ifdef USE_NTLM_SSO + if(1 && +#else if(conn->bits.user_passwd && +#endif ((data->req.httpcode == 401) || (conn->bits.authneg && data->req.httpcode < 300))) { pickhost = pickoneauth(&data->state.authhost); if(!pickhost) data->state.authproblem = TRUE; } +#ifdef USE_NTLM_SSO + if(1 && +#else if(conn->bits.proxy_user_passwd && +#endif ((data->req.httpcode == 407) || (conn->bits.authneg && data->req.httpcode < 300))) { pickproxy = pickoneauth(&data->state.authproxy); @@ -630,9 +638,13 @@ Curl_http_output_auth(struct connectdata *conn, conn->bits.user_passwd) /* continue please */ ; else { +#ifdef USE_NTLM_SSO + /* NTLM single-sign-on, continue please */ ; +#else authhost->done = TRUE; authproxy->done = TRUE; return CURLE_OK; /* no authentication with no user or password */ +#endif } if(authhost->want && !authhost->picked) @@ -3349,7 +3361,6 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, /* We might have reached the end of the header part here, but there might be a non-header part left in the end of the read buffer. */ - return CURLE_OK; } diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c index c3ceadc..88c50ef 100644 --- a/lib/http_ntlm.c +++ b/lib/http_ntlm.c @@ -43,6 +43,15 @@ #include <unistd.h> #endif +#ifdef USE_NTLM_SSO +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <errno.h> +#endif + #if (defined(NETWARE) && !defined(__NOVELL_LIBC__)) #include <netdb.h> #endif @@ -321,6 +330,11 @@ CURLntlm Curl_input_ntlm(struct connectdata *conn, ntlm->flags = readint_le(&buffer[20]); memcpy(ntlm->nonce, &buffer[24], 8); +#ifdef USE_NTLM_SSO + if(conn->sso_data) + conn->sso_data->challenge_header = strdup(header); +#endif + DEBUG_OUT({ fprintf(stderr, "**** TYPE2 header flags=0x%08.8lx ", ntlm->flags); print_flags(stderr, ntlm->flags); @@ -674,6 +688,133 @@ static void unicodecpy(unsigned char *dest, } #endif +#ifdef USE_NTLM_SSO +static void sso_ntlm_close(struct ntlmhelper *sso_data) +{ + if(sso_data->fd_helper != -1) { + close (sso_data->fd_helper); + sso_data->fd_helper = -1; + } + + if(sso_data->pid) { + int ret, i; + for(i = 0; i < 4; i++) { + ret = waitpid (sso_data->pid, NULL, WNOHANG); + if(ret == sso_data->pid || errno == ECHILD) + break; + switch (i) { + case 0: + kill (sso_data->pid, SIGTERM); + break; + case 2: + kill (sso_data->pid, SIGKILL); + break; + case 1: + case 3: + sleep (1); + break; + } + } + sso_data->pid = 0; + } + + if(sso_data->challenge_header) + free(sso_data->challenge_header); +} + +static bool sso_ntlm_initiate(struct ntlmhelper *sso_data) +{ + int sockfds[2]; + pid_t pid; + char *username = NULL, *slash, *domain = NULL; + + if(sso_data->fd_helper != -1 || sso_data->pid) { + return TRUE; + } + + username = getenv("NTLMUSER"); + if(!username) + username = getenv("USER"); + if(!username) + goto done; + + slash = strpbrk(username, "\\/"); + if(slash) { + domain = strdup(username); + slash = domain + (slash - username); + *slash = '\0'; + username = slash + 1; + } + + if(access(NTLM_AUTH, X_OK) != 0) + goto done; + + if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) + goto done; + + pid = fork(); + if(!pid) { + /* child process */ + if(dup2 (sockfds[1], 0) == -1 || dup2 (sockfds[1], 1) == -1) + exit(1); + + execl(NTLM_AUTH, "--helper-protocol", "ntlmssp-client-1", + "--use-cached-creds", "--username", username, + domain?"--domain":NULL, domain, NULL); + exit(1); + }else if(pid == -1) { + close (sockfds[0]); + close (sockfds[1]); + goto done; + } + + close (sockfds[1]); + sso_data->fd_helper = sockfds[0]; + sso_data->pid = pid; + if(domain) + free (domain); + return TRUE; + + done: + if(domain) + free (domain); + return FALSE; +} + +static char *sso_ntlm_response (struct ntlmhelper *sso_data, + const char *input, curlntlm state) +{ + ssize_t size; + char buf[1024], *response = NULL; + + if(write (sso_data->fd_helper, input, strlen (input)) < 0 || + (size = read (sso_data->fd_helper, buf, 1024)) < 5 ) { + goto done; + } + /* Samba/winbind installed but not configured */ + if(state == NTLMSTATE_TYPE1 && + buf[0] == 'P' && buf[1] == 'W' && buf[size-1] == '\n') { + response = strdup ("PW"); + goto done; + } + /* invalid response */ + if(state == NTLMSTATE_TYPE1 && + (buf[0] != 'Y' || buf[1] != 'R' || buf[2] != ' ' || buf[size-1] != '\n')) + goto done; + if(state == NTLMSTATE_TYPE2 && + (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ' || buf[size-1]!='\n') && + (buf[0]!='A' || buf[1]!='F' || buf[2]!=' ' || buf[size-1]!='\n')) + goto done; + + response = strndup(buf + 3, size - 4); + *(response + size - 4) = '\0'; + response = aprintf("NTLM %s", response); + goto done; +done: + return response; +} +#endif /* USE_NTLM_AUTH */ + /* this is for creating ntlm header output */ CURLcode Curl_output_ntlm(struct connectdata *conn, bool proxy) @@ -832,6 +973,50 @@ CURLcode Curl_output_ntlm(struct connectdata *conn, size = buf.cbBuffer; } #else + +#ifdef USE_NTLM_SSO + /* Use Samba's 'winbind' daemon to support NTLM single-sign-on, + * by delegating the NTLM challenge/response protocal to a helper + * in ntlm_auth. + * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html + * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html + * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html + * The preprocessor variable 'USE_NTLM_AUTH' indicates whether + * this feature is enabled. Another one 'NTLM_AUTH' contains absolute + * path of it. + * If NTLM single-sign-on fails, go back to original request + * handling process. + */ + /* Fix me later */ + conn->sso_data = calloc(1, sizeof(struct ntlmhelper)); + conn->sso_data->fd_helper = -1; + conn->sso_data->pid = 0; + conn->sso_data->challenge_header = NULL; + + if(sso_ntlm_initiate(conn->sso_data)) { + char *header; + header = sso_ntlm_response(conn->sso_data, "YR\n", ntlm->state); + if(!header) + /* invalid response */ + goto ssofailure_type1; + if(!strcasecmp(header, "PW")) { + /* ntml_auth ask for password */ + free(header); + header = NULL; + goto ssofailure_type1; + } + Curl_safefree(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: %s\r\n", + proxy?"Proxy-":"", + header); + DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); + free(header); + break; + } +ssofailure_type1: + DEBUG_OUT(fprintf(stderr, "**** ntlm_auth failed on type1\n")); +#endif + hostoff = 0; domoff = hostoff + hostlen; /* This is 0: remember that host and domain are empty */ @@ -998,6 +1183,31 @@ CURLcode Curl_output_ntlm(struct connectdata *conn, size_t userlen; CURLcode res; +#ifdef USE_NTLM_SSO + char *input, *header; + input = aprintf ("TT %s\n", conn->sso_data->challenge_header); + header = sso_ntlm_response(conn->sso_data, + input, + ntlm->state); + sso_ntlm_close(conn->sso_data); + if(!header) { + free(input); + goto ssofailure_type2; + } + Curl_safefree(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: %s\r\n", + proxy?"Proxy-":"", + header); + DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); + ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */ + authp->done = TRUE; + free(input); + free(header); + break; +ssofailure_type2: + DEBUG_OUT(fprintf(stderr, "**** ntlm_auth failed on type2\n")); +#endif + user = strchr(userp, '\\'); if(!user) user = strchr(userp, '/'); diff --git a/lib/setup.h b/lib/setup.h index 1e96c76..c742188 100644 --- a/lib/setup.h +++ b/lib/setup.h @@ -567,6 +567,11 @@ int netware_init(void); #if defined(USE_SSLEAY) || defined(USE_WINDOWS_SSPI) || \ defined(USE_GNUTLS) || defined(USE_NSS) #define USE_NTLM +#if defined(USE_NTLM_AUTH) +/* Support NTLM single-sign-on by using Samba's winbind daemon helper + 'ntlm_auth' */ +#define USE_NTLM_SSO +#endif #endif #endif diff --git a/lib/urldata.h b/lib/urldata.h index d256968..5a08816 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -69,6 +69,10 @@ #include "cookie.h" #include "formdata.h" +#ifdef USE_NTLM_SSO +#include <sys/types.h> +#endif + #ifdef USE_SSLEAY #ifdef USE_OPENSSL #include <openssl/rsa.h> @@ -367,6 +371,16 @@ struct ntlmdata { #endif }; +/* Struct used for NTLM single-sign-on with Samba's winbind daemon helper + ntlm_auth */ +#ifdef USE_NTLM_SSO +struct ntlmhelper { + int fd_helper; + pid_t pid; + char* challenge_header; +}; +#endif + #ifdef USE_HTTP_NEGOTIATE struct negotiatedata { /* when doing Negotiate we first need to receive an auth token and then we @@ -905,6 +919,12 @@ struct connectdata { single requests! */ struct ntlmdata proxyntlm; /* NTLM data for proxy */ +#ifdef USE_NTLM_SSO + /* data used for communication with Samba's winbind daemon helper + ntlm_auth */ + struct ntlmhelper *sso_data; +#endif + char syserr_buf [256]; /* buffer for Curl_strerror() */ #ifdef CURLRES_ASYNCH ------------------------------------------------------------------- List admin: http://cool.haxx.se/list/listinfo/curl-library Etiquette: http://curl.haxx.se/mail/etiquette.html
