diff --git a/configure.ac b/configure.ac index e9b49c788..b5adde5ec 100644 --- a/configure.ac +++ b/configure.ac @@ -2686,6 +2686,34 @@ then USE_MANUAL="no"; fi +dnl ********************************************************** +dnl path of NTLM single-sign-on helper ntlm_auth +dnl +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", + [if test "$ac_cv_native_windows" = "yes"; then ntlm_auth="no"; else ntlm_auth="/usr/bin/ntlm_auth"; fi]) + +AC_MSG_CHECKING([if using ntlm_auth is requested]) + +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]) + AC_SUBST(USE_NTLM_AUTH, [1]) + if test "$ntlm_auth" = "yes"; then + dnl --with-ntlm-auth (without path) used, use default path + ntlm_auth="/usr/bin/ntlm_auth" + fi + AC_MSG_RESULT($ntlm_auth) +else + AC_MSG_RESULT(no) +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]) + + dnl ************************************************************************* dnl If the manual variable still is set, then we go with providing a built-in dnl manual @@ -2969,6 +2997,9 @@ if test "x$CURL_DISABLE_HTTP" != "x1"; then if test "x$USE_SSLEAY" = "x1" -o "x$USE_WINDOWS_SSPI" = "x1" \ -o "x$GNUTLS_ENABLED" = "x1" -o "x$NSS_ENABLED" = "x1"; then SUPPORT_FEATURES="$SUPPORT_FEATURES NTLM" + if test "x$USE_NTLM_AUTH" = "x1"; then + SUPPORT_FEATURES="$SUPPORT_FEATURES NTLM_SSO" + fi fi fi if test "x$USE_TLS_SRP" = "x1"; then diff --git a/include/curl/curl.h b/include/curl/curl.h index a9d42fad6..998c109b9 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -600,6 +600,7 @@ typedef enum { #define CURLAUTH_GSSNEGOTIATE (1<<2) /* GSS-Negotiate */ #define CURLAUTH_NTLM (1<<3) /* NTLM */ #define CURLAUTH_DIGEST_IE (1<<4) /* Digest with IE flavour */ +#define CURLAUTH_NTLM_SSO (1<<5) /* NTLM single-sign-on */ #define CURLAUTH_ONLY (1<<31) /* used together with a single other type to force no auth or just that single type */ @@ -2092,8 +2093,9 @@ typedef struct { #define CURL_VERSION_CONV (1<<12) /* character conversions supported */ #define CURL_VERSION_CURLDEBUG (1<<13) /* debug memory tracking supported */ #define CURL_VERSION_TLSAUTH_SRP (1<<14) /* TLS-SRP auth is supported */ - -/* +#define CURL_VERSION_NTLM_SSO (1<<15) /* NTLM single-sign-on is supported + by using ntlm_auth */ + /* * NAME curl_version_info() * * DESCRIPTION diff --git a/lib/http.c b/lib/http.c index 92f7c02a1..bb6af2cb4 100644 --- a/lib/http.c +++ b/lib/http.c @@ -307,6 +307,8 @@ static bool pickoneauth(struct auth *pick) pick->picked = CURLAUTH_DIGEST; else if(avail & CURLAUTH_NTLM) pick->picked = CURLAUTH_NTLM; + else if(avail & CURLAUTH_NTLM_SSO) + pick->picked = CURLAUTH_NTLM_SSO; else if(avail & CURLAUTH_BASIC) pick->picked = CURLAUTH_BASIC; else { @@ -393,7 +395,9 @@ static CURLcode http_perhapsrewind(struct connectdata *conn) if((expectsend == -1) || (expectsend > bytessent)) { /* There is still data left to send */ if((data->state.authproxy.picked == CURLAUTH_NTLM) || - (data->state.authhost.picked == CURLAUTH_NTLM)) { + (data->state.authhost.picked == CURLAUTH_NTLM) || + (data->state.authproxy.picked == CURLAUTH_NTLM_SSO) || + (data->state.authhost.picked == CURLAUTH_NTLM_SSO)) { if(((expectsend - bytessent) < 2000) || (conn->ntlm.state != NTLMSTATE_NONE)) { /* The NTLM-negotiation has started *OR* there is just a little (<2K) @@ -554,6 +558,15 @@ output_auth_headers(struct connectdata *conn, } else #endif +#ifdef USE_NTLM_SSO + if(authstatus->picked == CURLAUTH_NTLM_SSO) { + auth="NTLM_SSO"; + result = Curl_output_ntlm_sso(conn, proxy); + if(result) + return result; + } + else +#endif #ifndef CURL_DISABLE_CRYPTO_AUTH if(authstatus->picked == CURLAUTH_DIGEST) { auth="Digest"; @@ -766,13 +779,35 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, if(checkprefix("NTLM", start)) { *availp |= CURLAUTH_NTLM; authp->avail |= CURLAUTH_NTLM; - if(authp->picked == CURLAUTH_NTLM) { + if(authp->picked == CURLAUTH_NTLM || + authp->picked == CURLAUTH_NTLM_SSO) { /* NTLM authentication is picked and activated */ CURLntlm ntlm = Curl_input_ntlm(conn, (bool)(httpcode == 407), start); - - if(CURLNTLM_BAD != ntlm) + if(CURLNTLM_BAD != ntlm) { data->state.authproblem = FALSE; +#ifdef USE_NTLM_SSO + if(authp->picked == CURLAUTH_NTLM_SSO) { + *availp &= ~CURLAUTH_NTLM; + authp->avail &= ~CURLAUTH_NTLM; + *availp |= CURLAUTH_NTLM_SSO; + authp->avail |= CURLAUTH_NTLM_SSO; + + /* Get the challenge-message which will be passed to + * ntlm_auth for generating the type 3 message later */ + while(*start && ISSPACE(*start)) + start++; + if(checkprefix("NTLM", start)) { + start += strlen("NTLM"); + while(*start && ISSPACE(*start)) + start++; + if(*start) + if((conn->challenge_header = strdup(start)) == NULL) + return CURLE_OUT_OF_MEMORY; + } + } +#endif + } else { infof(data, "Authentication problem. Ignoring this.\n"); data->state.authproblem = TRUE; diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c index c3ceadc35..85b4e937e 100644 --- a/lib/http_ntlm.c +++ b/lib/http_ntlm.c @@ -43,6 +43,15 @@ #include #endif +#ifdef USE_NTLM_SSO +#include +#include +#include +#include +#include +#include +#endif + #if (defined(NETWARE) && !defined(__NOVELL_LIBC__)) #include #endif @@ -674,6 +683,277 @@ static void unicodecpy(unsigned char *dest, } #endif +#ifdef USE_NTLM_SSO +static void sso_ntlm_close(struct connectdata *conn) +{ + if(conn->fd_helper != -1) { + close(conn->fd_helper); + conn->fd_helper = -1; + } + + if(conn->pid) { + int ret, i; + for(i = 0; i < 4; i++) { + ret = waitpid(conn->pid, NULL, WNOHANG); + if(ret == conn->pid || errno == ECHILD) + break; + switch(i) { + case 0: + kill(conn->pid, SIGTERM); + break; + case 2: + kill(conn->pid, SIGKILL); + break; + case 1: + case 3: + break; + } + } + conn->pid = 0; + } + + Curl_safefree(conn->challenge_header); + conn->challenge_header = NULL; + Curl_safefree(conn->response_header); + conn->response_header = NULL; +} + +static CURLcode sso_ntlm_initiate(struct connectdata *conn, + const char *userp) +{ + int sockfds[2]; + pid_t pid; + const char *username; + char *slash, *domain = NULL; + const char *ntlm_auth; + + /* Return if communication with ntlm_auth already set up */ + if(conn->fd_helper != -1 || conn->pid) { + return CURLE_OK; + } + + username = userp; + slash = strpbrk(username, "\\/"); + if(slash) { + if((domain = strdup(username)) == NULL) + return CURLE_OUT_OF_MEMORY; + slash = domain + (slash - username); + *slash = '\0'; + username = username + (slash - domain) + 1; + } + + /* When DEBUGBUILD is defined and environment variable NTLM_AUTH is set + * (in test case 2005), use a fake_ntlm to do NTLM challenge/response, + * which only accept commands and output strings pre-written/saved in + * test case 2005 */ +#ifdef DEBUGBUILD + ntlm_auth=getenv("NTLM_AUTH"); +#endif + if(!ntlm_auth) + ntlm_auth = NTLM_AUTH; + + 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]); + conn->fd_helper = sockfds[0]; + conn->pid = pid; + Curl_safefree(domain); + return CURLE_OK; + +done: + Curl_safefree(domain); + return CURLE_REMOTE_ACCESS_DENIED; +} + +static CURLcode sso_ntlm_response(struct connectdata *conn, + const char *input, curlntlm state) +{ + ssize_t size; + char buf[200]; /* enough, type 1, 3 message length is less then 200 */ + char *tmpbuf = buf; + size_t len_in = strlen(input), len_out = sizeof(buf); + + while(len_in > 0) { + int written = write(conn->fd_helper, input, len_in); + if(written == -1) { + /* Interrupted by a signal, retry it */ + if(errno == EINTR) + continue; + /* write failed if other errors happen */ + goto done; + } + input += written; + len_in -= written; + } + /* Read one line */ + while(len_out > 0) { + size = read(conn->fd_helper, tmpbuf, len_out); + if(size == -1) { + if(errno == EINTR) + continue; + goto done; + } + else if(size == 0) + goto done; + else if(tmpbuf[size - 1] == '\n') { + tmpbuf[size - 1] = '\0'; + goto wrfinish; + } + tmpbuf += size; + len_out -= size; + } + goto done; +wrfinish: + /* Samba/winbind installed but not configured */ + if(state == NTLMSTATE_TYPE1 && + size == 3 && + buf[0] == 'P' && buf[1] == 'W') + return CURLE_REMOTE_ACCESS_DENIED; + /* invalid response */ + if(size < 4) + goto done; + if(state == NTLMSTATE_TYPE1 && + (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' ')) + goto done; + if(state == NTLMSTATE_TYPE2 && + (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') && + (buf[0]!='A' || buf[1]!='F' || buf[2]!=' ')) + goto done; + + conn->response_header = aprintf("NTLM %.*s", size - 4, buf + 3); + return CURLE_OK; +done: + return CURLE_REMOTE_ACCESS_DENIED; +} + +/*this is for creating ntlm header output by delegating challenge/response + *to a Samba's daemon helper ntlm_auth */ +CURLcode Curl_output_ntlm_sso(struct connectdata *conn, + bool proxy) +{ + /* point to the address of the pointer that holds the string to sent to the + server, which is for a plain host or for a HTTP proxy */ + char **allocuserpwd; + /* point to the name and password for this */ + const char *userp; + /* point to the correct struct with this */ + struct ntlmdata *ntlm; + struct auth *authp; + + CURLcode res = CURLE_OK; + char *input; + + DEBUGASSERT(conn); + DEBUGASSERT(conn->data); + + if(proxy) { + allocuserpwd = &conn->allocptr.proxyuserpwd; + userp = conn->proxyuser; + ntlm = &conn->proxyntlm; + authp = &conn->data->state.authproxy; + } + else { + allocuserpwd = &conn->allocptr.userpwd; + userp = conn->user; + ntlm = &conn->ntlm; + authp = &conn->data->state.authhost; + } + authp->done = FALSE; + + /* not set means empty */ + if(!userp) + userp=""; + + switch(ntlm->state) { + case NTLMSTATE_TYPE1: + default: + /* 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. + */ + /* Clean data before using them */ + conn->fd_helper = -1; + conn->pid = 0; + conn->challenge_header = NULL; + conn->response_header = NULL; + /* Create communication with ntlm_auth */ + res = sso_ntlm_initiate(conn, userp); + if(res) + return res; + res = sso_ntlm_response(conn, "YR\n", ntlm->state); + if(res) + return res; + + Curl_safefree(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: %s\r\n", + proxy?"Proxy-":"", + conn->response_header); + DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); + Curl_safefree(conn->response_header); + conn->response_header = NULL; + break; + case NTLMSTATE_TYPE2: + input = aprintf("TT %s\n", conn->challenge_header); + res = sso_ntlm_response(conn, + input, + ntlm->state); + if(res) + return res; + + Curl_safefree(*allocuserpwd); + *allocuserpwd = aprintf("%sAuthorization: %s\r\n", + proxy?"Proxy-":"", + conn->response_header); + DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); + ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */ + authp->done = TRUE; + sso_ntlm_close(conn); + free(input); + break; + case NTLMSTATE_TYPE3: + /* connection is already authenticated, + * don't send a header in future requests */ + if(*allocuserpwd) { + free(*allocuserpwd); + *allocuserpwd=NULL; + } + authp->done = TRUE; + break; + } + + return CURLE_OK; +} +#endif /* USE_NTLM_SSO */ + /* this is for creating ntlm header output */ CURLcode Curl_output_ntlm(struct connectdata *conn, bool proxy) @@ -1320,6 +1600,9 @@ Curl_ntlm_cleanup(struct connectdata *conn) ntlm_sspi_cleanup(&conn->ntlm); ntlm_sspi_cleanup(&conn->proxyntlm); #else +#ifdef USE_NTLM_SSO + sso_ntlm_close(conn); +#endif (void)conn; #endif } diff --git a/lib/http_ntlm.h b/lib/http_ntlm.h index c7422d701..faa7b0f31 100644 --- a/lib/http_ntlm.h +++ b/lib/http_ntlm.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -38,6 +38,12 @@ CURLntlm Curl_input_ntlm(struct connectdata *conn, bool proxy, /* this is for creating ntlm header output */ CURLcode Curl_output_ntlm(struct connectdata *conn, bool proxy); +#ifdef USE_NTLM_SSO +/* this is for creating ntlm header output by delegating challenge/response + * to a Samba's daemon helper ntlm_auth */ +CURLcode Curl_output_ntlm_sso(struct connectdata *conn, bool proxy); +#endif + void Curl_ntlm_cleanup(struct connectdata *conn); #ifndef USE_NTLM #define Curl_ntlm_cleanup(x) diff --git a/lib/setup.h b/lib/setup.h index 35dfa5874..91a655713 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/url.c b/lib/url.c index c5b642fde..adb96c4cc 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1406,6 +1406,10 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, #ifndef USE_NTLM auth &= ~CURLAUTH_NTLM; /* no NTLM without SSL */ #endif +#ifndef USE_NTLM_SSO + auth &= ~CURLAUTH_NTLM_SSO; /* no NTLM single-sign-on without SSL + and ntlm_auth */ +#endif #ifndef USE_HTTP_NEGOTIATE auth &= ~CURLAUTH_GSSNEGOTIATE; /* no GSS-Negotiate without GSSAPI or WINDOWS_SSPI */ @@ -1467,6 +1471,10 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, #ifndef USE_NTLM auth &= ~CURLAUTH_NTLM; /* no NTLM without SSL */ #endif +#ifndef USE_NTLM_SSO + auth &= ~CURLAUTH_NTLM_SSO; /* no NTLM single-sign-on without SSL + and ntlm_auth */ +#endif #ifndef USE_HTTP_NEGOTIATE auth &= ~CURLAUTH_GSSNEGOTIATE; /* no GSS-Negotiate without GSSAPI or WINDOWS_SSPI */ @@ -3002,7 +3010,8 @@ ConnectionExists(struct SessionHandle *data, } if((needle->handler->protocol & CURLPROTO_FTP) || ((needle->handler->protocol & CURLPROTO_HTTP) && - (data->state.authhost.want==CURLAUTH_NTLM))) { + ((data->state.authhost.want==CURLAUTH_NTLM) || + (data->state.authhost.want==CURLAUTH_NTLM_SSO)))) { /* This is FTP or HTTP+NTLM, verify that we're using the same name and password as well */ if(!strequal(needle->user, check->user) || diff --git a/lib/urldata.h b/lib/urldata.h index d256968db..d2638aa93 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -905,6 +905,15 @@ 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 */ + int fd_helper; + pid_t pid; + char* challenge_header; + char* response_header; +#endif + char syserr_buf [256]; /* buffer for Curl_strerror() */ #ifdef CURLRES_ASYNCH diff --git a/lib/version.c b/lib/version.c index c471dc106..6b9ff5070 100644 --- a/lib/version.c +++ b/lib/version.c @@ -243,6 +243,9 @@ static curl_version_info_data version_info = { #ifdef USE_NTLM | CURL_VERSION_NTLM #endif +#ifdef USE_NTLM_SSO + | CURL_VERSION_NTLM_SSO +#endif #ifdef USE_WINDOWS_SSPI | CURL_VERSION_SSPI #endif diff --git a/src/main.c b/src/main.c index 154694461..a68963df0 100644 --- a/src/main.c +++ b/src/main.c @@ -1869,6 +1869,7 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ {"*k", "digest", FALSE}, {"*l", "negotiate", FALSE}, {"*m", "ntlm", FALSE}, + {"*M", "ntlm-sso", FALSE}, {"*n", "basic", FALSE}, {"*o", "anyauth", FALSE}, #ifdef USE_WATT32 @@ -2223,6 +2224,17 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ config->authtype &= ~CURLAUTH_NTLM; break; + case 'M': /* --ntlm-sso */ + if(toggle) { + if(curlinfo->features & CURL_VERSION_NTLM_SSO) + config->authtype |= CURLAUTH_NTLM_SSO; + else + return PARAM_LIBCURL_DOESNT_SUPPORT; + } + else + config->authtype &= ~CURLAUTH_NTLM_SSO; + break; + case 'n': /* --basic for completeness */ if(toggle) config->authtype |= CURLAUTH_BASIC; @@ -3203,6 +3215,7 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ {"IPv6", CURL_VERSION_IPV6}, {"Largefile", CURL_VERSION_LARGEFILE}, {"NTLM", CURL_VERSION_NTLM}, + {"NTLM_SSO", CURL_VERSION_NTLM_SSO}, {"SPNEGO", CURL_VERSION_SPNEGO}, {"SSL", CURL_VERSION_SSL}, {"SSPI", CURL_VERSION_SSPI},