From de4610a55f9e99bebaf448ca33e2e43baf977c74 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 28 Jan 2009 21:33:58 +0000 Subject: [PATCH] - Markus Moeller introduced two new options to libcurl: CURLOPT_SOCKS5_GSSAPI_SERVICE and CURLOPT_SOCKS5_GSSAPI_NEC to allow libcurl to do GSS-style authentication with SOCKS5 proxies. The curl tool got the options called --socks5-gssapi-service and --socks5-gssapi-nec to enable these. --- CHANGES | 7 + RELEASE-NOTES | 9 +- TODO-RELEASE | 2 - docs/curl.1 | 17 +- docs/libcurl/curl_easy_setopt.3 | 10 + include/curl/curl.h | 6 + lib/Makefile.inc | 3 +- lib/Makefile.vc6 | 3 +- lib/socks.c | 78 +++- lib/socks.h | 24 +- lib/socks_gssapi.c | 547 +++++++++++++++++++++++++++ lib/socks_sspi.c | 649 ++++++++++++++++++++++++++++++++ lib/url.c | 31 ++ lib/url.h | 1 + lib/urldata.h | 9 + src/Makefile.vc6 | 4 +- src/main.c | 34 ++ 17 files changed, 1403 insertions(+), 31 deletions(-) create mode 100644 lib/socks_gssapi.c create mode 100644 lib/socks_sspi.c diff --git a/CHANGES b/CHANGES index 2c14092da..01ed882a1 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,13 @@ Changelog +Daniel Stenberg (28 Jan 2009) +- Markus Moeller introduced two new options to libcurl: + CURLOPT_SOCKS5_GSSAPI_SERVICE and CURLOPT_SOCKS5_GSSAPI_NEC to allow libcurl + to do GSS-style authentication with SOCKS5 proxies. The curl tool got the + options called --socks5-gssapi-service and --socks5-gssapi-nec to enable + these. + Daniel Stenberg (26 Jan 2009) - Chad Monroe provided the new CURLOPT_TFTP_BLKSIZE option that allows an app to set desired block size to use for TFTP transfers instead of the default diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 8eab2378c..b73fa3fca 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -1,8 +1,8 @@ Curl and libcurl 7.19.4 Public curl releases: 110 - Command line options: 129 - curl_easy_setopt() options: 159 + Command line options: 131 + curl_easy_setopt() options: 161 Public functions in libcurl: 58 Known libcurl bindings: 37 Contributors: 700 @@ -13,6 +13,9 @@ This release includes the following changes: o the OpenSSL-specific code disables TICKET (rfc5077) which is enabled by default in openssl 0.9.8j o Added CURLOPT_TFTP_BLKSIZE + o Added CURLOPT_SOCKS5_GSSAPI_SERVICE and CURLOPT_SOCKS5_GSSAPI_NEC - with + the corresponding curl options --socks5-gssapi-service and + --socks5-gssapi-nec This release includes the following bugfixes: @@ -28,6 +31,6 @@ This release would not have looked like this without help, code, reports and advice from friends like these: Lisa Xu, Daniel Fandrich, Craig A West, Alexey Borzov, Sharad Gupta, - Peter Sylvester, Chad Monroe + Peter Sylvester, Chad Monroe, Markus Moeller Thanks! (and sorry if I forgot to mention someone) diff --git a/TODO-RELEASE b/TODO-RELEASE index decf186ff..41dc98eac 100644 --- a/TODO-RELEASE +++ b/TODO-RELEASE @@ -3,8 +3,6 @@ To be addressed in 7.19.4 (planned release: March 2009) 206 - A. Craig West's CURLOPT_HTTP_VERSION change for CONNECT -208 - Patch to allow GSSAPI authentication to a socks5 server - 214 - progress bar prefix, second try (for the curl tool) 215 - Patch for Metalink Support (for the curl tool) diff --git a/docs/curl.1 b/docs/curl.1 index 5b7c111f5..c7bc972b6 100644 --- a/docs/curl.1 +++ b/docs/curl.1 @@ -821,7 +821,7 @@ The only wildcard is a single * character, which matches all hosts, and effectively disables the proxy. Each name in this list is matched as either a domain which contains the hostname, or the hostname itself. For example, local.com would match local.com, local.com:80, and www.local.com, but not -www.notlocal.com. +www.notlocal.com. (Added in 7.19.4). .IP "--ntlm" (HTTP) Enables NTLM authentication. The NTLM authentication method was designed by Microsoft and is used by IIS web servers. It is a proprietary @@ -1114,6 +1114,21 @@ mutually exclusive. If this option is used several times, the last one will be used. (This option was previously wrongly documented and used as --socks without the number appended.) +.IP "--socks5-gssapi-service " +The default service name for a socks server is rcmd/server-fqdn. This option +allows you to change it. + +Examples: + --socks5 proxy-name \fI--socks5-gssapi-service\fP sockd would use +sockd/proxy-name + --socks5 proxy-name \fI--socks5-gssapi-service\fP sockd/real-name would use +sockd/real-name for cases where the proxy-name does not match the princpal name. + (Added in 7.19.4). +.IP "--socks5-gssapi-nec" +As part of the gssapi negotiation a protection mode is negotiated. The rfc1961 +says in section 4.3/4.4 it should be protected, but the NEC reference +implementation does not. The option \fI--socks5-gssapi-nec\fP allows the +unprotected exchange of the protection mode negotiation. (Added in 7.19.4). .IP "--stderr " Redirect all writes to stderr to the specified file instead. If the file name is a plain '-', it is instead written to stdout. This option has no point when diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index e0a6bc687..c6e6013b5 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -487,6 +487,16 @@ Set the parameter to 1 to make the library tunnel all operations through a given HTTP proxy. There is a big difference between using a proxy and to tunnel through it. If you don't know what this means, you probably don't want this tunneling option. +.IP CURLOPT_SOCKS5_GSSAPI_SERVICE +Pass a char * as parameter to a string holding the name of the service. The +default service name for a SOCKS5 server is rcmd/server-fqdn. This option +allows you to change it. (Added in 7.19.4) +.IP CURLOPT_SOCKS5_GSSAPI_NEC +Pass a long set to 1 to enable or 0 to disable. As part of the gssapi +negotiation a protection mode is negotiated. The rfc1961 says in section +4.3/4.4 it should be protected, but the NEC reference implementation does not. +If enabled, this option allows the unprotected exchange of the protection mode +negotiation. (Added in 7.19.4). .IP CURLOPT_INTERFACE Pass a char * as parameter. This sets the interface name to use as outgoing network interface. The name can be an interface name, an IP address, or a host diff --git a/include/curl/curl.h b/include/curl/curl.h index 0b9ec4aef..005a613f0 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -1162,6 +1162,12 @@ typedef enum { /* block size for TFTP transfers */ CINIT(TFTP_BLKSIZE, LONG, 178), + /* Socks Service */ + CINIT(SOCKS5_GSSAPI_SERVICE, LONG, 179), + + /* Socks Service */ + CINIT(SOCKS5_GSSAPI_NEC, LONG, 180), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/lib/Makefile.inc b/lib/Makefile.inc index e3489904b..85fc24e99 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -9,7 +9,8 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ http_negotiate.c http_ntlm.c inet_pton.c strtoofft.c strerror.c \ hostares.c hostasyn.c hostip4.c hostip6.c hostsyn.c hostthre.c \ inet_ntop.c parsedate.c select.c gtls.c sslgen.c tftp.c splay.c \ - strdup.c socks.c ssh.c nss.c qssl.c rawstr.c curl_addrinfo.c + strdup.c socks.c ssh.c nss.c qssl.c rawstr.c curl_addrinfo.c \ + socks_gssapi.c socks_sspi.c HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h \ progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \ diff --git a/lib/Makefile.vc6 b/lib/Makefile.vc6 index 9fd540b5a..d9d627711 100644 --- a/lib/Makefile.vc6 +++ b/lib/Makefile.vc6 @@ -101,7 +101,7 @@ LFLAGS = /nologo /machine:$(MACHINE) SSLLIBS = libeay32.lib ssleay32.lib ZLIBLIBSDLL= zdll.lib ZLIBLIBS = zlib.lib -WINLIBS = wsock32.lib wldap32.lib +WINLIBS = wsock32.lib wldap32.lib secur32.lib CFLAGS = $(CFLAGS) CFGSET = FALSE @@ -489,6 +489,7 @@ X_OBJS= \ $(DIROBJ)\sendf.obj \ $(DIROBJ)\share.obj \ $(DIROBJ)\socks.obj \ + $(DIROBJ)\socks_sspi.obj \ $(DIROBJ)\speedcheck.obj \ $(DIROBJ)\splay.obj \ $(DIROBJ)\ssh.obj \ diff --git a/lib/socks.c b/lib/socks.c index 693573209..9fc5b7948 100644 --- a/lib/socks.c +++ b/lib/socks.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2008, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2009, 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 @@ -60,14 +60,14 @@ * This is STUPID BLOCKING behaviour which we frown upon, but right now this * is what we have... */ -static int blockread_all(struct connectdata *conn, /* connection data */ - curl_socket_t sockfd, /* read from this socket */ - char *buf, /* store read data here */ - ssize_t buffersize, /* max amount to read */ - ssize_t *n, /* amount bytes read */ - long conn_timeout) /* timeout for data wait - relative to - conn->created */ +int Curl_blockread_all(struct connectdata *conn, /* connection data */ + curl_socket_t sockfd, /* read from this socket */ + char *buf, /* store read data here */ + ssize_t buffersize, /* max amount to read */ + ssize_t *n, /* amount bytes read */ + long conn_timeout) /* timeout for data wait + relative to + conn->created */ { ssize_t nread; ssize_t allread = 0; @@ -264,7 +264,7 @@ CURLcode Curl_SOCKS4(const char *proxy_name, packetsize = 8; /* receive data size */ /* Receive response */ - result = blockread_all(conn, sock, (char *)socksreq, packetsize, + result = Curl_blockread_all(conn, sock, (char *)socksreq, packetsize, &actualread, timeout); if((result != CURLE_OK) || (actualread != packetsize)) { failf(data, "Failed to receive SOCKS4 connect request ack."); @@ -429,9 +429,16 @@ CURLcode Curl_SOCKS5(const char *proxy_name, } socksreq[0] = 5; /* version */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + socksreq[1] = (char)(proxy_name ? 3 : 2); /* number of methods (below) */ + socksreq[2] = 0; /* no authentication */ + socksreq[3] = 1; /* gssapi */ + socksreq[4] = 2; /* username/password */ +#else socksreq[1] = (char)(proxy_name ? 2 : 1); /* number of methods (below) */ socksreq[2] = 0; /* no authentication */ socksreq[3] = 2; /* username/password */ +#endif Curl_nonblock(sock, FALSE); @@ -462,7 +469,8 @@ CURLcode Curl_SOCKS5(const char *proxy_name, Curl_nonblock(sock, FALSE); - result=blockread_all(conn, sock, (char *)socksreq, 2, &actualread, timeout); + result=Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread, + timeout); if((result != CURLE_OK) || (actualread != 2)) { failf(data, "Unable to receive initial SOCKS5 response."); return CURLE_COULDNT_CONNECT; @@ -476,6 +484,15 @@ CURLcode Curl_SOCKS5(const char *proxy_name, /* Nothing to do, no authentication needed */ ; } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + else if(socksreq[1] == 1) { + code = Curl_SOCKS5_gssapi_negotiate(sockindex, conn); + if(code != CURLE_OK) { + failf(data, "Unable to negotiate SOCKS5 gssapi context."); + return CURLE_COULDNT_CONNECT; + } + } +#endif else if(socksreq[1] == 2) { /* Needs user name and password */ size_t userlen, pwlen; @@ -511,7 +528,7 @@ CURLcode Curl_SOCKS5(const char *proxy_name, return CURLE_COULDNT_CONNECT; } - result=blockread_all(conn, sock, (char *)socksreq, 2, &actualread, + result=Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread, timeout); if((result != CURLE_OK) || (actualread != 2)) { failf(data, "Unable to receive SOCKS5 sub-negotiation response."); @@ -529,12 +546,16 @@ CURLcode Curl_SOCKS5(const char *proxy_name, } else { /* error */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(socksreq[1] == 255) { +#else if(socksreq[1] == 1) { failf(data, "SOCKS5 GSSAPI per-message authentication is not supported."); return CURLE_COULDNT_CONNECT; } else if(socksreq[1] == 255) { +#endif if(!proxy_name || !*proxy_name) { failf(data, "No authentication method was acceptable. (It is quite likely" @@ -616,6 +637,11 @@ CURLcode Curl_SOCKS5(const char *proxy_name, *((unsigned short*)&socksreq[8]) = htons((unsigned short)remote_port); } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(conn->socks5_gssapi_enctype) { + failf(data, "SOCKS5 gssapi protection not yet implemented."); + } else +#endif code = Curl_write_plain(conn, sock, (char *)socksreq, packetsize, &written); if((code != CURLE_OK) || (written != packetsize)) { failf(data, "Failed to send SOCKS5 connect request."); @@ -624,7 +650,12 @@ CURLcode Curl_SOCKS5(const char *proxy_name, packetsize = 10; /* minimum packet size is 10 */ - result = blockread_all(conn, sock, (char *)socksreq, packetsize, +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(conn->socks5_gssapi_enctype) { + failf(data, "SOCKS5 gssapi protection not yet implemented."); + } else +#endif + result = Curl_blockread_all(conn, sock, (char *)socksreq, packetsize, &actualread, timeout); if((result != CURLE_OK) || (actualread != packetsize)) { failf(data, "Failed to receive SOCKS5 connect request ack."); @@ -674,15 +705,22 @@ CURLcode Curl_SOCKS5(const char *proxy_name, } /* At this point we already read first 10 bytes */ - if(packetsize > 10) { - packetsize -= 10; - result = blockread_all(conn, sock, (char *)&socksreq[10], packetsize, - &actualread, timeout); - if((result != CURLE_OK) || (actualread != packetsize)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLE_COULDNT_CONNECT; +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(!conn->socks5_gssapi_enctype) { + /* decrypt_gssapi_blockread already read the whole packet */ +#endif + if(packetsize > 10) { + packetsize -= 10; + result = Curl_blockread_all(conn, sock, (char *)&socksreq[10], + packetsize, &actualread, timeout); + if((result != CURLE_OK) || (actualread != packetsize)) { + failf(data, "Failed to receive SOCKS5 connect request ack."); + return CURLE_COULDNT_CONNECT; + } } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) } +#endif Curl_nonblock(sock, TRUE); return CURLE_OK; /* Proxy was successful! */ diff --git a/lib/socks.h b/lib/socks.h index 756ecc3db..9f4c6f09a 100644 --- a/lib/socks.h +++ b/lib/socks.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2008, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2009, 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 @@ -23,6 +23,20 @@ * $Id$ ***************************************************************************/ +/* + * Helper read-from-socket functions. Does the same as Curl_read() but it + * blocks until all bytes amount of buffersize will be read. No more, no less. + * + * This is STUPID BLOCKING behaviour which we frown upon, but right now this + * is what we have... + */ +int Curl_blockread_all(struct connectdata *conn, + curl_socket_t sockfd, + char *buf, + ssize_t buffersize, + ssize_t *n, + long conn_timeout); + /* * This function logs in to a SOCKS4(a) proxy and sends the specifics to the * final destination server. @@ -45,4 +59,12 @@ CURLcode Curl_SOCKS5(const char *proxy_name, int sockindex, struct connectdata *conn); +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) +/* + * This function handles the sockss5 gssapie negotiation and initialisation + */ +CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, + struct connectdata *conn); #endif + +#endif /* __SOCKS_H */ diff --git a/lib/socks_gssapi.c b/lib/socks_gssapi.c new file mode 100644 index 000000000..cd9e1a4d3 --- /dev/null +++ b/lib/socks_gssapi.c @@ -0,0 +1,547 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2009, Markus Moeller, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + +#include "setup.h" + +#ifdef HAVE_GSSAPI +#ifdef HAVE_OLD_GSSMIT +#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +#endif +#ifndef gss_nt_service_name +#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE +#endif +#include + +#ifdef NEED_MALLOC_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif + +#include "urldata.h" +#include "sendf.h" +#include "connect.h" +#include "timeval.h" +#include "socks.h" + +static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Helper gssapi error functions. + */ +static int check_gss_err(struct SessionHandle *data, + OM_uint32 major_status, + OM_uint32 minor_status, + const char* function) +{ + if(GSS_ERROR(major_status)) { + OM_uint32 maj_stat,min_stat; + OM_uint32 msg_ctx = 0; + gss_buffer_desc status_string; + char buf[1024]; + size_t len; + + len = 0; + msg_ctx = 0; + while(!msg_ctx) { + /* convert major status code (GSS-API error) to text */ + maj_stat = gss_display_status(&min_stat, major_status, + GSS_C_GSS_CODE, + GSS_C_NULL_OID, + &msg_ctx, &status_string); + if(maj_stat == GSS_S_COMPLETE) { + if(sizeof(buf) > len + status_string.length + 1) { + strcpy(buf+len, (char*) status_string.value); + len += status_string.length; + } + gss_release_buffer(&min_stat, &status_string); + break; + } + gss_release_buffer(&min_stat, &status_string); + } + if(sizeof(buf) > len + 3) { + strcpy(buf+len, ".\n"); + len += 2; + } + msg_ctx = 0; + while(!msg_ctx) { + /* convert minor status code (underlying routine error) to text */ + maj_stat = gss_display_status(&min_stat, minor_status, + GSS_C_MECH_CODE, + GSS_C_NULL_OID, + &msg_ctx, &status_string); + if(maj_stat == GSS_S_COMPLETE) { + if(sizeof(buf) > len + status_string.length) { + strcpy(buf+len, (char*) status_string.value); + len += status_string.length; + } + gss_release_buffer(&min_stat, &status_string); + break; + } + gss_release_buffer(&min_stat, &status_string); + } + failf(data, "GSSAPI error: %s failed:\n%s\n", function, buf); + return(1); + } + + return(0); +} + +CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, + struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + curl_socket_t sock = conn->sock[sockindex]; + CURLcode code; + ssize_t actualread; + ssize_t written; + int result; + long timeout; + OM_uint32 gss_major_status, gss_minor_status, gss_status; + OM_uint32 gss_ret_flags; + int gss_conf_state, gss_enc; + gss_buffer_desc service = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_send_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_recv_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc gss_w_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc* gss_token = GSS_C_NO_BUFFER; + gss_name_t server = GSS_C_NO_NAME; + gss_name_t gss_client_name = GSS_C_NO_NAME; + u_short us_length; + char *user=NULL; + unsigned char socksreq[4]; /* room for gssapi exchange header only */ + char *serviceptr = data->set.str[STRING_SOCKS5_GSSAPI_SERVICE]; + + /* get timeout */ + timeout = Curl_timeleft(conn, NULL, TRUE); + + /* GSSAPI request looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ + + /* prepare service name */ + if (strchr(serviceptr,'/')) { + service.value = malloc(strlen(serviceptr)); + if(!service.value) + return CURLE_OUT_OF_MEMORY; + service.length = strlen(serviceptr); + memcpy(service.value, serviceptr, service.length); + + gss_major_status = gss_import_name(&gss_minor_status, &service, + (gss_OID) GSS_C_NULL_OID, &server); + } + else { + service.value = malloc(strlen(serviceptr) +strlen(conn->proxy.name)+2); + if(!service.value) + return CURLE_OUT_OF_MEMORY; + service.length = strlen(serviceptr) +strlen(conn->proxy.name)+1; + snprintf(service.value, service.length+1, "%s@%s", + serviceptr, conn->proxy.name); + + gss_major_status = gss_import_name(&gss_minor_status, &service, + gss_nt_service_name, &server); + } + + gss_release_buffer(&gss_status, &service); /* clear allocated memory */ + + if(check_gss_err(data,gss_major_status, + gss_minor_status,"gss_import_name()")) { + failf(data, "Failed to create service name."); + gss_release_name(&gss_status, &server); + return CURLE_COULDNT_CONNECT; + } + + /* As long as we need to keep sending some context info, and there's no */ + /* errors, keep sending it... */ + for(;;) { + gss_major_status = gss_init_sec_context(&gss_minor_status, + GSS_C_NO_CREDENTIAL, + &gss_context, server, + GSS_C_NULL_OID, + GSS_C_MUTUAL_FLAG | + GSS_C_REPLAY_FLAG, + 0, + NULL, + gss_token, + NULL, + &gss_send_token, + &gss_ret_flags, + NULL); + + if(gss_token != GSS_C_NO_BUFFER) + gss_release_buffer(&gss_status, &gss_recv_token); + if(check_gss_err(data,gss_major_status, + gss_minor_status,"gss_init_sec_context")) { + gss_release_name(&gss_status, &server); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_release_buffer(&gss_status, &gss_send_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + failf(data, "Failed to initial GSSAPI token."); + return CURLE_COULDNT_CONNECT; + } + + if(gss_send_token.length != 0) { + socksreq[0] = 1; /* gssapi subnegotiation version */ + socksreq[1] = 1; /* authentication message type */ + us_length = htons((short)gss_send_token.length); + memcpy(socksreq+2,&us_length,sizeof(short)); + + code = Curl_write_plain(conn, sock, (char *)socksreq, 4, &written); + if((code != CURLE_OK) || (4 != written)) { + failf(data, "Failed to send GSSAPI authentication request."); + gss_release_name(&gss_status, &server); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_release_buffer(&gss_status, &gss_send_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + code = Curl_write_plain(conn, sock, (char *)gss_send_token.value, + gss_send_token.length, &written); + + if((code != CURLE_OK) || ((ssize_t)gss_send_token.length != written)) { + failf(data, "Failed to send GSSAPI authentication token."); + gss_release_name(&gss_status, &server); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_release_buffer(&gss_status, &gss_send_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + } + + gss_release_buffer(&gss_status, &gss_send_token); + gss_release_buffer(&gss_status, &gss_recv_token); + if(gss_major_status != GSS_S_CONTINUE_NEEDED) break; + + /* analyse response */ + + /* GSSAPI response looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ + + result=Curl_blockread_all(conn, sock, (char *)socksreq, 4, + &actualread, timeout); + if(result != CURLE_OK || actualread != 4) { + failf(data, "Failed to receive GSSAPI authentication response."); + gss_release_name(&gss_status, &server); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] == 255) { /* status / message type */ + failf(data, "User was rejected by the SOCKS5 server (%d %d).", + socksreq[0], socksreq[1]); + gss_release_name(&gss_status, &server); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[1] != 1) { /* status / messgae type */ + failf(data, "Invalid GSSAPI authentication response type (%d %d).", + socksreq[0], socksreq[1]); + gss_release_name(&gss_status, &server); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + memcpy(&us_length, socksreq+2, sizeof(short)); + us_length = ntohs(us_length); + + gss_recv_token.length=us_length; + gss_recv_token.value=malloc(us_length); + if(!gss_recv_token.value) { + failf(data, + "Could not allocate memory for GSSAPI authentication " + "response token."); + gss_release_name(&gss_status, &server); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_OUT_OF_MEMORY; + } + + result=Curl_blockread_all(conn, sock, (char *)gss_recv_token.value, + gss_recv_token.length, + &actualread, timeout); + + if(result != CURLE_OK || actualread != us_length) { + failf(data, "Failed to receive GSSAPI authentication token."); + gss_release_name(&gss_status, &server); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + gss_token = &gss_recv_token; + } + + gss_release_name(&gss_status, &server); + + /* Everything is good so far, user was authenticated! */ + gss_major_status = gss_inquire_context (&gss_minor_status, gss_context, + &gss_client_name, NULL, NULL, NULL, + NULL, NULL, NULL); + if(check_gss_err(data,gss_major_status, + gss_minor_status,"gss_inquire_context")) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, &gss_client_name); + failf(data, "Failed to determine user name."); + return CURLE_COULDNT_CONNECT; + } + gss_major_status = gss_display_name(&gss_minor_status, gss_client_name, + &gss_send_token, NULL); + if(check_gss_err(data,gss_major_status, + gss_minor_status,"gss_display_name")) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, &gss_client_name); + gss_release_buffer(&gss_status, &gss_send_token); + failf(data, "Failed to determine user name."); + return CURLE_COULDNT_CONNECT; + } + user=malloc(gss_send_token.length+1); + if(!user) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + gss_release_name(&gss_status, &gss_client_name); + gss_release_buffer(&gss_status, &gss_send_token); + return CURLE_OUT_OF_MEMORY; + } + + memcpy(user, gss_send_token.value, gss_send_token.length); + user[gss_send_token.length] = '\0'; + gss_release_name(&gss_status, &gss_client_name); + gss_release_buffer(&gss_status, &gss_send_token); + infof(data, "SOCKS5 server authencticated user %s with gssapi.\n",user); + free(user); + user=NULL; + + /* Do encryption */ + socksreq[0] = 1; /* gssapi subnegotiation version */ + socksreq[1] = 2; /* encryption message type */ + + gss_enc = 0; /* no data protection */ + /* do confidentiality protection if supported */ + if(gss_ret_flags & GSS_C_CONF_FLAG) + gss_enc = 2; + /* else do integrity protection */ + else if(gss_ret_flags & GSS_C_INTEG_FLAG) + gss_enc = 1; + + infof(data, "SOCKS5 server supports gssapi %s data protection.\n", + (gss_enc==0)?"no":((gss_enc==1)?"integrity":"confidentiality")); + /* force for the moment to no data protection */ + gss_enc = 0; + /* + * Sending the encryption type in clear seems wrong. It should be + * protected with gss_seal()/gss_wrap(). See RFC1961 extract below + * The NEC reference implementations on which this is based is + * therefore at fault + * + * +------+------+------+.......................+ + * + ver | mtyp | len | token | + * +------+------+------+.......................+ + * + 0x01 | 0x02 | 0x02 | up to 2^16 - 1 octets | + * +------+------+------+.......................+ + * + * Where: + * + * - "ver" is the protocol version number, here 1 to represent the + * first version of the SOCKS/GSS-API protocol + * + * - "mtyp" is the message type, here 2 to represent a protection + * -level negotiation message + * + * - "len" is the length of the "token" field in octets + * + * - "token" is the GSS-API encapsulated protection level + * + * The token is produced by encapsulating an octet containing the + * required protection level using gss_seal()/gss_wrap() with conf_req + * set to FALSE. The token is verified using gss_unseal()/ + * gss_unwrap(). + * + */ + if(data->set.socks5_gssapi_nec) { + us_length = htons((short)1); + memcpy(socksreq+2,&us_length,sizeof(short)); + } + else { + gss_send_token.length = 1; + gss_send_token.value = malloc(1); + if(!gss_send_token.value) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_OUT_OF_MEMORY; + } + memcpy(gss_send_token.value, &gss_enc, 1); + + gss_major_status = gss_wrap(&gss_minor_status, gss_context, 0, + GSS_C_QOP_DEFAULT, &gss_send_token, + &gss_conf_state, &gss_w_token); + + if(check_gss_err(data,gss_major_status,gss_minor_status,"gss_wrap")) { + gss_release_buffer(&gss_status, &gss_send_token); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + failf(data, "Failed to wrap GSSAPI encryption value into token."); + return CURLE_COULDNT_CONNECT; + } + gss_release_buffer(&gss_status, &gss_send_token); + + us_length = htons((short)gss_w_token.length); + memcpy(socksreq+2,&us_length,sizeof(short)); + } + + code = Curl_write_plain(conn, sock, (char *)socksreq, 4, &written); + if((code != CURLE_OK) || (4 != written)) { + failf(data, "Failed to send GSSAPI encryption request."); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + if(data->set.socks5_gssapi_nec) { + memcpy(socksreq, &gss_enc, 1); + code = Curl_write_plain(conn, sock, socksreq, 1, &written); + if((code != CURLE_OK) || ( 1 != written)) { + failf(data, "Failed to send GSSAPI encryption type."); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + } else { + code = Curl_write_plain(conn, sock, (char *)gss_w_token.value, + gss_w_token.length, &written); + if((code != CURLE_OK) || ((ssize_t)gss_w_token.length != written)) { + failf(data, "Failed to send GSSAPI encryption type."); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + gss_release_buffer(&gss_status, &gss_w_token); + } + + result=Curl_blockread_all(conn, sock, (char *)socksreq, 4, + &actualread, timeout); + if(result != CURLE_OK || actualread != 4) { + failf(data, "Failed to receive GSSAPI encryption response."); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] == 255) { /* status / message type */ + failf(data, "User was rejected by the SOCKS5 server (%d %d).", + socksreq[0], socksreq[1]); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[1] != 2) { /* status / messgae type */ + failf(data, "Invalid GSSAPI encryption response type (%d %d).", + socksreq[0], socksreq[1]); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + memcpy(&us_length, socksreq+2, sizeof(short)); + us_length = ntohs(us_length); + + gss_recv_token.length= us_length; + gss_recv_token.value=malloc(gss_recv_token.length); + if(!gss_recv_token.value) { + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_OUT_OF_MEMORY; + } + result=Curl_blockread_all(conn, sock, (char *)gss_recv_token.value, + gss_recv_token.length, + &actualread, timeout); + + if(result != CURLE_OK || actualread != us_length) { + failf(data, "Failed to receive GSSAPI encryptrion type."); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + if(!data->set.socks5_gssapi_nec) { + gss_major_status = gss_unwrap(&gss_minor_status, gss_context, + &gss_recv_token, &gss_w_token, + 0, GSS_C_QOP_DEFAULT); + + if(check_gss_err(data,gss_major_status,gss_minor_status,"gss_unwrap")) { + gss_release_buffer(&gss_status, &gss_recv_token); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + failf(data, "Failed to unwrap GSSAPI encryption value into token."); + return CURLE_COULDNT_CONNECT; + } + gss_release_buffer(&gss_status, &gss_recv_token); + + if(gss_w_token.length != 1) { + failf(data, "Invalid GSSAPI encryption response length (%d).", + gss_w_token.length); + gss_release_buffer(&gss_status, &gss_w_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + memcpy(socksreq,gss_w_token.value,gss_w_token.length); + gss_release_buffer(&gss_status, &gss_w_token); + } + else { + if(gss_recv_token.length != 1) { + failf(data, "Invalid GSSAPI encryption response length (%d).", + gss_recv_token.length); + gss_release_buffer(&gss_status, &gss_recv_token); + gss_delete_sec_context(&gss_status, &gss_context, NULL); + return CURLE_COULDNT_CONNECT; + } + + memcpy(socksreq,gss_recv_token.value,gss_recv_token.length); + gss_release_buffer(&gss_status, &gss_recv_token); + } + + infof(data, "SOCKS5 access with%s protection granted.\n", + (socksreq[0]==0)?"out gssapi data": + ((socksreq[0]==1)?" gssapi integrity":" gssapi confidentiality")); + + conn->socks5_gssapi_enctype = socksreq[0]; + if(socksreq[0] == 0) + gss_delete_sec_context(&gss_status, &gss_context, NULL); + + return CURLE_OK; +} +#endif diff --git a/lib/socks_sspi.c b/lib/socks_sspi.c new file mode 100644 index 000000000..2a35af96b --- /dev/null +++ b/lib/socks_sspi.c @@ -0,0 +1,649 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2009, Markus Moeller, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + + +#include "setup.h" + +#ifdef USE_WINDOWS_SSPI +#include + +#include + +#ifdef NEED_MALLOC_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif + +#include "urldata.h" +#include "sendf.h" +#include "connect.h" +#include "timeval.h" +#include "socks.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +/* + * Helper sspi error functions. + */ +typedef long OM_uint32; +static int check_sspi_err(struct SessionHandle *data, + OM_uint32 major_status, + OM_uint32 minor_status, + const char* function) +{ + char *txt; + + if(major_status != SEC_E_OK && + major_status != SEC_I_COMPLETE_AND_CONTINUE && + major_status != SEC_I_COMPLETE_NEEDED && + major_status != SEC_I_CONTINUE_NEEDED) { + failf(data, "SSPI error: %s failed: %d\n", function, major_status); + switch (major_status) { + case SEC_I_COMPLETE_AND_CONTINUE: + txt="SEC_I_COMPLETE_AND_CONTINUE"; + break; + case SEC_I_COMPLETE_NEEDED: + txt="SEC_I_COMPLETE_NEEDED"; + break; + case SEC_I_CONTINUE_NEEDED: + txt="SEC_I_CONTINUE_NEEDED"; + break; + case SEC_I_INCOMPLETE_CREDENTIALS: + txt="SEC_I_INCOMPLETE_CREDENTIALS"; + break; + case SEC_E_INSUFFICIENT_MEMORY: + txt="SEC_E_INSUFFICIENT_MEMORY"; + break; + case SEC_E_INTERNAL_ERROR: + txt="SEC_E_INTERNAL_ERROR"; + break; + case SEC_E_INVALID_HANDLE: + txt="SEC_E_INVALID_HANDLE"; + break; + case SEC_E_INVALID_TOKEN: + txt="SEC_E_INVALID_TOKEN"; + break; + case SEC_E_LOGON_DENIED: + txt="SEC_E_LOGON_DENIED"; + break; + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + txt="SEC_E_NO_AUTHENTICATING_AUTHORITY"; + break; + case SEC_E_NO_CREDENTIALS: + txt="SEC_E_NO_CREDENTIALS"; + break; + case SEC_E_TARGET_UNKNOWN: + txt="SEC_E_TARGET_UNKNOWN"; + break; + case SEC_E_UNSUPPORTED_FUNCTION: + txt="SEC_E_UNSUPPORTED_FUNCTION"; + break; + case SEC_E_WRONG_PRINCIPAL: + txt="SEC_E_WRONG_PRINCIPAL"; + break; + default: + txt="Unknown error"; + + } + failf(data, "SSPI error: %s failed: %s\n", function, txt); + return(1); + } + return(0); +} + +/* This is the SSPI-using version of this function */ +CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, + struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + curl_socket_t sock = conn->sock[sockindex]; + CURLcode code; + ssize_t actualread; + ssize_t written; + int result; + long timeout; + /* Needs GSSAPI authentication */ + OM_uint32 sspi_major_status, sspi_minor_status=0; + OM_uint32 sspi_ret_flags=0; + int gss_enc; + SecBuffer sspi_send_token, sspi_recv_token, sspi_w_token[3]; + SecBufferDesc input_desc, output_desc, wrap_desc; + SecPkgContext_Sizes sspi_sizes; + CredHandle cred_handle; + CtxtHandle sspi_context; + PCtxtHandle context_handle = NULL; + SecPkgCredentials_Names names; + TimeStamp expiry; + char *service_name=NULL; + u_short us_length; + ULONG qop; + unsigned char socksreq[4]; /* room for gssapi exchange header only */ + char *service = data->set.str[STRING_SOCKS5_GSSAPI_SERVICE]; + + /* get timeout */ + timeout = Curl_timeleft(conn, NULL, TRUE); + + /* GSSAPI request looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ + + /* prepare service name */ + if (strchr(service, '/')) { + service_name = malloc(strlen(service)); + if(!service_name) + return CURLE_OUT_OF_MEMORY; + memcpy(service_name, service, strlen(service)); + } + else { + service_name = malloc(strlen(service) + strlen(conn->proxy.name) + 2); + if(!service_name) + return CURLE_OUT_OF_MEMORY; + _snprintf(service_name,strlen(service) +strlen(conn->proxy.name)+2,"%s/%s", + service,conn->proxy.name); + } + + input_desc.cBuffers = 1; + input_desc.pBuffers = &sspi_recv_token; + input_desc.ulVersion = SECBUFFER_VERSION; + + sspi_recv_token.BufferType = SECBUFFER_TOKEN; + sspi_recv_token.cbBuffer = 0; + sspi_recv_token.pvBuffer = NULL; + + output_desc.cBuffers = 1; + output_desc.pBuffers = &sspi_send_token; + output_desc.ulVersion = SECBUFFER_VERSION; + + sspi_send_token.BufferType = SECBUFFER_TOKEN; + sspi_send_token.cbBuffer = 0; + sspi_send_token.pvBuffer = NULL; + + wrap_desc.cBuffers = 3; + wrap_desc.pBuffers = sspi_w_token; + wrap_desc.ulVersion = SECBUFFER_VERSION; + + cred_handle.dwLower = 0; + cred_handle.dwUpper = 0; + + sspi_major_status = AcquireCredentialsHandle( NULL, + "Kerberos", + SECPKG_CRED_OUTBOUND, + NULL, + NULL, + NULL, + NULL, + &cred_handle, + &expiry); + + if(check_sspi_err(data, sspi_major_status,sspi_minor_status, + "AcquireCredentialsHandle") ) { + failf(data, "Failed to acquire credentials."); + free(service_name); + service_name=NULL; + FreeCredentialsHandle(&cred_handle); + return CURLE_COULDNT_CONNECT; + } + + /* As long as we need to keep sending some context info, and there's no */ + /* errors, keep sending it... */ + for(;;) { + + sspi_major_status = InitializeSecurityContext( &cred_handle, + context_handle, + service_name, + ISC_REQ_MUTUAL_AUTH | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT, + 0, + SECURITY_NATIVE_DREP, + &input_desc, + 0, + &sspi_context, + &output_desc, + &sspi_ret_flags, + &expiry); + + if(sspi_recv_token.pvBuffer) { + FreeContextBuffer(sspi_recv_token.pvBuffer); + sspi_recv_token.pvBuffer = NULL; + sspi_recv_token.cbBuffer = 0; + } + + if(check_sspi_err(data,sspi_major_status,sspi_minor_status, + "InitializeSecurityContext") ){ + free(service_name); + service_name=NULL; + FreeCredentialsHandle(&cred_handle); + DeleteSecurityContext(&sspi_context); + FreeContextBuffer(sspi_recv_token.pvBuffer); + failf(data, "Failed to initialise security context."); + return CURLE_COULDNT_CONNECT; + } + + if(sspi_send_token.cbBuffer != 0) { + socksreq[0] = 1; /* gssapi subnegotiation version */ + socksreq[1] = 1; /* authentication message type */ + us_length = htons((short)sspi_send_token.cbBuffer); + memcpy(socksreq+2, &us_length, sizeof(short)); + + code = Curl_write_plain(conn, sock, (char *)socksreq, 4, &written); + if((code != CURLE_OK) || (4 != written)) { + failf(data, "Failed to send SSPI authentication request."); + free(service_name); + service_name=NULL; + FreeContextBuffer(sspi_send_token.pvBuffer); + FreeContextBuffer(sspi_recv_token.pvBuffer); + FreeCredentialsHandle(&cred_handle); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + code = Curl_write_plain(conn, sock, (char *)sspi_send_token.pvBuffer, + sspi_send_token.cbBuffer, &written); + if((code != CURLE_OK) || (sspi_send_token.cbBuffer != written)) { + failf(data, "Failed to send SSPI authentication token."); + free(service_name); + service_name=NULL; + FreeContextBuffer(sspi_send_token.pvBuffer); + FreeContextBuffer(sspi_recv_token.pvBuffer); + FreeCredentialsHandle(&cred_handle); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + } + + FreeContextBuffer(sspi_send_token.pvBuffer); + sspi_send_token.pvBuffer = NULL; + sspi_send_token.cbBuffer = 0; + FreeContextBuffer(sspi_recv_token.pvBuffer); + sspi_recv_token.pvBuffer = NULL; + sspi_recv_token.cbBuffer = 0; + if(sspi_major_status != SEC_I_CONTINUE_NEEDED) break; + + /* analyse response */ + + /* GSSAPI response looks like + * +----+------+-----+----------------+ + * |VER | MTYP | LEN | TOKEN | + * +----+------+----------------------+ + * | 1 | 1 | 2 | up to 2^16 - 1 | + * +----+------+-----+----------------+ + */ + + result=Curl_blockread_all(conn, sock, (char *)socksreq, 4, + &actualread, timeout); + if(result != CURLE_OK || actualread != 4) { + failf(data, "Failed to receive SSPI authentication response."); + free(service_name); + service_name=NULL; + FreeCredentialsHandle(&cred_handle); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] == 255) { /* status / message type */ + failf(data, "User was rejected by the SOCKS5 server (%d %d).", + socksreq[0], socksreq[1]); + free(service_name); + service_name=NULL; + FreeCredentialsHandle(&cred_handle); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[1] != 1) { /* status / messgae type */ + failf(data, "Invalid SSPI authentication response type (%d %d).", + socksreq[0], socksreq[1]); + free(service_name); + service_name=NULL; + FreeCredentialsHandle(&cred_handle); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + memcpy(&us_length, socksreq+2, sizeof(short)); + us_length = ntohs(us_length); + + sspi_recv_token.cbBuffer = us_length; + sspi_recv_token.pvBuffer = malloc(us_length); + + if(!sspi_recv_token.pvBuffer) { + free(service_name); + service_name=NULL; + FreeCredentialsHandle(&cred_handle); + DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + result = Curl_blockread_all(conn, sock, (char *)sspi_recv_token.pvBuffer, + sspi_recv_token.cbBuffer, + &actualread, timeout); + + if(result != CURLE_OK || actualread != us_length) { + failf(data, "Failed to receive SSPI authentication token."); + free(service_name); + service_name=NULL; + FreeContextBuffer(sspi_recv_token.pvBuffer); + FreeCredentialsHandle(&cred_handle); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + context_handle = &sspi_context; + } + + free(service_name); + service_name=NULL; + + /* Everything is good so far, user was authenticated! */ + sspi_major_status = + QueryCredentialsAttributes(&cred_handle, SECPKG_CRED_ATTR_NAMES, &names); + FreeCredentialsHandle(&cred_handle); + if(check_sspi_err(data,sspi_major_status,sspi_minor_status, + "QueryCredentialAttributes") ){ + DeleteSecurityContext(&sspi_context); + FreeContextBuffer(names.sUserName); + failf(data, "Failed to determine user name."); + return CURLE_COULDNT_CONNECT; + } + infof(data, "SOCKS5 server authencticated user %s with gssapi.\n", + names.sUserName); + FreeContextBuffer(names.sUserName); + + /* Do encryption */ + socksreq[0] = 1; /* gssapi subnegotiation version */ + socksreq[1] = 2; /* encryption message type */ + + gss_enc = 0; /* no data protection */ + /* do confidentiality protection if supported */ + if(sspi_ret_flags & ISC_REQ_CONFIDENTIALITY) + gss_enc = 2; + /* else do integrity protection */ + else if(sspi_ret_flags & ISC_REQ_INTEGRITY) + gss_enc = 1; + + infof(data, "SOCKS5 server supports gssapi %s data protection.\n", + (gss_enc==0)?"no":((gss_enc==1)?"integrity":"confidentiality") ); + /* force to no data protection, avoid encryption/decryption for now */ + gss_enc = 0; + /* + * Sending the encryption type in clear seems wrong. It should be + * protected with gss_seal()/gss_wrap(). See RFC1961 extract below + * The NEC reference implementations on which this is based is + * therefore at fault + * + * +------+------+------+.......................+ + * + ver | mtyp | len | token | + * +------+------+------+.......................+ + * + 0x01 | 0x02 | 0x02 | up to 2^16 - 1 octets | + * +------+------+------+.......................+ + * + * Where: + * + * - "ver" is the protocol version number, here 1 to represent the + * first version of the SOCKS/GSS-API protocol + * + * - "mtyp" is the message type, here 2 to represent a protection + * -level negotiation message + * + * - "len" is the length of the "token" field in octets + * + * - "token" is the GSS-API encapsulated protection level + * + * The token is produced by encapsulating an octet containing the + * required protection level using gss_seal()/gss_wrap() with conf_req + * set to FALSE. The token is verified using gss_unseal()/ + * gss_unwrap(). + * + */ + + if(data->set.socks5_gssapi_nec) { + us_length = htons((short)1); + memcpy(socksreq+2, &us_length, sizeof(short)); + } + else { + sspi_major_status = QueryContextAttributes( &sspi_context, + SECPKG_ATTR_SIZES, + &sspi_sizes); + if(check_sspi_err(data,sspi_major_status,sspi_minor_status, + "QueryContextAttributes")) { + DeleteSecurityContext(&sspi_context); + failf(data, "Failed to query security context attributes."); + return CURLE_COULDNT_CONNECT; + } + + sspi_w_token[0].cbBuffer = sspi_sizes.cbSecurityTrailer; + sspi_w_token[0].BufferType = SECBUFFER_TOKEN; + sspi_w_token[0].pvBuffer = malloc(sspi_sizes.cbSecurityTrailer); + + if(!sspi_w_token[0].pvBuffer) { + DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + + sspi_w_token[1].cbBuffer = 1; + sspi_w_token[1].pvBuffer = malloc(1); + if(!sspi_w_token[1].pvBuffer){ + FreeContextBuffer(sspi_w_token[0].pvBuffer); + DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + + memcpy(sspi_w_token[1].pvBuffer,&gss_enc,1); + sspi_w_token[2].BufferType = SECBUFFER_PADDING; + sspi_w_token[2].cbBuffer = sspi_sizes.cbBlockSize; + sspi_w_token[2].pvBuffer = malloc(sspi_sizes.cbBlockSize); + if(!sspi_w_token[2].pvBuffer) { + FreeContextBuffer(sspi_w_token[0].pvBuffer); + FreeContextBuffer(sspi_w_token[1].pvBuffer); + DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + sspi_major_status = EncryptMessage( &sspi_context, + KERB_WRAP_NO_ENCRYPT, + &wrap_desc, + 0); + if(check_sspi_err(data,sspi_major_status,sspi_minor_status, + "EncryptMessage") ) { + FreeContextBuffer(sspi_w_token[0].pvBuffer); + FreeContextBuffer(sspi_w_token[1].pvBuffer); + FreeContextBuffer(sspi_w_token[2].pvBuffer); + DeleteSecurityContext(&sspi_context); + failf(data, "Failed to query security context attributes."); + return CURLE_COULDNT_CONNECT; + } + sspi_send_token.cbBuffer = sspi_w_token[0].cbBuffer + + sspi_w_token[1].cbBuffer + + sspi_w_token[2].cbBuffer; + sspi_send_token.pvBuffer = malloc(sspi_send_token.cbBuffer); + if(!sspi_send_token.pvBuffer) { + FreeContextBuffer(sspi_w_token[0].pvBuffer); + FreeContextBuffer(sspi_w_token[1].pvBuffer); + FreeContextBuffer(sspi_w_token[2].pvBuffer); + DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + + memcpy(sspi_send_token.pvBuffer, sspi_w_token[0].pvBuffer, + sspi_w_token[0].cbBuffer); + memcpy((PUCHAR) sspi_send_token.pvBuffer +(int)sspi_w_token[0].cbBuffer, + sspi_w_token[1].pvBuffer, sspi_w_token[1].cbBuffer); + memcpy((PUCHAR) sspi_send_token.pvBuffer + +sspi_w_token[0].cbBuffer + +sspi_w_token[1].cbBuffer, + sspi_w_token[2].pvBuffer, sspi_w_token[2].cbBuffer); + + FreeContextBuffer(sspi_w_token[0].pvBuffer); + sspi_w_token[0].pvBuffer = NULL; + sspi_w_token[0].cbBuffer = 0; + FreeContextBuffer(sspi_w_token[1].pvBuffer); + sspi_w_token[1].pvBuffer = NULL; + sspi_w_token[1].cbBuffer = 0; + FreeContextBuffer(sspi_w_token[2].pvBuffer); + sspi_w_token[2].pvBuffer = NULL; + sspi_w_token[2].cbBuffer = 0; + + us_length = htons((short)sspi_send_token.cbBuffer); + memcpy(socksreq+2,&us_length,sizeof(short)); + } + + code = Curl_write_plain(conn, sock, (char *)socksreq, 4, &written); + if((code != CURLE_OK) || (4 != written)) { + failf(data, "Failed to send SSPI encryption request."); + FreeContextBuffer(sspi_send_token.pvBuffer); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + if(data->set.socks5_gssapi_nec) { + memcpy(socksreq,&gss_enc,1); + code = Curl_write_plain(conn, sock, (char *)socksreq, 1, &written); + if((code != CURLE_OK) || (1 != written)) { + failf(data, "Failed to send SSPI encryption type."); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + } else { + code = Curl_write_plain(conn, sock, (char *)sspi_send_token.pvBuffer, + sspi_send_token.cbBuffer, &written); + if((code != CURLE_OK) || (sspi_send_token.cbBuffer != written)) { + failf(data, "Failed to send SSPI encryption type."); + FreeContextBuffer(sspi_send_token.pvBuffer); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + FreeContextBuffer(sspi_send_token.pvBuffer); + } + + result=Curl_blockread_all(conn, sock, (char *)socksreq, 4, + &actualread, timeout); + if(result != CURLE_OK || actualread != 4) { + failf(data, "Failed to receive SSPI encryption response."); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] == 255) { /* status / message type */ + failf(data, "User was rejected by the SOCKS5 server (%d %d).", + socksreq[0], socksreq[1]); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[1] != 2) { /* status / message type */ + failf(data, "Invalid SSPI encryption response type (%d %d).", + socksreq[0], socksreq[1]); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + memcpy(&us_length, socksreq+2, sizeof(short)); + us_length = ntohs(us_length); + + sspi_w_token[0].cbBuffer = us_length; + sspi_w_token[0].pvBuffer = malloc(us_length); + if(!sspi_w_token[0].pvBuffer) { + DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + + result=Curl_blockread_all(conn, sock, (char *)sspi_w_token[0].pvBuffer, + sspi_w_token[0].cbBuffer, + &actualread, timeout); + + if(result != CURLE_OK || actualread != us_length) { + failf(data, "Failed to receive SSPI encryption type."); + FreeContextBuffer(sspi_w_token[0].pvBuffer); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + + if(!data->set.socks5_gssapi_nec) { + wrap_desc.cBuffers = 2; + sspi_w_token[0].BufferType = SECBUFFER_STREAM; + sspi_w_token[1].BufferType = SECBUFFER_DATA; + sspi_w_token[1].cbBuffer = 0; + sspi_w_token[1].pvBuffer = NULL; + + sspi_major_status = DecryptMessage(&sspi_context, &wrap_desc, 0, &qop); + + if(check_sspi_err(data,sspi_major_status,sspi_minor_status, + "DecryptMessage")) { + FreeContextBuffer(sspi_w_token[0].pvBuffer); + FreeContextBuffer(sspi_w_token[1].pvBuffer); + DeleteSecurityContext(&sspi_context); + failf(data, "Failed to query security context attributes."); + return CURLE_COULDNT_CONNECT; + } + + if(sspi_w_token[1].cbBuffer != 1) { + failf(data, "Invalid SSPI encryption response length (%d).", + sspi_w_token[1].cbBuffer); + FreeContextBuffer(sspi_w_token[0].pvBuffer); + FreeContextBuffer(sspi_w_token[1].pvBuffer); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + memcpy(socksreq,sspi_w_token[1].pvBuffer,sspi_w_token[1].cbBuffer); + FreeContextBuffer(sspi_w_token[0].pvBuffer); + FreeContextBuffer(sspi_w_token[1].pvBuffer); + } else { + if(sspi_w_token[0].cbBuffer != 1) { + failf(data, "Invalid SSPI encryption response length (%d).", + sspi_w_token[0].cbBuffer); + FreeContextBuffer(sspi_w_token[0].pvBuffer); + DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + memcpy(socksreq,sspi_w_token[0].pvBuffer,sspi_w_token[0].cbBuffer); + FreeContextBuffer(sspi_w_token[0].pvBuffer); + } + + infof(data, "SOCKS5 access with%s protection granted.\n", + (socksreq[0]==0)?"out gssapi data": + ((socksreq[0]==1)?" gssapi integrity":" gssapi confidentiality")); + + /* For later use if encryption is required + conn->socks5_gssapi_enctype = socksreq[0]; + if (socksreq[0] != 0) + conn->socks5_sspi_context = sspi_context; + else { + DeleteSecurityContext(&sspi_context); + conn->socks5_sspi_context = sspi_context; + } + */ + return CURLE_OK; +} +#endif diff --git a/lib/url.c b/lib/url.c index be5b20990..bd9a7e690 100644 --- a/lib/url.c +++ b/lib/url.c @@ -698,6 +698,20 @@ CURLcode Curl_init_userdefined(struct UserDefined *set) set->new_file_perms = 0644; /* Default permissions */ set->new_directory_perms = 0755; /* Default permissions */ + +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + /* + * disallow unprotected protection negotiation NEC reference implementation + * seem not to follow rfc1961 section 4.3/4.4 + */ + set->socks5_gssapi_nec = FALSE; + /* set default gssapi service name */ + res = setstropt(&set->str[STRING_SOCKS5_GSSAPI_SERVICE], + (char *) CURL_DEFAULT_SOCKS5_GSSAPI_SERVICE); + if (res != CURLE_OK) + return res; +#endif + /* This is our preferred CA cert bundle/path since install time */ #if defined(CURL_CA_BUNDLE) res = setstropt(&set->str[STRING_SSL_CAFILE], (char *) CURL_CA_BUNDLE); @@ -1463,6 +1477,23 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, break; #endif +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + case CURLOPT_SOCKS5_GSSAPI_SERVICE: + /* + * Set gssapi service name + */ + result = setstropt(&data->set.str[STRING_SOCKS5_GSSAPI_SERVICE], + va_arg(param, char *)); + break; + + case CURLOPT_SOCKS5_GSSAPI_NEC: + /* + * set flag for nec socks5 support + */ + data->set.socks5_gssapi_nec = (bool)(0 != va_arg(param, long)); + break; +#endif + case CURLOPT_WRITEHEADER: /* * Custom pointer to pass the header write callback function diff --git a/lib/url.h b/lib/url.h index ec4da99c2..6cb0b7ec0 100644 --- a/lib/url.h +++ b/lib/url.h @@ -84,5 +84,6 @@ void Curl_close_connections(struct SessionHandle *data); void Curl_reset_reqproto(struct connectdata *conn); #define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */ +#define CURL_DEFAULT_SOCKS5_GSSAPI_SERVICE "rcmd" /* default socks5 gssapi service */ #endif diff --git a/lib/urldata.h b/lib/urldata.h index a06fb5eb4..15e4818f6 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1051,6 +1051,9 @@ struct connectdata { } proto; int cselect_bits; /* bitmask of socket events */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + int socks5_gssapi_enctype; +#endif }; /* The end of connectdata. */ @@ -1366,6 +1369,9 @@ enum dupstring { STRING_PROXYPASSWORD, /* Proxy , if used */ STRING_NOPROXY, /* List of hosts which should not use the proxy, if used */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + STRING_SOCKS5_GSSAPI_SERVICE, /* GSSAPI service name */ +#endif /* -- end of strings -- */ STRING_LAST /* not used, just an end-of-list marker */ @@ -1524,6 +1530,9 @@ struct UserDefined { via an HTTP proxy */ char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */ unsigned int scope; /* address scope for IPv6 */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + long socks5_gssapi_nec; /* flag to support nec socks5 server */ +#endif }; struct Names { diff --git a/src/Makefile.vc6 b/src/Makefile.vc6 index e85ca2f7f..b7616763b 100644 --- a/src/Makefile.vc6 +++ b/src/Makefile.vc6 @@ -221,8 +221,8 @@ LFLAGS = $(LFLAGS) $(SSL_IMP_LFLAGS) $(ZLIB_LFLAGS) !ENDIF -LINKLIBS = $(LINKLIBS) wsock32.lib wldap32.lib -LINKLIBS_DEBUG = $(LINKLIBS_DEBUG) wsock32.lib wldap32.lib +LINKLIBS = $(LINKLIBS) wsock32.lib wldap32.lib secur32.lib +LINKLIBS_DEBUG = $(LINKLIBS_DEBUG) wsock32.lib wldap32.lib secur32.lib all : release diff --git a/src/main.c b/src/main.c index f99550dcf..17fc415ff 100644 --- a/src/main.c +++ b/src/main.c @@ -536,6 +536,10 @@ struct Configurable { char *socksproxy; /* set to server string */ int socksver; /* set to CURLPROXY_SOCKS* define */ + char *socks5_gssapi_service; /* set service name for gssapi principal + * default rcmd */ + int socks5_gssapi_nec ; /* The NEC reference server does not protect + * the encryption type exchange */ bool tcp_nodelay; long req_retry; /* number of retries */ @@ -807,6 +811,10 @@ static void help(void) " --socks4a SOCKS4a proxy on given host + port", " --socks5 SOCKS5 proxy on given host + port", " --socks5-hostname SOCKS5 proxy, pass host name to proxy", +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + " --socks5-gssapi-service SOCKS5 proxy service name for gssapi", + " --socks5-gssapi-nec Compatibility with NEC SOCKS5 server", +#endif " -Y/--speed-limit Stop transfer if below speed-limit for 'speed-time' secs", " -y/--speed-time Time needed to trig speed-limit abort. Defaults to 30", " -2/--sslv2 Use SSLv2 (SSL)", @@ -1669,6 +1677,10 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ {"$3", "keepalive-time", TRUE}, {"$4", "post302", FALSE}, {"$5", "noproxy", TRUE}, +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + {"$6", "socks5-gssapi-service", TRUE}, + {"$7", "socks5-gssapi-nec", FALSE}, +#endif {"0", "http1.0", FALSE}, {"1", "tlsv1", FALSE}, @@ -2182,6 +2194,14 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ /* This specifies the noproxy list */ GetStr(&config->noproxy, nextarg); break; +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + case '6': /* --socks5-gssapi-service */ + GetStr(&config->socks5_gssapi_service, nextarg); + break; + case '7': /* --socks5-gssapi-nec*/ + config->socks5_gssapi_nec = TRUE; + break; +#endif } break; case '#': /* --progress-bar */ @@ -3700,6 +3720,10 @@ static void free_config_fields(struct Configurable *config) free(config->referer); if (config->hostpubmd5) free(config->hostpubmd5); +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(config->socks5_gssapi_service) + free(config->socks5_gssapi_service); +#endif curl_slist_free_all(config->quote); /* checks for config->quote == NULL */ curl_slist_free_all(config->prequote); @@ -4768,6 +4792,16 @@ operate(struct Configurable *config, int argc, argv_item_t argv[]) my_setopt(curl, CURLOPT_PROXYTYPE, config->socksver); } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + /* new in curl 7.19.4 */ + if(config->socks5_gssapi_service) + my_setopt(curl, CURLOPT_SOCKS5_GSSAPI_SERVICE, + config->socks5_gssapi_service); + + /* new in curl 7.19.4 */ + if(config->socks5_gssapi_nec) + my_setopt(curl, CURLOPT_SOCKS5_GSSAPI_NEC, config->socks5_gssapi_nec); +#endif /* curl 7.13.0 */ my_setopt(curl, CURLOPT_FTP_ACCOUNT, config->ftp_account);