From 54967d2a3ab5559631407f7b7f67ef48c2dda6dd Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sun, 1 Jul 2007 22:01:18 +0000 Subject: Thomas J. Moore provided a patch that introduces Kerberos5 support in libcurl. This also makes the options change name to --krb (from --krb4) and CURLOPT_KRBLEVEL (from CURLOPT_KRB4LEVEL) but the old names are still --- CHANGES | 4 + RELEASE-NOTES | 4 +- docs/FEATURES | 2 +- docs/MANUAL | 15 +- docs/curl.1 | 11 +- docs/libcurl/curl_easy_setopt.3 | 12 +- include/curl/curl.h | 10 +- lib/Makefile.inc | 1 + lib/ftp.c | 97 ++++++++---- lib/hostip.c | 2 + lib/hostip4.c | 3 + lib/hostip6.c | 7 +- lib/krb4.h | 5 + lib/krb5.c | 316 ++++++++++++++++++++++++++++++++++++++++ lib/security.c | 61 ++++++-- lib/sendf.c | 4 +- lib/url.c | 11 +- lib/urldata.h | 15 +- src/main.c | 21 +-- 19 files changed, 509 insertions(+), 92 deletions(-) create mode 100644 lib/krb5.c diff --git a/CHANGES b/CHANGES index 88771532d..218d28df5 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,10 @@ Changelog Daniel S (1 July 2007) +- Thomas J. Moore provided a patch that introduces Kerberos5 support in + libcurl. This also makes the options change name to --krb (from --krb4) and + CURLOPT_KRBLEVEL (from CURLOPT_KRB4LEVEL) but the old names are still + - Song Ma helped me verify and extend a fix for doing FTP over a SOCKS4/5 proxy. diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 134144e90..3fd9ea5a3 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -13,6 +13,7 @@ This release includes the following changes: o added CURLOPT_NEW_FILE_PERMS and CURLOPT_NEW_DIRECTORY_PERMS o improved hashing of sockets for the multi_socket API + o ftp kerberos5 support added This release includes the following bugfixes: @@ -36,6 +37,7 @@ New curl mirrors: This release would not have looked like this without help, code, reports and advice from friends like these: - Robert Iakobashvili, James Housley, Günter Knauf, James Bursa, Song Ma + Robert Iakobashvili, James Housley, Günter Knauf, James Bursa, Song Ma, + Thomas J. Moore Thanks! (and sorry if I forgot to mention someone) diff --git a/docs/FEATURES b/docs/FEATURES index 75aefce60..08f342cd8 100644 --- a/docs/FEATURES +++ b/docs/FEATURES @@ -73,7 +73,7 @@ HTTPS (*1) FTP - download - authentication - - kerberos4 (*5) + - kerberos4 (*5), kerberos5 (*3) - active/passive using PORT, EPRT, PASV or EPSV - single file size information (compare to HTTP HEAD) - 'type=' URL support diff --git a/docs/MANUAL b/docs/MANUAL index 1ea13f142..dba224d91 100644 --- a/docs/MANUAL +++ b/docs/MANUAL @@ -809,18 +809,19 @@ CUSTOM OUTPUT curl -w 'We downloaded %{size_download} bytes\n' www.download.com -KERBEROS4 FTP TRANSFER +KERBEROS FTP TRANSFER - Curl supports kerberos4 for FTP transfers. You need the kerberos package - installed and used at curl build time for it to be used. + Curl supports kerberos4 and kerberos5/GSSAPI for FTP transfers. You need + the kerberos package installed and used at curl build time for it to be + used. - First, get the krb-ticket the normal way, like with the kauth tool. Then use - curl in way similar to: + First, get the krb-ticket the normal way, like with the kinit/kauth tool. + Then use curl in way similar to: - curl --krb4 private ftp://krb4site.com -u username:fakepwd + curl --krb private ftp://krb4site.com -u username:fakepwd There's no use for a password on the -u switch, but a blank one will make - curl ask for one and you already entered the real password to kauth. + curl ask for one and you already entered the real password to kinit/kauth. TELNET diff --git a/docs/curl.1 b/docs/curl.1 index 629f99dc7..59753fa48 100644 --- a/docs/curl.1 +++ b/docs/curl.1 @@ -593,13 +593,14 @@ private key is. DER, PEM and ENG are supported. If not specified, PEM is assumed. If this option is used several times, the last one will be used. -.IP "--krb4 " -(FTP) Enable Kerberos4 authentication and use. The level must be entered and +.IP "--krb " +(FTP) Enable Kerberos authentication and use. The level must be entered and should be one of 'clear', 'safe', 'confidential' or 'private'. Should you use a level that is not one of these, 'private' will instead be used. -This option requires that the library was built with Kerberos4 support. This -is not very common. Use \fI-V/--version\fP to see if your curl supports it. +This option requires that the library was built with kerberos4 or GSSAPI +(GSS-Negotiate) support. This is not very common. Use \fI-V/--version\fP to +see if your curl supports it. If this option is used several times, the last one will be used. .IP "-K/--config " @@ -1153,7 +1154,7 @@ Automatic decompression of compressed files over HTTP is supported. .IP "NTLM" NTLM authentication is supported. .IP "GSS-Negotiate" -Negotiate authentication is supported. +Negotiate authentication and krb5 for ftp is supported. .IP "Debug" This curl uses a libcurl built with Debug. This enables more error-tracking and memory debugging etc. For curl-developers only! diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3 index 01e4dc3c0..cbbf411a0 100644 --- a/docs/libcurl/curl_easy_setopt.3 +++ b/docs/libcurl/curl_easy_setopt.3 @@ -1370,12 +1370,12 @@ this to 1 to enable it. By default all transfers are done using the cache. Note that while nothing ever should get hurt by attempting to reuse SSL session-IDs, there seem to be broken SSL implementations in the wild that may require you to disable this in order for you to succeed. (Added in 7.16.0) -.IP CURLOPT_KRB4LEVEL -Pass a char * as parameter. Set the krb4 security level, this also enables -krb4 awareness. This is a string, 'clear', 'safe', 'confidential' or -\&'private'. If the string is set but doesn't match one of these, 'private' -will be used. Set the string to NULL to disable Kerberos4. The Kerberos -support only works for FTP. +.IP CURLOPT_KRBLEVEL +Pass a char * as parameter. Set the kerberos security level for FTP; this +also enables kerberos awareness. This is a string, 'clear', 'safe', +'confidential' or \&'private'. If the string is set but doesn't match one +of these, 'private' will be used. Set the string to NULL to disable kerberos +support for FTP. .SH SSH OPTIONS .IP CURLOPT_SSH_AUTH_TYPES Pass a long set to a bitmask consisting of one or more of diff --git a/include/curl/curl.h b/include/curl/curl.h index ca229cb96..fe413d0a7 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -736,10 +736,12 @@ typedef enum { /* Set the interface string to use as outgoing network interface */ CINIT(INTERFACE, OBJECTPOINT, 62), - /* Set the krb4 security level, this also enables krb4 awareness. This is a - * string, 'clear', 'safe', 'confidential' or 'private'. If the string is - * set but doesn't match one of these, 'private' will be used. */ - CINIT(KRB4LEVEL, OBJECTPOINT, 63), + /* Set the krb4/5 security level, this also enables krb4/5 awareness. This + * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string + * is set but doesn't match one of these, 'private' will be used. */ + CINIT(KRBLEVEL, OBJECTPOINT, 63), + /* This is for compatibility with older curl releases */ +#define CURLOPT_KRB4LEVEL CURLOPT_KRBLEVEL /* Set if we should verify the peer in ssl handshake, set 1 to verify. */ CINIT(SSL_VERIFYPEER, LONG, 64), diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 3985cefbf..714b15f8a 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -4,6 +4,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ cookie.c http.c sendf.c ftp.c url.c dict.c if2ip.c speedcheck.c \ ldap.c ssluse.c version.c getenv.c escape.c mprintf.c telnet.c \ netrc.c getinfo.c transfer.c strequal.c easy.c security.c krb4.c \ + krb5.c \ memdebug.c http_chunks.c strtok.c connect.c llist.c hash.c multi.c \ content_encoding.c share.c http_digest.c md5.c http_negotiate.c \ http_ntlm.c inet_pton.c strtoofft.c strerror.c hostares.c hostasyn.c \ diff --git a/lib/ftp.c b/lib/ftp.c index 5e04b7e81..91f6d91f7 100644 --- a/lib/ftp.c +++ b/lib/ftp.c @@ -75,7 +75,7 @@ #include "socks.h" #include "ftp.h" -#ifdef HAVE_KRB4 +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) #include "krb4.h" #endif @@ -319,8 +319,17 @@ static CURLcode ftp_readresp(curl_socket_t sockfd, ftpc->cache_size = 0; /* zero the size just in case */ } else { - int res = Curl_read(conn, sockfd, ptr, BUFSIZE-ftpc->nread_resp, - &gotbytes); + int res; +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + enum protection_level prot = conn->data_prot; + + conn->data_prot = 0; +#endif + res = Curl_read(conn, sockfd, ptr, BUFSIZE-ftpc->nread_resp, + &gotbytes); +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + conn->data_prot = prot; +#endif if(res < 0) /* EWOULDBLOCK */ return CURLE_OK; /* return */ @@ -360,6 +369,9 @@ static CURLcode ftp_readresp(curl_socket_t sockfd, the line isn't really terminated until the LF comes */ /* output debug output if that is requested */ +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + if(!conn->sec_complete) +#endif if(data->set.verbose) Curl_debug(data, CURLINFO_HEADER_IN, ftpc->linestart_resp, (size_t)perline, conn); @@ -414,18 +426,18 @@ static CURLcode ftp_readresp(curl_socket_t sockfd, if(!result) code = atoi(buf); -#ifdef HAVE_KRB4 +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) /* handle the security-oriented responses 6xx ***/ /* FIXME: some errorchecking perhaps... ***/ switch(code) { case 631: - Curl_sec_read_msg(conn, buf, prot_safe); + code = Curl_sec_read_msg(conn, buf, prot_safe); break; case 632: - Curl_sec_read_msg(conn, buf, prot_private); + code = Curl_sec_read_msg(conn, buf, prot_private); break; case 633: - Curl_sec_read_msg(conn, buf, prot_confidential); + code = Curl_sec_read_msg(conn, buf, prot_confidential); break; default: /* normal ftp stuff we pass through! */ @@ -553,7 +565,17 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ ftpc->cache_size = 0; /* zero the size just in case */ } else { - int res = Curl_read(conn, sockfd, ptr, BUFSIZE-*nreadp, &gotbytes); + int res; +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + enum protection_level prot = conn->data_prot; + + conn->data_prot = 0; +#endif + res = Curl_read(conn, sockfd, ptr, BUFSIZE-*nreadp, + &gotbytes); +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + conn->data_prot = prot; +#endif if(res < 0) /* EWOULDBLOCK */ continue; /* go looping again */ @@ -593,6 +615,9 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ the line isn't really terminated until the LF comes */ /* output debug output if that is requested */ +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + if(!conn->sec_complete) +#endif if(data->set.verbose) Curl_debug(data, CURLINFO_HEADER_IN, line_start, (size_t)perline, conn); @@ -646,18 +671,18 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ if(!result) code = atoi(buf); -#ifdef HAVE_KRB4 +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) /* handle the security-oriented responses 6xx ***/ /* FIXME: some errorchecking perhaps... ***/ switch(code) { case 631: - Curl_sec_read_msg(conn, buf, prot_safe); + code = Curl_sec_read_msg(conn, buf, prot_safe); break; case 632: - Curl_sec_read_msg(conn, buf, prot_private); + code = Curl_sec_read_msg(conn, buf, prot_private); break; case 633: - Curl_sec_read_msg(conn, buf, prot_confidential); + code = Curl_sec_read_msg(conn, buf, prot_confidential); break; default: /* normal ftp stuff we pass through! */ @@ -2299,14 +2324,7 @@ static CURLcode ftp_state_loggedin(struct connectdata *conn) CURLcode result = CURLE_OK; #ifdef HAVE_KRB4 - if(conn->data->set.krb4) { - /* We are logged in, asked to use Kerberos. Set the requested - * protection level - */ - if(conn->sec_complete) - /* BLOCKING */ - Curl_sec_set_protection_level(conn); - + if(conn->data->set.krb) { /* We may need to issue a KAUTH here to have access to the files * do it if user supplied a password */ @@ -2353,7 +2371,8 @@ static CURLcode ftp_state_user_resp(struct connectdata *conn, struct ftp_conn *ftpc = &conn->proto.ftpc; (void)instate; /* no use for this yet */ - if((ftpcode == 331) && (ftpc->state == FTP_USER)) { + /* some need password anyway, and others just return 2xx ignored */ + if((ftpcode == 331 || ftpcode/100 == 2) && (ftpc->state == FTP_USER)) { /* 331 Password required for ... (the server requires to send the user's password too) */ NBFTPSENDF(conn, "PASS %s", ftp->passwd?ftp->passwd:""); @@ -2461,15 +2480,15 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) } /* We have received a 220 response fine, now we proceed. */ -#ifdef HAVE_KRB4 - if(data->set.krb4) { +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + if(data->set.krb) { /* If not anonymous login, try a secure login. Note that this procedure is still BLOCKING. */ Curl_sec_request_prot(conn, "private"); /* We set private first as default, in case the line below fails to set a valid level */ - Curl_sec_request_prot(conn, data->set.krb4_level); + Curl_sec_request_prot(conn, data->set.krb_level); if(Curl_sec_login(conn) != 0) infof(data, "Logging in with password in cleartext!\n"); @@ -3086,7 +3105,7 @@ CURLcode Curl_ftp_done(struct connectdata *conn, CURLcode status, bool premature /* free the dir tree and file parts */ freedirs(conn); -#ifdef HAVE_KRB4 +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) Curl_sec_fflush_fd(conn, conn->sock[SECONDARYSOCKET]); #endif @@ -3496,16 +3515,21 @@ CURLcode Curl_nbftpsendf(struct connectdata *conn, const char *fmt, ...) { ssize_t bytes_written; - char s[256]; +/* may still not be big enough for some krb5 tokens */ +#define SBUF_SIZE 1024 + char s[SBUF_SIZE]; size_t write_len; char *sptr=s; CURLcode res = CURLE_OK; struct SessionHandle *data = conn->data; struct ftp_conn *ftpc = &conn->proto.ftpc; +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + enum protection_level data_sec = conn->data_prot; +#endif va_list ap; va_start(ap, fmt); - vsnprintf(s, 250, fmt, ap); + vsnprintf(s, SBUF_SIZE-3, fmt, ap); va_end(ap); strcat(s, "\r\n"); /* append a trailing CRLF */ @@ -3523,8 +3547,14 @@ CURLcode Curl_nbftpsendf(struct connectdata *conn, } #endif /* CURL_DOES_CONVERSIONS */ +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + conn->data_prot = prot_cmd; +#endif res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len, &bytes_written); +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + conn->data_prot = data_sec; +#endif if(CURLE_OK != res) return res; @@ -3557,14 +3587,17 @@ CURLcode Curl_ftpsendf(struct connectdata *conn, const char *fmt, ...) { ssize_t bytes_written; - char s[256]; + char s[SBUF_SIZE]; size_t write_len; char *sptr=s; CURLcode res = CURLE_OK; +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + enum protection_level data_sec = conn->data_prot; +#endif va_list ap; va_start(ap, fmt); - vsnprintf(s, 250, fmt, ap); + vsnprintf(s, SBUF_SIZE-3, fmt, ap); va_end(ap); strcat(s, "\r\n"); /* append a trailing CRLF */ @@ -3581,8 +3614,14 @@ CURLcode Curl_ftpsendf(struct connectdata *conn, #endif /* CURL_DOES_CONVERSIONS */ while(1) { +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + conn->data_prot = prot_cmd; +#endif res = Curl_write(conn, conn->sock[FIRSTSOCKET], sptr, write_len, &bytes_written); +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + conn->data_prot = data_sec; +#endif if(CURLE_OK != res) break; diff --git a/lib/hostip.c b/lib/hostip.c index 5a7da5016..189d0683d 100644 --- a/lib/hostip.c +++ b/lib/hostip.c @@ -575,6 +575,8 @@ void Curl_freeaddrinfo(Curl_addrinfo *ai) /* walk over the list and free all entries */ while(ai) { next = ai->ai_next; + if(ai->ai_canonname) + free(ai->ai_canonname); free(ai); ai = next; } diff --git a/lib/hostip4.c b/lib/hostip4.c index 43b7c6961..9ff883892 100644 --- a/lib/hostip4.c +++ b/lib/hostip4.c @@ -380,6 +380,9 @@ Curl_addrinfo *Curl_he2ai(const struct hostent *he, int port) and use that area to store the address */ ai->ai_addr = (struct sockaddr *) ((char*)ai + sizeof(Curl_addrinfo)); + /* FIXME: need to free this eventually */ + ai->ai_canonname = strdup(he->h_name); + /* leave the rest of the struct filled with zero */ addr = (struct sockaddr_in *)ai->ai_addr; /* storage area for this info */ diff --git a/lib/hostip6.c b/lib/hostip6.c index 777b1e319..6d49b9c79 100644 --- a/lib/hostip6.c +++ b/lib/hostip6.c @@ -279,9 +279,10 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, /* the given address is numerical only, prevent a reverse lookup */ hints.ai_flags = AI_NUMERICHOST; } -#if 0 /* removed nov 8 2005 before 7.15.1 */ - else - hints.ai_flags = AI_CANONNAME; +#ifdef HAVE_GSSAPI + if(conn->data->set.krb) + /* if krb is used, we (might) need the canonical host name */ + hints.ai_flags |= AI_CANONNAME; #endif if(port) { diff --git a/lib/krb4.h b/lib/krb4.h index f46416e62..405454ccb 100644 --- a/lib/krb4.h +++ b/lib/krb4.h @@ -40,7 +40,12 @@ struct Curl_sec_client_mech { #define AUTH_CONTINUE 1 #define AUTH_ERROR 2 +#ifdef HAVE_KRB4 extern struct Curl_sec_client_mech Curl_krb4_client_mech; +#endif +#ifdef HAVE_GSSAPI +extern struct Curl_sec_client_mech Curl_krb5_client_mech; +#endif CURLcode Curl_krb_kauth(struct connectdata *conn); int Curl_sec_fflush_fd(struct connectdata *conn, int fd); diff --git a/lib/krb5.c b/lib/krb5.c new file mode 100644 index 000000000..6655973d4 --- /dev/null +++ b/lib/krb5.c @@ -0,0 +1,316 @@ +/* This source code was modified by Martin Hedenfalk for + * use in Curl. His latest changes were done 2000-09-18. + * + * It has since been patched away like a madman by Daniel Stenberg + * to make it better applied to curl conditions, and to make + * it not use globals, pollute name space and more. This source code awaits a + * rewrite to work around the paragraph 2 in the BSD licenses as explained + * below. + * + * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. */ + +#include "setup.h" + +#ifndef CURL_DISABLE_FTP +#ifdef HAVE_GSSAPI + +#include +#ifdef HAVE_NETDB_H +#include +#endif +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include /* for getpid() */ +#endif + +#include "urldata.h" +#include "base64.h" +#include "ftp.h" +#include "sendf.h" +#include "krb4.h" +#include "memory.h" + +#if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL) +#include "inet_ntoa_r.h" +#endif + +/* The last #include file should be: */ +#include "memdebug.h" + +#define LOCAL_ADDR (&conn->local_addr) +#define REMOTE_ADDR conn->ip_addr->ai_addr +#define myctladdr LOCAL_ADDR +#define hisctladdr REMOTE_ADDR + +static int +krb5_check_prot(void *app_data, int level) +{ + gss_ctx_id_t *context = app_data; + app_data = NULL; /* prevent compiler warning */ + if(level == prot_confidential) + return -1; + return 0; +} + +static int +krb5_decode(void *app_data, void *buf, int len, int level, + struct connectdata *conn) +{ + gss_ctx_id_t *context = app_data; + OM_uint32 maj, min; + gss_buffer_desc enc, dec; + + enc.value = buf; + enc.length = len; + maj = gss_unseal(&min, *context, &enc, &dec, NULL, NULL); + if(maj != GSS_S_COMPLETE) { + if(len >= 4) + strcpy(buf, "599 "); + return -1; + } + + memcpy(buf, dec.value, dec.length); + len = dec.length; + gss_release_buffer(&min, &dec); + + return len; +} + +static int +krb5_overhead(void *app_data, int level, int len) +{ + gss_ctx_id_t *context = app_data; + /* no arguments are used, just init them to prevent compiler warnings */ + app_data = NULL; + level = 0; + len = 0; + return 0; +} + +static int +krb5_encode(void *app_data, void *from, int length, int level, void **to, + struct connectdata *conn) +{ + gss_ctx_id_t *context = app_data; + gss_buffer_desc dec, enc; + OM_uint32 maj, min; + int state; + int len; + + dec.value = from; + dec.length = length; + maj = gss_seal(&min, *context, + level == prot_private, + GSS_C_QOP_DEFAULT, + &dec, &state, &enc); + + if(maj != GSS_S_COMPLETE) + return -1; + + /* malloc a new buffer, in case gss_release_buffer doesn't work as expected */ + *to = malloc(enc.length); + if(!*to) + return -1; + memcpy(*to, enc.value, enc.length); + len = enc.length; + gss_release_buffer(&min, &enc); + return len; +} + +static int +krb5_auth(void *app_data, struct connectdata *conn) +{ + int ret; + char *p; + unsigned char *ptr; + size_t len; + u_int32_t cs; + const char *host = conn->dns_entry->addr->ai_canonname; + ssize_t nread; + unsigned int l = sizeof(conn->local_addr); + struct SessionHandle *data = conn->data; + CURLcode result; + const char *service = "ftp", *srv_host = "host"; + gss_buffer_desc gssbuf, _gssresp, *gssresp; + OM_uint32 maj, min; + gss_name_t gssname; + gss_ctx_id_t *context = app_data; + struct gss_channel_bindings_struct chan; + + if(getsockname(conn->sock[FIRSTSOCKET], + (struct sockaddr *)LOCAL_ADDR, &l) < 0) + perror("getsockname()"); + + chan.initiator_addrtype = GSS_C_AF_INET; + chan.initiator_address.length = l - 4; + chan.initiator_address.value = &((struct sockaddr_in *)LOCAL_ADDR)->sin_addr.s_addr; + chan.acceptor_addrtype = GSS_C_AF_INET; + chan.acceptor_address.length = l - 4; + chan.acceptor_address.value = &((struct sockaddr_in *)REMOTE_ADDR)->sin_addr.s_addr; + chan.application_data.length = 0; + chan.application_data.value = NULL; + + /* this loop will execute twice (once for service, once for host) */ + while(1) { + /* this really shouldn't be repeated here, but can't help it */ + if(service == srv_host) { + result = Curl_ftpsendf(conn, "AUTH GSSAPI"); + + if(result) + return -2; + if(Curl_GetFTPResponse(&nread, conn, NULL)) + return -1; + + if(data->state.buffer[0] != '3') + return -1; + } + + gssbuf.value = data->state.buffer; + gssbuf.length = snprintf(gssbuf.value, BUFSIZE, "%s@%s", service, host); + maj = gss_import_name(&min, &gssbuf, gss_nt_service_name, &gssname); + if(maj != GSS_S_COMPLETE) { + gss_release_name(&min, &gssname); + if(service == srv_host) { + Curl_failf(data, "Error importing service name %s", gssbuf.value); + return AUTH_ERROR; + } + service = srv_host; + continue; + } + { + gss_OID t; + gss_display_name(&min, gssname, &gssbuf, &t); + Curl_infof(data, "Trying against %s\n", gssbuf.value); + gss_release_buffer(&min, &gssbuf); + } + gssresp = GSS_C_NO_BUFFER; + *context = GSS_C_NO_CONTEXT; + + do { + ret = AUTH_OK; + maj = gss_init_sec_context(&min, + GSS_C_NO_CREDENTIAL, + context, + gssname, + GSS_C_NO_OID, + GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG, + 0, + &chan, + gssresp, + NULL, + &gssbuf, + NULL, + NULL); + + if(gssresp) { + free(_gssresp.value); + gssresp = NULL; + } + + if(maj != GSS_S_COMPLETE && maj != GSS_S_CONTINUE_NEEDED) { + Curl_infof(data, "Error creating security context"); + ret = AUTH_ERROR; + break; + } + + if(gssbuf.length != 0) { + if(Curl_base64_encode(data, (char *)gssbuf.value, gssbuf.length, &p) < 1) { + Curl_infof(data, "Out of memory base64-encoding"); + ret = AUTH_CONTINUE; + break; + } + + result = Curl_ftpsendf(conn, "ADAT %s", p); + + free(p); + + if(result) { + ret = -2; + break; + } + + if(Curl_GetFTPResponse(&nread, conn, NULL)) { + ret = -1; + break; + } + + if(data->state.buffer[0] != '2' && data->state.buffer[0] != '3'){ + Curl_infof(data, "Server didn't accept auth data\n"); + ret = AUTH_ERROR; + break; + } + + p = data->state.buffer + 4; + p = strstr(p, "ADAT="); + if(p) { + _gssresp.length = Curl_base64_decode(p + 5, (unsigned char **)&_gssresp.value); + if(_gssresp.length < 1) { + Curl_failf(data, "Out of memory base64-encoding"); + ret = AUTH_CONTINUE; + break; + } + } + + gssresp = &_gssresp; + } + } while(maj == GSS_S_CONTINUE_NEEDED); + + gss_release_name(&min, &gssname); + + if(gssresp) + free(_gssresp.value); + + if(ret == AUTH_OK || service == srv_host) + return ret; + + service = srv_host; + } +} + +struct Curl_sec_client_mech Curl_krb5_client_mech = { + "GSSAPI", + sizeof(gss_ctx_id_t), + NULL, /* init */ + krb5_auth, + NULL, /* end */ + krb5_check_prot, + krb5_overhead, + krb5_encode, + krb5_decode +}; + +#endif /* HAVE_GSSAPI */ +#endif /* CURL_DISABLE_FTP */ diff --git a/lib/security.c b/lib/security.c index 4c9aed812..5aa6ebfb5 100644 --- a/lib/security.c +++ b/lib/security.c @@ -41,7 +41,7 @@ #include "setup.h" #ifndef CURL_DISABLE_FTP -#ifdef HAVE_KRB4 +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) #define _MPRINTF_REPLACE /* we want curl-functions instead of native ones */ #include @@ -87,8 +87,8 @@ name_to_level(const char *name) } static const struct Curl_sec_client_mech * const mechs[] = { -#ifdef KRB5 - /* not supported */ +#ifdef HAVE_GSSAPI + &Curl_krb5_client_mech, #endif #ifdef HAVE_KRB4 &Curl_krb4_client_mech, @@ -118,6 +118,8 @@ block_read(int fd, void *buf, size_t len) b = read(fd, p, len); if (b == 0) return 0; + else if (b < 0 && (errno == EINTR || errno == EAGAIN)) + continue; else if (b < 0) return -1; len -= b; @@ -133,7 +135,9 @@ block_write(int fd, void *buf, size_t len) int b; while(len) { b = write(fd, p, len); - if(b < 0) + if (b < 0 && (errno == EINTR || errno == EAGAIN)) + continue; + else if(b < 0) return -1; len -= b; p += b; @@ -155,7 +159,7 @@ sec_get_data(struct connectdata *conn, return -1; len = ntohl(len); buf->data = realloc(buf->data, len); - b = block_read(fd, buf->data, len); + b = buf->data ? block_read(fd, buf->data, len) : -1; if (b == 0) return 0; else if (b < 0) @@ -234,11 +238,36 @@ sec_send(struct connectdata *conn, int fd, char *from, int length) { int bytes; void *buf; - bytes = (conn->mech->encode)(conn->app_data, from, length, conn->data_prot, + enum protection_level protlevel = conn->data_prot; + int iscmd = protlevel == prot_cmd; + + if(iscmd) { + if(!strncmp(from, "PASS ", 5) || !strncmp(from, "ACCT ", 5)) + protlevel = prot_private; + else + protlevel = conn->command_prot; + } + bytes = (conn->mech->encode)(conn->app_data, from, length, protlevel, &buf, conn); - bytes = htonl(bytes); - block_write(fd, &bytes, sizeof(bytes)); - block_write(fd, buf, ntohl(bytes)); + if(iscmd) { + char *cmdbuf; + + bytes = Curl_base64_encode(conn->data, (char *)buf, bytes, &cmdbuf); + if(bytes > 0) { + if(protlevel == prot_private) + block_write(fd, "ENC ", 4); + else + block_write(fd, "MIC ", 4); + block_write(fd, cmdbuf, bytes); + block_write(fd, "\r\n", 2); + Curl_infof(conn->data, "%s %s\n", protlevel == prot_private ? "ENC" : "MIC", cmdbuf); + free(cmdbuf); + } + } else { + bytes = htonl(bytes); + block_write(fd, &bytes, sizeof(bytes)); + block_write(fd, buf, ntohl(bytes)); + } free(buf); return length; } @@ -267,6 +296,8 @@ Curl_sec_write(struct connectdata *conn, int fd, char *buffer, int length) return write(fd, buffer, length); len -= (conn->mech->overhead)(conn->app_data, conn->data_prot, len); + if(len <= 0) + len = length; while(length){ if(length < len) len = length; @@ -319,6 +350,11 @@ Curl_sec_read_msg(struct connectdata *conn, char *s, int level) return -1; } + if(conn->data->set.verbose) { + buf[len] = '\n'; + Curl_debug(conn->data, CURLINFO_HEADER_IN, (char *)buf, len + 1, conn); + } + buf[len] = '\0'; if(buf[3] == '-') @@ -360,7 +396,7 @@ sec_prot_internal(struct connectdata *conn, int level) if(Curl_GetFTPResponse(&nread, conn, &code)) return -1; - if(code/100 != '2'){ + if(code/100 != 2){ failf(conn->data, "Failed to set protection buffer size."); return -1; } @@ -385,6 +421,8 @@ sec_prot_internal(struct connectdata *conn, int level) } conn->data_prot = (enum protection_level)level; + if(level == prot_private) + conn->command_prot = (enum protection_level)level; return 0; } @@ -468,6 +506,9 @@ Curl_sec_login(struct connectdata *conn) conn->mech = *m; conn->sec_complete = 1; conn->command_prot = prot_safe; + /* Set the requested protection level */ + /* BLOCKING */ + Curl_sec_set_protection_level(conn); break; } diff --git a/lib/sendf.c b/lib/sendf.c index a00444f4f..7a6fd5401 100644 --- a/lib/sendf.c +++ b/lib/sendf.c @@ -47,7 +47,7 @@ #define _MPRINTF_REPLACE /* use the internal *printf() functions */ #include -#ifdef HAVE_KRB4 +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) #include "krb4.h" #else #define Curl_sec_send(a,b,c,d) -1 @@ -365,7 +365,7 @@ CURLcode Curl_write(struct connectdata *conn, bytes_written = Curl_sftp_send(conn, num, mem, len); #endif /* !USE_LIBSSH2 */ else if(conn->sec_complete) - /* only TRUE if krb4 enabled */ + /* only TRUE if krb enabled */ bytes_written = Curl_sec_send(conn, num, mem, len); else bytes_written = Curl_plain_send(conn, num, mem, len); diff --git a/lib/url.c b/lib/url.c index b5b83effb..aa2aafe09 100644 --- a/lib/url.c +++ b/lib/url.c @@ -146,9 +146,6 @@ void idn_free (void *ptr); /* prototype from idn-free.h, not provided by #define _MPRINTF_REPLACE /* use our functions only */ #include -#ifdef HAVE_KRB4 -#include "krb4.h" -#endif #include "memory.h" /* The last #include file should be: */ @@ -1498,12 +1495,12 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, */ data->set.localportrange = (int) va_arg(param, long); break; - case CURLOPT_KRB4LEVEL: + case CURLOPT_KRBLEVEL: /* - * A string that defines the krb4 security level. + * A string that defines the kerberos security level. */ - data->set.krb4_level = va_arg(param, char *); - data->set.krb4 = (bool)(NULL != data->set.krb4_level); + data->set.krb_level = va_arg(param, char *); + data->set.krb = (bool)(NULL != data->set.krb_level); break; case CURLOPT_SSL_VERIFYPEER: /* diff --git a/lib/urldata.h b/lib/urldata.h index bd012b817..29000b210 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -133,8 +133,8 @@ We prefix with CURL to prevent name collisions. */ #define CURLMAX(x,y) ((x)>(y)?(x):(y)) -#ifdef HAVE_KRB4 -/* Types needed for krb4-ftp connections */ +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) +/* Types needed for krb4/5-ftp connections */ struct krb4buffer { void *data; size_t size; @@ -145,7 +145,8 @@ enum protection_level { prot_clear, prot_safe, prot_confidential, - prot_private + prot_private, + prot_cmd }; #endif @@ -882,8 +883,8 @@ struct connectdata { char *cookiehost; /* free later if not NULL */ } allocptr; - int sec_complete; /* if krb4 is enabled for this connection */ -#ifdef HAVE_KRB4 + int sec_complete; /* if kerberos is enabled for this connection */ +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) enum protection_level command_prot; enum protection_level data_prot; enum protection_level request_data_prot; @@ -1277,7 +1278,7 @@ struct UserDefined { * to which to send the authorization data to, and no other * host (which location-following otherwise could lead to) */ - char *krb4_level; /* what security level */ + char *krb_level; /* what security level */ struct ssl_config_data ssl; /* user defined SSL stuff */ curl_proxytype proxytype; /* what kind of proxy that is in use */ @@ -1331,7 +1332,7 @@ struct UserDefined { char *netrc_file; /* if not NULL, use this instead of trying to find $HOME/.netrc */ bool verbose; - bool krb4; /* kerberos4 connection requested */ + bool krb; /* kerberos connection requested */ bool reuse_forbid; /* forbidden to be reused, close after use */ bool reuse_fresh; /* do not re-use an existing connection */ bool ftp_use_epsv; /* if EPSV is to be attempted or not */ diff --git a/src/main.c b/src/main.c index 1869ce16e..17a961ee0 100644 --- a/src/main.c +++ b/src/main.c @@ -411,7 +411,7 @@ struct Configurable { bool list_engines; bool crlf; char *customrequest; - char *krb4level; + char *krblevel; char *trace_dump; /* file to dump the network trace to, or NULL */ FILE *trace_stream; bool trace_fopened; @@ -665,7 +665,7 @@ static void help(void) " -I/--head Show document info only", " -j/--junk-session-cookies Ignore session cookies read from file (H)", " --interface Specify network interface/address to use", - " --krb4 Enable krb4 with specified security level (F)", + " --krb Enable kerberos with specified security level (F)", " -k/--insecure Allow connections to SSL sites without certs (H)", " -K/--config Specify which config file to read", " --libcurl Dump libcurl equivalent code of this command line", @@ -1476,7 +1476,8 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ {"*u", "crlf", FALSE}, {"*v", "stderr", TRUE}, {"*w", "interface", TRUE}, - {"*x", "krb4", TRUE}, + {"*x", "krb" , TRUE}, + {"*x", "krb4" , TRUE}, /* this is the previous name */ {"*y", "max-filesize", TRUE}, {"*z", "disable-eprt", FALSE}, {"$a", "ftp-ssl", FALSE}, @@ -1809,10 +1810,10 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */ /* interface */ GetStr(&config->iface, nextarg); break; - case 'x': /* --krb4 */ - /* krb4 level string */ - if(curlinfo->features & CURL_VERSION_KERBEROS4) - GetStr(&config->krb4level, nextarg); + case 'x': /* --krb */ + /* kerberos level string */ + if(curlinfo->features & (CURL_VERSION_KERBEROS4 | CURL_VERSION_GSSNEGOTIATE)) + GetStr(&config->krblevel, nextarg); else return PARAM_LIBCURL_DOESNT_SUPPORT; break; @@ -3260,8 +3261,8 @@ static void free_config_fields(struct Configurable *config) free(config->cookie); if(config->cookiefile) free(config->cookiefile); - if(config->krb4level) - free(config->krb4level); + if(config->krblevel) + free(config->krblevel); if(config->headerfile) free(config->headerfile); if(config->ftpport) @@ -4221,7 +4222,7 @@ operate(struct Configurable *config, int argc, argv_item_t argv[]) /* three new ones in libcurl 7.3: */ my_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, config->proxytunnel); my_setopt(curl, CURLOPT_INTERFACE, config->iface); - my_setopt(curl, CURLOPT_KRB4LEVEL, config->krb4level); + my_setopt(curl, CURLOPT_KRBLEVEL, config->krblevel); progressbarinit(&progressbar, config); if((config->progressmode == CURL_PROGRESS_BAR) && -- cgit v1.2.3