From 5b6e7927c6891d93edc16695ae786dc686274bab Mon Sep 17 00:00:00 2001 From: Yang Tse Date: Thu, 3 Jan 2013 06:13:18 +0100 Subject: build: rename 93 lib/*.c files 93 lib/*.c source files renamed to use our standard naming scheme. This commit only does the file renaming. ---------------------------------------- renamed: lib/amigaos.c -> lib/curl_amigaos.c renamed: lib/asyn-ares.c -> lib/curl_asyn_ares.c renamed: lib/asyn-thread.c -> lib/curl_asyn_thread.c renamed: lib/axtls.c -> lib/curl_axtls.c renamed: lib/base64.c -> lib/curl_base64.c renamed: lib/bundles.c -> lib/curl_bundles.c renamed: lib/conncache.c -> lib/curl_conncache.c renamed: lib/connect.c -> lib/curl_connect.c renamed: lib/content_encoding.c -> lib/curl_content_encoding.c renamed: lib/cookie.c -> lib/curl_cookie.c renamed: lib/cyassl.c -> lib/curl_cyassl.c renamed: lib/dict.c -> lib/curl_dict.c renamed: lib/easy.c -> lib/curl_easy.c renamed: lib/escape.c -> lib/curl_escape.c renamed: lib/file.c -> lib/curl_file.c renamed: lib/fileinfo.c -> lib/curl_fileinfo.c renamed: lib/formdata.c -> lib/curl_formdata.c renamed: lib/ftp.c -> lib/curl_ftp.c renamed: lib/ftplistparser.c -> lib/curl_ftplistparser.c renamed: lib/getenv.c -> lib/curl_getenv.c renamed: lib/getinfo.c -> lib/curl_getinfo.c renamed: lib/gopher.c -> lib/curl_gopher.c renamed: lib/gtls.c -> lib/curl_gtls.c renamed: lib/hash.c -> lib/curl_hash.c renamed: lib/hmac.c -> lib/curl_hmac.c renamed: lib/hostasyn.c -> lib/curl_hostasyn.c renamed: lib/hostcheck.c -> lib/curl_hostcheck.c renamed: lib/hostip.c -> lib/curl_hostip.c renamed: lib/hostip4.c -> lib/curl_hostip4.c renamed: lib/hostip6.c -> lib/curl_hostip6.c renamed: lib/hostsyn.c -> lib/curl_hostsyn.c renamed: lib/http.c -> lib/curl_http.c renamed: lib/http_chunks.c -> lib/curl_http_chunks.c renamed: lib/http_digest.c -> lib/curl_http_digest.c renamed: lib/http_negotiate.c -> lib/curl_http_negotiate.c renamed: lib/http_negotiate_sspi.c -> lib/curl_http_negotiate_sspi.c renamed: lib/http_proxy.c -> lib/curl_http_proxy.c renamed: lib/idn_win32.c -> lib/curl_idn_win32.c renamed: lib/if2ip.c -> lib/curl_if2ip.c renamed: lib/imap.c -> lib/curl_imap.c renamed: lib/inet_ntop.c -> lib/curl_inet_ntop.c renamed: lib/inet_pton.c -> lib/curl_inet_pton.c renamed: lib/krb4.c -> lib/curl_krb4.c renamed: lib/krb5.c -> lib/curl_krb5.c renamed: lib/ldap.c -> lib/curl_ldap.c renamed: lib/llist.c -> lib/curl_llist.c renamed: lib/md4.c -> lib/curl_md4.c renamed: lib/md5.c -> lib/curl_md5.c renamed: lib/memdebug.c -> lib/curl_memdebug.c renamed: lib/mprintf.c -> lib/curl_mprintf.c renamed: lib/multi.c -> lib/curl_multi.c renamed: lib/netrc.c -> lib/curl_netrc.c renamed: lib/non-ascii.c -> lib/curl_non_ascii.c renamed: lib/curl_non-ascii.h -> lib/curl_non_ascii.h renamed: lib/nonblock.c -> lib/curl_nonblock.c renamed: lib/nss.c -> lib/curl_nss.c renamed: lib/nwlib.c -> lib/curl_nwlib.c renamed: lib/nwos.c -> lib/curl_nwos.c renamed: lib/openldap.c -> lib/curl_openldap.c renamed: lib/parsedate.c -> lib/curl_parsedate.c renamed: lib/pingpong.c -> lib/curl_pingpong.c renamed: lib/polarssl.c -> lib/curl_polarssl.c renamed: lib/pop3.c -> lib/curl_pop3.c renamed: lib/progress.c -> lib/curl_progress.c renamed: lib/qssl.c -> lib/curl_qssl.c renamed: lib/rawstr.c -> lib/curl_rawstr.c renamed: lib/rtsp.c -> lib/curl_rtsp.c renamed: lib/security.c -> lib/curl_security.c renamed: lib/select.c -> lib/curl_select.c renamed: lib/sendf.c -> lib/curl_sendf.c renamed: lib/share.c -> lib/curl_share.c renamed: lib/slist.c -> lib/curl_slist.c renamed: lib/smtp.c -> lib/curl_smtp.c renamed: lib/socks.c -> lib/curl_socks.c renamed: lib/socks_gssapi.c -> lib/curl_socks_gssapi.c renamed: lib/socks_sspi.c -> lib/curl_socks_sspi.c renamed: lib/speedcheck.c -> lib/curl_speedcheck.c renamed: lib/splay.c -> lib/curl_splay.c renamed: lib/ssh.c -> lib/curl_ssh.c renamed: lib/sslgen.c -> lib/curl_sslgen.c renamed: lib/ssluse.c -> lib/curl_ssluse.c renamed: lib/strdup.c -> lib/curl_strdup.c renamed: lib/strequal.c -> lib/curl_strequal.c renamed: lib/strerror.c -> lib/curl_strerror.c renamed: lib/strtok.c -> lib/curl_strtok.c renamed: lib/strtoofft.c -> lib/curl_strtoofft.c renamed: lib/telnet.c -> lib/curl_telnet.c renamed: lib/tftp.c -> lib/curl_tftp.c renamed: lib/timeval.c -> lib/curl_timeval.c renamed: lib/transfer.c -> lib/curl_transfer.c renamed: lib/url.c -> lib/curl_url.c renamed: lib/version.c -> lib/curl_version.c renamed: lib/warnless.c -> lib/curl_warnless.c renamed: lib/wildcard.c -> lib/curl_wildcard.c ---------------------------------------- --- lib/amigaos.c | 77 - lib/asyn-ares.c | 639 ----- lib/asyn-thread.c | 679 ----- lib/axtls.c | 547 ---- lib/base64.c | 248 -- lib/bundles.c | 110 - lib/conncache.c | 285 --- lib/connect.c | 1248 --------- lib/content_encoding.c | 435 ---- lib/cookie.c | 1163 --------- lib/curl_amigaos.c | 77 + lib/curl_asyn_ares.c | 639 +++++ lib/curl_asyn_thread.c | 679 +++++ lib/curl_axtls.c | 547 ++++ lib/curl_base64.c | 248 ++ lib/curl_bundles.c | 110 + lib/curl_conncache.c | 285 +++ lib/curl_connect.c | 1248 +++++++++ lib/curl_content_encoding.c | 435 ++++ lib/curl_cookie.c | 1163 +++++++++ lib/curl_cyassl.c | 611 +++++ lib/curl_dict.c | 284 +++ lib/curl_easy.c | 897 +++++++ lib/curl_escape.c | 233 ++ lib/curl_file.c | 591 +++++ lib/curl_fileinfo.c | 54 + lib/curl_formdata.c | 1494 +++++++++++ lib/curl_ftp.c | 4596 ++++++++++++++++++++++++++++++++++ lib/curl_ftplistparser.c | 1050 ++++++++ lib/curl_getenv.c | 61 + lib/curl_getinfo.c | 330 +++ lib/curl_gopher.c | 169 ++ lib/curl_gtls.c | 1118 +++++++++ lib/curl_hash.c | 400 +++ lib/curl_hmac.c | 133 + lib/curl_hostasyn.c | 157 ++ lib/curl_hostcheck.c | 96 + lib/curl_hostip.c | 820 ++++++ lib/curl_hostip4.c | 310 +++ lib/curl_hostip6.c | 224 ++ lib/curl_hostsyn.c | 75 + lib/curl_http.c | 3506 ++++++++++++++++++++++++++ lib/curl_http_chunks.c | 397 +++ lib/curl_http_digest.c | 583 +++++ lib/curl_http_negotiate.c | 373 +++ lib/curl_http_negotiate_sspi.c | 308 +++ lib/curl_http_proxy.c | 595 +++++ lib/curl_idn_win32.c | 85 + lib/curl_if2ip.c | 189 ++ lib/curl_imap.c | 1139 +++++++++ lib/curl_inet_ntop.c | 199 ++ lib/curl_inet_pton.c | 234 ++ lib/curl_krb4.c | 440 ++++ lib/curl_krb5.c | 341 +++ lib/curl_ldap.c | 725 ++++++ lib/curl_llist.c | 212 ++ lib/curl_md4.c | 282 +++ lib/curl_md5.c | 521 ++++ lib/curl_memdebug.c | 445 ++++ lib/curl_mprintf.c | 1197 +++++++++ lib/curl_multi.c | 2814 +++++++++++++++++++++ lib/curl_netrc.c | 186 ++ lib/curl_non-ascii.h | 63 - lib/curl_non_ascii.c | 343 +++ lib/curl_non_ascii.h | 63 + lib/curl_nonblock.c | 91 + lib/curl_nss.c | 1572 ++++++++++++ lib/curl_nwlib.c | 329 +++ lib/curl_nwos.c | 88 + lib/curl_openldap.c | 652 +++++ lib/curl_parsedate.c | 580 +++++ lib/curl_pingpong.c | 538 ++++ lib/curl_polarssl.c | 596 +++++ lib/curl_pop3.c | 1764 +++++++++++++ lib/curl_progress.c | 474 ++++ lib/curl_qssl.c | 501 ++++ lib/curl_rawstr.c | 142 ++ lib/curl_rtsp.c | 807 ++++++ lib/curl_security.c | 604 +++++ lib/curl_select.c | 529 ++++ lib/curl_sendf.c | 687 +++++ lib/curl_share.c | 254 ++ lib/curl_slist.c | 127 + lib/curl_smtp.c | 1750 +++++++++++++ lib/curl_socks.c | 744 ++++++ lib/curl_socks_gssapi.c | 533 ++++ lib/curl_socks_sspi.c | 591 +++++ lib/curl_speedcheck.c | 74 + lib/curl_splay.c | 288 +++ lib/curl_ssh.c | 3310 ++++++++++++++++++++++++ lib/curl_sslgen.c | 541 ++++ lib/curl_ssluse.c | 2736 ++++++++++++++++++++ lib/curl_strdup.c | 52 + lib/curl_strequal.c | 124 + lib/curl_strerror.c | 1119 +++++++++ lib/curl_strtok.c | 66 + lib/curl_strtoofft.c | 188 ++ lib/curl_telnet.c | 1678 +++++++++++++ lib/curl_tftp.c | 1500 +++++++++++ lib/curl_timeval.c | 134 + lib/curl_transfer.c | 2338 +++++++++++++++++ lib/curl_url.c | 5423 ++++++++++++++++++++++++++++++++++++++++ lib/curl_version.c | 343 +++ lib/curl_warnless.c | 431 ++++ lib/curl_wildcard.c | 77 + lib/cyassl.c | 611 ----- lib/dict.c | 284 --- lib/easy.c | 897 ------- lib/escape.c | 233 -- lib/file.c | 591 ----- lib/fileinfo.c | 54 - lib/formdata.c | 1494 ----------- lib/ftp.c | 4596 ---------------------------------- lib/ftplistparser.c | 1050 -------- lib/getenv.c | 61 - lib/getinfo.c | 330 --- lib/gopher.c | 169 -- lib/gtls.c | 1118 --------- lib/hash.c | 400 --- lib/hmac.c | 133 - lib/hostasyn.c | 157 -- lib/hostcheck.c | 96 - lib/hostip.c | 820 ------ lib/hostip4.c | 310 --- lib/hostip6.c | 224 -- lib/hostsyn.c | 75 - lib/http.c | 3506 -------------------------- lib/http_chunks.c | 397 --- lib/http_digest.c | 583 ----- lib/http_negotiate.c | 373 --- lib/http_negotiate_sspi.c | 308 --- lib/http_proxy.c | 595 ----- lib/idn_win32.c | 85 - lib/if2ip.c | 189 -- lib/imap.c | 1139 --------- lib/inet_ntop.c | 199 -- lib/inet_pton.c | 234 -- lib/krb4.c | 440 ---- lib/krb5.c | 341 --- lib/ldap.c | 725 ------ lib/llist.c | 212 -- lib/md4.c | 282 --- lib/md5.c | 521 ---- lib/memdebug.c | 445 ---- lib/mprintf.c | 1197 --------- lib/multi.c | 2814 --------------------- lib/netrc.c | 186 -- lib/non-ascii.c | 343 --- lib/nonblock.c | 91 - lib/nss.c | 1572 ------------ lib/nwlib.c | 329 --- lib/nwos.c | 88 - lib/openldap.c | 652 ----- lib/parsedate.c | 580 ----- lib/pingpong.c | 538 ---- lib/polarssl.c | 596 ----- lib/pop3.c | 1764 ------------- lib/progress.c | 474 ---- lib/qssl.c | 501 ---- lib/rawstr.c | 142 -- lib/rtsp.c | 807 ------ lib/security.c | 604 ----- lib/select.c | 529 ---- lib/sendf.c | 687 ----- lib/share.c | 254 -- lib/slist.c | 127 - lib/smtp.c | 1750 ------------- lib/socks.c | 744 ------ lib/socks_gssapi.c | 533 ---- lib/socks_sspi.c | 591 ----- lib/speedcheck.c | 74 - lib/splay.c | 288 --- lib/ssh.c | 3310 ------------------------ lib/sslgen.c | 541 ---- lib/ssluse.c | 2736 -------------------- lib/strdup.c | 52 - lib/strequal.c | 124 - lib/strerror.c | 1119 --------- lib/strtok.c | 66 - lib/strtoofft.c | 188 -- lib/telnet.c | 1678 ------------- lib/tftp.c | 1500 ----------- lib/timeval.c | 134 - lib/transfer.c | 2338 ----------------- lib/url.c | 5423 ---------------------------------------- lib/version.c | 343 --- lib/warnless.c | 431 ---- lib/wildcard.c | 77 - 188 files changed, 68686 insertions(+), 68686 deletions(-) delete mode 100644 lib/amigaos.c delete mode 100644 lib/asyn-ares.c delete mode 100644 lib/asyn-thread.c delete mode 100644 lib/axtls.c delete mode 100644 lib/base64.c delete mode 100644 lib/bundles.c delete mode 100644 lib/conncache.c delete mode 100644 lib/connect.c delete mode 100644 lib/content_encoding.c delete mode 100644 lib/cookie.c create mode 100644 lib/curl_amigaos.c create mode 100644 lib/curl_asyn_ares.c create mode 100644 lib/curl_asyn_thread.c create mode 100644 lib/curl_axtls.c create mode 100644 lib/curl_base64.c create mode 100644 lib/curl_bundles.c create mode 100644 lib/curl_conncache.c create mode 100644 lib/curl_connect.c create mode 100644 lib/curl_content_encoding.c create mode 100644 lib/curl_cookie.c create mode 100644 lib/curl_cyassl.c create mode 100644 lib/curl_dict.c create mode 100644 lib/curl_easy.c create mode 100644 lib/curl_escape.c create mode 100644 lib/curl_file.c create mode 100644 lib/curl_fileinfo.c create mode 100644 lib/curl_formdata.c create mode 100644 lib/curl_ftp.c create mode 100644 lib/curl_ftplistparser.c create mode 100644 lib/curl_getenv.c create mode 100644 lib/curl_getinfo.c create mode 100644 lib/curl_gopher.c create mode 100644 lib/curl_gtls.c create mode 100644 lib/curl_hash.c create mode 100644 lib/curl_hmac.c create mode 100644 lib/curl_hostasyn.c create mode 100644 lib/curl_hostcheck.c create mode 100644 lib/curl_hostip.c create mode 100644 lib/curl_hostip4.c create mode 100644 lib/curl_hostip6.c create mode 100644 lib/curl_hostsyn.c create mode 100644 lib/curl_http.c create mode 100644 lib/curl_http_chunks.c create mode 100644 lib/curl_http_digest.c create mode 100644 lib/curl_http_negotiate.c create mode 100644 lib/curl_http_negotiate_sspi.c create mode 100644 lib/curl_http_proxy.c create mode 100644 lib/curl_idn_win32.c create mode 100644 lib/curl_if2ip.c create mode 100644 lib/curl_imap.c create mode 100644 lib/curl_inet_ntop.c create mode 100644 lib/curl_inet_pton.c create mode 100644 lib/curl_krb4.c create mode 100644 lib/curl_krb5.c create mode 100644 lib/curl_ldap.c create mode 100644 lib/curl_llist.c create mode 100644 lib/curl_md4.c create mode 100644 lib/curl_md5.c create mode 100644 lib/curl_memdebug.c create mode 100644 lib/curl_mprintf.c create mode 100644 lib/curl_multi.c create mode 100644 lib/curl_netrc.c delete mode 100644 lib/curl_non-ascii.h create mode 100644 lib/curl_non_ascii.c create mode 100644 lib/curl_non_ascii.h create mode 100644 lib/curl_nonblock.c create mode 100644 lib/curl_nss.c create mode 100644 lib/curl_nwlib.c create mode 100644 lib/curl_nwos.c create mode 100644 lib/curl_openldap.c create mode 100644 lib/curl_parsedate.c create mode 100644 lib/curl_pingpong.c create mode 100644 lib/curl_polarssl.c create mode 100644 lib/curl_pop3.c create mode 100644 lib/curl_progress.c create mode 100644 lib/curl_qssl.c create mode 100644 lib/curl_rawstr.c create mode 100644 lib/curl_rtsp.c create mode 100644 lib/curl_security.c create mode 100644 lib/curl_select.c create mode 100644 lib/curl_sendf.c create mode 100644 lib/curl_share.c create mode 100644 lib/curl_slist.c create mode 100644 lib/curl_smtp.c create mode 100644 lib/curl_socks.c create mode 100644 lib/curl_socks_gssapi.c create mode 100644 lib/curl_socks_sspi.c create mode 100644 lib/curl_speedcheck.c create mode 100644 lib/curl_splay.c create mode 100644 lib/curl_ssh.c create mode 100644 lib/curl_sslgen.c create mode 100644 lib/curl_ssluse.c create mode 100644 lib/curl_strdup.c create mode 100644 lib/curl_strequal.c create mode 100644 lib/curl_strerror.c create mode 100644 lib/curl_strtok.c create mode 100644 lib/curl_strtoofft.c create mode 100644 lib/curl_telnet.c create mode 100644 lib/curl_tftp.c create mode 100644 lib/curl_timeval.c create mode 100644 lib/curl_transfer.c create mode 100644 lib/curl_url.c create mode 100644 lib/curl_version.c create mode 100644 lib/curl_warnless.c create mode 100644 lib/curl_wildcard.c delete mode 100644 lib/cyassl.c delete mode 100644 lib/dict.c delete mode 100644 lib/easy.c delete mode 100644 lib/escape.c delete mode 100644 lib/file.c delete mode 100644 lib/fileinfo.c delete mode 100644 lib/formdata.c delete mode 100644 lib/ftp.c delete mode 100644 lib/ftplistparser.c delete mode 100644 lib/getenv.c delete mode 100644 lib/getinfo.c delete mode 100644 lib/gopher.c delete mode 100644 lib/gtls.c delete mode 100644 lib/hash.c delete mode 100644 lib/hmac.c delete mode 100644 lib/hostasyn.c delete mode 100644 lib/hostcheck.c delete mode 100644 lib/hostip.c delete mode 100644 lib/hostip4.c delete mode 100644 lib/hostip6.c delete mode 100644 lib/hostsyn.c delete mode 100644 lib/http.c delete mode 100644 lib/http_chunks.c delete mode 100644 lib/http_digest.c delete mode 100644 lib/http_negotiate.c delete mode 100644 lib/http_negotiate_sspi.c delete mode 100644 lib/http_proxy.c delete mode 100644 lib/idn_win32.c delete mode 100644 lib/if2ip.c delete mode 100644 lib/imap.c delete mode 100644 lib/inet_ntop.c delete mode 100644 lib/inet_pton.c delete mode 100644 lib/krb4.c delete mode 100644 lib/krb5.c delete mode 100644 lib/ldap.c delete mode 100644 lib/llist.c delete mode 100644 lib/md4.c delete mode 100644 lib/md5.c delete mode 100644 lib/memdebug.c delete mode 100644 lib/mprintf.c delete mode 100644 lib/multi.c delete mode 100644 lib/netrc.c delete mode 100644 lib/non-ascii.c delete mode 100644 lib/nonblock.c delete mode 100644 lib/nss.c delete mode 100644 lib/nwlib.c delete mode 100644 lib/nwos.c delete mode 100644 lib/openldap.c delete mode 100644 lib/parsedate.c delete mode 100644 lib/pingpong.c delete mode 100644 lib/polarssl.c delete mode 100644 lib/pop3.c delete mode 100644 lib/progress.c delete mode 100644 lib/qssl.c delete mode 100644 lib/rawstr.c delete mode 100644 lib/rtsp.c delete mode 100644 lib/security.c delete mode 100644 lib/select.c delete mode 100644 lib/sendf.c delete mode 100644 lib/share.c delete mode 100644 lib/slist.c delete mode 100644 lib/smtp.c delete mode 100644 lib/socks.c delete mode 100644 lib/socks_gssapi.c delete mode 100644 lib/socks_sspi.c delete mode 100644 lib/speedcheck.c delete mode 100644 lib/splay.c delete mode 100644 lib/ssh.c delete mode 100644 lib/sslgen.c delete mode 100644 lib/ssluse.c delete mode 100644 lib/strdup.c delete mode 100644 lib/strequal.c delete mode 100644 lib/strerror.c delete mode 100644 lib/strtok.c delete mode 100644 lib/strtoofft.c delete mode 100644 lib/telnet.c delete mode 100644 lib/tftp.c delete mode 100644 lib/timeval.c delete mode 100644 lib/transfer.c delete mode 100644 lib/url.c delete mode 100644 lib/version.c delete mode 100644 lib/warnless.c delete mode 100644 lib/wildcard.c (limited to 'lib') diff --git a/lib/amigaos.c b/lib/amigaos.c deleted file mode 100644 index c726abb68..000000000 --- a/lib/amigaos.c +++ /dev/null @@ -1,77 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(__AMIGA__) && !defined(__ixemul__) - -#include - -#include "curl_amigaos.h" - -struct Library *SocketBase = NULL; -extern int errno, h_errno; - -#ifdef __libnix__ -#include -void __request(const char *msg); -#else -# define __request( msg ) Printf( msg "\n\a") -#endif - -void Curl_amiga_cleanup() -{ - if(SocketBase) { - CloseLibrary(SocketBase); - SocketBase = NULL; - } -} - -bool Curl_amiga_init() -{ - if(!SocketBase) - SocketBase = OpenLibrary("bsdsocket.library", 4); - - if(!SocketBase) { - __request("No TCP/IP Stack running!"); - return FALSE; - } - - if(SocketBaseTags(SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(errno))), (ULONG) &errno, - SBTM_SETVAL(SBTC_LOGTAGPTR), (ULONG) "cURL", - TAG_DONE)) { - __request("SocketBaseTags ERROR"); - return FALSE; - } - -#ifndef __libnix__ - atexit(Curl_amiga_cleanup); -#endif - - return TRUE; -} - -#ifdef __libnix__ -ADD2EXIT(Curl_amiga_cleanup,-50); -#endif - -#endif /* __AMIGA__ && ! __ixemul__ */ diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c deleted file mode 100644 index 6e09e9b98..000000000 --- a/lib/asyn-ares.c +++ /dev/null @@ -1,639 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_LIMITS_H -#include -#endif -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#ifdef HAVE_PROCESS_H -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -/*********************************************************************** - * Only for ares-enabled builds - * And only for functions that fulfill the asynch resolver backend API - * as defined in curl_asyn.h, nothing else belongs in this file! - **********************************************************************/ - -#ifdef CURLRES_ARES - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_hostip.h" -#include "curl_hash.h" -#include "curl_share.h" -#include "curl_strerror.h" -#include "curl_url.h" -#include "curl_multiif.h" -#include "curl_inet_pton.h" -#include "curl_connect.h" -#include "curl_select.h" -#include "curl_progress.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ - (defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__)) -# define CARES_STATICLIB -# endif -# include -# include /* really old c-ares didn't include this by - itself */ - -#if ARES_VERSION >= 0x010500 -/* c-ares 1.5.0 or later, the callback proto is modified */ -#define HAVE_CARES_CALLBACK_TIMEOUTS 1 -#endif - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -struct ResolverResults { - int num_pending; /* number of ares_gethostbyname() requests */ - Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares parts */ - int last_status; -}; - -/* - * Curl_resolver_global_init() - the generic low-level asynchronous name - * resolve API. Called from curl_global_init() to initialize global resolver - * environment. Initializes ares library. - */ -int Curl_resolver_global_init(void) -{ -#ifdef CARES_HAVE_ARES_LIBRARY_INIT - if(ares_library_init(ARES_LIB_INIT_ALL)) { - return CURLE_FAILED_INIT; - } -#endif - return CURLE_OK; -} - -/* - * Curl_resolver_global_cleanup() - * - * Called from curl_global_cleanup() to destroy global resolver environment. - * Deinitializes ares library. - */ -void Curl_resolver_global_cleanup(void) -{ -#ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP - ares_library_cleanup(); -#endif -} - -/* - * Curl_resolver_init() - * - * Called from curl_easy_init() -> Curl_open() to initialize resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Fills the passed pointer by the initialized ares_channel. - */ -CURLcode Curl_resolver_init(void **resolver) -{ - int status = ares_init((ares_channel*)resolver); - if(status != ARES_SUCCESS) { - if(status == ARES_ENOMEM) - return CURLE_OUT_OF_MEMORY; - else - return CURLE_FAILED_INIT; - } - return CURLE_OK; - /* make sure that all other returns from this function should destroy the - ares channel before returning error! */ -} - -/* - * Curl_resolver_cleanup() - * - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Destroys the ares channel. - */ -void Curl_resolver_cleanup(void *resolver) -{ - ares_destroy((ares_channel)resolver); -} - -/* - * Curl_resolver_duphandle() - * - * Called from curl_easy_duphandle() to duplicate resolver URL-state specific - * environment ('resolver' member of the UrlState structure). Duplicates the - * 'from' ares channel and passes the resulting channel to the 'to' pointer. - */ -int Curl_resolver_duphandle(void **to, void *from) -{ - /* Clone the ares channel for the new handle */ - if(ARES_SUCCESS != ares_dup((ares_channel*)to,(ares_channel)from)) - return CURLE_FAILED_INIT; - return CURLE_OK; -} - -static void destroy_async_data (struct Curl_async *async); - -/* - * Cancel all possibly still on-going resolves for this connection. - */ -void Curl_resolver_cancel(struct connectdata *conn) -{ - if(conn && conn->data && conn->data->state.resolver) - ares_cancel((ares_channel)conn->data->state.resolver); - destroy_async_data(&conn->async); -} - -/* - * destroy_async_data() cleans up async resolver data. - */ -static void destroy_async_data (struct Curl_async *async) -{ - if(async->hostname) - free(async->hostname); - - if(async->os_specific) { - struct ResolverResults *res = (struct ResolverResults *)async->os_specific; - if(res) { - if(res->temp_ai) { - Curl_freeaddrinfo(res->temp_ai); - res->temp_ai = NULL; - } - free(res); - } - async->os_specific = NULL; - } - - async->hostname = NULL; -} - -/* - * Curl_resolver_fdset() is called when someone from the outside world (using - * curl_multi_fdset()) wants to get our fd_set setup and we're talking with - * ares. The caller must make sure that this function is only called when we - * have a working ares channel. - * - * Returns: CURLE_OK always! - */ - -int Curl_resolver_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) - -{ - struct timeval maxtime; - struct timeval timebuf; - struct timeval *timeout; - long milli; - int max = ares_getsock((ares_channel)conn->data->state.resolver, - (ares_socket_t *)socks, numsocks); - - maxtime.tv_sec = CURL_TIMEOUT_RESOLVE; - maxtime.tv_usec = 0; - - timeout = ares_timeout((ares_channel)conn->data->state.resolver, &maxtime, - &timebuf); - milli = (timeout->tv_sec * 1000) + (timeout->tv_usec/1000); - if(milli == 0) - milli += 10; - Curl_expire(conn->data, milli); - - return max; -} - -/* - * waitperform() - * - * 1) Ask ares what sockets it currently plays with, then - * 2) wait for the timeout period to check for action on ares' sockets. - * 3) tell ares to act on all the sockets marked as "with action" - * - * return number of sockets it worked on - */ - -static int waitperform(struct connectdata *conn, int timeout_ms) -{ - struct SessionHandle *data = conn->data; - int nfds; - int bitmask; - ares_socket_t socks[ARES_GETSOCK_MAXNUM]; - struct pollfd pfd[ARES_GETSOCK_MAXNUM]; - int i; - int num = 0; - - bitmask = ares_getsock((ares_channel)data->state.resolver, socks, - ARES_GETSOCK_MAXNUM); - - for(i=0; i < ARES_GETSOCK_MAXNUM; i++) { - pfd[i].events = 0; - pfd[i].revents = 0; - if(ARES_GETSOCK_READABLE(bitmask, i)) { - pfd[i].fd = socks[i]; - pfd[i].events |= POLLRDNORM|POLLIN; - } - if(ARES_GETSOCK_WRITABLE(bitmask, i)) { - pfd[i].fd = socks[i]; - pfd[i].events |= POLLWRNORM|POLLOUT; - } - if(pfd[i].events != 0) - num++; - else - break; - } - - if(num) - nfds = Curl_poll(pfd, num, timeout_ms); - else - nfds = 0; - - if(!nfds) - /* Call ares_process() unconditonally here, even if we simply timed out - above, as otherwise the ares name resolve won't timeout! */ - ares_process_fd((ares_channel)data->state.resolver, ARES_SOCKET_BAD, - ARES_SOCKET_BAD); - else { - /* move through the descriptors and ask for processing on them */ - for(i=0; i < num; i++) - ares_process_fd((ares_channel)data->state.resolver, - pfd[i].revents & (POLLRDNORM|POLLIN)? - pfd[i].fd:ARES_SOCKET_BAD, - pfd[i].revents & (POLLWRNORM|POLLOUT)? - pfd[i].fd:ARES_SOCKET_BAD); - } - return nfds; -} - -/* - * Curl_resolver_is_resolved() is called repeatedly to check if a previous - * name resolve request has completed. It should also make sure to time-out if - * the operation seems to take too long. - * - * Returns normal CURLcode errors. - */ -CURLcode Curl_resolver_is_resolved(struct connectdata *conn, - struct Curl_dns_entry **dns) -{ - struct SessionHandle *data = conn->data; - struct ResolverResults *res = (struct ResolverResults *) - conn->async.os_specific; - - *dns = NULL; - - waitperform(conn, 0); - - if(res && !res->num_pending) { - (void)Curl_addrinfo_callback(conn, res->last_status, res->temp_ai); - /* temp_ai ownership is moved to the connection, so we need not free-up - them */ - res->temp_ai = NULL; - destroy_async_data(&conn->async); - if(!conn->async.dns) { - failf(data, "Could not resolve %s: %s (%s)", - conn->bits.proxy?"proxy":"host", - conn->host.dispname, - ares_strerror(conn->async.status)); - return conn->bits.proxy?CURLE_COULDNT_RESOLVE_PROXY: - CURLE_COULDNT_RESOLVE_HOST; - } - *dns = conn->async.dns; - } - - return CURLE_OK; -} - -/* - * Curl_resolver_wait_resolv() - * - * waits for a resolve to finish. This function should be avoided since using - * this risk getting the multi interface to "hang". - * - * If 'entry' is non-NULL, make it point to the resolved dns entry - * - * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, and - * CURLE_OPERATION_TIMEDOUT if a time-out occurred. - */ -CURLcode Curl_resolver_wait_resolv(struct connectdata *conn, - struct Curl_dns_entry **entry) -{ - CURLcode rc=CURLE_OK; - struct SessionHandle *data = conn->data; - long timeout; - struct timeval now = Curl_tvnow(); - struct Curl_dns_entry *temp_entry; - - timeout = Curl_timeleft(data, &now, TRUE); - if(!timeout) - timeout = CURL_TIMEOUT_RESOLVE * 1000; /* default name resolve timeout */ - - /* Wait for the name resolve query to complete. */ - for(;;) { - struct timeval *tvp, tv, store; - long timediff; - int itimeout; - int timeout_ms; - - itimeout = (timeout > (long)INT_MAX) ? INT_MAX : (int)timeout; - - store.tv_sec = itimeout/1000; - store.tv_usec = (itimeout%1000)*1000; - - tvp = ares_timeout((ares_channel)data->state.resolver, &store, &tv); - - /* use the timeout period ares returned to us above if less than one - second is left, otherwise just use 1000ms to make sure the progress - callback gets called frequent enough */ - if(!tvp->tv_sec) - timeout_ms = (int)(tvp->tv_usec/1000); - else - timeout_ms = 1000; - - waitperform(conn, timeout_ms); - Curl_resolver_is_resolved(conn,&temp_entry); - - if(conn->async.done) - break; - - if(Curl_pgrsUpdate(conn)) { - rc = CURLE_ABORTED_BY_CALLBACK; - timeout = -1; /* trigger the cancel below */ - } - else { - struct timeval now2 = Curl_tvnow(); - timediff = Curl_tvdiff(now2, now); /* spent time */ - timeout -= timediff?timediff:1; /* always deduct at least 1 */ - now = now2; /* for next loop */ - } - if(timeout < 0) { - /* our timeout, so we cancel the ares operation */ - ares_cancel((ares_channel)data->state.resolver); - break; - } - } - - /* Operation complete, if the lookup was successful we now have the entry - in the cache. */ - - if(entry) - *entry = conn->async.dns; - - if(!conn->async.dns) { - /* a name was not resolved */ - if((timeout < 0) || (conn->async.status == ARES_ETIMEOUT)) { - if(conn->bits.proxy) { - failf(data, "Resolving proxy timed out: %s", conn->proxy.dispname); - rc = CURLE_COULDNT_RESOLVE_PROXY; - } - else { - failf(data, "Resolving host timed out: %s", conn->host.dispname); - rc = CURLE_COULDNT_RESOLVE_HOST; - } - } - else if(conn->async.done) { - if(conn->bits.proxy) { - failf(data, "Could not resolve proxy: %s (%s)", conn->proxy.dispname, - ares_strerror(conn->async.status)); - rc = CURLE_COULDNT_RESOLVE_PROXY; - } - else { - failf(data, "Could not resolve host: %s (%s)", conn->host.dispname, - ares_strerror(conn->async.status)); - rc = CURLE_COULDNT_RESOLVE_HOST; - } - } - else - rc = CURLE_OPERATION_TIMEDOUT; - - /* close the connection, since we can't return failure here without - cleaning up this connection properly */ - conn->bits.close = TRUE; - } - - return rc; -} - -/* Connects results to the list */ -static void compound_results(struct ResolverResults *res, - Curl_addrinfo *ai) -{ - Curl_addrinfo *ai_tail; - if(!ai) - return; - ai_tail = ai; - - while(ai_tail->ai_next) - ai_tail = ai_tail->ai_next; - - /* Add the new results to the list of old results. */ - ai_tail->ai_next = res->temp_ai; - res->temp_ai = ai; -} - -/* - * ares_query_completed_cb() is the callback that ares will call when - * the host query initiated by ares_gethostbyname() from Curl_getaddrinfo(), - * when using ares, is completed either successfully or with failure. - */ -static void query_completed_cb(void *arg, /* (struct connectdata *) */ - int status, -#ifdef HAVE_CARES_CALLBACK_TIMEOUTS - int timeouts, -#endif - struct hostent *hostent) -{ - struct connectdata *conn = (struct connectdata *)arg; - struct ResolverResults *res; - -#ifdef HAVE_CARES_CALLBACK_TIMEOUTS - (void)timeouts; /* ignored */ -#endif - - if(ARES_EDESTRUCTION == status) - /* when this ares handle is getting destroyed, the 'arg' pointer may not - be valid so only defer it when we know the 'status' says its fine! */ - return; - - res = (struct ResolverResults *)conn->async.os_specific; - res->num_pending--; - - if(CURL_ASYNC_SUCCESS == status) { - Curl_addrinfo *ai = Curl_he2ai(hostent, conn->async.port); - if(ai) { - compound_results(res, ai); - } - } - /* A successful result overwrites any previous error */ - if(res->last_status != ARES_SUCCESS) - res->last_status = status; -} - -/* - * Curl_resolver_getaddrinfo() - when using ares - * - * Returns name information about the given hostname and port number. If - * successful, the 'hostent' is returned and the forth argument will point to - * memory we need to free after use. That memory *MUST* be freed with - * Curl_freeaddrinfo(), nothing else. - */ -Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) -{ - char *bufp; - struct SessionHandle *data = conn->data; - struct in_addr in; - int family = PF_INET; -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ - struct in6_addr in6; -#endif /* CURLRES_IPV6 */ - - *waitp = 0; /* default to synchronous response */ - - /* First check if this is an IPv4 address string */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { - /* This is a dotted IP address 123.123.123.123-style */ - return Curl_ip2addr(AF_INET, &in, hostname, port); - } - -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ - /* Otherwise, check if this is an IPv6 address string */ - if(Curl_inet_pton (AF_INET6, hostname, &in6) > 0) - /* This must be an IPv6 address literal. */ - return Curl_ip2addr(AF_INET6, &in6, hostname, port); - - switch(conn->ip_version) { - default: -#if ARES_VERSION >= 0x010601 - family = PF_UNSPEC; /* supported by c-ares since 1.6.1, so for older - c-ares versions this just falls through and defaults - to PF_INET */ - break; -#endif - case CURL_IPRESOLVE_V4: - family = PF_INET; - break; - case CURL_IPRESOLVE_V6: - family = PF_INET6; - break; - } -#endif /* CURLRES_IPV6 */ - - bufp = strdup(hostname); - if(bufp) { - struct ResolverResults *res = NULL; - Curl_safefree(conn->async.hostname); - conn->async.hostname = bufp; - conn->async.port = port; - conn->async.done = FALSE; /* not done */ - conn->async.status = 0; /* clear */ - conn->async.dns = NULL; /* clear */ - res = calloc(sizeof(struct ResolverResults),1); - if(!res) { - Curl_safefree(conn->async.hostname); - conn->async.hostname = NULL; - return NULL; - } - conn->async.os_specific = res; - - /* initial status - failed */ - res->last_status = ARES_ENOTFOUND; -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ - if(family == PF_UNSPEC) { - if(Curl_ipv6works()) { - res->num_pending = 2; - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.resolver, hostname, - PF_INET, query_completed_cb, conn); - ares_gethostbyname((ares_channel)data->state.resolver, hostname, - PF_INET6, query_completed_cb, conn); - } - else { - res->num_pending = 1; - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.resolver, hostname, - PF_INET, query_completed_cb, conn); - } - } - else -#endif /* CURLRES_IPV6 */ - { - res->num_pending = 1; - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.resolver, hostname, family, - query_completed_cb, conn); - } - - *waitp = 1; /* expect asynchronous response */ - } - return NULL; /* no struct yet */ -} - -CURLcode Curl_set_dns_servers(struct SessionHandle *data, - char *servers) -{ - CURLcode result = CURLE_NOT_BUILT_IN; -#if (ARES_VERSION >= 0x010704) - int ares_result = ares_set_servers_csv(data->state.resolver, servers); - switch(ares_result) { - case ARES_SUCCESS: - result = CURLE_OK; - break; - case ARES_ENOMEM: - result = CURLE_OUT_OF_MEMORY; - break; - case ARES_ENOTINITIALIZED: - case ARES_ENODATA: - case ARES_EBADSTR: - default: - result = CURLE_BAD_FUNCTION_ARGUMENT; - break; - } -#else /* too old c-ares version! */ - (void)data; - (void)servers; -#endif - return result; -} -#endif /* CURLRES_ARES */ diff --git a/lib/asyn-thread.c b/lib/asyn-thread.c deleted file mode 100644 index 6d3667fab..000000000 --- a/lib/asyn-thread.c +++ /dev/null @@ -1,679 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if defined(USE_THREADS_POSIX) -# ifdef HAVE_PTHREAD_H -# include -# endif -#elif defined(USE_THREADS_WIN32) -# ifdef HAVE_PROCESS_H -# include -# endif -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -#ifdef HAVE_GETADDRINFO -# define RESOLVER_ENOMEM EAI_MEMORY -#else -# define RESOLVER_ENOMEM ENOMEM -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_hostip.h" -#include "curl_hash.h" -#include "curl_share.h" -#include "curl_strerror.h" -#include "curl_url.h" -#include "curl_multiif.h" -#include "curl_inet_pton.h" -#include "curl_inet_ntop.h" -#include "curl_threads.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/*********************************************************************** - * Only for threaded name resolves builds - **********************************************************************/ -#ifdef CURLRES_THREADED - -/* - * Curl_resolver_global_init() - * Called from curl_global_init() to initialize global resolver environment. - * Does nothing here. - */ -int Curl_resolver_global_init(void) -{ - return CURLE_OK; -} - -/* - * Curl_resolver_global_cleanup() - * Called from curl_global_cleanup() to destroy global resolver environment. - * Does nothing here. - */ -void Curl_resolver_global_cleanup(void) -{ -} - -/* - * Curl_resolver_init() - * Called from curl_easy_init() -> Curl_open() to initialize resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Does nothing here. - */ -CURLcode Curl_resolver_init(void **resolver) -{ - (void)resolver; - return CURLE_OK; -} - -/* - * Curl_resolver_cleanup() - * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver - * URL-state specific environment ('resolver' member of the UrlState - * structure). Does nothing here. - */ -void Curl_resolver_cleanup(void *resolver) -{ - (void)resolver; -} - -/* - * Curl_resolver_duphandle() - * Called from curl_easy_duphandle() to duplicate resolver URL state-specific - * environment ('resolver' member of the UrlState structure). Does nothing - * here. - */ -int Curl_resolver_duphandle(void **to, void *from) -{ - (void)to; - (void)from; - return CURLE_OK; -} - -static void destroy_async_data(struct Curl_async *); - -/* - * Cancel all possibly still on-going resolves for this connection. - */ -void Curl_resolver_cancel(struct connectdata *conn) -{ - destroy_async_data(&conn->async); -} - -/* This function is used to init a threaded resolve */ -static bool init_resolve_thread(struct connectdata *conn, - const char *hostname, int port, - const struct addrinfo *hints); - - -/* Data for synchronization between resolver thread and its parent */ -struct thread_sync_data { - curl_mutex_t * mtx; - int done; - - char * hostname; /* hostname to resolve, Curl_async.hostname - duplicate */ - int port; - int sock_error; - Curl_addrinfo *res; -#ifdef HAVE_GETADDRINFO - struct addrinfo hints; -#endif -}; - -struct thread_data { - curl_thread_t thread_hnd; - unsigned int poll_interval; - int interval_end; - struct thread_sync_data tsd; -}; - -static struct thread_sync_data *conn_thread_sync_data(struct connectdata *conn) -{ - return &(((struct thread_data *)conn->async.os_specific)->tsd); -} - -#define CONN_THREAD_SYNC_DATA(conn) &(((conn)->async.os_specific)->tsd); - -/* Destroy resolver thread synchronization data */ -static -void destroy_thread_sync_data(struct thread_sync_data * tsd) -{ - if(tsd->mtx) { - Curl_mutex_destroy(tsd->mtx); - free(tsd->mtx); - } - - if(tsd->hostname) - free(tsd->hostname); - - if(tsd->res) - Curl_freeaddrinfo(tsd->res); - - memset(tsd,0,sizeof(*tsd)); -} - -/* Initialize resolver thread synchronization data */ -static -int init_thread_sync_data(struct thread_sync_data * tsd, - const char * hostname, - int port, - const struct addrinfo *hints) -{ - memset(tsd, 0, sizeof(*tsd)); - - tsd->port = port; -#ifdef CURLRES_IPV6 - DEBUGASSERT(hints); - tsd->hints = *hints; -#else - (void) hints; -#endif - - tsd->mtx = malloc(sizeof(curl_mutex_t)); - if(tsd->mtx == NULL) - goto err_exit; - - Curl_mutex_init(tsd->mtx); - - tsd->sock_error = CURL_ASYNC_SUCCESS; - - /* Copying hostname string because original can be destroyed by parent - * thread during gethostbyname execution. - */ - tsd->hostname = strdup(hostname); - if(!tsd->hostname) - goto err_exit; - - return 1; - - err_exit: - /* Memory allocation failed */ - destroy_thread_sync_data(tsd); - return 0; -} - -static int getaddrinfo_complete(struct connectdata *conn) -{ - struct thread_sync_data *tsd = conn_thread_sync_data(conn); - int rc; - - rc = Curl_addrinfo_callback(conn, tsd->sock_error, tsd->res); - /* The tsd->res structure has been copied to async.dns and perhaps the DNS - cache. Set our copy to NULL so destroy_thread_sync_data doesn't free it. - */ - tsd->res = NULL; - - return rc; -} - - -#ifdef HAVE_GETADDRINFO - -/* - * getaddrinfo_thread() resolves a name and then exits. - * - * For builds without ARES, but with ENABLE_IPV6, create a resolver thread - * and wait on it. - */ -static unsigned int CURL_STDCALL getaddrinfo_thread (void *arg) -{ - struct thread_sync_data *tsd = (struct thread_sync_data*)arg; - char service [NI_MAXSERV]; - int rc; - - snprintf(service, sizeof(service), "%d", tsd->port); - - rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res); - - if(rc != 0) { - tsd->sock_error = SOCKERRNO?SOCKERRNO:rc; - if(tsd->sock_error == 0) - tsd->sock_error = RESOLVER_ENOMEM; - } - - Curl_mutex_acquire(tsd->mtx); - tsd->done = 1; - Curl_mutex_release(tsd->mtx); - - return 0; -} - -#else /* HAVE_GETADDRINFO */ - -/* - * gethostbyname_thread() resolves a name and then exits. - */ -static unsigned int CURL_STDCALL gethostbyname_thread (void *arg) -{ - struct thread_sync_data *tsd = (struct thread_sync_data *)arg; - - tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port); - - if(!tsd->res) { - tsd->sock_error = SOCKERRNO; - if(tsd->sock_error == 0) - tsd->sock_error = RESOLVER_ENOMEM; - } - - Curl_mutex_acquire(tsd->mtx); - tsd->done = 1; - Curl_mutex_release(tsd->mtx); - - return 0; -} - -#endif /* HAVE_GETADDRINFO */ - -/* - * destroy_async_data() cleans up async resolver data and thread handle. - */ -static void destroy_async_data (struct Curl_async *async) -{ - if(async->hostname) - free(async->hostname); - - if(async->os_specific) { - struct thread_data *td = (struct thread_data*) async->os_specific; - - if(td->thread_hnd != curl_thread_t_null) - Curl_thread_join(&td->thread_hnd); - - destroy_thread_sync_data(&td->tsd); - - free(async->os_specific); - } - async->hostname = NULL; - async->os_specific = NULL; -} - -/* - * init_resolve_thread() starts a new thread that performs the actual - * resolve. This function returns before the resolve is done. - * - * Returns FALSE in case of failure, otherwise TRUE. - */ -static bool init_resolve_thread (struct connectdata *conn, - const char *hostname, int port, - const struct addrinfo *hints) -{ - struct thread_data *td = calloc(1, sizeof(struct thread_data)); - int err = RESOLVER_ENOMEM; - - conn->async.os_specific = (void*) td; - if(!td) - goto err_exit; - - conn->async.port = port; - conn->async.done = FALSE; - conn->async.status = 0; - conn->async.dns = NULL; - td->thread_hnd = curl_thread_t_null; - - if(!init_thread_sync_data(&td->tsd, hostname, port, hints)) - goto err_exit; - - Curl_safefree(conn->async.hostname); - conn->async.hostname = strdup(hostname); - if(!conn->async.hostname) - goto err_exit; - -#ifdef HAVE_GETADDRINFO - td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd); -#else - td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd); -#endif - - if(!td->thread_hnd) { -#ifndef _WIN32_WCE - err = errno; -#endif - goto err_exit; - } - - return TRUE; - - err_exit: - destroy_async_data(&conn->async); - - SET_ERRNO(err); - - return FALSE; -} - -#if defined(HAVE_GETADDRINFO) && !defined(HAVE_GAI_STRERROR) && !defined(WIN32) -/* NetWare has getaddrinfo but lacks gai_strerror. - Windows has a gai_strerror but it is bad (not thread-safe) and the generic - socket error string function can be used for this pupose. */ -static const char *gai_strerror(int ecode) -{ - switch (ecode) { - case EAI_AGAIN: - return "The name could not be resolved at this time"; - case EAI_BADFLAGS: - return "The flags parameter had an invalid value"; - case EAI_FAIL: - return "A non-recoverable error occurred when attempting to " - "resolve the name"; - case EAI_FAMILY: - return "The address family was not recognized"; - case EAI_MEMORY: - return "Out of memory"; - case EAI_NONAME: - return "The name does not resolve for the supplied parameters"; - case EAI_SERVICE: - return "The service passed was not recognized for the " - "specified socket type" - case EAI_SOCKTYPE: - return "The intended socket type was not recognized" - case EAI_SYSTEM: - return "A system error occurred"; - case EAI_OVERFLOW: - return "An argument buffer overflowed"; - default: - return "Unknown error"; - -/* define this now as this is a private implementation of said function */ -#define HAVE_GAI_STRERROR -} -#endif - - -/* - * resolver_error() calls failf() with the appropriate message after a resolve - * error - */ - -static void resolver_error(struct connectdata *conn, const char *host_or_proxy) -{ - failf(conn->data, "Could not resolve %s: %s; %s", host_or_proxy, - conn->async.hostname, -#ifdef HAVE_GAI_STRERROR - /* NetWare doesn't have gai_strerror and on Windows it isn't deemed - thread-safe */ - gai_strerror(conn->async.status) -#else - Curl_strerror(conn, conn->async.status) -#endif - ); -} - -/* - * Curl_resolver_wait_resolv() - * - * waits for a resolve to finish. This function should be avoided since using - * this risk getting the multi interface to "hang". - * - * If 'entry' is non-NULL, make it point to the resolved dns entry - * - * This is the version for resolves-in-a-thread. - */ -CURLcode Curl_resolver_wait_resolv(struct connectdata *conn, - struct Curl_dns_entry **entry) -{ - struct thread_data *td = (struct thread_data*) conn->async.os_specific; - CURLcode rc = CURLE_OK; - - DEBUGASSERT(conn && td); - - /* wait for the thread to resolve the name */ - if(Curl_thread_join(&td->thread_hnd)) - rc = getaddrinfo_complete(conn); - else - DEBUGASSERT(0); - - conn->async.done = TRUE; - - if(entry) - *entry = conn->async.dns; - - if(!conn->async.dns) { - /* a name was not resolved */ - if(conn->bits.httpproxy) { - resolver_error(conn, "proxy"); - rc = CURLE_COULDNT_RESOLVE_PROXY; - } - else { - resolver_error(conn, "host"); - rc = CURLE_COULDNT_RESOLVE_HOST; - } - } - - destroy_async_data(&conn->async); - - if(!conn->async.dns) - conn->bits.close = TRUE; - - return (rc); -} - -/* - * Curl_resolver_is_resolved() is called repeatedly to check if a previous - * name resolve request has completed. It should also make sure to time-out if - * the operation seems to take too long. - */ -CURLcode Curl_resolver_is_resolved(struct connectdata *conn, - struct Curl_dns_entry **entry) -{ - struct SessionHandle *data = conn->data; - struct thread_data *td = (struct thread_data*) conn->async.os_specific; - int done = 0; - - *entry = NULL; - - if(!td) { - DEBUGASSERT(td); - return CURLE_COULDNT_RESOLVE_HOST; - } - - Curl_mutex_acquire(td->tsd.mtx); - done = td->tsd.done; - Curl_mutex_release(td->tsd.mtx); - - if(done) { - getaddrinfo_complete(conn); - destroy_async_data(&conn->async); - - if(!conn->async.dns) { - resolver_error(conn, "host"); - return CURLE_COULDNT_RESOLVE_HOST; - } - *entry = conn->async.dns; - } - else { - /* poll for name lookup done with exponential backoff up to 250ms */ - int elapsed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle); - if(elapsed < 0) - elapsed = 0; - - if(td->poll_interval == 0) - /* Start at 1ms poll interval */ - td->poll_interval = 1; - else if(elapsed >= td->interval_end) - /* Back-off exponentially if last interval expired */ - td->poll_interval *= 2; - - if(td->poll_interval > 250) - td->poll_interval = 250; - - td->interval_end = elapsed + td->poll_interval; - Curl_expire(conn->data, td->poll_interval); - } - - return CURLE_OK; -} - -int Curl_resolver_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - (void)conn; - (void)socks; - (void)numsocks; - return 0; -} - -#ifndef HAVE_GETADDRINFO -/* - * Curl_getaddrinfo() - for platforms without getaddrinfo - */ -Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) -{ - struct in_addr in; - - *waitp = 0; /* default to synchronous response */ - - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - return Curl_ip2addr(AF_INET, &in, hostname, port); - - /* fire up a new resolver thread! */ - if(init_resolve_thread(conn, hostname, port, NULL)) { - *waitp = 1; /* expect asynchronous response */ - return NULL; - } - - /* fall-back to blocking version */ - return Curl_ipv4_resolve_r(hostname, port); -} - -#else /* !HAVE_GETADDRINFO */ - -/* - * Curl_resolver_getaddrinfo() - for getaddrinfo - */ -Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) -{ - struct addrinfo hints; - struct in_addr in; - Curl_addrinfo *res; - int error; - char sbuf[NI_MAXSERV]; - int pf = PF_INET; -#ifdef CURLRES_IPV6 - struct in6_addr in6; -#endif /* CURLRES_IPV6 */ - - *waitp = 0; /* default to synchronous response */ - - /* First check if this is an IPv4 address string */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - return Curl_ip2addr(AF_INET, &in, hostname, port); - -#ifdef CURLRES_IPV6 - /* check if this is an IPv6 address string */ - if(Curl_inet_pton (AF_INET6, hostname, &in6) > 0) - /* This is an IPv6 address literal */ - return Curl_ip2addr(AF_INET6, &in6, hostname, port); - - /* - * Check if a limited name resolve has been requested. - */ - switch(conn->ip_version) { - case CURL_IPRESOLVE_V4: - pf = PF_INET; - break; - case CURL_IPRESOLVE_V6: - pf = PF_INET6; - break; - default: - pf = PF_UNSPEC; - break; - } - - if((pf != PF_INET) && !Curl_ipv6works()) - /* the stack seems to be a non-ipv6 one */ - pf = PF_INET; - -#endif /* CURLRES_IPV6 */ - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = pf; - hints.ai_socktype = conn->socktype; - - snprintf(sbuf, sizeof(sbuf), "%d", port); - - /* fire up a new resolver thread! */ - if(init_resolve_thread(conn, hostname, port, &hints)) { - *waitp = 1; /* expect asynchronous response */ - return NULL; - } - - /* fall-back to blocking version */ - infof(conn->data, "init_resolve_thread() failed for %s; %s\n", - hostname, Curl_strerror(conn, ERRNO)); - - error = Curl_getaddrinfo_ex(hostname, sbuf, &hints, &res); - if(error) { - infof(conn->data, "getaddrinfo() failed for %s:%d; %s\n", - hostname, port, Curl_strerror(conn, SOCKERRNO)); - return NULL; - } - return res; -} - -#endif /* !HAVE_GETADDRINFO */ - -CURLcode Curl_set_dns_servers(struct SessionHandle *data, - char *servers) -{ - (void)data; - (void)servers; - return CURLE_NOT_BUILT_IN; - -} - -#endif /* CURLRES_THREADED */ diff --git a/lib/axtls.c b/lib/axtls.c deleted file mode 100644 index 8bd606a40..000000000 --- a/lib/axtls.c +++ /dev/null @@ -1,547 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2010, DirecTV * contact: Eric Hu - * Copyright (C) 2010 - 2013, 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 - * 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. - * - ***************************************************************************/ - -/* - * Source file for all axTLS-specific code for the TLS/SSL layer. No code - * but curl_sslgen.c should ever call or use these functions. - */ - -#include "curl_setup.h" - -#ifdef USE_AXTLS -#include -#include "curl_axtls.h" - -#include "curl_sendf.h" -#include "curl_inet_pton.h" -#include "curl_sslgen.h" -#include "curl_parsedate.h" -#include "curl_connect.h" /* for the connect timeout */ -#include "curl_select.h" -#define _MPRINTF_REPLACE /* use our functions only */ -#include -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" -#include "curl_hostcheck.h" - - -/* SSL_read is opied from axTLS compat layer */ -static int SSL_read(SSL *ssl, void *buf, int num) -{ - uint8_t *read_buf; - int ret; - - while((ret = ssl_read(ssl, &read_buf)) == SSL_OK); - - if(ret > SSL_OK) { - memcpy(buf, read_buf, ret > num ? num : ret); - } - - return ret; -} - -/* Global axTLS init, called from Curl_ssl_init() */ -int Curl_axtls_init(void) -{ -/* axTLS has no global init. Everything is done through SSL and SSL_CTX - * structs stored in connectdata structure. Perhaps can move to curl_axtls.h. - */ - return 1; -} - -int Curl_axtls_cleanup(void) -{ - /* axTLS has no global cleanup. Perhaps can move this to curl_axtls.h. */ - return 1; -} - -static CURLcode map_error_to_curl(int axtls_err) -{ - switch (axtls_err) { - case SSL_ERROR_NOT_SUPPORTED: - case SSL_ERROR_INVALID_VERSION: - case -70: /* protocol version alert from server */ - return CURLE_UNSUPPORTED_PROTOCOL; - break; - case SSL_ERROR_NO_CIPHER: - return CURLE_SSL_CIPHER; - break; - case SSL_ERROR_BAD_CERTIFICATE: /* this may be bad server cert too */ - case SSL_ERROR_NO_CERT_DEFINED: - case -42: /* bad certificate alert from server */ - case -43: /* unsupported cert alert from server */ - case -44: /* cert revoked alert from server */ - case -45: /* cert expired alert from server */ - case -46: /* cert unknown alert from server */ - return CURLE_SSL_CERTPROBLEM; - break; - case SSL_X509_ERROR(X509_NOT_OK): - case SSL_X509_ERROR(X509_VFY_ERROR_NO_TRUSTED_CERT): - case SSL_X509_ERROR(X509_VFY_ERROR_BAD_SIGNATURE): - case SSL_X509_ERROR(X509_VFY_ERROR_NOT_YET_VALID): - case SSL_X509_ERROR(X509_VFY_ERROR_EXPIRED): - case SSL_X509_ERROR(X509_VFY_ERROR_SELF_SIGNED): - case SSL_X509_ERROR(X509_VFY_ERROR_INVALID_CHAIN): - case SSL_X509_ERROR(X509_VFY_ERROR_UNSUPPORTED_DIGEST): - case SSL_X509_ERROR(X509_INVALID_PRIV_KEY): - return CURLE_PEER_FAILED_VERIFICATION; - break; - case -48: /* unknown ca alert from server */ - return CURLE_SSL_CACERT; - break; - case -49: /* access denied alert from server */ - return CURLE_REMOTE_ACCESS_DENIED; - break; - case SSL_ERROR_CONN_LOST: - case SSL_ERROR_SOCK_SETUP_FAILURE: - case SSL_ERROR_INVALID_HANDSHAKE: - case SSL_ERROR_INVALID_PROT_MSG: - case SSL_ERROR_INVALID_HMAC: - case SSL_ERROR_INVALID_SESSION: - case SSL_ERROR_INVALID_KEY: /* it's too bad this doesn't map better */ - case SSL_ERROR_FINISHED_INVALID: - case SSL_ERROR_NO_CLIENT_RENOG: - default: - return CURLE_SSL_CONNECT_ERROR; - break; - } -} - -static Curl_recv axtls_recv; -static Curl_send axtls_send; - -/* - * This function is called after the TCP connect has completed. Setup the TLS - * layer and do all necessary magic. - */ -CURLcode -Curl_axtls_connect(struct connectdata *conn, - int sockindex) - -{ - struct SessionHandle *data = conn->data; - SSL_CTX *ssl_ctx; - SSL *ssl; - int cert_types[] = {SSL_OBJ_X509_CERT, SSL_OBJ_PKCS12, 0}; - int key_types[] = {SSL_OBJ_RSA_KEY, SSL_OBJ_PKCS8, SSL_OBJ_PKCS12, 0}; - int i, ssl_fcn_return; - const uint8_t *ssl_sessionid; - size_t ssl_idsize; - const char *peer_CN; - uint32_t dns_altname_index; - const char *dns_altname; - int8_t found_subject_alt_names = 0; - int8_t found_subject_alt_name_matching_conn = 0; - - /* Assuming users will not compile in custom key/cert to axTLS */ - uint32_t client_option = SSL_NO_DEFAULT_KEY|SSL_SERVER_VERIFY_LATER; - - if(conn->ssl[sockindex].state == ssl_connection_complete) - /* to make us tolerant against being called more than once for the - same connection */ - return CURLE_OK; - - /* axTLS only supports TLSv1 */ - /* check to see if we've been told to use an explicit SSL/TLS version */ - switch(data->set.ssl.version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - break; - default: - failf(data, "axTLS only supports TLSv1"); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef AXTLSDEBUG - client_option |= SSL_DISPLAY_STATES | SSL_DISPLAY_RSA | SSL_DISPLAY_CERTS; -#endif /* AXTLSDEBUG */ - - /* Allocate an SSL_CTX struct */ - ssl_ctx = ssl_ctx_new(client_option, SSL_DEFAULT_CLNT_SESS); - if(ssl_ctx == NULL) { - failf(data, "unable to create client SSL context"); - return CURLE_SSL_CONNECT_ERROR; - } - - /* Load the trusted CA cert bundle file */ - if(data->set.ssl.CAfile) { - if(ssl_obj_load(ssl_ctx, SSL_OBJ_X509_CACERT, data->set.ssl.CAfile, NULL) - != SSL_OK) { - infof(data, "error reading ca cert file %s \n", - data->set.ssl.CAfile); - if(data->set.ssl.verifypeer) { - Curl_axtls_close(conn, sockindex); - return CURLE_SSL_CACERT_BADFILE; - } - } - else - infof(data, "found certificates in %s\n", data->set.ssl.CAfile); - } - - /* curl_gtls.c tasks we're skipping for now: - * 1) certificate revocation list checking - * 2) dns name assignment to host - * 3) set protocol priority. axTLS is TLSv1 only, so can probably ignore - * 4) set certificate priority. axTLS ignores type and sends certs in - * order added. can probably ignore this. - */ - - /* Load client certificate */ - if(data->set.str[STRING_CERT]) { - i=0; - /* Instead of trying to analyze cert type here, let axTLS try them all. */ - while(cert_types[i] != 0) { - ssl_fcn_return = ssl_obj_load(ssl_ctx, cert_types[i], - data->set.str[STRING_CERT], NULL); - if(ssl_fcn_return == SSL_OK) { - infof(data, "successfully read cert file %s \n", - data->set.str[STRING_CERT]); - break; - } - i++; - } - /* Tried all cert types, none worked. */ - if(cert_types[i] == 0) { - failf(data, "%s is not x509 or pkcs12 format", - data->set.str[STRING_CERT]); - Curl_axtls_close(conn, sockindex); - return CURLE_SSL_CERTPROBLEM; - } - } - - /* Load client key. - If a pkcs12 file successfully loaded a cert, then there's nothing to do - because the key has already been loaded. */ - if(data->set.str[STRING_KEY] && cert_types[i] != SSL_OBJ_PKCS12) { - i=0; - /* Instead of trying to analyze key type here, let axTLS try them all. */ - while(key_types[i] != 0) { - ssl_fcn_return = ssl_obj_load(ssl_ctx, key_types[i], - data->set.str[STRING_KEY], NULL); - if(ssl_fcn_return == SSL_OK) { - infof(data, "successfully read key file %s \n", - data->set.str[STRING_KEY]); - break; - } - i++; - } - /* Tried all key types, none worked. */ - if(key_types[i] == 0) { - failf(data, "Failure: %s is not a supported key file", - data->set.str[STRING_KEY]); - Curl_axtls_close(conn, sockindex); - return CURLE_SSL_CONNECT_ERROR; - } - } - - /* curl_gtls.c does more here that is being left out for now - * 1) set session credentials. can probably ignore since axtls puts this - * info in the ssl_ctx struct - * 2) setting up callbacks. these seem gnutls specific - */ - - /* In axTLS, handshaking happens inside ssl_client_new. */ - if(!Curl_ssl_getsessionid(conn, (void **) &ssl_sessionid, &ssl_idsize)) { - /* we got a session id, use it! */ - infof (data, "SSL re-using session ID\n"); - ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], - ssl_sessionid, (uint8_t)ssl_idsize); - } - else - ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], NULL, 0); - - /* Check to make sure handshake was ok. */ - ssl_fcn_return = ssl_handshake_status(ssl); - if(ssl_fcn_return != SSL_OK) { - Curl_axtls_close(conn, sockindex); - ssl_display_error(ssl_fcn_return); /* goes to stdout. */ - return map_error_to_curl(ssl_fcn_return); - } - infof (data, "handshake completed successfully\n"); - - /* Here, curl_gtls.c gets the peer certificates and fails out depending on - * settings in "data." axTLS api doesn't have get cert chain fcn, so omit? - */ - - /* Verify server's certificate */ - if(data->set.ssl.verifypeer) { - if(ssl_verify_cert(ssl) != SSL_OK) { - Curl_axtls_close(conn, sockindex); - failf(data, "server cert verify failed"); - return CURLE_SSL_CONNECT_ERROR; - } - } - else - infof(data, "\t server certificate verification SKIPPED\n"); - - /* Here, curl_gtls.c does issuer verification. axTLS has no straightforward - * equivalent, so omitting for now.*/ - - /* Here, curl_gtls.c does the following - * 1) x509 hostname checking per RFC2818. axTLS doesn't support this, but - * it seems useful. This is now implemented, by Oscar Koeroo - * 2) checks cert validity based on time. axTLS does this in ssl_verify_cert - * 3) displays a bunch of cert information. axTLS doesn't support most of - * this, but a couple fields are available. - */ - - - /* There is no (DNS) Altnames count in the version 1.4.8 API. There is a - risk of an inifite loop */ - for(dns_altname_index = 0; ; dns_altname_index++) { - dns_altname = ssl_get_cert_subject_alt_dnsname(ssl, dns_altname_index); - if(dns_altname == NULL) { - break; - } - found_subject_alt_names = 1; - - infof(data, "\tComparing subject alt name DNS with hostname: %s <-> %s\n", - dns_altname, conn->host.name); - if(Curl_cert_hostcheck(dns_altname, conn->host.name)) { - found_subject_alt_name_matching_conn = 1; - break; - } - } - - /* RFC2818 checks */ - if(found_subject_alt_names && !found_subject_alt_name_matching_conn) { - /* Break connection ! */ - Curl_axtls_close(conn, sockindex); - failf(data, "\tsubjectAltName(s) do not match %s\n", conn->host.dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - else if(found_subject_alt_names == 0) { - /* Per RFC2818, when no Subject Alt Names were available, examine the peer - CN as a legacy fallback */ - peer_CN = ssl_get_cert_dn(ssl, SSL_X509_CERT_COMMON_NAME); - if(peer_CN == NULL) { - /* Similar behaviour to the OpenSSL interface */ - Curl_axtls_close(conn, sockindex); - failf(data, "unable to obtain common name from peer certificate"); - return CURLE_PEER_FAILED_VERIFICATION; - } - else { - if(!Curl_cert_hostcheck((const char *)peer_CN, conn->host.name)) { - if(data->set.ssl.verifyhost) { - /* Break connection ! */ - Curl_axtls_close(conn, sockindex); - failf(data, "\tcommon name \"%s\" does not match \"%s\"\n", - peer_CN, conn->host.dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, "\tcommon name \"%s\" does not match \"%s\"\n", - peer_CN, conn->host.dispname); - } - } - } - - /* General housekeeping */ - conn->ssl[sockindex].state = ssl_connection_complete; - conn->ssl[sockindex].ssl = ssl; - conn->ssl[sockindex].ssl_ctx = ssl_ctx; - conn->recv[sockindex] = axtls_recv; - conn->send[sockindex] = axtls_send; - - /* Put our freshly minted SSL session in cache */ - ssl_idsize = ssl_get_session_id_size(ssl); - ssl_sessionid = ssl_get_session_id(ssl); - if(Curl_ssl_addsessionid(conn, (void *) ssl_sessionid, ssl_idsize) - != CURLE_OK) - infof (data, "failed to add session to cache\n"); - - return CURLE_OK; -} - - -/* return number of sent (non-SSL) bytes */ -static ssize_t axtls_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *err) -{ - /* ssl_write() returns 'int' while write() and send() returns 'size_t' */ - int rc = ssl_write(conn->ssl[sockindex].ssl, mem, (int)len); - - infof(conn->data, " axtls_send\n"); - - if(rc < 0 ) { - *err = map_error_to_curl(rc); - rc = -1; /* generic error code for send failure */ - } - - *err = CURLE_OK; - return rc; -} - -void Curl_axtls_close_all(struct SessionHandle *data) -{ - (void)data; - infof(data, " Curl_axtls_close_all\n"); -} - -void Curl_axtls_close(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - infof(conn->data, " Curl_axtls_close\n"); - if(connssl->ssl) { - /* line from curl_ssluse.c: (void)SSL_shutdown(connssl->ssl); - axTLS compat layer does nothing for SSL_shutdown */ - - /* The following line is from curl_ssluse.c. There seems to be no axTLS - equivalent. ssl_free and ssl_ctx_free close things. - SSL_set_connect_state(connssl->handle); */ - - ssl_free (connssl->ssl); - connssl->ssl = NULL; - } - if(connssl->ssl_ctx) { - ssl_ctx_free (connssl->ssl_ctx); - connssl->ssl_ctx = NULL; - } -} - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -int Curl_axtls_shutdown(struct connectdata *conn, int sockindex) -{ - /* Outline taken from curl_ssluse.c since functions are in axTLS compat - layer. axTLS's error set is much smaller, so a lot of error-handling - was removed. - */ - int retval = 0; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct SessionHandle *data = conn->data; - char buf[120]; /* We will use this for the OpenSSL error buffer, so it has - to be at least 120 bytes long. */ - ssize_t nread; - - infof(conn->data, " Curl_axtls_shutdown\n"); - - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - /* axTLS compat layer does nothing for SSL_shutdown, so we do nothing too - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - (void)SSL_shutdown(connssl->ssl); - */ - - if(connssl->ssl) { - int what = Curl_socket_ready(conn->sock[sockindex], - CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - nread = (ssize_t)SSL_read(conn->ssl[sockindex].ssl, buf, - sizeof(buf)); - - if(nread < SSL_OK) { - failf(data, "close notify alert not received during shutdown"); - retval = -1; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - } - - ssl_free (connssl->ssl); - connssl->ssl = NULL; - } - return retval; -} - -static ssize_t axtls_recv(struct connectdata *conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *err) -{ - struct ssl_connect_data *connssl = &conn->ssl[num]; - ssize_t ret = 0; - - infof(conn->data, " axtls_recv\n"); - - if(connssl) { - ret = (ssize_t)SSL_read(conn->ssl[num].ssl, buf, (int)buffersize); - - /* axTLS isn't terribly generous about error reporting */ - /* With patched axTLS, SSL_CLOSE_NOTIFY=-3. Hard-coding until axTLS - team approves proposed fix. */ - if(ret == -3 ) { - Curl_axtls_close(conn, num); - } - else if(ret < 0) { - failf(conn->data, "axTLS recv error (%d)", (int)ret); - *err = map_error_to_curl(ret); - return -1; - } - } - - *err = CURLE_OK; - return ret; -} - -/* - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -int Curl_axtls_check_cxn(struct connectdata *conn) -{ - /* curl_ssluse.c line: - rc = SSL_peek(conn->ssl[FIRSTSOCKET].ssl, (void*)&buf, 1); - axTLS compat layer always returns the last argument, so connection is - always alive? */ - - infof(conn->data, " Curl_axtls_check_cxn\n"); - return 1; /* connection still in place */ -} - -void Curl_axtls_session_free(void *ptr) -{ - (void)ptr; - /* free the ID */ - /* both curl_ssluse.c and curl_gtls.c do something here, but axTLS's - OpenSSL compatibility layer does nothing, so we do nothing too. */ -} - -size_t Curl_axtls_version(char *buffer, size_t size) -{ - return snprintf(buffer, size, "axTLS/%s", ssl_version()); -} - -#endif /* USE_AXTLS */ diff --git a/lib/base64.c b/lib/base64.c deleted file mode 100644 index 45c7a95bc..000000000 --- a/lib/base64.c +++ /dev/null @@ -1,248 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -/* Base64 encoding/decoding */ - -#include "curl_setup.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_urldata.h" /* for the SessionHandle definition */ -#include "curl_warnless.h" -#include "curl_base64.h" -#include "curl_memory.h" -#include "curl_non_ascii.h" - -/* include curl_memdebug.h last */ -#include "curl_memdebug.h" - -/* ---- Base64 Encoding/Decoding Table --- */ -static const char table64[]= - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -static void decodeQuantum(unsigned char *dest, const char *src) -{ - const char *s, *p; - unsigned long i, v, x = 0; - - for(i = 0, s = src; i < 4; i++, s++) { - v = 0; - p = table64; - while(*p && (*p != *s)) { - v++; - p++; - } - if(*p == *s) - x = (x << 6) + v; - else if(*s == '=') - x = (x << 6); - } - - dest[2] = curlx_ultouc(x & 0xFFUL); - x >>= 8; - dest[1] = curlx_ultouc(x & 0xFFUL); - x >>= 8; - dest[0] = curlx_ultouc(x & 0xFFUL); -} - -/* - * Curl_base64_decode() - * - * Given a base64 NUL-terminated string at src, decode it and return a - * pointer in *outptr to a newly allocated memory area holding decoded - * data. Size of decoded data is returned in variable pointed by outlen. - * - * Returns CURLE_OK on success, otherwise specific error code. Function - * output shall not be considered valid unless CURLE_OK is returned. - * - * When decoded data length is 0, returns NULL in *outptr. - * - * @unittest: 1302 - */ -CURLcode Curl_base64_decode(const char *src, - unsigned char **outptr, size_t *outlen) -{ - size_t length = 0; - size_t equalsTerm = 0; - size_t i; - size_t numQuantums; - unsigned char lastQuantum[3]; - size_t rawlen = 0; - unsigned char *newstr; - - *outptr = NULL; - *outlen = 0; - - while((src[length] != '=') && src[length]) - length++; - /* A maximum of two = padding characters is allowed */ - if(src[length] == '=') { - equalsTerm++; - if(src[length+equalsTerm] == '=') - equalsTerm++; - } - numQuantums = (length + equalsTerm) / 4; - - /* Don't allocate a buffer if the decoded length is 0 */ - if(numQuantums == 0) - return CURLE_OK; - - rawlen = (numQuantums * 3) - equalsTerm; - - /* The buffer must be large enough to make room for the last quantum - (which may be partially thrown out) and the zero terminator. */ - newstr = malloc(rawlen+4); - if(!newstr) - return CURLE_OUT_OF_MEMORY; - - *outptr = newstr; - - /* Decode all but the last quantum (which may not decode to a - multiple of 3 bytes) */ - for(i = 0; i < numQuantums - 1; i++) { - decodeQuantum(newstr, src); - newstr += 3; src += 4; - } - - /* This final decode may actually read slightly past the end of the buffer - if the input string is missing pad bytes. This will almost always be - harmless. */ - decodeQuantum(lastQuantum, src); - for(i = 0; i < 3 - equalsTerm; i++) - newstr[i] = lastQuantum[i]; - - newstr[i] = '\0'; /* zero terminate */ - - *outlen = rawlen; /* return size of decoded data */ - - return CURLE_OK; -} - -/* - * Curl_base64_encode() - * - * Given a pointer to an input buffer and an input size, encode it and - * return a pointer in *outptr to a newly allocated memory area holding - * encoded data. Size of encoded data is returned in variable pointed by - * outlen. - * - * Input length of 0 indicates input buffer holds a NUL-terminated string. - * - * Returns CURLE_OK on success, otherwise specific error code. Function - * output shall not be considered valid unless CURLE_OK is returned. - * - * When encoded data length is 0, returns NULL in *outptr. - * - * @unittest: 1302 - */ -CURLcode Curl_base64_encode(struct SessionHandle *data, - const char *inputbuff, size_t insize, - char **outptr, size_t *outlen) -{ - CURLcode error; - unsigned char ibuf[3]; - unsigned char obuf[4]; - int i; - int inputparts; - char *output; - char *base64data; - char *convbuf = NULL; - - const char *indata = inputbuff; - - *outptr = NULL; - *outlen = 0; - - if(0 == insize) - insize = strlen(indata); - - base64data = output = malloc(insize*4/3+4); - if(NULL == output) - return CURLE_OUT_OF_MEMORY; - - /* - * The base64 data needs to be created using the network encoding - * not the host encoding. And we can't change the actual input - * so we copy it to a buffer, translate it, and use that instead. - */ - error = Curl_convert_clone(data, indata, insize, &convbuf); - if(error) { - free(output); - return error; - } - - if(convbuf) - indata = (char *)convbuf; - - while(insize > 0) { - for(i = inputparts = 0; i < 3; i++) { - if(insize > 0) { - inputparts++; - ibuf[i] = (unsigned char) *indata; - indata++; - insize--; - } - else - ibuf[i] = 0; - } - - obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2); - obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \ - ((ibuf[1] & 0xF0) >> 4)); - obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \ - ((ibuf[2] & 0xC0) >> 6)); - obuf[3] = (unsigned char) (ibuf[2] & 0x3F); - - switch(inputparts) { - case 1: /* only one byte read */ - snprintf(output, 5, "%c%c==", - table64[obuf[0]], - table64[obuf[1]]); - break; - case 2: /* two bytes read */ - snprintf(output, 5, "%c%c%c=", - table64[obuf[0]], - table64[obuf[1]], - table64[obuf[2]]); - break; - default: - snprintf(output, 5, "%c%c%c%c", - table64[obuf[0]], - table64[obuf[1]], - table64[obuf[2]], - table64[obuf[3]] ); - break; - } - output += 4; - } - *output = '\0'; - *outptr = base64data; /* return pointer to new data, allocated memory */ - - if(convbuf) - free(convbuf); - - *outlen = strlen(base64data); /* return the length of the new data */ - - return CURLE_OK; -} -/* ---- End of Base64 Encoding ---- */ diff --git a/lib/bundles.c b/lib/bundles.c deleted file mode 100644 index efbaeee4f..000000000 --- a/lib/bundles.c +++ /dev/null @@ -1,110 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012, Linus Nielsen Feltzing, - * Copyright (C) 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include - -#include "curl_urldata.h" -#include "curl_url.h" -#include "curl_progress.h" -#include "curl_multiif.h" -#include "curl_bundles.h" -#include "curl_sendf.h" -#include "curl_rawstr.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -static void conn_llist_dtor(void *user, void *element) -{ - struct connectdata *data = element; - (void)user; - - data->bundle = NULL; -} - -CURLcode Curl_bundle_create(struct SessionHandle *data, - struct connectbundle **cb_ptr) -{ - (void)data; - DEBUGASSERT(*cb_ptr == NULL); - *cb_ptr = malloc(sizeof(struct connectbundle)); - if(!*cb_ptr) - return CURLE_OUT_OF_MEMORY; - - (*cb_ptr)->num_connections = 0; - (*cb_ptr)->server_supports_pipelining = FALSE; - - (*cb_ptr)->conn_list = Curl_llist_alloc((curl_llist_dtor) conn_llist_dtor); - if(!(*cb_ptr)->conn_list) { - Curl_safefree(*cb_ptr); - return CURLE_OUT_OF_MEMORY; - } - return CURLE_OK; -} - -void Curl_bundle_destroy(struct connectbundle *cb_ptr) -{ - if(!cb_ptr) - return; - - if(cb_ptr->conn_list) { - Curl_llist_destroy(cb_ptr->conn_list, NULL); - cb_ptr->conn_list = NULL; - } - Curl_safefree(cb_ptr); -} - -/* Add a connection to a bundle */ -CURLcode Curl_bundle_add_conn(struct connectbundle *cb_ptr, - struct connectdata *conn) -{ - if(!Curl_llist_insert_next(cb_ptr->conn_list, cb_ptr->conn_list->tail, conn)) - return CURLE_OUT_OF_MEMORY; - - conn->bundle = cb_ptr; - - cb_ptr->num_connections++; - return CURLE_OK; -} - -/* Remove a connection from a bundle */ -int Curl_bundle_remove_conn(struct connectbundle *cb_ptr, - struct connectdata *conn) -{ - struct curl_llist_element *curr; - - curr = cb_ptr->conn_list->head; - while(curr) { - if(curr->ptr == conn) { - Curl_llist_remove(cb_ptr->conn_list, curr, NULL); - cb_ptr->num_connections--; - conn->bundle = NULL; - return 1; /* we removed a handle */ - } - curr = curr->next; - } - return 0; -} diff --git a/lib/conncache.c b/lib/conncache.c deleted file mode 100644 index bc95e07df..000000000 --- a/lib/conncache.c +++ /dev/null @@ -1,285 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012, Linus Nielsen Feltzing, - * Copyright (C) 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include - -#include "curl_urldata.h" -#include "curl_url.h" -#include "curl_progress.h" -#include "curl_multiif.h" -#include "curl_sendf.h" -#include "curl_rawstr.h" -#include "curl_bundles.h" -#include "curl_conncache.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#define CONNECTION_HASH_SIZE 97 - -static void free_bundle_hash_entry(void *freethis) -{ - struct connectbundle *b = (struct connectbundle *) freethis; - - Curl_bundle_destroy(b); -} - -struct conncache *Curl_conncache_init(conncachetype type) -{ - struct conncache *connc; - - connc = calloc(1, sizeof(struct conncache)); - if(!connc) - return NULL; - - connc->hash = Curl_hash_alloc(CONNECTION_HASH_SIZE, Curl_hash_str, - Curl_str_key_compare, free_bundle_hash_entry); - - if(!connc->hash) { - free(connc); - return NULL; - } - - connc->type = type; - connc->num_connections = 0; - - return connc; -} - -void Curl_conncache_destroy(struct conncache *connc) -{ - if(connc) { - Curl_hash_destroy(connc->hash); - connc->hash = NULL; - free(connc); - } -} - -struct connectbundle *Curl_conncache_find_bundle(struct conncache *connc, - char *hostname) -{ - struct connectbundle *bundle = NULL; - - if(connc) - bundle = Curl_hash_pick(connc->hash, hostname, strlen(hostname)+1); - - return bundle; -} - -static bool conncache_add_bundle(struct conncache *connc, - char *hostname, - struct connectbundle *bundle) -{ - void *p; - - p = Curl_hash_add(connc->hash, hostname, strlen(hostname)+1, bundle); - - return p?TRUE:FALSE; -} - -static void conncache_remove_bundle(struct conncache *connc, - struct connectbundle *bundle) -{ - struct curl_hash_iterator iter; - struct curl_hash_element *he; - - if(!connc) - return; - - Curl_hash_start_iterate(connc->hash, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - if(he->ptr == bundle) { - /* The bundle is destroyed by the hash destructor function, - free_bundle_hash_entry() */ - Curl_hash_delete(connc->hash, he->key, he->key_len); - return; - } - - he = Curl_hash_next_element(&iter); - } -} - -CURLcode Curl_conncache_add_conn(struct conncache *connc, - struct connectdata *conn) -{ - CURLcode result; - struct connectbundle *bundle; - struct connectbundle *new_bundle = NULL; - struct SessionHandle *data = conn->data; - - bundle = Curl_conncache_find_bundle(data->state.conn_cache, - conn->host.name); - if(!bundle) { - result = Curl_bundle_create(data, &new_bundle); - if(result != CURLE_OK) - return result; - - if(!conncache_add_bundle(data->state.conn_cache, - conn->host.name, new_bundle)) { - Curl_bundle_destroy(new_bundle); - return CURLE_OUT_OF_MEMORY; - } - bundle = new_bundle; - } - - result = Curl_bundle_add_conn(bundle, conn); - if(result != CURLE_OK) { - if(new_bundle) - conncache_remove_bundle(data->state.conn_cache, new_bundle); - return result; - } - - connc->num_connections++; - - return CURLE_OK; -} - -void Curl_conncache_remove_conn(struct conncache *connc, - struct connectdata *conn) -{ - struct connectbundle *bundle = conn->bundle; - - /* The bundle pointer can be NULL, since this function can be called - due to a failed connection attempt, before being added to a bundle */ - if(bundle) { - Curl_bundle_remove_conn(bundle, conn); - if(bundle->num_connections == 0) { - conncache_remove_bundle(connc, bundle); - } - connc->num_connections--; - - DEBUGF(infof(conn->data, "The cache now contains %d members\n", - connc->num_connections)); - } -} - -/* This function iterates the entire connection cache and calls the - function func() with the connection pointer as the first argument - and the supplied 'param' argument as the other, - - Return 0 from func() to continue the loop, return 1 to abort it. - */ -void Curl_conncache_foreach(struct conncache *connc, - void *param, - int (*func)(struct connectdata *conn, void *param)) -{ - struct curl_hash_iterator iter; - struct curl_llist_element *curr; - struct curl_hash_element *he; - - if(!connc) - return; - - Curl_hash_start_iterate(connc->hash, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - struct connectbundle *bundle; - struct connectdata *conn; - - bundle = he->ptr; - - curr = bundle->conn_list->head; - while(curr) { - /* Yes, we need to update curr before calling func(), because func() - might decide to remove the connection */ - conn = curr->ptr; - curr = curr->next; - - if(1 == func(conn, param)) - return; - } - - he = Curl_hash_next_element(&iter); - } -} - -/* Return the first connection found in the cache. Used when closing all - connections */ -struct connectdata * -Curl_conncache_find_first_connection(struct conncache *connc) -{ - struct curl_hash_iterator iter; - struct curl_llist_element *curr; - struct curl_hash_element *he; - struct connectbundle *bundle; - - Curl_hash_start_iterate(connc->hash, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - bundle = he->ptr; - - curr = bundle->conn_list->head; - if(curr) { - return curr->ptr; - } - - he = Curl_hash_next_element(&iter); - } - - return NULL; -} - - -#if 0 -/* Useful for debugging the connection cache */ -void Curl_conncache_print(struct conncache *connc) -{ - struct curl_hash_iterator iter; - struct curl_llist_element *curr; - struct curl_hash_element *he; - - if(!connc) - return; - - fprintf(stderr, "=Bundle cache=\n"); - - Curl_hash_start_iterate(connc->hash, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - struct connectbundle *bundle; - struct connectdata *conn; - - bundle = he->ptr; - - fprintf(stderr, "%s -", he->key); - curr = bundle->conn_list->head; - while(curr) { - conn = curr->ptr; - - fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse); - curr = curr->next; - } - fprintf(stderr, "\n"); - - he = Curl_hash_next_element(&iter); - } -} -#endif diff --git a/lib/connect.c b/lib/connect.c deleted file mode 100644 index 85226d808..000000000 --- a/lib/connect.c +++ /dev/null @@ -1,1248 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include /* may need it */ -#endif -#ifdef HAVE_SYS_UN_H -#include /* for sockaddr_un */ -#endif -#ifdef HAVE_NETINET_TCP_H -#include /* for TCP_NODELAY */ -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_FCNTL_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif - -#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE)) -#include -#endif -#ifdef NETWARE -#undef in_addr_t -#define in_addr_t unsigned long -#endif -#ifdef __VMS -#include -#include -#endif - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_if2ip.h" -#include "curl_strerror.h" -#include "curl_connect.h" -#include "curl_memory.h" -#include "curl_select.h" -#include "curl_url.h" -#include "curl_multiif.h" -#include "curl_sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "curl_inet_ntop.h" -#include "curl_inet_pton.h" -#include "curl_sslgen.h" /* for Curl_ssl_check_cxn() */ -#include "curl_progress.h" -#include "curl_warnless.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifdef __SYMBIAN32__ -/* This isn't actually supported under Symbian OS */ -#undef SO_NOSIGPIPE -#endif - -static bool verifyconnect(curl_socket_t sockfd, int *error); - -#ifdef __DragonFly__ -/* DragonFlyBSD uses millisecond as KEEPIDLE and KEEPINTVL units */ -#define KEEPALIVE_FACTOR(x) (x *= 1000) -#else -#define KEEPALIVE_FACTOR(x) -#endif - -static void -tcpkeepalive(struct SessionHandle *data, - curl_socket_t sockfd) -{ - int optval = data->set.tcp_keepalive?1:0; - - /* only set IDLE and INTVL if setting KEEPALIVE is successful */ - if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set SO_KEEPALIVE on fd %d\n", sockfd); - } - else { -#ifdef TCP_KEEPIDLE - optval = curlx_sltosi(data->set.tcp_keepidle); - KEEPALIVE_FACTOR(optval); - if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPIDLE on fd %d\n", sockfd); - } -#endif -#ifdef TCP_KEEPINTVL - optval = curlx_sltosi(data->set.tcp_keepintvl); - KEEPALIVE_FACTOR(optval); - if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, - (void *)&optval, sizeof(optval)) < 0) { - infof(data, "Failed to set TCP_KEEPINTVL on fd %d\n", sockfd); - } -#endif - } -} - -static CURLcode -singleipconnect(struct connectdata *conn, - const Curl_addrinfo *ai, /* start connecting to this */ - long timeout_ms, - curl_socket_t *sock, - bool *connected); - -/* - * Curl_timeleft() returns the amount of milliseconds left allowed for the - * transfer/connection. If the value is negative, the timeout time has already - * elapsed. - * - * The start time is stored in progress.t_startsingle - as set with - * Curl_pgrsTime(..., TIMER_STARTSINGLE); - * - * If 'nowp' is non-NULL, it points to the current time. - * 'duringconnect' is FALSE if not during a connect, as then of course the - * connect timeout is not taken into account! - * - * @unittest: 1303 - */ -long Curl_timeleft(struct SessionHandle *data, - struct timeval *nowp, - bool duringconnect) -{ - int timeout_set = 0; - long timeout_ms = duringconnect?DEFAULT_CONNECT_TIMEOUT:0; - struct timeval now; - - /* if a timeout is set, use the most restrictive one */ - - if(data->set.timeout > 0) - timeout_set |= 1; - if(duringconnect && (data->set.connecttimeout > 0)) - timeout_set |= 2; - - switch (timeout_set) { - case 1: - timeout_ms = data->set.timeout; - break; - case 2: - timeout_ms = data->set.connecttimeout; - break; - case 3: - if(data->set.timeout < data->set.connecttimeout) - timeout_ms = data->set.timeout; - else - timeout_ms = data->set.connecttimeout; - break; - default: - /* use the default */ - if(!duringconnect) - /* if we're not during connect, there's no default timeout so if we're - at zero we better just return zero and not make it a negative number - by the math below */ - return 0; - break; - } - - if(!nowp) { - now = Curl_tvnow(); - nowp = &now; - } - - /* subtract elapsed time */ - timeout_ms -= Curl_tvdiff(*nowp, data->progress.t_startsingle); - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; - - return timeout_ms; -} - -/* - * waitconnect() waits for a TCP connect on the given socket for the specified - * number if milliseconds. It returns: - */ - -#define WAITCONN_CONNECTED 0 -#define WAITCONN_SELECT_ERROR -1 -#define WAITCONN_TIMEOUT 1 -#define WAITCONN_FDSET_ERROR 2 -#define WAITCONN_ABORTED 3 - -static -int waitconnect(struct connectdata *conn, - curl_socket_t sockfd, /* socket */ - long timeout_msec) -{ - int rc; -#ifdef mpeix - /* Call this function once now, and ignore the results. We do this to - "clear" the error state on the socket so that we can later read it - reliably. This is reported necessary on the MPE/iX operating system. */ - (void)verifyconnect(sockfd, NULL); -#endif - - for(;;) { - - /* now select() until we get connect or timeout */ - rc = Curl_socket_ready(CURL_SOCKET_BAD, sockfd, timeout_msec>1000? - 1000:timeout_msec); - if(Curl_pgrsUpdate(conn)) - return WAITCONN_ABORTED; - - if(-1 == rc) - /* error, no connect here, try next */ - return WAITCONN_SELECT_ERROR; - - else if(0 == rc) { - /* timeout */ - timeout_msec -= 1000; - if(timeout_msec <= 0) - return WAITCONN_TIMEOUT; - - continue; - } - - if(rc & CURL_CSELECT_ERR) - /* error condition caught */ - return WAITCONN_FDSET_ERROR; - - break; - } - return WAITCONN_CONNECTED; -} - -static CURLcode bindlocal(struct connectdata *conn, - curl_socket_t sockfd, int af) -{ - struct SessionHandle *data = conn->data; - - struct Curl_sockaddr_storage sa; - struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ - curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ - struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; -#endif - - struct Curl_dns_entry *h=NULL; - unsigned short port = data->set.localport; /* use this port number, 0 for - "random" */ - /* how many port numbers to try to bind to, increasing one at a time */ - int portnum = data->set.localportrange; - const char *dev = data->set.str[STRING_DEVICE]; - int error; - char myhost[256] = ""; - int done = 0; /* -1 for error, 1 for address found */ - bool is_interface = FALSE; - bool is_host = FALSE; - static const char *if_prefix = "if!"; - static const char *host_prefix = "host!"; - - /************************************************************* - * Select device to bind socket to - *************************************************************/ - if(!dev && !port) - /* no local kind of binding was requested */ - return CURLE_OK; - - memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); - - if(dev && (strlen(dev)<255) ) { - if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) { - dev += strlen(if_prefix); - is_interface = TRUE; - } - else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) { - dev += strlen(host_prefix); - is_host = TRUE; - } - - /* interface */ - if(!is_host && (is_interface || Curl_if_is_interface_name(dev))) { - if(Curl_if2ip(af, dev, myhost, sizeof(myhost)) == NULL) - return CURLE_INTERFACE_FAILED; - - /* - * We now have the numerical IP address in the 'myhost' buffer - */ - infof(data, "Local Interface %s is ip %s using address family %i\n", - dev, myhost, af); - done = 1; - -#ifdef SO_BINDTODEVICE - /* I am not sure any other OSs than Linux that provide this feature, and - * at the least I cannot test. --Ben - * - * This feature allows one to tightly bind the local socket to a - * particular interface. This will force even requests to other local - * interfaces to go out the external interface. - * - * - * Only bind to the interface when specified as interface, not just as a - * hostname or ip address. - */ - if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, - dev, (curl_socklen_t)strlen(dev)+1) != 0) { - error = SOCKERRNO; - infof(data, "SO_BINDTODEVICE %s failed with errno %d: %s;" - " will do regular bind\n", - dev, error, Curl_strerror(conn, error)); - /* This is typically "errno 1, error: Operation not permitted" if - you're not running as root or another suitable privileged user */ - } -#endif - } - else { - /* - * This was not an interface, resolve the name as a host name - * or IP number - * - * Temporarily force name resolution to use only the address type - * of the connection. The resolve functions should really be changed - * to take a type parameter instead. - */ - long ipver = conn->ip_version; - int rc; - - if(af == AF_INET) - conn->ip_version = CURL_IPRESOLVE_V4; -#ifdef ENABLE_IPV6 - else if(af == AF_INET6) - conn->ip_version = CURL_IPRESOLVE_V6; -#endif - - rc = Curl_resolv(conn, dev, 0, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(conn, &h); - conn->ip_version = ipver; - - if(h) { - /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ - Curl_printable_address(h->addr, myhost, sizeof(myhost)); - infof(data, "Name '%s' family %i resolved to '%s' family %i\n", - dev, af, myhost, h->addr->ai_family); - Curl_resolv_unlock(data, h); - done = 1; - } - else { - /* - * provided dev was no interface (or interfaces are not supported - * e.g. solaris) no ip address and no domain we fail here - */ - done = -1; - } - } - - if(done > 0) { -#ifdef ENABLE_IPV6 - /* ipv6 address */ - if((af == AF_INET6) && - (Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0)) { - si6->sin6_family = AF_INET6; - si6->sin6_port = htons(port); - sizeof_sa = sizeof(struct sockaddr_in6); - } - else -#endif - /* ipv4 address */ - if((af == AF_INET) && - (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { - si4->sin_family = AF_INET; - si4->sin_port = htons(port); - sizeof_sa = sizeof(struct sockaddr_in); - } - } - - if(done < 1) { - failf(data, "Couldn't bind to '%s'", dev); - return CURLE_INTERFACE_FAILED; - } - } - else { - /* no device was given, prepare sa to match af's needs */ -#ifdef ENABLE_IPV6 - if(af == AF_INET6) { - si6->sin6_family = AF_INET6; - si6->sin6_port = htons(port); - sizeof_sa = sizeof(struct sockaddr_in6); - } - else -#endif - if(af == AF_INET) { - si4->sin_family = AF_INET; - si4->sin_port = htons(port); - sizeof_sa = sizeof(struct sockaddr_in); - } - } - - for(;;) { - if(bind(sockfd, sock, sizeof_sa) >= 0) { - /* we succeeded to bind */ - struct Curl_sockaddr_storage add; - curl_socklen_t size = sizeof(add); - memset(&add, 0, sizeof(struct Curl_sockaddr_storage)); - if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { - data->state.os_errno = error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return CURLE_INTERFACE_FAILED; - } - infof(data, "Local port: %hu\n", port); - conn->bits.bound = TRUE; - return CURLE_OK; - } - - if(--portnum > 0) { - infof(data, "Bind to local port %hu failed, trying next\n", port); - port++; /* try next port */ - /* We re-use/clobber the port variable here below */ - if(sock->sa_family == AF_INET) - si4->sin_port = ntohs(port); -#ifdef ENABLE_IPV6 - else - si6->sin6_port = ntohs(port); -#endif - } - else - break; - } - - data->state.os_errno = error = SOCKERRNO; - failf(data, "bind failed with errno %d: %s", - error, Curl_strerror(conn, error)); - - return CURLE_INTERFACE_FAILED; -} - -/* - * verifyconnect() returns TRUE if the connect really has happened. - */ -static bool verifyconnect(curl_socket_t sockfd, int *error) -{ - bool rc = TRUE; -#ifdef SO_ERROR - int err = 0; - curl_socklen_t errSize = sizeof(err); - -#ifdef WIN32 - /* - * In October 2003 we effectively nullified this function on Windows due to - * problems with it using all CPU in multi-threaded cases. - * - * In May 2004, we bring it back to offer more info back on connect failures. - * Gisle Vanem could reproduce the former problems with this function, but - * could avoid them by adding this SleepEx() call below: - * - * "I don't have Rational Quantify, but the hint from his post was - * ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe - * just Sleep(0) would be enough?) would release whatever - * mutex/critical-section the ntdll call is waiting on. - * - * Someone got to verify this on Win-NT 4.0, 2000." - */ - -#ifdef _WIN32_WCE - Sleep(0); -#else - SleepEx(0, FALSE); -#endif - -#endif - - if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize)) - err = SOCKERRNO; -#ifdef _WIN32_WCE - /* Old WinCE versions don't support SO_ERROR */ - if(WSAENOPROTOOPT == err) { - SET_SOCKERRNO(0); - err = 0; - } -#endif -#ifdef __minix - /* Minix 3.1.x doesn't support getsockopt on UDP sockets */ - if(EBADIOCTL == err) { - SET_SOCKERRNO(0); - err = 0; - } -#endif - if((0 == err) || (EISCONN == err)) - /* we are connected, awesome! */ - rc = TRUE; - else - /* This wasn't a successful connect */ - rc = FALSE; - if(error) - *error = err; -#else - (void)sockfd; - if(error) - *error = SOCKERRNO; -#endif - return rc; -} - -/* Used within the multi interface. Try next IP address, return TRUE if no - more address exists or error */ -static CURLcode trynextip(struct connectdata *conn, - int sockindex, - bool *connected) -{ - curl_socket_t sockfd; - Curl_addrinfo *ai; - - /* First clean up after the failed socket. - Don't close it yet to ensure that the next IP's socket gets a different - file descriptor, which can prevent bugs when the curl_multi_socket_action - interface is used with certain select() replacements such as kqueue. */ - curl_socket_t fd_to_close = conn->sock[sockindex]; - conn->sock[sockindex] = CURL_SOCKET_BAD; - *connected = FALSE; - - if(sockindex != FIRSTSOCKET) { - Curl_closesocket(conn, fd_to_close); - return CURLE_COULDNT_CONNECT; /* no next */ - } - - /* try the next address */ - ai = conn->ip_addr->ai_next; - - while(ai) { - CURLcode res = singleipconnect(conn, ai, 0L, &sockfd, connected); - if(res) - return res; - if(sockfd != CURL_SOCKET_BAD) { - /* store the new socket descriptor */ - conn->sock[sockindex] = sockfd; - conn->ip_addr = ai; - Curl_closesocket(conn, fd_to_close); - return CURLE_OK; - } - ai = ai->ai_next; - } - Curl_closesocket(conn, fd_to_close); - return CURLE_COULDNT_CONNECT; -} - -/* Copies connection info into the session handle to make it available - when the session handle is no longer associated with a connection. */ -void Curl_persistconninfo(struct connectdata *conn) -{ - memcpy(conn->data->info.conn_primary_ip, conn->primary_ip, MAX_IPADR_LEN); - memcpy(conn->data->info.conn_local_ip, conn->local_ip, MAX_IPADR_LEN); - conn->data->info.conn_primary_port = conn->primary_port; - conn->data->info.conn_local_port = conn->local_port; -} - -/* retrieves ip address and port from a sockaddr structure */ -static bool getaddressinfo(struct sockaddr* sa, char* addr, - long* port) -{ - unsigned short us_port; - struct sockaddr_in* si = NULL; -#ifdef ENABLE_IPV6 - struct sockaddr_in6* si6 = NULL; -#endif -#if defined(HAVE_SYS_UN_H) && defined(AF_UNIX) - struct sockaddr_un* su = NULL; -#endif - - switch (sa->sa_family) { - case AF_INET: - si = (struct sockaddr_in*) sa; - if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, - addr, MAX_IPADR_LEN)) { - us_port = ntohs(si->sin_port); - *port = us_port; - return TRUE; - } - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - si6 = (struct sockaddr_in6*)sa; - if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, - addr, MAX_IPADR_LEN)) { - us_port = ntohs(si6->sin6_port); - *port = us_port; - return TRUE; - } - break; -#endif -#if defined(HAVE_SYS_UN_H) && defined(AF_UNIX) - case AF_UNIX: - su = (struct sockaddr_un*)sa; - snprintf(addr, MAX_IPADR_LEN, "%s", su->sun_path); - *port = 0; - return TRUE; -#endif - default: - break; - } - - addr[0] = '\0'; - *port = 0; - - return FALSE; -} - -/* retrieves the start/end point information of a socket of an established - connection */ -void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd) -{ - int error; - curl_socklen_t len; - struct Curl_sockaddr_storage ssrem; - struct Curl_sockaddr_storage ssloc; - struct SessionHandle *data = conn->data; - - if(!conn->bits.reuse) { - - len = sizeof(struct Curl_sockaddr_storage); - if(getpeername(sockfd, (struct sockaddr*) &ssrem, &len)) { - error = SOCKERRNO; - failf(data, "getpeername() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return; - } - - len = sizeof(struct Curl_sockaddr_storage); - if(getsockname(sockfd, (struct sockaddr*) &ssloc, &len)) { - error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return; - } - - if(!getaddressinfo((struct sockaddr*)&ssrem, - conn->primary_ip, &conn->primary_port)) { - error = ERRNO; - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return; - } - - if(!getaddressinfo((struct sockaddr*)&ssloc, - conn->local_ip, &conn->local_port)) { - error = ERRNO; - failf(data, "ssloc inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return; - } - - } - - /* persist connection info in session handle */ - Curl_persistconninfo(conn); -} - -/* - * Curl_is_connected() is used from the multi interface to check if the - * firstsocket has connected. - */ - -CURLcode Curl_is_connected(struct connectdata *conn, - int sockindex, - bool *connected) -{ - int rc; - struct SessionHandle *data = conn->data; - CURLcode code = CURLE_OK; - curl_socket_t sockfd = conn->sock[sockindex]; - long allow = DEFAULT_CONNECT_TIMEOUT; - int error = 0; - struct timeval now; - - DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); - - *connected = FALSE; /* a very negative world view is best */ - - if(conn->bits.tcpconnect[sockindex]) { - /* we are connected already! */ - *connected = TRUE; - return CURLE_OK; - } - - now = Curl_tvnow(); - - /* figure out how long time we have left to connect */ - allow = Curl_timeleft(data, &now, TRUE); - - if(allow < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* check for connect without timeout as we want to return immediately */ - rc = waitconnect(conn, sockfd, 0); - if(WAITCONN_TIMEOUT == rc) { - if(curlx_tvdiff(now, conn->connecttime) >= conn->timeoutms_per_addr) { - infof(data, "After %ldms connect time, move on!\n", - conn->timeoutms_per_addr); - goto next; - } - - /* not an error, but also no connection yet */ - return code; - } - - if(WAITCONN_CONNECTED == rc) { - if(verifyconnect(sockfd, &error)) { - /* we are connected with TCP, awesome! */ - - /* see if we need to do any proxy magic first once we connected */ - code = Curl_connected_proxy(conn); - if(code) - return code; - - conn->bits.tcpconnect[sockindex] = TRUE; - *connected = TRUE; - if(sockindex == FIRSTSOCKET) - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - Curl_verboseconnect(conn); - Curl_updateconninfo(conn, sockfd); - - return CURLE_OK; - } - /* nope, not connected for real */ - } - else { - /* nope, not connected */ - if(WAITCONN_FDSET_ERROR == rc) { - (void)verifyconnect(sockfd, &error); - infof(data, "%s\n",Curl_strerror(conn, error)); - } - else - infof(data, "Connection failed\n"); - } - - /* - * The connection failed here, we should attempt to connect to the "next - * address" for the given host. But first remember the latest error. - */ - if(error) { - data->state.os_errno = error; - SET_SOCKERRNO(error); - } - next: - - conn->timeoutms_per_addr = conn->ip_addr->ai_next == NULL ? - allow : allow / 2; - code = trynextip(conn, sockindex, connected); - - if(code) { - error = SOCKERRNO; - data->state.os_errno = error; - failf(data, "Failed connect to %s:%ld; %s", - conn->host.name, conn->port, Curl_strerror(conn, error)); - } - - return code; -} - -static void tcpnodelay(struct connectdata *conn, - curl_socket_t sockfd) -{ -#ifdef TCP_NODELAY - struct SessionHandle *data= conn->data; - curl_socklen_t onoff = (curl_socklen_t) data->set.tcp_nodelay; - int level = IPPROTO_TCP; - -#if 0 - /* The use of getprotobyname() is disabled since it isn't thread-safe on - numerous systems. On these getprotobyname_r() should be used instead, but - that exists in at least one 4 arg version and one 5 arg version, and - since the proto number rarely changes anyway we now just use the hard - coded number. The "proper" fix would need a configure check for the - correct function much in the same style the gethostbyname_r versions are - detected. */ - struct protoent *pe = getprotobyname("tcp"); - if(pe) - level = pe->p_proto; -#endif - - if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, - sizeof(onoff)) < 0) - infof(data, "Could not set TCP_NODELAY: %s\n", - Curl_strerror(conn, SOCKERRNO)); - else - infof(data,"TCP_NODELAY set\n"); -#else - (void)conn; - (void)sockfd; -#endif -} - -#ifdef SO_NOSIGPIPE -/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when - sending data to a dead peer (instead of relying on the 4th argument to send - being MSG_NOSIGNAL). Possibly also existing and in use on other BSD - systems? */ -static void nosigpipe(struct connectdata *conn, - curl_socket_t sockfd) -{ - struct SessionHandle *data= conn->data; - int onoff = 1; - if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, - sizeof(onoff)) < 0) - infof(data, "Could not set SO_NOSIGPIPE: %s\n", - Curl_strerror(conn, SOCKERRNO)); -} -#else -#define nosigpipe(x,y) Curl_nop_stmt -#endif - -#ifdef USE_WINSOCK -/* When you run a program that uses the Windows Sockets API, you may - experience slow performance when you copy data to a TCP server. - - http://support.microsoft.com/kb/823764 - - Work-around: Make the Socket Send Buffer Size Larger Than the Program Send - Buffer Size - -*/ -void Curl_sndbufset(curl_socket_t sockfd) -{ - int val = CURL_MAX_WRITE_SIZE + 32; - int curval = 0; - int curlen = sizeof(curval); - - if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0) - if(curval > val) - return; - - setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val)); -} -#endif - - -/* - * singleipconnect() - * - * Note that even on connect fail it returns CURLE_OK, but with 'sock' set to - * CURL_SOCKET_BAD. Other errors will however return proper errors. - * - * singleipconnect() connects to the given IP only, and it may return without - * having connected if used from the multi interface. - */ -static CURLcode -singleipconnect(struct connectdata *conn, - const Curl_addrinfo *ai, - long timeout_ms, - curl_socket_t *sockp, - bool *connected) -{ - struct Curl_sockaddr_ex addr; - int rc; - int error = 0; - bool isconnected = FALSE; - struct SessionHandle *data = conn->data; - curl_socket_t sockfd; - CURLcode res = CURLE_OK; - - *sockp = CURL_SOCKET_BAD; - *connected = FALSE; /* default is not connected */ - - res = Curl_socket(conn, ai, &addr, &sockfd); - if(res) - /* Failed to create the socket, but still return OK since we signal the - lack of socket as well. This allows the parent function to keep looping - over alternative addresses/socket families etc. */ - return CURLE_OK; - - /* store remote address and port used in this connection attempt */ - if(!getaddressinfo((struct sockaddr*)&addr.sa_addr, - conn->primary_ip, &conn->primary_port)) { - /* malformed address or bug in inet_ntop, try next address */ - error = ERRNO; - failf(data, "sa_addr inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - Curl_closesocket(conn, sockfd); - return CURLE_OK; - } - memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN); - infof(data, " Trying %s...\n", conn->ip_addr_str); - - Curl_persistconninfo(conn); - - if(data->set.tcp_nodelay) - tcpnodelay(conn, sockfd); - - nosigpipe(conn, sockfd); - - Curl_sndbufset(sockfd); - - if(data->set.tcp_keepalive) - tcpkeepalive(data, sockfd); - - if(data->set.fsockopt) { - /* activate callback for setting socket options */ - error = data->set.fsockopt(data->set.sockopt_client, - sockfd, - CURLSOCKTYPE_IPCXN); - - if(error == CURL_SOCKOPT_ALREADY_CONNECTED) - isconnected = TRUE; - else if(error) { - Curl_closesocket(conn, sockfd); /* close the socket and bail out */ - return CURLE_ABORTED_BY_CALLBACK; - } - } - - /* possibly bind the local end to an IP, interface or port */ - res = bindlocal(conn, sockfd, addr.family); - if(res) { - Curl_closesocket(conn, sockfd); /* close socket and bail out */ - return res; - } - - /* set socket non-blocking */ - curlx_nonblock(sockfd, TRUE); - - /* Connect TCP sockets, bind UDP */ - if(!isconnected && (conn->socktype == SOCK_STREAM)) { - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); - if(-1 == rc) - error = SOCKERRNO; - conn->connecttime = Curl_tvnow(); - if(conn->num_addr > 1) - Curl_expire(data, conn->timeoutms_per_addr); - } - else - rc = 0; - - if(-1 == rc) { - switch (error) { - case EINPROGRESS: - case EWOULDBLOCK: -#if defined(EAGAIN) -#if (EAGAIN) != (EWOULDBLOCK) - /* On some platforms EAGAIN and EWOULDBLOCK are the - * same value, and on others they are different, hence - * the odd #if - */ - case EAGAIN: -#endif -#endif - rc = waitconnect(conn, sockfd, timeout_ms); - if(WAITCONN_ABORTED == rc) { - Curl_closesocket(conn, sockfd); - return CURLE_ABORTED_BY_CALLBACK; - } - break; - default: - /* unknown error, fallthrough and try another address! */ - failf(data, "Failed to connect to %s: %s", - conn->ip_addr_str, Curl_strerror(conn,error)); - data->state.os_errno = error; - break; - } - } - - /* The 'WAITCONN_TIMEOUT == rc' comes from the waitconnect(), and not from - connect(). We can be sure of this since connect() cannot return 1. */ - if((WAITCONN_TIMEOUT == rc) && - (data->state.used_interface == Curl_if_multi)) { - /* Timeout when running the multi interface */ - *sockp = sockfd; - return CURLE_OK; - } - - if(!isconnected) - isconnected = verifyconnect(sockfd, &error); - - if(!rc && isconnected) { - /* we are connected, awesome! */ - *connected = TRUE; /* this is a true connect */ - infof(data, "connected\n"); -#ifdef ENABLE_IPV6 - conn->bits.ipv6 = (addr.family == AF_INET6)?TRUE:FALSE; -#endif - - Curl_updateconninfo(conn, sockfd); - *sockp = sockfd; - return CURLE_OK; - } - else if(WAITCONN_TIMEOUT == rc) - infof(data, "Timeout\n"); - else { - data->state.os_errno = error; - infof(data, "%s\n", Curl_strerror(conn, error)); - } - - /* connect failed or timed out */ - Curl_closesocket(conn, sockfd); - - return CURLE_OK; -} - -/* - * TCP connect to the given host with timeout, proxy or remote doesn't matter. - * There might be more than one IP address to try out. Fill in the passed - * pointer with the connected socket. - */ - -CURLcode Curl_connecthost(struct connectdata *conn, /* context */ - const struct Curl_dns_entry *remotehost, - curl_socket_t *sockconn, /* the connected socket */ - Curl_addrinfo **addr, /* the one we used */ - bool *connected) /* really connected? */ -{ - struct SessionHandle *data = conn->data; - curl_socket_t sockfd = CURL_SOCKET_BAD; - Curl_addrinfo *ai; - Curl_addrinfo *curr_addr; - - struct timeval after; - struct timeval before = Curl_tvnow(); - - /************************************************************* - * Figure out what maximum time we have left - *************************************************************/ - long timeout_ms; - - DEBUGASSERT(sockconn); - *connected = FALSE; /* default to not connected */ - - /* get the timeout left */ - timeout_ms = Curl_timeleft(data, &before, TRUE); - - if(timeout_ms < 0) { - /* a precaution, no need to continue if time already is up */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - conn->num_addr = Curl_num_addresses(remotehost->addr); - - ai = remotehost->addr; - - /* Below is the loop that attempts to connect to all IP-addresses we - * know for the given host. One by one until one IP succeeds. - */ - - /* - * Connecting with a Curl_addrinfo chain - */ - for(curr_addr = ai; curr_addr; curr_addr = curr_addr->ai_next) { - CURLcode res; - - /* Max time for the next address */ - conn->timeoutms_per_addr = curr_addr->ai_next == NULL ? - timeout_ms : timeout_ms / 2; - - /* start connecting to the IP curr_addr points to */ - res = singleipconnect(conn, curr_addr, - /* don't hang when doing multi */ - (data->state.used_interface == Curl_if_multi)?0: - conn->timeoutms_per_addr, &sockfd, connected); - if(res) - return res; - - if(sockfd != CURL_SOCKET_BAD) - break; - - /* get a new timeout for next attempt */ - after = Curl_tvnow(); - timeout_ms -= Curl_tvdiff(after, before); - if(timeout_ms < 0) { - failf(data, "connect() timed out!"); - return CURLE_OPERATION_TIMEDOUT; - } - before = after; - } /* end of connect-to-each-address loop */ - - *sockconn = sockfd; /* the socket descriptor we've connected */ - - if(sockfd == CURL_SOCKET_BAD) { - /* no good connect was made */ - failf(data, "couldn't connect to %s at %s:%d", - conn->bits.proxy?"proxy":"host", - conn->bits.proxy?conn->proxy.name:conn->host.name, conn->port); - return CURLE_COULDNT_CONNECT; - } - - /* leave the socket in non-blocking mode */ - - /* store the address we use */ - if(addr) - *addr = curr_addr; - - data->info.numconnects++; /* to track the number of connections made */ - - return CURLE_OK; -} - -/* - * Used to extract socket and connectdata struct for the most recent - * transfer on the given SessionHandle. - * - * The returned socket will be CURL_SOCKET_BAD in case of failure! - */ -curl_socket_t Curl_getconnectinfo(struct SessionHandle *data, - struct connectdata **connp) -{ - curl_socket_t sockfd; - - DEBUGASSERT(data); - - if(data->state.lastconnect) { - struct connectdata *c = data->state.lastconnect; - if(connp) - /* only store this if the caller cares for it */ - *connp = c; - sockfd = c->sock[FIRSTSOCKET]; - /* we have a socket connected, let's determine if the server shut down */ - /* determine if ssl */ - if(c->ssl[FIRSTSOCKET].use) { - /* use the SSL context */ - if(!Curl_ssl_check_cxn(c)) - return CURL_SOCKET_BAD; /* FIN received */ - } -/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */ -#ifdef MSG_PEEK - else { - /* use the socket */ - char buf; - if(recv((RECV_TYPE_ARG1)c->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf, - (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) { - return CURL_SOCKET_BAD; /* FIN received */ - } - } -#endif - } - else - return CURL_SOCKET_BAD; - - return sockfd; -} - -/* - * Close a socket. - * - * 'conn' can be NULL, beware! - */ -int Curl_closesocket(struct connectdata *conn, - curl_socket_t sock) -{ - if(conn && conn->fclosesocket) { - if((sock == conn->sock[SECONDARYSOCKET]) && - conn->sock_accepted[SECONDARYSOCKET]) - /* if this socket matches the second socket, and that was created with - accept, then we MUST NOT call the callback but clear the accepted - status */ - conn->sock_accepted[SECONDARYSOCKET] = FALSE; - else - return conn->fclosesocket(conn->closesocket_client, sock); - } - return sclose(sock); -} - -/* - * Create a socket based on info from 'conn' and 'ai'. - * - * 'addr' should be a pointer to the correct struct to get data back, or NULL. - * 'sockfd' must be a pointer to a socket descriptor. - * - * If the open socket callback is set, used that! - * - */ -CURLcode Curl_socket(struct connectdata *conn, - const Curl_addrinfo *ai, - struct Curl_sockaddr_ex *addr, - curl_socket_t *sockfd) -{ - struct SessionHandle *data = conn->data; - struct Curl_sockaddr_ex dummy; - - if(!addr) - /* if the caller doesn't want info back, use a local temp copy */ - addr = &dummy; - - /* - * The Curl_sockaddr_ex structure is basically libcurl's external API - * curl_sockaddr structure with enough space available to directly hold - * any protocol-specific address structures. The variable declared here - * will be used to pass / receive data to/from the fopensocket callback - * if this has been set, before that, it is initialized from parameters. - */ - - addr->family = ai->ai_family; - addr->socktype = conn->socktype; - addr->protocol = conn->socktype==SOCK_DGRAM?IPPROTO_UDP:ai->ai_protocol; - addr->addrlen = ai->ai_addrlen; - - if(addr->addrlen > sizeof(struct Curl_sockaddr_storage)) - addr->addrlen = sizeof(struct Curl_sockaddr_storage); - memcpy(&addr->sa_addr, ai->ai_addr, addr->addrlen); - - if(data->set.fopensocket) - /* - * If the opensocket callback is set, all the destination address - * information is passed to the callback. Depending on this information the - * callback may opt to abort the connection, this is indicated returning - * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When - * the callback returns a valid socket the destination address information - * might have been changed and this 'new' address will actually be used - * here to connect. - */ - *sockfd = data->set.fopensocket(data->set.opensocket_client, - CURLSOCKTYPE_IPCXN, - (struct curl_sockaddr *)addr); - else - /* opensocket callback not set, so simply create the socket now */ - *sockfd = socket(addr->family, addr->socktype, addr->protocol); - - if(*sockfd == CURL_SOCKET_BAD) - /* no socket, no connection */ - return CURLE_COULDNT_CONNECT; - -#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) - if(conn->scope && (addr->family == AF_INET6)) { - struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; - sa6->sin6_scope_id = conn->scope; - } -#endif - - return CURLE_OK; - -} diff --git a/lib/content_encoding.c b/lib/content_encoding.c deleted file mode 100644 index 6f4d1428a..000000000 --- a/lib/content_encoding.c +++ /dev/null @@ -1,435 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_LIBZ - -#include "curl_urldata.h" -#include -#include "curl_sendf.h" -#include "curl_content_encoding.h" -#include "curl_memory.h" - -#include "curl_memdebug.h" - -/* Comment this out if zlib is always going to be at least ver. 1.2.0.4 - (doing so will reduce code size slightly). */ -#define OLD_ZLIB_SUPPORT 1 - -#define DSIZ CURL_MAX_WRITE_SIZE /* buffer size for decompressed data */ - -#define GZIP_MAGIC_0 0x1f -#define GZIP_MAGIC_1 0x8b - -/* gzip flag byte */ -#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ -#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ -#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ -#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ -#define COMMENT 0x10 /* bit 4 set: file comment present */ -#define RESERVED 0xE0 /* bits 5..7: reserved */ - -static voidpf -zalloc_cb(voidpf opaque, unsigned int items, unsigned int size) -{ - (void) opaque; - /* not a typo, keep it calloc() */ - return (voidpf) calloc(items, size); -} - -static void -zfree_cb(voidpf opaque, voidpf ptr) -{ - (void) opaque; - free(ptr); -} - -static CURLcode -process_zlib_error(struct connectdata *conn, z_stream *z) -{ - struct SessionHandle *data = conn->data; - if(z->msg) - failf (data, "Error while processing content unencoding: %s", - z->msg); - else - failf (data, "Error while processing content unencoding: " - "Unknown failure within decompression software."); - - return CURLE_BAD_CONTENT_ENCODING; -} - -static CURLcode -exit_zlib(z_stream *z, zlibInitState *zlib_init, CURLcode result) -{ - inflateEnd(z); - *zlib_init = ZLIB_UNINIT; - return result; -} - -static CURLcode -inflate_stream(struct connectdata *conn, - struct SingleRequest *k) -{ - int allow_restart = 1; - z_stream *z = &k->z; /* zlib state structure */ - uInt nread = z->avail_in; - Bytef *orig_in = z->next_in; - int status; /* zlib status */ - CURLcode result = CURLE_OK; /* Curl_client_write status */ - char *decomp; /* Put the decompressed data here. */ - - /* Dynamically allocate a buffer for decompression because it's uncommonly - large to hold on the stack */ - decomp = malloc(DSIZ); - if(decomp == NULL) { - return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); - } - - /* because the buffer size is fixed, iteratively decompress and transfer to - the client via client_write. */ - for(;;) { - /* (re)set buffer for decompressed output for every iteration */ - z->next_out = (Bytef *)decomp; - z->avail_out = DSIZ; - - status = inflate(z, Z_SYNC_FLUSH); - if(status == Z_OK || status == Z_STREAM_END) { - allow_restart = 0; - if((DSIZ - z->avail_out) && (!k->ignorebody)) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, decomp, - DSIZ - z->avail_out); - /* if !CURLE_OK, clean up, return */ - if(result) { - free(decomp); - return exit_zlib(z, &k->zlib_init, result); - } - } - - /* Done? clean up, return */ - if(status == Z_STREAM_END) { - free(decomp); - if(inflateEnd(z) == Z_OK) - return exit_zlib(z, &k->zlib_init, result); - else - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); - } - - /* Done with these bytes, exit */ - - /* status is always Z_OK at this point! */ - if(z->avail_in == 0) { - free(decomp); - return result; - } - } - else if(allow_restart && status == Z_DATA_ERROR) { - /* some servers seem to not generate zlib headers, so this is an attempt - to fix and continue anyway */ - - (void) inflateEnd(z); /* don't care about the return code */ - if(inflateInit2(z, -MAX_WBITS) != Z_OK) { - free(decomp); - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); - } - z->next_in = orig_in; - z->avail_in = nread; - allow_restart = 0; - continue; - } - else { /* Error; exit loop, handle below */ - free(decomp); - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); - } - } - /* Will never get here */ -} - -CURLcode -Curl_unencode_deflate_write(struct connectdata *conn, - struct SingleRequest *k, - ssize_t nread) -{ - z_stream *z = &k->z; /* zlib state structure */ - - /* Initialize zlib? */ - if(k->zlib_init == ZLIB_UNINIT) { - memset(z, 0, sizeof(z_stream)); - z->zalloc = (alloc_func)zalloc_cb; - z->zfree = (free_func)zfree_cb; - - if(inflateInit(z) != Z_OK) - return process_zlib_error(conn, z); - k->zlib_init = ZLIB_INIT; - } - - /* Set the compressed input when this function is called */ - z->next_in = (Bytef *)k->str; - z->avail_in = (uInt)nread; - - /* Now uncompress the data */ - return inflate_stream(conn, k); -} - -#ifdef OLD_ZLIB_SUPPORT -/* Skip over the gzip header */ -static enum { - GZIP_OK, - GZIP_BAD, - GZIP_UNDERFLOW -} check_gzip_header(unsigned char const *data, ssize_t len, ssize_t *headerlen) -{ - int method, flags; - const ssize_t totallen = len; - - /* The shortest header is 10 bytes */ - if(len < 10) - return GZIP_UNDERFLOW; - - if((data[0] != GZIP_MAGIC_0) || (data[1] != GZIP_MAGIC_1)) - return GZIP_BAD; - - method = data[2]; - flags = data[3]; - - if(method != Z_DEFLATED || (flags & RESERVED) != 0) { - /* Can't handle this compression method or unknown flag */ - return GZIP_BAD; - } - - /* Skip over time, xflags, OS code and all previous bytes */ - len -= 10; - data += 10; - - if(flags & EXTRA_FIELD) { - ssize_t extra_len; - - if(len < 2) - return GZIP_UNDERFLOW; - - extra_len = (data[1] << 8) | data[0]; - - if(len < (extra_len+2)) - return GZIP_UNDERFLOW; - - len -= (extra_len + 2); - data += (extra_len + 2); - } - - if(flags & ORIG_NAME) { - /* Skip over NUL-terminated file name */ - while(len && *data) { - --len; - ++data; - } - if(!len || *data) - return GZIP_UNDERFLOW; - - /* Skip over the NUL */ - --len; - ++data; - } - - if(flags & COMMENT) { - /* Skip over NUL-terminated comment */ - while(len && *data) { - --len; - ++data; - } - if(!len || *data) - return GZIP_UNDERFLOW; - - /* Skip over the NUL */ - --len; - } - - if(flags & HEAD_CRC) { - if(len < 2) - return GZIP_UNDERFLOW; - - len -= 2; - } - - *headerlen = totallen - len; - return GZIP_OK; -} -#endif - -CURLcode -Curl_unencode_gzip_write(struct connectdata *conn, - struct SingleRequest *k, - ssize_t nread) -{ - z_stream *z = &k->z; /* zlib state structure */ - - /* Initialize zlib? */ - if(k->zlib_init == ZLIB_UNINIT) { - memset(z, 0, sizeof(z_stream)); - z->zalloc = (alloc_func)zalloc_cb; - z->zfree = (free_func)zfree_cb; - - if(strcmp(zlibVersion(), "1.2.0.4") >= 0) { - /* zlib ver. >= 1.2.0.4 supports transparent gzip decompressing */ - if(inflateInit2(z, MAX_WBITS+32) != Z_OK) { - return process_zlib_error(conn, z); - } - k->zlib_init = ZLIB_INIT_GZIP; /* Transparent gzip decompress state */ - } - else { - /* we must parse the gzip header ourselves */ - if(inflateInit2(z, -MAX_WBITS) != Z_OK) { - return process_zlib_error(conn, z); - } - k->zlib_init = ZLIB_INIT; /* Initial call state */ - } - } - - if(k->zlib_init == ZLIB_INIT_GZIP) { - /* Let zlib handle the gzip decompression entirely */ - z->next_in = (Bytef *)k->str; - z->avail_in = (uInt)nread; - /* Now uncompress the data */ - return inflate_stream(conn, k); - } - -#ifndef OLD_ZLIB_SUPPORT - /* Support for old zlib versions is compiled away and we are running with - an old version, so return an error. */ - return exit_zlib(z, &k->zlib_init, CURLE_FUNCTION_NOT_FOUND); - -#else - /* This next mess is to get around the potential case where there isn't - * enough data passed in to skip over the gzip header. If that happens, we - * malloc a block and copy what we have then wait for the next call. If - * there still isn't enough (this is definitely a worst-case scenario), we - * make the block bigger, copy the next part in and keep waiting. - * - * This is only required with zlib versions < 1.2.0.4 as newer versions - * can handle the gzip header themselves. - */ - - switch (k->zlib_init) { - /* Skip over gzip header? */ - case ZLIB_INIT: - { - /* Initial call state */ - ssize_t hlen; - - switch (check_gzip_header((unsigned char *)k->str, nread, &hlen)) { - case GZIP_OK: - z->next_in = (Bytef *)k->str + hlen; - z->avail_in = (uInt)(nread - hlen); - k->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ - break; - - case GZIP_UNDERFLOW: - /* We need more data so we can find the end of the gzip header. It's - * possible that the memory block we malloc here will never be freed if - * the transfer abruptly aborts after this point. Since it's unlikely - * that circumstances will be right for this code path to be followed in - * the first place, and it's even more unlikely for a transfer to fail - * immediately afterwards, it should seldom be a problem. - */ - z->avail_in = (uInt)nread; - z->next_in = malloc(z->avail_in); - if(z->next_in == NULL) { - return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); - } - memcpy(z->next_in, k->str, z->avail_in); - k->zlib_init = ZLIB_GZIP_HEADER; /* Need more gzip header data state */ - /* We don't have any data to inflate yet */ - return CURLE_OK; - - case GZIP_BAD: - default: - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); - } - - } - break; - - case ZLIB_GZIP_HEADER: - { - /* Need more gzip header data state */ - ssize_t hlen; - unsigned char *oldblock = z->next_in; - - z->avail_in += (uInt)nread; - z->next_in = realloc(z->next_in, z->avail_in); - if(z->next_in == NULL) { - free(oldblock); - return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); - } - /* Append the new block of data to the previous one */ - memcpy(z->next_in + z->avail_in - nread, k->str, nread); - - switch (check_gzip_header(z->next_in, z->avail_in, &hlen)) { - case GZIP_OK: - /* This is the zlib stream data */ - free(z->next_in); - /* Don't point into the malloced block since we just freed it */ - z->next_in = (Bytef *)k->str + hlen + nread - z->avail_in; - z->avail_in = (uInt)(z->avail_in - hlen); - k->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ - break; - - case GZIP_UNDERFLOW: - /* We still don't have any data to inflate! */ - return CURLE_OK; - - case GZIP_BAD: - default: - free(z->next_in); - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); - } - - } - break; - - case ZLIB_GZIP_INFLATING: - default: - /* Inflating stream state */ - z->next_in = (Bytef *)k->str; - z->avail_in = (uInt)nread; - break; - } - - if(z->avail_in == 0) { - /* We don't have any data to inflate; wait until next time */ - return CURLE_OK; - } - - /* We've parsed the header, now uncompress the data */ - return inflate_stream(conn, k); -#endif -} - -void Curl_unencode_cleanup(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - struct SingleRequest *k = &data->req; - z_stream *z = &k->z; - if(k->zlib_init != ZLIB_UNINIT) - (void) exit_zlib(z, &k->zlib_init, CURLE_OK); -} - -#endif /* HAVE_LIBZ */ diff --git a/lib/cookie.c b/lib/cookie.c deleted file mode 100644 index 90ee884bb..000000000 --- a/lib/cookie.c +++ /dev/null @@ -1,1163 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -/*** - - -RECEIVING COOKIE INFORMATION -============================ - -struct CookieInfo *cookie_init(char *file); - - Inits a cookie struct to store data in a local file. This is always - called before any cookies are set. - -int cookies_set(struct CookieInfo *cookie, char *cookie_line); - - The 'cookie_line' parameter is a full "Set-cookie:" line as - received from a server. - - The function need to replace previously stored lines that this new - line superceeds. - - It may remove lines that are expired. - - It should return an indication of success/error. - - -SENDING COOKIE INFORMATION -========================== - -struct Cookies *cookie_getlist(struct CookieInfo *cookie, - char *host, char *path, bool secure); - - For a given host and path, return a linked list of cookies that - the client should send to the server if used now. The secure - boolean informs the cookie if a secure connection is achieved or - not. - - It shall only return cookies that haven't expired. - - -Example set of cookies: - - Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure - Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; - domain=.fidelity.com; path=/ftgw; secure - Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; - domain=.fidelity.com; path=/; secure - Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; - domain=.fidelity.com; path=/; secure - Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; - domain=.fidelity.com; path=/; secure - Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; - domain=.fidelity.com; path=/; secure - Set-cookie: - Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday, - 13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure -****/ - - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - -#define _MPRINTF_REPLACE -#include - -#include "curl_urldata.h" -#include "curl_cookie.h" -#include "curl_strequal.h" -#include "curl_strtok.h" -#include "curl_sendf.h" -#include "curl_memory.h" -#include "curl_share.h" -#include "curl_strtoofft.h" -#include "curl_rawstr.h" -#include "curl_memrchr.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -static void freecookie(struct Cookie *co) -{ - if(co->expirestr) - free(co->expirestr); - if(co->domain) - free(co->domain); - if(co->path) - free(co->path); - if(co->name) - free(co->name); - if(co->value) - free(co->value); - if(co->maxage) - free(co->maxage); - if(co->version) - free(co->version); - - free(co); -} - -static bool tailmatch(const char *little, const char *bigone) -{ - size_t littlelen = strlen(little); - size_t biglen = strlen(bigone); - - if(littlelen > biglen) - return FALSE; - - return Curl_raw_equal(little, bigone+biglen-littlelen) ? TRUE : FALSE; -} - -/* - * Load cookies from all given cookie files (CURLOPT_COOKIEFILE). - */ -void Curl_cookie_loadfiles(struct SessionHandle *data) -{ - struct curl_slist *list = data->change.cookielist; - if(list) { - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - while(list) { - data->cookies = Curl_cookie_init(data, - list->data, - data->cookies, - data->set.cookiesession); - list = list->next; - } - curl_slist_free_all(data->change.cookielist); /* clean up list */ - data->change.cookielist = NULL; /* don't do this again! */ - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } -} - -/* - * strstore() makes a strdup() on the 'newstr' and if '*str' is non-NULL - * that will be freed before the allocated string is stored there. - * - * It is meant to easily replace strdup() - */ -static void strstore(char **str, const char *newstr) -{ - if(*str) - free(*str); - *str = strdup(newstr); -} - - -/**************************************************************************** - * - * Curl_cookie_add() - * - * Add a single cookie line to the cookie keeping object. - * - ***************************************************************************/ - -struct Cookie * -Curl_cookie_add(struct SessionHandle *data, - /* The 'data' pointer here may be NULL at times, and thus - must only be used very carefully for things that can deal - with data being NULL. Such as infof() and similar */ - - struct CookieInfo *c, - bool httpheader, /* TRUE if HTTP header-style line */ - char *lineptr, /* first character of the line */ - const char *domain, /* default domain */ - const char *path) /* full path used when this cookie is set, - used to get default path for the cookie - unless set */ -{ - struct Cookie *clist; - char name[MAX_NAME]; - struct Cookie *co; - struct Cookie *lastc=NULL; - time_t now = time(NULL); - bool replace_old = FALSE; - bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */ - -#ifdef CURL_DISABLE_VERBOSE_STRINGS - (void)data; -#endif - - /* First, alloc and init a new struct for it */ - co = calloc(1, sizeof(struct Cookie)); - if(!co) - return NULL; /* bail out if we're this low on memory */ - - if(httpheader) { - /* This line was read off a HTTP-header */ - const char *ptr; - const char *semiptr; - char *what; - - what = malloc(MAX_COOKIE_LINE); - if(!what) { - free(co); - return NULL; - } - - semiptr=strchr(lineptr, ';'); /* first, find a semicolon */ - - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; - - ptr = lineptr; - do { - /* we have a = pair or a stand-alone word here */ - name[0]=what[0]=0; /* init the buffers */ - if(1 <= sscanf(ptr, "%" MAX_NAME_TXT "[^;\r\n =]=%" - MAX_COOKIE_LINE_TXT "[^;\r\n]", - name, what)) { - /* Use strstore() below to properly deal with received cookie - headers that have the same string property set more than once, - and then we use the last one. */ - const char *whatptr; - bool done = FALSE; - bool sep; - size_t len=strlen(what); - const char *endofn = &ptr[ strlen(name) ]; - - /* skip trailing spaces in name */ - while(*endofn && ISBLANK(*endofn)) - endofn++; - - /* name ends with a '=' ? */ - sep = (*endofn == '=')?TRUE:FALSE; - - /* Strip off trailing whitespace from the 'what' */ - while(len && ISBLANK(what[len-1])) { - what[len-1]=0; - len--; - } - - /* Skip leading whitespace from the 'what' */ - whatptr=what; - while(*whatptr && ISBLANK(*whatptr)) - whatptr++; - - if(!len) { - /* this was a "=" with no content, and we must allow - 'secure' and 'httponly' specified this weirdly */ - done = TRUE; - if(Curl_raw_equal("secure", name)) - co->secure = TRUE; - else if(Curl_raw_equal("httponly", name)) - co->httponly = TRUE; - else if(sep) - /* there was a '=' so we're not done parsing this field */ - done = FALSE; - } - if(done) - ; - else if(Curl_raw_equal("path", name)) { - strstore(&co->path, whatptr); - if(!co->path) { - badcookie = TRUE; /* out of memory bad */ - break; - } - } - else if(Curl_raw_equal("domain", name)) { - /* note that this name may or may not have a preceding dot, but - we don't care about that, we treat the names the same anyway */ - - const char *domptr=whatptr; - const char *nextptr; - int dotcount=1; - - /* Count the dots, we need to make sure that there are enough - of them. */ - - if('.' == whatptr[0]) - /* don't count the initial dot, assume it */ - domptr++; - - do { - nextptr = strchr(domptr, '.'); - if(nextptr) { - if(domptr != nextptr) - dotcount++; - domptr = nextptr+1; - } - } while(nextptr); - - /* The original Netscape cookie spec defined that this domain name - MUST have three dots (or two if one of the seven holy TLDs), - but it seems that these kinds of cookies are in use "out there" - so we cannot be that strict. I've therefore lowered the check - to not allow less than two dots. */ - - if(dotcount < 2) { - /* Received and skipped a cookie with a domain using too few - dots. */ - badcookie=TRUE; /* mark this as a bad cookie */ - infof(data, "skipped cookie with illegal dotcount domain: %s\n", - whatptr); - } - else { - /* Now, we make sure that our host is within the given domain, - or the given domain is not valid and thus cannot be set. */ - - if('.' == whatptr[0]) - whatptr++; /* ignore preceding dot */ - - if(!domain || tailmatch(whatptr, domain)) { - const char *tailptr=whatptr; - if(tailptr[0] == '.') - tailptr++; - strstore(&co->domain, tailptr); /* don't prefix w/dots - internally */ - if(!co->domain) { - badcookie = TRUE; - break; - } - co->tailmatch=TRUE; /* we always do that if the domain name was - given */ - } - else { - /* we did not get a tailmatch and then the attempted set domain - is not a domain to which the current host belongs. Mark as - bad. */ - badcookie=TRUE; - infof(data, "skipped cookie with bad tailmatch domain: %s\n", - whatptr); - } - } - } - else if(Curl_raw_equal("version", name)) { - strstore(&co->version, whatptr); - if(!co->version) { - badcookie = TRUE; - break; - } - } - else if(Curl_raw_equal("max-age", name)) { - /* Defined in RFC2109: - - Optional. The Max-Age attribute defines the lifetime of the - cookie, in seconds. The delta-seconds value is a decimal non- - negative integer. After delta-seconds seconds elapse, the - client should discard the cookie. A value of zero means the - cookie should be discarded immediately. - - */ - strstore(&co->maxage, whatptr); - if(!co->maxage) { - badcookie = TRUE; - break; - } - co->expires = - strtol((*co->maxage=='\"')?&co->maxage[1]:&co->maxage[0],NULL,10) - + (long)now; - } - else if(Curl_raw_equal("expires", name)) { - strstore(&co->expirestr, whatptr); - if(!co->expirestr) { - badcookie = TRUE; - break; - } - /* Note that if the date couldn't get parsed for whatever reason, - the cookie will be treated as a session cookie */ - co->expires = curl_getdate(what, &now); - - /* Session cookies have expires set to 0 so if we get that back - from the date parser let's add a second to make it a - non-session cookie */ - if(co->expires == 0) - co->expires = 1; - else if(co->expires < 0) - co->expires = 0; - } - else if(!co->name) { - co->name = strdup(name); - co->value = strdup(whatptr); - if(!co->name || !co->value) { - badcookie = TRUE; - break; - } - } - /* - else this is the second (or more) name we don't know - about! */ - } - else { - /* this is an "illegal" = pair */ - } - - if(!semiptr || !*semiptr) { - /* we already know there are no more cookies */ - semiptr = NULL; - continue; - } - - ptr=semiptr+1; - while(*ptr && ISBLANK(*ptr)) - ptr++; - semiptr=strchr(ptr, ';'); /* now, find the next semicolon */ - - if(!semiptr && *ptr) - /* There are no more semicolons, but there's a final name=value pair - coming up */ - semiptr=strchr(ptr, '\0'); - } while(semiptr); - - if(!badcookie && !co->domain) { - if(domain) { - /* no domain was given in the header line, set the default */ - co->domain=strdup(domain); - if(!co->domain) - badcookie = TRUE; - } - } - - if(!badcookie && !co->path && path) { - /* No path was given in the header line, set the default. - Note that the passed-in path to this function MAY have a '?' and - following part that MUST not be stored as part of the path. */ - char *queryp = strchr(path, '?'); - - /* queryp is where the interesting part of the path ends, so now we - want to the find the last */ - char *endslash; - if(!queryp) - endslash = strrchr(path, '/'); - else - endslash = memrchr(path, '/', (size_t)(queryp - path)); - if(endslash) { - size_t pathlen = (size_t)(endslash-path+1); /* include ending slash */ - co->path=malloc(pathlen+1); /* one extra for the zero byte */ - if(co->path) { - memcpy(co->path, path, pathlen); - co->path[pathlen]=0; /* zero terminate */ - } - else - badcookie = TRUE; - } - } - - free(what); - - if(badcookie || !co->name) { - /* we didn't get a cookie name or a bad one, - this is an illegal line, bail out */ - freecookie(co); - return NULL; - } - - } - else { - /* This line is NOT a HTTP header style line, we do offer support for - reading the odd netscape cookies-file format here */ - char *ptr; - char *firstptr; - char *tok_buf=NULL; - int fields; - - /* IE introduced HTTP-only cookies to prevent XSS attacks. Cookies - marked with httpOnly after the domain name are not accessible - from javascripts, but since curl does not operate at javascript - level, we include them anyway. In Firefox's cookie files, these - lines are preceded with #HttpOnly_ and then everything is - as usual, so we skip 10 characters of the line.. - */ - if(strncmp(lineptr, "#HttpOnly_", 10) == 0) { - lineptr += 10; - co->httponly = TRUE; - } - - if(lineptr[0]=='#') { - /* don't even try the comments */ - free(co); - return NULL; - } - /* strip off the possible end-of-line characters */ - ptr=strchr(lineptr, '\r'); - if(ptr) - *ptr=0; /* clear it */ - ptr=strchr(lineptr, '\n'); - if(ptr) - *ptr=0; /* clear it */ - - firstptr=strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */ - - /* Here's a quick check to eliminate normal HTTP-headers from this */ - if(!firstptr || strchr(firstptr, ':')) { - free(co); - return NULL; - } - - /* Now loop through the fields and init the struct we already have - allocated */ - for(ptr=firstptr, fields=0; ptr && !badcookie; - ptr=strtok_r(NULL, "\t", &tok_buf), fields++) { - switch(fields) { - case 0: - if(ptr[0]=='.') /* skip preceding dots */ - ptr++; - co->domain = strdup(ptr); - if(!co->domain) - badcookie = TRUE; - break; - case 1: - /* This field got its explanation on the 23rd of May 2001 by - Andrés García: - - flag: A TRUE/FALSE value indicating if all machines within a given - domain can access the variable. This value is set automatically by - the browser, depending on the value you set for the domain. - - As far as I can see, it is set to true when the cookie says - .domain.com and to false when the domain is complete www.domain.com - */ - co->tailmatch = Curl_raw_equal(ptr, "TRUE")?TRUE:FALSE; - break; - case 2: - /* It turns out, that sometimes the file format allows the path - field to remain not filled in, we try to detect this and work - around it! Andrés García made us aware of this... */ - if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) { - /* only if the path doesn't look like a boolean option! */ - co->path = strdup(ptr); - if(!co->path) - badcookie = TRUE; - break; - } - /* this doesn't look like a path, make one up! */ - co->path = strdup("/"); - if(!co->path) - badcookie = TRUE; - fields++; /* add a field and fall down to secure */ - /* FALLTHROUGH */ - case 3: - co->secure = Curl_raw_equal(ptr, "TRUE")?TRUE:FALSE; - break; - case 4: - co->expires = curlx_strtoofft(ptr, NULL, 10); - break; - case 5: - co->name = strdup(ptr); - if(!co->name) - badcookie = TRUE; - break; - case 6: - co->value = strdup(ptr); - if(!co->value) - badcookie = TRUE; - break; - } - } - if(6 == fields) { - /* we got a cookie with blank contents, fix it */ - co->value = strdup(""); - if(!co->value) - badcookie = TRUE; - else - fields++; - } - - if(!badcookie && (7 != fields)) - /* we did not find the sufficient number of fields */ - badcookie = TRUE; - - if(badcookie) { - freecookie(co); - return NULL; - } - - } - - if(!c->running && /* read from a file */ - c->newsession && /* clean session cookies */ - !co->expires) { /* this is a session cookie since it doesn't expire! */ - freecookie(co); - return NULL; - } - - co->livecookie = c->running; - - /* now, we have parsed the incoming line, we must now check if this - superceeds an already existing cookie, which it may if the previous have - the same domain and path as this */ - - clist = c->cookies; - replace_old = FALSE; - while(clist) { - if(Curl_raw_equal(clist->name, co->name)) { - /* the names are identical */ - - if(clist->domain && co->domain) { - if(Curl_raw_equal(clist->domain, co->domain)) - /* The domains are identical */ - replace_old=TRUE; - } - else if(!clist->domain && !co->domain) - replace_old = TRUE; - - if(replace_old) { - /* the domains were identical */ - - if(clist->path && co->path) { - if(Curl_raw_equal(clist->path, co->path)) { - replace_old = TRUE; - } - else - replace_old = FALSE; - } - else if(!clist->path && !co->path) - replace_old = TRUE; - else - replace_old = FALSE; - - } - - if(replace_old && !co->livecookie && clist->livecookie) { - /* Both cookies matched fine, except that the already present - cookie is "live", which means it was set from a header, while - the new one isn't "live" and thus only read from a file. We let - live cookies stay alive */ - - /* Free the newcomer and get out of here! */ - freecookie(co); - return NULL; - } - - if(replace_old) { - co->next = clist->next; /* get the next-pointer first */ - - /* then free all the old pointers */ - free(clist->name); - if(clist->value) - free(clist->value); - if(clist->domain) - free(clist->domain); - if(clist->path) - free(clist->path); - if(clist->expirestr) - free(clist->expirestr); - - if(clist->version) - free(clist->version); - if(clist->maxage) - free(clist->maxage); - - *clist = *co; /* then store all the new data */ - - free(co); /* free the newly alloced memory */ - co = clist; /* point to the previous struct instead */ - - /* We have replaced a cookie, now skip the rest of the list but - make sure the 'lastc' pointer is properly set */ - do { - lastc = clist; - clist = clist->next; - } while(clist); - break; - } - } - lastc = clist; - clist = clist->next; - } - - if(c->running) - /* Only show this when NOT reading the cookies from a file */ - infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, " - "expire %" FORMAT_OFF_T "\n", - replace_old?"Replaced":"Added", co->name, co->value, - co->domain, co->path, co->expires); - - if(!replace_old) { - /* then make the last item point on this new one */ - if(lastc) - lastc->next = co; - else - c->cookies = co; - } - - c->numcookies++; /* one more cookie in the jar */ - return co; -} - -/***************************************************************************** - * - * Curl_cookie_init() - * - * Inits a cookie struct to read data from a local file. This is always - * called before any cookies are set. File may be NULL. - * - * If 'newsession' is TRUE, discard all "session cookies" on read from file. - * - ****************************************************************************/ -struct CookieInfo *Curl_cookie_init(struct SessionHandle *data, - const char *file, - struct CookieInfo *inc, - bool newsession) -{ - struct CookieInfo *c; - FILE *fp; - bool fromfile=TRUE; - - if(NULL == inc) { - /* we didn't get a struct, create one */ - c = calloc(1, sizeof(struct CookieInfo)); - if(!c) - return NULL; /* failed to get memory */ - c->filename = strdup(file?file:"none"); /* copy the name just in case */ - } - else { - /* we got an already existing one, use that */ - c = inc; - } - c->running = FALSE; /* this is not running, this is init */ - - if(file && strequal(file, "-")) { - fp = stdin; - fromfile=FALSE; - } - else if(file && !*file) { - /* points to a "" string */ - fp = NULL; - } - else - fp = file?fopen(file, "r"):NULL; - - c->newsession = newsession; /* new session? */ - - if(fp) { - char *lineptr; - bool headerline; - - char *line = malloc(MAX_COOKIE_LINE); - if(line) { - while(fgets(line, MAX_COOKIE_LINE, fp)) { - if(checkprefix("Set-Cookie:", line)) { - /* This is a cookie line, get it! */ - lineptr=&line[11]; - headerline=TRUE; - } - else { - lineptr=line; - headerline=FALSE; - } - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; - - Curl_cookie_add(data, c, headerline, lineptr, NULL, NULL); - } - free(line); /* free the line buffer */ - } - if(fromfile) - fclose(fp); - } - - c->running = TRUE; /* now, we're running */ - - return c; -} - -/* sort this so that the longest path gets before the shorter path */ -static int cookie_sort(const void *p1, const void *p2) -{ - struct Cookie *c1 = *(struct Cookie **)p1; - struct Cookie *c2 = *(struct Cookie **)p2; - - size_t l1 = c1->path?strlen(c1->path):0; - size_t l2 = c2->path?strlen(c2->path):0; - - return (l2 > l1) ? 1 : (l2 < l1) ? -1 : 0 ; -} - -/***************************************************************************** - * - * Curl_cookie_getlist() - * - * For a given host and path, return a linked list of cookies that the - * client should send to the server if used now. The secure boolean informs - * the cookie if a secure connection is achieved or not. - * - * It shall only return cookies that haven't expired. - * - ****************************************************************************/ - -struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, - const char *host, const char *path, - bool secure) -{ - struct Cookie *newco; - struct Cookie *co; - time_t now = time(NULL); - struct Cookie *mainco=NULL; - size_t matches = 0; - - if(!c || !c->cookies) - return NULL; /* no cookie struct or no cookies in the struct */ - - co = c->cookies; - - while(co) { - /* only process this cookie if it is not expired or had no expire - date AND that if the cookie requires we're secure we must only - continue if we are! */ - if((!co->expires || (co->expires > now)) && - (co->secure?secure:TRUE)) { - - /* now check if the domain is correct */ - if(!co->domain || - (co->tailmatch && tailmatch(co->domain, host)) || - (!co->tailmatch && Curl_raw_equal(host, co->domain)) ) { - /* the right part of the host matches the domain stuff in the - cookie data */ - - /* now check the left part of the path with the cookies path - requirement */ - if(!co->path || - /* not using checkprefix() because matching should be - case-sensitive */ - !strncmp(co->path, path, strlen(co->path)) ) { - - /* and now, we know this is a match and we should create an - entry for the return-linked-list */ - - newco = malloc(sizeof(struct Cookie)); - if(newco) { - /* first, copy the whole source cookie: */ - memcpy(newco, co, sizeof(struct Cookie)); - - /* then modify our next */ - newco->next = mainco; - - /* point the main to us */ - mainco = newco; - - matches++; - } - else { - fail: - /* failure, clear up the allocated chain and return NULL */ - while(mainco) { - co = mainco->next; - free(mainco); - mainco = co; - } - - return NULL; - } - } - } - } - co = co->next; - } - - if(matches) { - /* Now we need to make sure that if there is a name appearing more than - once, the longest specified path version comes first. To make this - the swiftest way, we just sort them all based on path length. */ - struct Cookie **array; - size_t i; - - /* alloc an array and store all cookie pointers */ - array = malloc(sizeof(struct Cookie *) * matches); - if(!array) - goto fail; - - co = mainco; - - for(i=0; co; co = co->next) - array[i++] = co; - - /* now sort the cookie pointers in path length order */ - qsort(array, matches, sizeof(struct Cookie *), cookie_sort); - - /* remake the linked list order according to the new order */ - - mainco = array[0]; /* start here */ - for(i=0; inext = array[i+1]; - array[matches-1]->next = NULL; /* terminate the list */ - - free(array); /* remove the temporary data again */ - } - - return mainco; /* return the new list */ -} - -/***************************************************************************** - * - * Curl_cookie_clearall() - * - * Clear all existing cookies and reset the counter. - * - ****************************************************************************/ -void Curl_cookie_clearall(struct CookieInfo *cookies) -{ - if(cookies) { - Curl_cookie_freelist(cookies->cookies, TRUE); - cookies->cookies = NULL; - cookies->numcookies = 0; - } -} - -/***************************************************************************** - * - * Curl_cookie_freelist() - * - * Free a list of cookies previously returned by Curl_cookie_getlist(); - * - * The 'cookiestoo' argument tells this function whether to just free the - * list or actually also free all cookies within the list as well. - * - ****************************************************************************/ - -void Curl_cookie_freelist(struct Cookie *co, bool cookiestoo) -{ - struct Cookie *next; - if(co) { - while(co) { - next = co->next; - if(cookiestoo) - freecookie(co); - else - free(co); /* we only free the struct since the "members" are all just - pointed out in the main cookie list! */ - co = next; - } - } -} - - -/***************************************************************************** - * - * Curl_cookie_clearsess() - * - * Free all session cookies in the cookies list. - * - ****************************************************************************/ -void Curl_cookie_clearsess(struct CookieInfo *cookies) -{ - struct Cookie *first, *curr, *next, *prev = NULL; - - if(!cookies || !cookies->cookies) - return; - - first = curr = prev = cookies->cookies; - - for(; curr; curr = next) { - next = curr->next; - if(!curr->expires) { - if(first == curr) - first = next; - - if(prev == curr) - prev = next; - else - prev->next = next; - - freecookie(curr); - cookies->numcookies--; - } - else - prev = curr; - } - - cookies->cookies = first; -} - - -/***************************************************************************** - * - * Curl_cookie_cleanup() - * - * Free a "cookie object" previous created with cookie_init(). - * - ****************************************************************************/ -void Curl_cookie_cleanup(struct CookieInfo *c) -{ - struct Cookie *co; - struct Cookie *next; - if(c) { - if(c->filename) - free(c->filename); - co = c->cookies; - - while(co) { - next = co->next; - freecookie(co); - co = next; - } - free(c); /* free the base struct as well */ - } -} - -/* get_netscape_format() - * - * Formats a string for Netscape output file, w/o a newline at the end. - * - * Function returns a char * to a formatted line. Has to be free()d -*/ -static char *get_netscape_format(const struct Cookie *co) -{ - return aprintf( - "%s" /* httponly preamble */ - "%s%s\t" /* domain */ - "%s\t" /* tailmatch */ - "%s\t" /* path */ - "%s\t" /* secure */ - "%" FORMAT_OFF_T "\t" /* expires */ - "%s\t" /* name */ - "%s", /* value */ - co->httponly?"#HttpOnly_":"", - /* Make sure all domains are prefixed with a dot if they allow - tailmatching. This is Mozilla-style. */ - (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"", - co->domain?co->domain:"unknown", - co->tailmatch?"TRUE":"FALSE", - co->path?co->path:"/", - co->secure?"TRUE":"FALSE", - co->expires, - co->name, - co->value?co->value:""); -} - -/* - * cookie_output() - * - * Writes all internally known cookies to the specified file. Specify - * "-" as file name to write to stdout. - * - * The function returns non-zero on write failure. - */ -static int cookie_output(struct CookieInfo *c, const char *dumphere) -{ - struct Cookie *co; - FILE *out; - bool use_stdout=FALSE; - - if((NULL == c) || (0 == c->numcookies)) - /* If there are no known cookies, we don't write or even create any - destination file */ - return 0; - - if(strequal("-", dumphere)) { - /* use stdout */ - out = stdout; - use_stdout=TRUE; - } - else { - out = fopen(dumphere, "w"); - if(!out) - return 1; /* failure */ - } - - if(c) { - char *format_ptr; - - fputs("# Netscape HTTP Cookie File\n" - "# http://curl.haxx.se/docs/http-cookies.html\n" - "# This file was generated by libcurl! Edit at your own risk.\n\n", - out); - co = c->cookies; - - while(co) { - format_ptr = get_netscape_format(co); - if(format_ptr == NULL) { - fprintf(out, "#\n# Fatal libcurl error\n"); - if(!use_stdout) - fclose(out); - return 1; - } - fprintf(out, "%s\n", format_ptr); - free(format_ptr); - co=co->next; - } - } - - if(!use_stdout) - fclose(out); - - return 0; -} - -struct curl_slist *Curl_cookie_list(struct SessionHandle *data) -{ - struct curl_slist *list = NULL; - struct curl_slist *beg; - struct Cookie *c; - char *line; - - if((data->cookies == NULL) || - (data->cookies->numcookies == 0)) - return NULL; - - c = data->cookies->cookies; - - while(c) { - /* fill the list with _all_ the cookies we know */ - line = get_netscape_format(c); - if(!line) { - curl_slist_free_all(list); - return NULL; - } - beg = curl_slist_append(list, line); - free(line); - if(!beg) { - curl_slist_free_all(list); - return NULL; - } - list = beg; - c = c->next; - } - - return list; -} - -void Curl_flush_cookies(struct SessionHandle *data, int cleanup) -{ - if(data->set.str[STRING_COOKIEJAR]) { - if(data->change.cookielist) { - /* If there is a list of cookie files to read, do it first so that - we have all the told files read before we write the new jar. - Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */ - Curl_cookie_loadfiles(data); - } - - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - - /* if we have a destination file for all the cookies to get dumped to */ - if(cookie_output(data->cookies, data->set.str[STRING_COOKIEJAR])) - infof(data, "WARNING: failed to save cookies in %s\n", - data->set.str[STRING_COOKIEJAR]); - } - else { - if(cleanup && data->change.cookielist) { - /* since nothing is written, we can just free the list of cookie file - names */ - curl_slist_free_all(data->change.cookielist); /* clean up list */ - data->change.cookielist = NULL; - } - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - } - - if(cleanup && (!data->share || (data->cookies != data->share->cookies))) { - Curl_cookie_cleanup(data->cookies); - } - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); -} - -#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */ diff --git a/lib/curl_amigaos.c b/lib/curl_amigaos.c new file mode 100644 index 000000000..c726abb68 --- /dev/null +++ b/lib/curl_amigaos.c @@ -0,0 +1,77 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(__AMIGA__) && !defined(__ixemul__) + +#include + +#include "curl_amigaos.h" + +struct Library *SocketBase = NULL; +extern int errno, h_errno; + +#ifdef __libnix__ +#include +void __request(const char *msg); +#else +# define __request( msg ) Printf( msg "\n\a") +#endif + +void Curl_amiga_cleanup() +{ + if(SocketBase) { + CloseLibrary(SocketBase); + SocketBase = NULL; + } +} + +bool Curl_amiga_init() +{ + if(!SocketBase) + SocketBase = OpenLibrary("bsdsocket.library", 4); + + if(!SocketBase) { + __request("No TCP/IP Stack running!"); + return FALSE; + } + + if(SocketBaseTags(SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(errno))), (ULONG) &errno, + SBTM_SETVAL(SBTC_LOGTAGPTR), (ULONG) "cURL", + TAG_DONE)) { + __request("SocketBaseTags ERROR"); + return FALSE; + } + +#ifndef __libnix__ + atexit(Curl_amiga_cleanup); +#endif + + return TRUE; +} + +#ifdef __libnix__ +ADD2EXIT(Curl_amiga_cleanup,-50); +#endif + +#endif /* __AMIGA__ && ! __ixemul__ */ diff --git a/lib/curl_asyn_ares.c b/lib/curl_asyn_ares.c new file mode 100644 index 000000000..6e09e9b98 --- /dev/null +++ b/lib/curl_asyn_ares.c @@ -0,0 +1,639 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef HAVE_PROCESS_H +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +/*********************************************************************** + * Only for ares-enabled builds + * And only for functions that fulfill the asynch resolver backend API + * as defined in curl_asyn.h, nothing else belongs in this file! + **********************************************************************/ + +#ifdef CURLRES_ARES + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_hostip.h" +#include "curl_hash.h" +#include "curl_share.h" +#include "curl_strerror.h" +#include "curl_url.h" +#include "curl_multiif.h" +#include "curl_inet_pton.h" +#include "curl_connect.h" +#include "curl_select.h" +#include "curl_progress.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ + (defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__)) +# define CARES_STATICLIB +# endif +# include +# include /* really old c-ares didn't include this by + itself */ + +#if ARES_VERSION >= 0x010500 +/* c-ares 1.5.0 or later, the callback proto is modified */ +#define HAVE_CARES_CALLBACK_TIMEOUTS 1 +#endif + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +struct ResolverResults { + int num_pending; /* number of ares_gethostbyname() requests */ + Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares parts */ + int last_status; +}; + +/* + * Curl_resolver_global_init() - the generic low-level asynchronous name + * resolve API. Called from curl_global_init() to initialize global resolver + * environment. Initializes ares library. + */ +int Curl_resolver_global_init(void) +{ +#ifdef CARES_HAVE_ARES_LIBRARY_INIT + if(ares_library_init(ARES_LIB_INIT_ALL)) { + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +/* + * Curl_resolver_global_cleanup() + * + * Called from curl_global_cleanup() to destroy global resolver environment. + * Deinitializes ares library. + */ +void Curl_resolver_global_cleanup(void) +{ +#ifdef CARES_HAVE_ARES_LIBRARY_CLEANUP + ares_library_cleanup(); +#endif +} + +/* + * Curl_resolver_init() + * + * Called from curl_easy_init() -> Curl_open() to initialize resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). Fills the passed pointer by the initialized ares_channel. + */ +CURLcode Curl_resolver_init(void **resolver) +{ + int status = ares_init((ares_channel*)resolver); + if(status != ARES_SUCCESS) { + if(status == ARES_ENOMEM) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_FAILED_INIT; + } + return CURLE_OK; + /* make sure that all other returns from this function should destroy the + ares channel before returning error! */ +} + +/* + * Curl_resolver_cleanup() + * + * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). Destroys the ares channel. + */ +void Curl_resolver_cleanup(void *resolver) +{ + ares_destroy((ares_channel)resolver); +} + +/* + * Curl_resolver_duphandle() + * + * Called from curl_easy_duphandle() to duplicate resolver URL-state specific + * environment ('resolver' member of the UrlState structure). Duplicates the + * 'from' ares channel and passes the resulting channel to the 'to' pointer. + */ +int Curl_resolver_duphandle(void **to, void *from) +{ + /* Clone the ares channel for the new handle */ + if(ARES_SUCCESS != ares_dup((ares_channel*)to,(ares_channel)from)) + return CURLE_FAILED_INIT; + return CURLE_OK; +} + +static void destroy_async_data (struct Curl_async *async); + +/* + * Cancel all possibly still on-going resolves for this connection. + */ +void Curl_resolver_cancel(struct connectdata *conn) +{ + if(conn && conn->data && conn->data->state.resolver) + ares_cancel((ares_channel)conn->data->state.resolver); + destroy_async_data(&conn->async); +} + +/* + * destroy_async_data() cleans up async resolver data. + */ +static void destroy_async_data (struct Curl_async *async) +{ + if(async->hostname) + free(async->hostname); + + if(async->os_specific) { + struct ResolverResults *res = (struct ResolverResults *)async->os_specific; + if(res) { + if(res->temp_ai) { + Curl_freeaddrinfo(res->temp_ai); + res->temp_ai = NULL; + } + free(res); + } + async->os_specific = NULL; + } + + async->hostname = NULL; +} + +/* + * Curl_resolver_fdset() is called when someone from the outside world (using + * curl_multi_fdset()) wants to get our fd_set setup and we're talking with + * ares. The caller must make sure that this function is only called when we + * have a working ares channel. + * + * Returns: CURLE_OK always! + */ + +int Curl_resolver_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) + +{ + struct timeval maxtime; + struct timeval timebuf; + struct timeval *timeout; + long milli; + int max = ares_getsock((ares_channel)conn->data->state.resolver, + (ares_socket_t *)socks, numsocks); + + maxtime.tv_sec = CURL_TIMEOUT_RESOLVE; + maxtime.tv_usec = 0; + + timeout = ares_timeout((ares_channel)conn->data->state.resolver, &maxtime, + &timebuf); + milli = (timeout->tv_sec * 1000) + (timeout->tv_usec/1000); + if(milli == 0) + milli += 10; + Curl_expire(conn->data, milli); + + return max; +} + +/* + * waitperform() + * + * 1) Ask ares what sockets it currently plays with, then + * 2) wait for the timeout period to check for action on ares' sockets. + * 3) tell ares to act on all the sockets marked as "with action" + * + * return number of sockets it worked on + */ + +static int waitperform(struct connectdata *conn, int timeout_ms) +{ + struct SessionHandle *data = conn->data; + int nfds; + int bitmask; + ares_socket_t socks[ARES_GETSOCK_MAXNUM]; + struct pollfd pfd[ARES_GETSOCK_MAXNUM]; + int i; + int num = 0; + + bitmask = ares_getsock((ares_channel)data->state.resolver, socks, + ARES_GETSOCK_MAXNUM); + + for(i=0; i < ARES_GETSOCK_MAXNUM; i++) { + pfd[i].events = 0; + pfd[i].revents = 0; + if(ARES_GETSOCK_READABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLRDNORM|POLLIN; + } + if(ARES_GETSOCK_WRITABLE(bitmask, i)) { + pfd[i].fd = socks[i]; + pfd[i].events |= POLLWRNORM|POLLOUT; + } + if(pfd[i].events != 0) + num++; + else + break; + } + + if(num) + nfds = Curl_poll(pfd, num, timeout_ms); + else + nfds = 0; + + if(!nfds) + /* Call ares_process() unconditonally here, even if we simply timed out + above, as otherwise the ares name resolve won't timeout! */ + ares_process_fd((ares_channel)data->state.resolver, ARES_SOCKET_BAD, + ARES_SOCKET_BAD); + else { + /* move through the descriptors and ask for processing on them */ + for(i=0; i < num; i++) + ares_process_fd((ares_channel)data->state.resolver, + pfd[i].revents & (POLLRDNORM|POLLIN)? + pfd[i].fd:ARES_SOCKET_BAD, + pfd[i].revents & (POLLWRNORM|POLLOUT)? + pfd[i].fd:ARES_SOCKET_BAD); + } + return nfds; +} + +/* + * Curl_resolver_is_resolved() is called repeatedly to check if a previous + * name resolve request has completed. It should also make sure to time-out if + * the operation seems to take too long. + * + * Returns normal CURLcode errors. + */ +CURLcode Curl_resolver_is_resolved(struct connectdata *conn, + struct Curl_dns_entry **dns) +{ + struct SessionHandle *data = conn->data; + struct ResolverResults *res = (struct ResolverResults *) + conn->async.os_specific; + + *dns = NULL; + + waitperform(conn, 0); + + if(res && !res->num_pending) { + (void)Curl_addrinfo_callback(conn, res->last_status, res->temp_ai); + /* temp_ai ownership is moved to the connection, so we need not free-up + them */ + res->temp_ai = NULL; + destroy_async_data(&conn->async); + if(!conn->async.dns) { + failf(data, "Could not resolve %s: %s (%s)", + conn->bits.proxy?"proxy":"host", + conn->host.dispname, + ares_strerror(conn->async.status)); + return conn->bits.proxy?CURLE_COULDNT_RESOLVE_PROXY: + CURLE_COULDNT_RESOLVE_HOST; + } + *dns = conn->async.dns; + } + + return CURLE_OK; +} + +/* + * Curl_resolver_wait_resolv() + * + * waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, and + * CURLE_OPERATION_TIMEDOUT if a time-out occurred. + */ +CURLcode Curl_resolver_wait_resolv(struct connectdata *conn, + struct Curl_dns_entry **entry) +{ + CURLcode rc=CURLE_OK; + struct SessionHandle *data = conn->data; + long timeout; + struct timeval now = Curl_tvnow(); + struct Curl_dns_entry *temp_entry; + + timeout = Curl_timeleft(data, &now, TRUE); + if(!timeout) + timeout = CURL_TIMEOUT_RESOLVE * 1000; /* default name resolve timeout */ + + /* Wait for the name resolve query to complete. */ + for(;;) { + struct timeval *tvp, tv, store; + long timediff; + int itimeout; + int timeout_ms; + + itimeout = (timeout > (long)INT_MAX) ? INT_MAX : (int)timeout; + + store.tv_sec = itimeout/1000; + store.tv_usec = (itimeout%1000)*1000; + + tvp = ares_timeout((ares_channel)data->state.resolver, &store, &tv); + + /* use the timeout period ares returned to us above if less than one + second is left, otherwise just use 1000ms to make sure the progress + callback gets called frequent enough */ + if(!tvp->tv_sec) + timeout_ms = (int)(tvp->tv_usec/1000); + else + timeout_ms = 1000; + + waitperform(conn, timeout_ms); + Curl_resolver_is_resolved(conn,&temp_entry); + + if(conn->async.done) + break; + + if(Curl_pgrsUpdate(conn)) { + rc = CURLE_ABORTED_BY_CALLBACK; + timeout = -1; /* trigger the cancel below */ + } + else { + struct timeval now2 = Curl_tvnow(); + timediff = Curl_tvdiff(now2, now); /* spent time */ + timeout -= timediff?timediff:1; /* always deduct at least 1 */ + now = now2; /* for next loop */ + } + if(timeout < 0) { + /* our timeout, so we cancel the ares operation */ + ares_cancel((ares_channel)data->state.resolver); + break; + } + } + + /* Operation complete, if the lookup was successful we now have the entry + in the cache. */ + + if(entry) + *entry = conn->async.dns; + + if(!conn->async.dns) { + /* a name was not resolved */ + if((timeout < 0) || (conn->async.status == ARES_ETIMEOUT)) { + if(conn->bits.proxy) { + failf(data, "Resolving proxy timed out: %s", conn->proxy.dispname); + rc = CURLE_COULDNT_RESOLVE_PROXY; + } + else { + failf(data, "Resolving host timed out: %s", conn->host.dispname); + rc = CURLE_COULDNT_RESOLVE_HOST; + } + } + else if(conn->async.done) { + if(conn->bits.proxy) { + failf(data, "Could not resolve proxy: %s (%s)", conn->proxy.dispname, + ares_strerror(conn->async.status)); + rc = CURLE_COULDNT_RESOLVE_PROXY; + } + else { + failf(data, "Could not resolve host: %s (%s)", conn->host.dispname, + ares_strerror(conn->async.status)); + rc = CURLE_COULDNT_RESOLVE_HOST; + } + } + else + rc = CURLE_OPERATION_TIMEDOUT; + + /* close the connection, since we can't return failure here without + cleaning up this connection properly */ + conn->bits.close = TRUE; + } + + return rc; +} + +/* Connects results to the list */ +static void compound_results(struct ResolverResults *res, + Curl_addrinfo *ai) +{ + Curl_addrinfo *ai_tail; + if(!ai) + return; + ai_tail = ai; + + while(ai_tail->ai_next) + ai_tail = ai_tail->ai_next; + + /* Add the new results to the list of old results. */ + ai_tail->ai_next = res->temp_ai; + res->temp_ai = ai; +} + +/* + * ares_query_completed_cb() is the callback that ares will call when + * the host query initiated by ares_gethostbyname() from Curl_getaddrinfo(), + * when using ares, is completed either successfully or with failure. + */ +static void query_completed_cb(void *arg, /* (struct connectdata *) */ + int status, +#ifdef HAVE_CARES_CALLBACK_TIMEOUTS + int timeouts, +#endif + struct hostent *hostent) +{ + struct connectdata *conn = (struct connectdata *)arg; + struct ResolverResults *res; + +#ifdef HAVE_CARES_CALLBACK_TIMEOUTS + (void)timeouts; /* ignored */ +#endif + + if(ARES_EDESTRUCTION == status) + /* when this ares handle is getting destroyed, the 'arg' pointer may not + be valid so only defer it when we know the 'status' says its fine! */ + return; + + res = (struct ResolverResults *)conn->async.os_specific; + res->num_pending--; + + if(CURL_ASYNC_SUCCESS == status) { + Curl_addrinfo *ai = Curl_he2ai(hostent, conn->async.port); + if(ai) { + compound_results(res, ai); + } + } + /* A successful result overwrites any previous error */ + if(res->last_status != ARES_SUCCESS) + res->last_status = status; +} + +/* + * Curl_resolver_getaddrinfo() - when using ares + * + * Returns name information about the given hostname and port number. If + * successful, the 'hostent' is returned and the forth argument will point to + * memory we need to free after use. That memory *MUST* be freed with + * Curl_freeaddrinfo(), nothing else. + */ +Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, + const char *hostname, + int port, + int *waitp) +{ + char *bufp; + struct SessionHandle *data = conn->data; + struct in_addr in; + int family = PF_INET; +#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ + struct in6_addr in6; +#endif /* CURLRES_IPV6 */ + + *waitp = 0; /* default to synchronous response */ + + /* First check if this is an IPv4 address string */ + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { + /* This is a dotted IP address 123.123.123.123-style */ + return Curl_ip2addr(AF_INET, &in, hostname, port); + } + +#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ + /* Otherwise, check if this is an IPv6 address string */ + if(Curl_inet_pton (AF_INET6, hostname, &in6) > 0) + /* This must be an IPv6 address literal. */ + return Curl_ip2addr(AF_INET6, &in6, hostname, port); + + switch(conn->ip_version) { + default: +#if ARES_VERSION >= 0x010601 + family = PF_UNSPEC; /* supported by c-ares since 1.6.1, so for older + c-ares versions this just falls through and defaults + to PF_INET */ + break; +#endif + case CURL_IPRESOLVE_V4: + family = PF_INET; + break; + case CURL_IPRESOLVE_V6: + family = PF_INET6; + break; + } +#endif /* CURLRES_IPV6 */ + + bufp = strdup(hostname); + if(bufp) { + struct ResolverResults *res = NULL; + Curl_safefree(conn->async.hostname); + conn->async.hostname = bufp; + conn->async.port = port; + conn->async.done = FALSE; /* not done */ + conn->async.status = 0; /* clear */ + conn->async.dns = NULL; /* clear */ + res = calloc(sizeof(struct ResolverResults),1); + if(!res) { + Curl_safefree(conn->async.hostname); + conn->async.hostname = NULL; + return NULL; + } + conn->async.os_specific = res; + + /* initial status - failed */ + res->last_status = ARES_ENOTFOUND; +#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ + if(family == PF_UNSPEC) { + if(Curl_ipv6works()) { + res->num_pending = 2; + + /* areschannel is already setup in the Curl_open() function */ + ares_gethostbyname((ares_channel)data->state.resolver, hostname, + PF_INET, query_completed_cb, conn); + ares_gethostbyname((ares_channel)data->state.resolver, hostname, + PF_INET6, query_completed_cb, conn); + } + else { + res->num_pending = 1; + + /* areschannel is already setup in the Curl_open() function */ + ares_gethostbyname((ares_channel)data->state.resolver, hostname, + PF_INET, query_completed_cb, conn); + } + } + else +#endif /* CURLRES_IPV6 */ + { + res->num_pending = 1; + + /* areschannel is already setup in the Curl_open() function */ + ares_gethostbyname((ares_channel)data->state.resolver, hostname, family, + query_completed_cb, conn); + } + + *waitp = 1; /* expect asynchronous response */ + } + return NULL; /* no struct yet */ +} + +CURLcode Curl_set_dns_servers(struct SessionHandle *data, + char *servers) +{ + CURLcode result = CURLE_NOT_BUILT_IN; +#if (ARES_VERSION >= 0x010704) + int ares_result = ares_set_servers_csv(data->state.resolver, servers); + switch(ares_result) { + case ARES_SUCCESS: + result = CURLE_OK; + break; + case ARES_ENOMEM: + result = CURLE_OUT_OF_MEMORY; + break; + case ARES_ENOTINITIALIZED: + case ARES_ENODATA: + case ARES_EBADSTR: + default: + result = CURLE_BAD_FUNCTION_ARGUMENT; + break; + } +#else /* too old c-ares version! */ + (void)data; + (void)servers; +#endif + return result; +} +#endif /* CURLRES_ARES */ diff --git a/lib/curl_asyn_thread.c b/lib/curl_asyn_thread.c new file mode 100644 index 000000000..6d3667fab --- /dev/null +++ b/lib/curl_asyn_thread.c @@ -0,0 +1,679 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if defined(USE_THREADS_POSIX) +# ifdef HAVE_PTHREAD_H +# include +# endif +#elif defined(USE_THREADS_WIN32) +# ifdef HAVE_PROCESS_H +# include +# endif +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#ifdef HAVE_GETADDRINFO +# define RESOLVER_ENOMEM EAI_MEMORY +#else +# define RESOLVER_ENOMEM ENOMEM +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_hostip.h" +#include "curl_hash.h" +#include "curl_share.h" +#include "curl_strerror.h" +#include "curl_url.h" +#include "curl_multiif.h" +#include "curl_inet_pton.h" +#include "curl_inet_ntop.h" +#include "curl_threads.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/*********************************************************************** + * Only for threaded name resolves builds + **********************************************************************/ +#ifdef CURLRES_THREADED + +/* + * Curl_resolver_global_init() + * Called from curl_global_init() to initialize global resolver environment. + * Does nothing here. + */ +int Curl_resolver_global_init(void) +{ + return CURLE_OK; +} + +/* + * Curl_resolver_global_cleanup() + * Called from curl_global_cleanup() to destroy global resolver environment. + * Does nothing here. + */ +void Curl_resolver_global_cleanup(void) +{ +} + +/* + * Curl_resolver_init() + * Called from curl_easy_init() -> Curl_open() to initialize resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). Does nothing here. + */ +CURLcode Curl_resolver_init(void **resolver) +{ + (void)resolver; + return CURLE_OK; +} + +/* + * Curl_resolver_cleanup() + * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver + * URL-state specific environment ('resolver' member of the UrlState + * structure). Does nothing here. + */ +void Curl_resolver_cleanup(void *resolver) +{ + (void)resolver; +} + +/* + * Curl_resolver_duphandle() + * Called from curl_easy_duphandle() to duplicate resolver URL state-specific + * environment ('resolver' member of the UrlState structure). Does nothing + * here. + */ +int Curl_resolver_duphandle(void **to, void *from) +{ + (void)to; + (void)from; + return CURLE_OK; +} + +static void destroy_async_data(struct Curl_async *); + +/* + * Cancel all possibly still on-going resolves for this connection. + */ +void Curl_resolver_cancel(struct connectdata *conn) +{ + destroy_async_data(&conn->async); +} + +/* This function is used to init a threaded resolve */ +static bool init_resolve_thread(struct connectdata *conn, + const char *hostname, int port, + const struct addrinfo *hints); + + +/* Data for synchronization between resolver thread and its parent */ +struct thread_sync_data { + curl_mutex_t * mtx; + int done; + + char * hostname; /* hostname to resolve, Curl_async.hostname + duplicate */ + int port; + int sock_error; + Curl_addrinfo *res; +#ifdef HAVE_GETADDRINFO + struct addrinfo hints; +#endif +}; + +struct thread_data { + curl_thread_t thread_hnd; + unsigned int poll_interval; + int interval_end; + struct thread_sync_data tsd; +}; + +static struct thread_sync_data *conn_thread_sync_data(struct connectdata *conn) +{ + return &(((struct thread_data *)conn->async.os_specific)->tsd); +} + +#define CONN_THREAD_SYNC_DATA(conn) &(((conn)->async.os_specific)->tsd); + +/* Destroy resolver thread synchronization data */ +static +void destroy_thread_sync_data(struct thread_sync_data * tsd) +{ + if(tsd->mtx) { + Curl_mutex_destroy(tsd->mtx); + free(tsd->mtx); + } + + if(tsd->hostname) + free(tsd->hostname); + + if(tsd->res) + Curl_freeaddrinfo(tsd->res); + + memset(tsd,0,sizeof(*tsd)); +} + +/* Initialize resolver thread synchronization data */ +static +int init_thread_sync_data(struct thread_sync_data * tsd, + const char * hostname, + int port, + const struct addrinfo *hints) +{ + memset(tsd, 0, sizeof(*tsd)); + + tsd->port = port; +#ifdef CURLRES_IPV6 + DEBUGASSERT(hints); + tsd->hints = *hints; +#else + (void) hints; +#endif + + tsd->mtx = malloc(sizeof(curl_mutex_t)); + if(tsd->mtx == NULL) + goto err_exit; + + Curl_mutex_init(tsd->mtx); + + tsd->sock_error = CURL_ASYNC_SUCCESS; + + /* Copying hostname string because original can be destroyed by parent + * thread during gethostbyname execution. + */ + tsd->hostname = strdup(hostname); + if(!tsd->hostname) + goto err_exit; + + return 1; + + err_exit: + /* Memory allocation failed */ + destroy_thread_sync_data(tsd); + return 0; +} + +static int getaddrinfo_complete(struct connectdata *conn) +{ + struct thread_sync_data *tsd = conn_thread_sync_data(conn); + int rc; + + rc = Curl_addrinfo_callback(conn, tsd->sock_error, tsd->res); + /* The tsd->res structure has been copied to async.dns and perhaps the DNS + cache. Set our copy to NULL so destroy_thread_sync_data doesn't free it. + */ + tsd->res = NULL; + + return rc; +} + + +#ifdef HAVE_GETADDRINFO + +/* + * getaddrinfo_thread() resolves a name and then exits. + * + * For builds without ARES, but with ENABLE_IPV6, create a resolver thread + * and wait on it. + */ +static unsigned int CURL_STDCALL getaddrinfo_thread (void *arg) +{ + struct thread_sync_data *tsd = (struct thread_sync_data*)arg; + char service [NI_MAXSERV]; + int rc; + + snprintf(service, sizeof(service), "%d", tsd->port); + + rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res); + + if(rc != 0) { + tsd->sock_error = SOCKERRNO?SOCKERRNO:rc; + if(tsd->sock_error == 0) + tsd->sock_error = RESOLVER_ENOMEM; + } + + Curl_mutex_acquire(tsd->mtx); + tsd->done = 1; + Curl_mutex_release(tsd->mtx); + + return 0; +} + +#else /* HAVE_GETADDRINFO */ + +/* + * gethostbyname_thread() resolves a name and then exits. + */ +static unsigned int CURL_STDCALL gethostbyname_thread (void *arg) +{ + struct thread_sync_data *tsd = (struct thread_sync_data *)arg; + + tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port); + + if(!tsd->res) { + tsd->sock_error = SOCKERRNO; + if(tsd->sock_error == 0) + tsd->sock_error = RESOLVER_ENOMEM; + } + + Curl_mutex_acquire(tsd->mtx); + tsd->done = 1; + Curl_mutex_release(tsd->mtx); + + return 0; +} + +#endif /* HAVE_GETADDRINFO */ + +/* + * destroy_async_data() cleans up async resolver data and thread handle. + */ +static void destroy_async_data (struct Curl_async *async) +{ + if(async->hostname) + free(async->hostname); + + if(async->os_specific) { + struct thread_data *td = (struct thread_data*) async->os_specific; + + if(td->thread_hnd != curl_thread_t_null) + Curl_thread_join(&td->thread_hnd); + + destroy_thread_sync_data(&td->tsd); + + free(async->os_specific); + } + async->hostname = NULL; + async->os_specific = NULL; +} + +/* + * init_resolve_thread() starts a new thread that performs the actual + * resolve. This function returns before the resolve is done. + * + * Returns FALSE in case of failure, otherwise TRUE. + */ +static bool init_resolve_thread (struct connectdata *conn, + const char *hostname, int port, + const struct addrinfo *hints) +{ + struct thread_data *td = calloc(1, sizeof(struct thread_data)); + int err = RESOLVER_ENOMEM; + + conn->async.os_specific = (void*) td; + if(!td) + goto err_exit; + + conn->async.port = port; + conn->async.done = FALSE; + conn->async.status = 0; + conn->async.dns = NULL; + td->thread_hnd = curl_thread_t_null; + + if(!init_thread_sync_data(&td->tsd, hostname, port, hints)) + goto err_exit; + + Curl_safefree(conn->async.hostname); + conn->async.hostname = strdup(hostname); + if(!conn->async.hostname) + goto err_exit; + +#ifdef HAVE_GETADDRINFO + td->thread_hnd = Curl_thread_create(getaddrinfo_thread, &td->tsd); +#else + td->thread_hnd = Curl_thread_create(gethostbyname_thread, &td->tsd); +#endif + + if(!td->thread_hnd) { +#ifndef _WIN32_WCE + err = errno; +#endif + goto err_exit; + } + + return TRUE; + + err_exit: + destroy_async_data(&conn->async); + + SET_ERRNO(err); + + return FALSE; +} + +#if defined(HAVE_GETADDRINFO) && !defined(HAVE_GAI_STRERROR) && !defined(WIN32) +/* NetWare has getaddrinfo but lacks gai_strerror. + Windows has a gai_strerror but it is bad (not thread-safe) and the generic + socket error string function can be used for this pupose. */ +static const char *gai_strerror(int ecode) +{ + switch (ecode) { + case EAI_AGAIN: + return "The name could not be resolved at this time"; + case EAI_BADFLAGS: + return "The flags parameter had an invalid value"; + case EAI_FAIL: + return "A non-recoverable error occurred when attempting to " + "resolve the name"; + case EAI_FAMILY: + return "The address family was not recognized"; + case EAI_MEMORY: + return "Out of memory"; + case EAI_NONAME: + return "The name does not resolve for the supplied parameters"; + case EAI_SERVICE: + return "The service passed was not recognized for the " + "specified socket type" + case EAI_SOCKTYPE: + return "The intended socket type was not recognized" + case EAI_SYSTEM: + return "A system error occurred"; + case EAI_OVERFLOW: + return "An argument buffer overflowed"; + default: + return "Unknown error"; + +/* define this now as this is a private implementation of said function */ +#define HAVE_GAI_STRERROR +} +#endif + + +/* + * resolver_error() calls failf() with the appropriate message after a resolve + * error + */ + +static void resolver_error(struct connectdata *conn, const char *host_or_proxy) +{ + failf(conn->data, "Could not resolve %s: %s; %s", host_or_proxy, + conn->async.hostname, +#ifdef HAVE_GAI_STRERROR + /* NetWare doesn't have gai_strerror and on Windows it isn't deemed + thread-safe */ + gai_strerror(conn->async.status) +#else + Curl_strerror(conn, conn->async.status) +#endif + ); +} + +/* + * Curl_resolver_wait_resolv() + * + * waits for a resolve to finish. This function should be avoided since using + * this risk getting the multi interface to "hang". + * + * If 'entry' is non-NULL, make it point to the resolved dns entry + * + * This is the version for resolves-in-a-thread. + */ +CURLcode Curl_resolver_wait_resolv(struct connectdata *conn, + struct Curl_dns_entry **entry) +{ + struct thread_data *td = (struct thread_data*) conn->async.os_specific; + CURLcode rc = CURLE_OK; + + DEBUGASSERT(conn && td); + + /* wait for the thread to resolve the name */ + if(Curl_thread_join(&td->thread_hnd)) + rc = getaddrinfo_complete(conn); + else + DEBUGASSERT(0); + + conn->async.done = TRUE; + + if(entry) + *entry = conn->async.dns; + + if(!conn->async.dns) { + /* a name was not resolved */ + if(conn->bits.httpproxy) { + resolver_error(conn, "proxy"); + rc = CURLE_COULDNT_RESOLVE_PROXY; + } + else { + resolver_error(conn, "host"); + rc = CURLE_COULDNT_RESOLVE_HOST; + } + } + + destroy_async_data(&conn->async); + + if(!conn->async.dns) + conn->bits.close = TRUE; + + return (rc); +} + +/* + * Curl_resolver_is_resolved() is called repeatedly to check if a previous + * name resolve request has completed. It should also make sure to time-out if + * the operation seems to take too long. + */ +CURLcode Curl_resolver_is_resolved(struct connectdata *conn, + struct Curl_dns_entry **entry) +{ + struct SessionHandle *data = conn->data; + struct thread_data *td = (struct thread_data*) conn->async.os_specific; + int done = 0; + + *entry = NULL; + + if(!td) { + DEBUGASSERT(td); + return CURLE_COULDNT_RESOLVE_HOST; + } + + Curl_mutex_acquire(td->tsd.mtx); + done = td->tsd.done; + Curl_mutex_release(td->tsd.mtx); + + if(done) { + getaddrinfo_complete(conn); + destroy_async_data(&conn->async); + + if(!conn->async.dns) { + resolver_error(conn, "host"); + return CURLE_COULDNT_RESOLVE_HOST; + } + *entry = conn->async.dns; + } + else { + /* poll for name lookup done with exponential backoff up to 250ms */ + int elapsed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle); + if(elapsed < 0) + elapsed = 0; + + if(td->poll_interval == 0) + /* Start at 1ms poll interval */ + td->poll_interval = 1; + else if(elapsed >= td->interval_end) + /* Back-off exponentially if last interval expired */ + td->poll_interval *= 2; + + if(td->poll_interval > 250) + td->poll_interval = 250; + + td->interval_end = elapsed + td->poll_interval; + Curl_expire(conn->data, td->poll_interval); + } + + return CURLE_OK; +} + +int Curl_resolver_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + (void)conn; + (void)socks; + (void)numsocks; + return 0; +} + +#ifndef HAVE_GETADDRINFO +/* + * Curl_getaddrinfo() - for platforms without getaddrinfo + */ +Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, + const char *hostname, + int port, + int *waitp) +{ + struct in_addr in; + + *waitp = 0; /* default to synchronous response */ + + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) + /* This is a dotted IP address 123.123.123.123-style */ + return Curl_ip2addr(AF_INET, &in, hostname, port); + + /* fire up a new resolver thread! */ + if(init_resolve_thread(conn, hostname, port, NULL)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + /* fall-back to blocking version */ + return Curl_ipv4_resolve_r(hostname, port); +} + +#else /* !HAVE_GETADDRINFO */ + +/* + * Curl_resolver_getaddrinfo() - for getaddrinfo + */ +Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, + const char *hostname, + int port, + int *waitp) +{ + struct addrinfo hints; + struct in_addr in; + Curl_addrinfo *res; + int error; + char sbuf[NI_MAXSERV]; + int pf = PF_INET; +#ifdef CURLRES_IPV6 + struct in6_addr in6; +#endif /* CURLRES_IPV6 */ + + *waitp = 0; /* default to synchronous response */ + + /* First check if this is an IPv4 address string */ + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) + /* This is a dotted IP address 123.123.123.123-style */ + return Curl_ip2addr(AF_INET, &in, hostname, port); + +#ifdef CURLRES_IPV6 + /* check if this is an IPv6 address string */ + if(Curl_inet_pton (AF_INET6, hostname, &in6) > 0) + /* This is an IPv6 address literal */ + return Curl_ip2addr(AF_INET6, &in6, hostname, port); + + /* + * Check if a limited name resolve has been requested. + */ + switch(conn->ip_version) { + case CURL_IPRESOLVE_V4: + pf = PF_INET; + break; + case CURL_IPRESOLVE_V6: + pf = PF_INET6; + break; + default: + pf = PF_UNSPEC; + break; + } + + if((pf != PF_INET) && !Curl_ipv6works()) + /* the stack seems to be a non-ipv6 one */ + pf = PF_INET; + +#endif /* CURLRES_IPV6 */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = conn->socktype; + + snprintf(sbuf, sizeof(sbuf), "%d", port); + + /* fire up a new resolver thread! */ + if(init_resolve_thread(conn, hostname, port, &hints)) { + *waitp = 1; /* expect asynchronous response */ + return NULL; + } + + /* fall-back to blocking version */ + infof(conn->data, "init_resolve_thread() failed for %s; %s\n", + hostname, Curl_strerror(conn, ERRNO)); + + error = Curl_getaddrinfo_ex(hostname, sbuf, &hints, &res); + if(error) { + infof(conn->data, "getaddrinfo() failed for %s:%d; %s\n", + hostname, port, Curl_strerror(conn, SOCKERRNO)); + return NULL; + } + return res; +} + +#endif /* !HAVE_GETADDRINFO */ + +CURLcode Curl_set_dns_servers(struct SessionHandle *data, + char *servers) +{ + (void)data; + (void)servers; + return CURLE_NOT_BUILT_IN; + +} + +#endif /* CURLRES_THREADED */ diff --git a/lib/curl_axtls.c b/lib/curl_axtls.c new file mode 100644 index 000000000..8bd606a40 --- /dev/null +++ b/lib/curl_axtls.c @@ -0,0 +1,547 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2010, DirecTV * contact: Eric Hu + * Copyright (C) 2010 - 2013, 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 + * 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. + * + ***************************************************************************/ + +/* + * Source file for all axTLS-specific code for the TLS/SSL layer. No code + * but curl_sslgen.c should ever call or use these functions. + */ + +#include "curl_setup.h" + +#ifdef USE_AXTLS +#include +#include "curl_axtls.h" + +#include "curl_sendf.h" +#include "curl_inet_pton.h" +#include "curl_sslgen.h" +#include "curl_parsedate.h" +#include "curl_connect.h" /* for the connect timeout */ +#include "curl_select.h" +#define _MPRINTF_REPLACE /* use our functions only */ +#include +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" +#include "curl_hostcheck.h" + + +/* SSL_read is opied from axTLS compat layer */ +static int SSL_read(SSL *ssl, void *buf, int num) +{ + uint8_t *read_buf; + int ret; + + while((ret = ssl_read(ssl, &read_buf)) == SSL_OK); + + if(ret > SSL_OK) { + memcpy(buf, read_buf, ret > num ? num : ret); + } + + return ret; +} + +/* Global axTLS init, called from Curl_ssl_init() */ +int Curl_axtls_init(void) +{ +/* axTLS has no global init. Everything is done through SSL and SSL_CTX + * structs stored in connectdata structure. Perhaps can move to curl_axtls.h. + */ + return 1; +} + +int Curl_axtls_cleanup(void) +{ + /* axTLS has no global cleanup. Perhaps can move this to curl_axtls.h. */ + return 1; +} + +static CURLcode map_error_to_curl(int axtls_err) +{ + switch (axtls_err) { + case SSL_ERROR_NOT_SUPPORTED: + case SSL_ERROR_INVALID_VERSION: + case -70: /* protocol version alert from server */ + return CURLE_UNSUPPORTED_PROTOCOL; + break; + case SSL_ERROR_NO_CIPHER: + return CURLE_SSL_CIPHER; + break; + case SSL_ERROR_BAD_CERTIFICATE: /* this may be bad server cert too */ + case SSL_ERROR_NO_CERT_DEFINED: + case -42: /* bad certificate alert from server */ + case -43: /* unsupported cert alert from server */ + case -44: /* cert revoked alert from server */ + case -45: /* cert expired alert from server */ + case -46: /* cert unknown alert from server */ + return CURLE_SSL_CERTPROBLEM; + break; + case SSL_X509_ERROR(X509_NOT_OK): + case SSL_X509_ERROR(X509_VFY_ERROR_NO_TRUSTED_CERT): + case SSL_X509_ERROR(X509_VFY_ERROR_BAD_SIGNATURE): + case SSL_X509_ERROR(X509_VFY_ERROR_NOT_YET_VALID): + case SSL_X509_ERROR(X509_VFY_ERROR_EXPIRED): + case SSL_X509_ERROR(X509_VFY_ERROR_SELF_SIGNED): + case SSL_X509_ERROR(X509_VFY_ERROR_INVALID_CHAIN): + case SSL_X509_ERROR(X509_VFY_ERROR_UNSUPPORTED_DIGEST): + case SSL_X509_ERROR(X509_INVALID_PRIV_KEY): + return CURLE_PEER_FAILED_VERIFICATION; + break; + case -48: /* unknown ca alert from server */ + return CURLE_SSL_CACERT; + break; + case -49: /* access denied alert from server */ + return CURLE_REMOTE_ACCESS_DENIED; + break; + case SSL_ERROR_CONN_LOST: + case SSL_ERROR_SOCK_SETUP_FAILURE: + case SSL_ERROR_INVALID_HANDSHAKE: + case SSL_ERROR_INVALID_PROT_MSG: + case SSL_ERROR_INVALID_HMAC: + case SSL_ERROR_INVALID_SESSION: + case SSL_ERROR_INVALID_KEY: /* it's too bad this doesn't map better */ + case SSL_ERROR_FINISHED_INVALID: + case SSL_ERROR_NO_CLIENT_RENOG: + default: + return CURLE_SSL_CONNECT_ERROR; + break; + } +} + +static Curl_recv axtls_recv; +static Curl_send axtls_send; + +/* + * This function is called after the TCP connect has completed. Setup the TLS + * layer and do all necessary magic. + */ +CURLcode +Curl_axtls_connect(struct connectdata *conn, + int sockindex) + +{ + struct SessionHandle *data = conn->data; + SSL_CTX *ssl_ctx; + SSL *ssl; + int cert_types[] = {SSL_OBJ_X509_CERT, SSL_OBJ_PKCS12, 0}; + int key_types[] = {SSL_OBJ_RSA_KEY, SSL_OBJ_PKCS8, SSL_OBJ_PKCS12, 0}; + int i, ssl_fcn_return; + const uint8_t *ssl_sessionid; + size_t ssl_idsize; + const char *peer_CN; + uint32_t dns_altname_index; + const char *dns_altname; + int8_t found_subject_alt_names = 0; + int8_t found_subject_alt_name_matching_conn = 0; + + /* Assuming users will not compile in custom key/cert to axTLS */ + uint32_t client_option = SSL_NO_DEFAULT_KEY|SSL_SERVER_VERIFY_LATER; + + if(conn->ssl[sockindex].state == ssl_connection_complete) + /* to make us tolerant against being called more than once for the + same connection */ + return CURLE_OK; + + /* axTLS only supports TLSv1 */ + /* check to see if we've been told to use an explicit SSL/TLS version */ + switch(data->set.ssl.version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + break; + default: + failf(data, "axTLS only supports TLSv1"); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef AXTLSDEBUG + client_option |= SSL_DISPLAY_STATES | SSL_DISPLAY_RSA | SSL_DISPLAY_CERTS; +#endif /* AXTLSDEBUG */ + + /* Allocate an SSL_CTX struct */ + ssl_ctx = ssl_ctx_new(client_option, SSL_DEFAULT_CLNT_SESS); + if(ssl_ctx == NULL) { + failf(data, "unable to create client SSL context"); + return CURLE_SSL_CONNECT_ERROR; + } + + /* Load the trusted CA cert bundle file */ + if(data->set.ssl.CAfile) { + if(ssl_obj_load(ssl_ctx, SSL_OBJ_X509_CACERT, data->set.ssl.CAfile, NULL) + != SSL_OK) { + infof(data, "error reading ca cert file %s \n", + data->set.ssl.CAfile); + if(data->set.ssl.verifypeer) { + Curl_axtls_close(conn, sockindex); + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found certificates in %s\n", data->set.ssl.CAfile); + } + + /* curl_gtls.c tasks we're skipping for now: + * 1) certificate revocation list checking + * 2) dns name assignment to host + * 3) set protocol priority. axTLS is TLSv1 only, so can probably ignore + * 4) set certificate priority. axTLS ignores type and sends certs in + * order added. can probably ignore this. + */ + + /* Load client certificate */ + if(data->set.str[STRING_CERT]) { + i=0; + /* Instead of trying to analyze cert type here, let axTLS try them all. */ + while(cert_types[i] != 0) { + ssl_fcn_return = ssl_obj_load(ssl_ctx, cert_types[i], + data->set.str[STRING_CERT], NULL); + if(ssl_fcn_return == SSL_OK) { + infof(data, "successfully read cert file %s \n", + data->set.str[STRING_CERT]); + break; + } + i++; + } + /* Tried all cert types, none worked. */ + if(cert_types[i] == 0) { + failf(data, "%s is not x509 or pkcs12 format", + data->set.str[STRING_CERT]); + Curl_axtls_close(conn, sockindex); + return CURLE_SSL_CERTPROBLEM; + } + } + + /* Load client key. + If a pkcs12 file successfully loaded a cert, then there's nothing to do + because the key has already been loaded. */ + if(data->set.str[STRING_KEY] && cert_types[i] != SSL_OBJ_PKCS12) { + i=0; + /* Instead of trying to analyze key type here, let axTLS try them all. */ + while(key_types[i] != 0) { + ssl_fcn_return = ssl_obj_load(ssl_ctx, key_types[i], + data->set.str[STRING_KEY], NULL); + if(ssl_fcn_return == SSL_OK) { + infof(data, "successfully read key file %s \n", + data->set.str[STRING_KEY]); + break; + } + i++; + } + /* Tried all key types, none worked. */ + if(key_types[i] == 0) { + failf(data, "Failure: %s is not a supported key file", + data->set.str[STRING_KEY]); + Curl_axtls_close(conn, sockindex); + return CURLE_SSL_CONNECT_ERROR; + } + } + + /* curl_gtls.c does more here that is being left out for now + * 1) set session credentials. can probably ignore since axtls puts this + * info in the ssl_ctx struct + * 2) setting up callbacks. these seem gnutls specific + */ + + /* In axTLS, handshaking happens inside ssl_client_new. */ + if(!Curl_ssl_getsessionid(conn, (void **) &ssl_sessionid, &ssl_idsize)) { + /* we got a session id, use it! */ + infof (data, "SSL re-using session ID\n"); + ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], + ssl_sessionid, (uint8_t)ssl_idsize); + } + else + ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], NULL, 0); + + /* Check to make sure handshake was ok. */ + ssl_fcn_return = ssl_handshake_status(ssl); + if(ssl_fcn_return != SSL_OK) { + Curl_axtls_close(conn, sockindex); + ssl_display_error(ssl_fcn_return); /* goes to stdout. */ + return map_error_to_curl(ssl_fcn_return); + } + infof (data, "handshake completed successfully\n"); + + /* Here, curl_gtls.c gets the peer certificates and fails out depending on + * settings in "data." axTLS api doesn't have get cert chain fcn, so omit? + */ + + /* Verify server's certificate */ + if(data->set.ssl.verifypeer) { + if(ssl_verify_cert(ssl) != SSL_OK) { + Curl_axtls_close(conn, sockindex); + failf(data, "server cert verify failed"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else + infof(data, "\t server certificate verification SKIPPED\n"); + + /* Here, curl_gtls.c does issuer verification. axTLS has no straightforward + * equivalent, so omitting for now.*/ + + /* Here, curl_gtls.c does the following + * 1) x509 hostname checking per RFC2818. axTLS doesn't support this, but + * it seems useful. This is now implemented, by Oscar Koeroo + * 2) checks cert validity based on time. axTLS does this in ssl_verify_cert + * 3) displays a bunch of cert information. axTLS doesn't support most of + * this, but a couple fields are available. + */ + + + /* There is no (DNS) Altnames count in the version 1.4.8 API. There is a + risk of an inifite loop */ + for(dns_altname_index = 0; ; dns_altname_index++) { + dns_altname = ssl_get_cert_subject_alt_dnsname(ssl, dns_altname_index); + if(dns_altname == NULL) { + break; + } + found_subject_alt_names = 1; + + infof(data, "\tComparing subject alt name DNS with hostname: %s <-> %s\n", + dns_altname, conn->host.name); + if(Curl_cert_hostcheck(dns_altname, conn->host.name)) { + found_subject_alt_name_matching_conn = 1; + break; + } + } + + /* RFC2818 checks */ + if(found_subject_alt_names && !found_subject_alt_name_matching_conn) { + /* Break connection ! */ + Curl_axtls_close(conn, sockindex); + failf(data, "\tsubjectAltName(s) do not match %s\n", conn->host.dispname); + return CURLE_PEER_FAILED_VERIFICATION; + } + else if(found_subject_alt_names == 0) { + /* Per RFC2818, when no Subject Alt Names were available, examine the peer + CN as a legacy fallback */ + peer_CN = ssl_get_cert_dn(ssl, SSL_X509_CERT_COMMON_NAME); + if(peer_CN == NULL) { + /* Similar behaviour to the OpenSSL interface */ + Curl_axtls_close(conn, sockindex); + failf(data, "unable to obtain common name from peer certificate"); + return CURLE_PEER_FAILED_VERIFICATION; + } + else { + if(!Curl_cert_hostcheck((const char *)peer_CN, conn->host.name)) { + if(data->set.ssl.verifyhost) { + /* Break connection ! */ + Curl_axtls_close(conn, sockindex); + failf(data, "\tcommon name \"%s\" does not match \"%s\"\n", + peer_CN, conn->host.dispname); + return CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, "\tcommon name \"%s\" does not match \"%s\"\n", + peer_CN, conn->host.dispname); + } + } + } + + /* General housekeeping */ + conn->ssl[sockindex].state = ssl_connection_complete; + conn->ssl[sockindex].ssl = ssl; + conn->ssl[sockindex].ssl_ctx = ssl_ctx; + conn->recv[sockindex] = axtls_recv; + conn->send[sockindex] = axtls_send; + + /* Put our freshly minted SSL session in cache */ + ssl_idsize = ssl_get_session_id_size(ssl); + ssl_sessionid = ssl_get_session_id(ssl); + if(Curl_ssl_addsessionid(conn, (void *) ssl_sessionid, ssl_idsize) + != CURLE_OK) + infof (data, "failed to add session to cache\n"); + + return CURLE_OK; +} + + +/* return number of sent (non-SSL) bytes */ +static ssize_t axtls_send(struct connectdata *conn, + int sockindex, + const void *mem, + size_t len, + CURLcode *err) +{ + /* ssl_write() returns 'int' while write() and send() returns 'size_t' */ + int rc = ssl_write(conn->ssl[sockindex].ssl, mem, (int)len); + + infof(conn->data, " axtls_send\n"); + + if(rc < 0 ) { + *err = map_error_to_curl(rc); + rc = -1; /* generic error code for send failure */ + } + + *err = CURLE_OK; + return rc; +} + +void Curl_axtls_close_all(struct SessionHandle *data) +{ + (void)data; + infof(data, " Curl_axtls_close_all\n"); +} + +void Curl_axtls_close(struct connectdata *conn, int sockindex) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + infof(conn->data, " Curl_axtls_close\n"); + if(connssl->ssl) { + /* line from curl_ssluse.c: (void)SSL_shutdown(connssl->ssl); + axTLS compat layer does nothing for SSL_shutdown */ + + /* The following line is from curl_ssluse.c. There seems to be no axTLS + equivalent. ssl_free and ssl_ctx_free close things. + SSL_set_connect_state(connssl->handle); */ + + ssl_free (connssl->ssl); + connssl->ssl = NULL; + } + if(connssl->ssl_ctx) { + ssl_ctx_free (connssl->ssl_ctx); + connssl->ssl_ctx = NULL; + } +} + +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +int Curl_axtls_shutdown(struct connectdata *conn, int sockindex) +{ + /* Outline taken from curl_ssluse.c since functions are in axTLS compat + layer. axTLS's error set is much smaller, so a lot of error-handling + was removed. + */ + int retval = 0; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct SessionHandle *data = conn->data; + char buf[120]; /* We will use this for the OpenSSL error buffer, so it has + to be at least 120 bytes long. */ + ssize_t nread; + + infof(conn->data, " Curl_axtls_shutdown\n"); + + /* This has only been tested on the proftpd server, and the mod_tls code + sends a close notify alert without waiting for a close notify alert in + response. Thus we wait for a close notify alert from the server, but + we do not send one. Let's hope other servers do the same... */ + + /* axTLS compat layer does nothing for SSL_shutdown, so we do nothing too + if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) + (void)SSL_shutdown(connssl->ssl); + */ + + if(connssl->ssl) { + int what = Curl_socket_ready(conn->sock[sockindex], + CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); + if(what > 0) { + /* Something to read, let's do it and hope that it is the close + notify alert from the server */ + nread = (ssize_t)SSL_read(conn->ssl[sockindex].ssl, buf, + sizeof(buf)); + + if(nread < SSL_OK) { + failf(data, "close notify alert not received during shutdown"); + retval = -1; + } + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + } + else { + /* anything that gets here is fatally bad */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + retval = -1; + } + + ssl_free (connssl->ssl); + connssl->ssl = NULL; + } + return retval; +} + +static ssize_t axtls_recv(struct connectdata *conn, /* connection data */ + int num, /* socketindex */ + char *buf, /* store read data here */ + size_t buffersize, /* max amount to read */ + CURLcode *err) +{ + struct ssl_connect_data *connssl = &conn->ssl[num]; + ssize_t ret = 0; + + infof(conn->data, " axtls_recv\n"); + + if(connssl) { + ret = (ssize_t)SSL_read(conn->ssl[num].ssl, buf, (int)buffersize); + + /* axTLS isn't terribly generous about error reporting */ + /* With patched axTLS, SSL_CLOSE_NOTIFY=-3. Hard-coding until axTLS + team approves proposed fix. */ + if(ret == -3 ) { + Curl_axtls_close(conn, num); + } + else if(ret < 0) { + failf(conn->data, "axTLS recv error (%d)", (int)ret); + *err = map_error_to_curl(ret); + return -1; + } + } + + *err = CURLE_OK; + return ret; +} + +/* + * Return codes: + * 1 means the connection is still in place + * 0 means the connection has been closed + * -1 means the connection status is unknown + */ +int Curl_axtls_check_cxn(struct connectdata *conn) +{ + /* curl_ssluse.c line: + rc = SSL_peek(conn->ssl[FIRSTSOCKET].ssl, (void*)&buf, 1); + axTLS compat layer always returns the last argument, so connection is + always alive? */ + + infof(conn->data, " Curl_axtls_check_cxn\n"); + return 1; /* connection still in place */ +} + +void Curl_axtls_session_free(void *ptr) +{ + (void)ptr; + /* free the ID */ + /* both curl_ssluse.c and curl_gtls.c do something here, but axTLS's + OpenSSL compatibility layer does nothing, so we do nothing too. */ +} + +size_t Curl_axtls_version(char *buffer, size_t size) +{ + return snprintf(buffer, size, "axTLS/%s", ssl_version()); +} + +#endif /* USE_AXTLS */ diff --git a/lib/curl_base64.c b/lib/curl_base64.c new file mode 100644 index 000000000..45c7a95bc --- /dev/null +++ b/lib/curl_base64.c @@ -0,0 +1,248 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +/* Base64 encoding/decoding */ + +#include "curl_setup.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_urldata.h" /* for the SessionHandle definition */ +#include "curl_warnless.h" +#include "curl_base64.h" +#include "curl_memory.h" +#include "curl_non_ascii.h" + +/* include curl_memdebug.h last */ +#include "curl_memdebug.h" + +/* ---- Base64 Encoding/Decoding Table --- */ +static const char table64[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static void decodeQuantum(unsigned char *dest, const char *src) +{ + const char *s, *p; + unsigned long i, v, x = 0; + + for(i = 0, s = src; i < 4; i++, s++) { + v = 0; + p = table64; + while(*p && (*p != *s)) { + v++; + p++; + } + if(*p == *s) + x = (x << 6) + v; + else if(*s == '=') + x = (x << 6); + } + + dest[2] = curlx_ultouc(x & 0xFFUL); + x >>= 8; + dest[1] = curlx_ultouc(x & 0xFFUL); + x >>= 8; + dest[0] = curlx_ultouc(x & 0xFFUL); +} + +/* + * Curl_base64_decode() + * + * Given a base64 NUL-terminated string at src, decode it and return a + * pointer in *outptr to a newly allocated memory area holding decoded + * data. Size of decoded data is returned in variable pointed by outlen. + * + * Returns CURLE_OK on success, otherwise specific error code. Function + * output shall not be considered valid unless CURLE_OK is returned. + * + * When decoded data length is 0, returns NULL in *outptr. + * + * @unittest: 1302 + */ +CURLcode Curl_base64_decode(const char *src, + unsigned char **outptr, size_t *outlen) +{ + size_t length = 0; + size_t equalsTerm = 0; + size_t i; + size_t numQuantums; + unsigned char lastQuantum[3]; + size_t rawlen = 0; + unsigned char *newstr; + + *outptr = NULL; + *outlen = 0; + + while((src[length] != '=') && src[length]) + length++; + /* A maximum of two = padding characters is allowed */ + if(src[length] == '=') { + equalsTerm++; + if(src[length+equalsTerm] == '=') + equalsTerm++; + } + numQuantums = (length + equalsTerm) / 4; + + /* Don't allocate a buffer if the decoded length is 0 */ + if(numQuantums == 0) + return CURLE_OK; + + rawlen = (numQuantums * 3) - equalsTerm; + + /* The buffer must be large enough to make room for the last quantum + (which may be partially thrown out) and the zero terminator. */ + newstr = malloc(rawlen+4); + if(!newstr) + return CURLE_OUT_OF_MEMORY; + + *outptr = newstr; + + /* Decode all but the last quantum (which may not decode to a + multiple of 3 bytes) */ + for(i = 0; i < numQuantums - 1; i++) { + decodeQuantum(newstr, src); + newstr += 3; src += 4; + } + + /* This final decode may actually read slightly past the end of the buffer + if the input string is missing pad bytes. This will almost always be + harmless. */ + decodeQuantum(lastQuantum, src); + for(i = 0; i < 3 - equalsTerm; i++) + newstr[i] = lastQuantum[i]; + + newstr[i] = '\0'; /* zero terminate */ + + *outlen = rawlen; /* return size of decoded data */ + + return CURLE_OK; +} + +/* + * Curl_base64_encode() + * + * Given a pointer to an input buffer and an input size, encode it and + * return a pointer in *outptr to a newly allocated memory area holding + * encoded data. Size of encoded data is returned in variable pointed by + * outlen. + * + * Input length of 0 indicates input buffer holds a NUL-terminated string. + * + * Returns CURLE_OK on success, otherwise specific error code. Function + * output shall not be considered valid unless CURLE_OK is returned. + * + * When encoded data length is 0, returns NULL in *outptr. + * + * @unittest: 1302 + */ +CURLcode Curl_base64_encode(struct SessionHandle *data, + const char *inputbuff, size_t insize, + char **outptr, size_t *outlen) +{ + CURLcode error; + unsigned char ibuf[3]; + unsigned char obuf[4]; + int i; + int inputparts; + char *output; + char *base64data; + char *convbuf = NULL; + + const char *indata = inputbuff; + + *outptr = NULL; + *outlen = 0; + + if(0 == insize) + insize = strlen(indata); + + base64data = output = malloc(insize*4/3+4); + if(NULL == output) + return CURLE_OUT_OF_MEMORY; + + /* + * The base64 data needs to be created using the network encoding + * not the host encoding. And we can't change the actual input + * so we copy it to a buffer, translate it, and use that instead. + */ + error = Curl_convert_clone(data, indata, insize, &convbuf); + if(error) { + free(output); + return error; + } + + if(convbuf) + indata = (char *)convbuf; + + while(insize > 0) { + for(i = inputparts = 0; i < 3; i++) { + if(insize > 0) { + inputparts++; + ibuf[i] = (unsigned char) *indata; + indata++; + insize--; + } + else + ibuf[i] = 0; + } + + obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2); + obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \ + ((ibuf[1] & 0xF0) >> 4)); + obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \ + ((ibuf[2] & 0xC0) >> 6)); + obuf[3] = (unsigned char) (ibuf[2] & 0x3F); + + switch(inputparts) { + case 1: /* only one byte read */ + snprintf(output, 5, "%c%c==", + table64[obuf[0]], + table64[obuf[1]]); + break; + case 2: /* two bytes read */ + snprintf(output, 5, "%c%c%c=", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]]); + break; + default: + snprintf(output, 5, "%c%c%c%c", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]], + table64[obuf[3]] ); + break; + } + output += 4; + } + *output = '\0'; + *outptr = base64data; /* return pointer to new data, allocated memory */ + + if(convbuf) + free(convbuf); + + *outlen = strlen(base64data); /* return the length of the new data */ + + return CURLE_OK; +} +/* ---- End of Base64 Encoding ---- */ diff --git a/lib/curl_bundles.c b/lib/curl_bundles.c new file mode 100644 index 000000000..efbaeee4f --- /dev/null +++ b/lib/curl_bundles.c @@ -0,0 +1,110 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2012, Linus Nielsen Feltzing, + * Copyright (C) 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#include "curl_urldata.h" +#include "curl_url.h" +#include "curl_progress.h" +#include "curl_multiif.h" +#include "curl_bundles.h" +#include "curl_sendf.h" +#include "curl_rawstr.h" + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +static void conn_llist_dtor(void *user, void *element) +{ + struct connectdata *data = element; + (void)user; + + data->bundle = NULL; +} + +CURLcode Curl_bundle_create(struct SessionHandle *data, + struct connectbundle **cb_ptr) +{ + (void)data; + DEBUGASSERT(*cb_ptr == NULL); + *cb_ptr = malloc(sizeof(struct connectbundle)); + if(!*cb_ptr) + return CURLE_OUT_OF_MEMORY; + + (*cb_ptr)->num_connections = 0; + (*cb_ptr)->server_supports_pipelining = FALSE; + + (*cb_ptr)->conn_list = Curl_llist_alloc((curl_llist_dtor) conn_llist_dtor); + if(!(*cb_ptr)->conn_list) { + Curl_safefree(*cb_ptr); + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} + +void Curl_bundle_destroy(struct connectbundle *cb_ptr) +{ + if(!cb_ptr) + return; + + if(cb_ptr->conn_list) { + Curl_llist_destroy(cb_ptr->conn_list, NULL); + cb_ptr->conn_list = NULL; + } + Curl_safefree(cb_ptr); +} + +/* Add a connection to a bundle */ +CURLcode Curl_bundle_add_conn(struct connectbundle *cb_ptr, + struct connectdata *conn) +{ + if(!Curl_llist_insert_next(cb_ptr->conn_list, cb_ptr->conn_list->tail, conn)) + return CURLE_OUT_OF_MEMORY; + + conn->bundle = cb_ptr; + + cb_ptr->num_connections++; + return CURLE_OK; +} + +/* Remove a connection from a bundle */ +int Curl_bundle_remove_conn(struct connectbundle *cb_ptr, + struct connectdata *conn) +{ + struct curl_llist_element *curr; + + curr = cb_ptr->conn_list->head; + while(curr) { + if(curr->ptr == conn) { + Curl_llist_remove(cb_ptr->conn_list, curr, NULL); + cb_ptr->num_connections--; + conn->bundle = NULL; + return 1; /* we removed a handle */ + } + curr = curr->next; + } + return 0; +} diff --git a/lib/curl_conncache.c b/lib/curl_conncache.c new file mode 100644 index 000000000..bc95e07df --- /dev/null +++ b/lib/curl_conncache.c @@ -0,0 +1,285 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2012, Linus Nielsen Feltzing, + * Copyright (C) 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#include "curl_urldata.h" +#include "curl_url.h" +#include "curl_progress.h" +#include "curl_multiif.h" +#include "curl_sendf.h" +#include "curl_rawstr.h" +#include "curl_bundles.h" +#include "curl_conncache.h" + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#define CONNECTION_HASH_SIZE 97 + +static void free_bundle_hash_entry(void *freethis) +{ + struct connectbundle *b = (struct connectbundle *) freethis; + + Curl_bundle_destroy(b); +} + +struct conncache *Curl_conncache_init(conncachetype type) +{ + struct conncache *connc; + + connc = calloc(1, sizeof(struct conncache)); + if(!connc) + return NULL; + + connc->hash = Curl_hash_alloc(CONNECTION_HASH_SIZE, Curl_hash_str, + Curl_str_key_compare, free_bundle_hash_entry); + + if(!connc->hash) { + free(connc); + return NULL; + } + + connc->type = type; + connc->num_connections = 0; + + return connc; +} + +void Curl_conncache_destroy(struct conncache *connc) +{ + if(connc) { + Curl_hash_destroy(connc->hash); + connc->hash = NULL; + free(connc); + } +} + +struct connectbundle *Curl_conncache_find_bundle(struct conncache *connc, + char *hostname) +{ + struct connectbundle *bundle = NULL; + + if(connc) + bundle = Curl_hash_pick(connc->hash, hostname, strlen(hostname)+1); + + return bundle; +} + +static bool conncache_add_bundle(struct conncache *connc, + char *hostname, + struct connectbundle *bundle) +{ + void *p; + + p = Curl_hash_add(connc->hash, hostname, strlen(hostname)+1, bundle); + + return p?TRUE:FALSE; +} + +static void conncache_remove_bundle(struct conncache *connc, + struct connectbundle *bundle) +{ + struct curl_hash_iterator iter; + struct curl_hash_element *he; + + if(!connc) + return; + + Curl_hash_start_iterate(connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + if(he->ptr == bundle) { + /* The bundle is destroyed by the hash destructor function, + free_bundle_hash_entry() */ + Curl_hash_delete(connc->hash, he->key, he->key_len); + return; + } + + he = Curl_hash_next_element(&iter); + } +} + +CURLcode Curl_conncache_add_conn(struct conncache *connc, + struct connectdata *conn) +{ + CURLcode result; + struct connectbundle *bundle; + struct connectbundle *new_bundle = NULL; + struct SessionHandle *data = conn->data; + + bundle = Curl_conncache_find_bundle(data->state.conn_cache, + conn->host.name); + if(!bundle) { + result = Curl_bundle_create(data, &new_bundle); + if(result != CURLE_OK) + return result; + + if(!conncache_add_bundle(data->state.conn_cache, + conn->host.name, new_bundle)) { + Curl_bundle_destroy(new_bundle); + return CURLE_OUT_OF_MEMORY; + } + bundle = new_bundle; + } + + result = Curl_bundle_add_conn(bundle, conn); + if(result != CURLE_OK) { + if(new_bundle) + conncache_remove_bundle(data->state.conn_cache, new_bundle); + return result; + } + + connc->num_connections++; + + return CURLE_OK; +} + +void Curl_conncache_remove_conn(struct conncache *connc, + struct connectdata *conn) +{ + struct connectbundle *bundle = conn->bundle; + + /* The bundle pointer can be NULL, since this function can be called + due to a failed connection attempt, before being added to a bundle */ + if(bundle) { + Curl_bundle_remove_conn(bundle, conn); + if(bundle->num_connections == 0) { + conncache_remove_bundle(connc, bundle); + } + connc->num_connections--; + + DEBUGF(infof(conn->data, "The cache now contains %d members\n", + connc->num_connections)); + } +} + +/* This function iterates the entire connection cache and calls the + function func() with the connection pointer as the first argument + and the supplied 'param' argument as the other, + + Return 0 from func() to continue the loop, return 1 to abort it. + */ +void Curl_conncache_foreach(struct conncache *connc, + void *param, + int (*func)(struct connectdata *conn, void *param)) +{ + struct curl_hash_iterator iter; + struct curl_llist_element *curr; + struct curl_hash_element *he; + + if(!connc) + return; + + Curl_hash_start_iterate(connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct connectbundle *bundle; + struct connectdata *conn; + + bundle = he->ptr; + + curr = bundle->conn_list->head; + while(curr) { + /* Yes, we need to update curr before calling func(), because func() + might decide to remove the connection */ + conn = curr->ptr; + curr = curr->next; + + if(1 == func(conn, param)) + return; + } + + he = Curl_hash_next_element(&iter); + } +} + +/* Return the first connection found in the cache. Used when closing all + connections */ +struct connectdata * +Curl_conncache_find_first_connection(struct conncache *connc) +{ + struct curl_hash_iterator iter; + struct curl_llist_element *curr; + struct curl_hash_element *he; + struct connectbundle *bundle; + + Curl_hash_start_iterate(connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + bundle = he->ptr; + + curr = bundle->conn_list->head; + if(curr) { + return curr->ptr; + } + + he = Curl_hash_next_element(&iter); + } + + return NULL; +} + + +#if 0 +/* Useful for debugging the connection cache */ +void Curl_conncache_print(struct conncache *connc) +{ + struct curl_hash_iterator iter; + struct curl_llist_element *curr; + struct curl_hash_element *he; + + if(!connc) + return; + + fprintf(stderr, "=Bundle cache=\n"); + + Curl_hash_start_iterate(connc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct connectbundle *bundle; + struct connectdata *conn; + + bundle = he->ptr; + + fprintf(stderr, "%s -", he->key); + curr = bundle->conn_list->head; + while(curr) { + conn = curr->ptr; + + fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse); + curr = curr->next; + } + fprintf(stderr, "\n"); + + he = Curl_hash_next_element(&iter); + } +} +#endif diff --git a/lib/curl_connect.c b/lib/curl_connect.c new file mode 100644 index 000000000..85226d808 --- /dev/null +++ b/lib/curl_connect.c @@ -0,0 +1,1248 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include /* may need it */ +#endif +#ifdef HAVE_SYS_UN_H +#include /* for sockaddr_un */ +#endif +#ifdef HAVE_NETINET_TCP_H +#include /* for TCP_NODELAY */ +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE)) +#include +#endif +#ifdef NETWARE +#undef in_addr_t +#define in_addr_t unsigned long +#endif +#ifdef __VMS +#include +#include +#endif + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_if2ip.h" +#include "curl_strerror.h" +#include "curl_connect.h" +#include "curl_memory.h" +#include "curl_select.h" +#include "curl_url.h" +#include "curl_multiif.h" +#include "curl_sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "curl_inet_ntop.h" +#include "curl_inet_pton.h" +#include "curl_sslgen.h" /* for Curl_ssl_check_cxn() */ +#include "curl_progress.h" +#include "curl_warnless.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifdef __SYMBIAN32__ +/* This isn't actually supported under Symbian OS */ +#undef SO_NOSIGPIPE +#endif + +static bool verifyconnect(curl_socket_t sockfd, int *error); + +#ifdef __DragonFly__ +/* DragonFlyBSD uses millisecond as KEEPIDLE and KEEPINTVL units */ +#define KEEPALIVE_FACTOR(x) (x *= 1000) +#else +#define KEEPALIVE_FACTOR(x) +#endif + +static void +tcpkeepalive(struct SessionHandle *data, + curl_socket_t sockfd) +{ + int optval = data->set.tcp_keepalive?1:0; + + /* only set IDLE and INTVL if setting KEEPALIVE is successful */ + if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set SO_KEEPALIVE on fd %d\n", sockfd); + } + else { +#ifdef TCP_KEEPIDLE + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPIDLE on fd %d\n", sockfd); + } +#endif +#ifdef TCP_KEEPINTVL + optval = curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPINTVL on fd %d\n", sockfd); + } +#endif + } +} + +static CURLcode +singleipconnect(struct connectdata *conn, + const Curl_addrinfo *ai, /* start connecting to this */ + long timeout_ms, + curl_socket_t *sock, + bool *connected); + +/* + * Curl_timeleft() returns the amount of milliseconds left allowed for the + * transfer/connection. If the value is negative, the timeout time has already + * elapsed. + * + * The start time is stored in progress.t_startsingle - as set with + * Curl_pgrsTime(..., TIMER_STARTSINGLE); + * + * If 'nowp' is non-NULL, it points to the current time. + * 'duringconnect' is FALSE if not during a connect, as then of course the + * connect timeout is not taken into account! + * + * @unittest: 1303 + */ +long Curl_timeleft(struct SessionHandle *data, + struct timeval *nowp, + bool duringconnect) +{ + int timeout_set = 0; + long timeout_ms = duringconnect?DEFAULT_CONNECT_TIMEOUT:0; + struct timeval now; + + /* if a timeout is set, use the most restrictive one */ + + if(data->set.timeout > 0) + timeout_set |= 1; + if(duringconnect && (data->set.connecttimeout > 0)) + timeout_set |= 2; + + switch (timeout_set) { + case 1: + timeout_ms = data->set.timeout; + break; + case 2: + timeout_ms = data->set.connecttimeout; + break; + case 3: + if(data->set.timeout < data->set.connecttimeout) + timeout_ms = data->set.timeout; + else + timeout_ms = data->set.connecttimeout; + break; + default: + /* use the default */ + if(!duringconnect) + /* if we're not during connect, there's no default timeout so if we're + at zero we better just return zero and not make it a negative number + by the math below */ + return 0; + break; + } + + if(!nowp) { + now = Curl_tvnow(); + nowp = &now; + } + + /* subtract elapsed time */ + timeout_ms -= Curl_tvdiff(*nowp, data->progress.t_startsingle); + if(!timeout_ms) + /* avoid returning 0 as that means no timeout! */ + return -1; + + return timeout_ms; +} + +/* + * waitconnect() waits for a TCP connect on the given socket for the specified + * number if milliseconds. It returns: + */ + +#define WAITCONN_CONNECTED 0 +#define WAITCONN_SELECT_ERROR -1 +#define WAITCONN_TIMEOUT 1 +#define WAITCONN_FDSET_ERROR 2 +#define WAITCONN_ABORTED 3 + +static +int waitconnect(struct connectdata *conn, + curl_socket_t sockfd, /* socket */ + long timeout_msec) +{ + int rc; +#ifdef mpeix + /* Call this function once now, and ignore the results. We do this to + "clear" the error state on the socket so that we can later read it + reliably. This is reported necessary on the MPE/iX operating system. */ + (void)verifyconnect(sockfd, NULL); +#endif + + for(;;) { + + /* now select() until we get connect or timeout */ + rc = Curl_socket_ready(CURL_SOCKET_BAD, sockfd, timeout_msec>1000? + 1000:timeout_msec); + if(Curl_pgrsUpdate(conn)) + return WAITCONN_ABORTED; + + if(-1 == rc) + /* error, no connect here, try next */ + return WAITCONN_SELECT_ERROR; + + else if(0 == rc) { + /* timeout */ + timeout_msec -= 1000; + if(timeout_msec <= 0) + return WAITCONN_TIMEOUT; + + continue; + } + + if(rc & CURL_CSELECT_ERR) + /* error condition caught */ + return WAITCONN_FDSET_ERROR; + + break; + } + return WAITCONN_CONNECTED; +} + +static CURLcode bindlocal(struct connectdata *conn, + curl_socket_t sockfd, int af) +{ + struct SessionHandle *data = conn->data; + + struct Curl_sockaddr_storage sa; + struct sockaddr *sock = (struct sockaddr *)&sa; /* bind to this address */ + curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */ + struct sockaddr_in *si4 = (struct sockaddr_in *)&sa; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa; +#endif + + struct Curl_dns_entry *h=NULL; + unsigned short port = data->set.localport; /* use this port number, 0 for + "random" */ + /* how many port numbers to try to bind to, increasing one at a time */ + int portnum = data->set.localportrange; + const char *dev = data->set.str[STRING_DEVICE]; + int error; + char myhost[256] = ""; + int done = 0; /* -1 for error, 1 for address found */ + bool is_interface = FALSE; + bool is_host = FALSE; + static const char *if_prefix = "if!"; + static const char *host_prefix = "host!"; + + /************************************************************* + * Select device to bind socket to + *************************************************************/ + if(!dev && !port) + /* no local kind of binding was requested */ + return CURLE_OK; + + memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); + + if(dev && (strlen(dev)<255) ) { + if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) { + dev += strlen(if_prefix); + is_interface = TRUE; + } + else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) { + dev += strlen(host_prefix); + is_host = TRUE; + } + + /* interface */ + if(!is_host && (is_interface || Curl_if_is_interface_name(dev))) { + if(Curl_if2ip(af, dev, myhost, sizeof(myhost)) == NULL) + return CURLE_INTERFACE_FAILED; + + /* + * We now have the numerical IP address in the 'myhost' buffer + */ + infof(data, "Local Interface %s is ip %s using address family %i\n", + dev, myhost, af); + done = 1; + +#ifdef SO_BINDTODEVICE + /* I am not sure any other OSs than Linux that provide this feature, and + * at the least I cannot test. --Ben + * + * This feature allows one to tightly bind the local socket to a + * particular interface. This will force even requests to other local + * interfaces to go out the external interface. + * + * + * Only bind to the interface when specified as interface, not just as a + * hostname or ip address. + */ + if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, + dev, (curl_socklen_t)strlen(dev)+1) != 0) { + error = SOCKERRNO; + infof(data, "SO_BINDTODEVICE %s failed with errno %d: %s;" + " will do regular bind\n", + dev, error, Curl_strerror(conn, error)); + /* This is typically "errno 1, error: Operation not permitted" if + you're not running as root or another suitable privileged user */ + } +#endif + } + else { + /* + * This was not an interface, resolve the name as a host name + * or IP number + * + * Temporarily force name resolution to use only the address type + * of the connection. The resolve functions should really be changed + * to take a type parameter instead. + */ + long ipver = conn->ip_version; + int rc; + + if(af == AF_INET) + conn->ip_version = CURL_IPRESOLVE_V4; +#ifdef ENABLE_IPV6 + else if(af == AF_INET6) + conn->ip_version = CURL_IPRESOLVE_V6; +#endif + + rc = Curl_resolv(conn, dev, 0, &h); + if(rc == CURLRESOLV_PENDING) + (void)Curl_resolver_wait_resolv(conn, &h); + conn->ip_version = ipver; + + if(h) { + /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */ + Curl_printable_address(h->addr, myhost, sizeof(myhost)); + infof(data, "Name '%s' family %i resolved to '%s' family %i\n", + dev, af, myhost, h->addr->ai_family); + Curl_resolv_unlock(data, h); + done = 1; + } + else { + /* + * provided dev was no interface (or interfaces are not supported + * e.g. solaris) no ip address and no domain we fail here + */ + done = -1; + } + } + + if(done > 0) { +#ifdef ENABLE_IPV6 + /* ipv6 address */ + if((af == AF_INET6) && + (Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0)) { + si6->sin6_family = AF_INET6; + si6->sin6_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in6); + } + else +#endif + /* ipv4 address */ + if((af == AF_INET) && + (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) { + si4->sin_family = AF_INET; + si4->sin_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in); + } + } + + if(done < 1) { + failf(data, "Couldn't bind to '%s'", dev); + return CURLE_INTERFACE_FAILED; + } + } + else { + /* no device was given, prepare sa to match af's needs */ +#ifdef ENABLE_IPV6 + if(af == AF_INET6) { + si6->sin6_family = AF_INET6; + si6->sin6_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in6); + } + else +#endif + if(af == AF_INET) { + si4->sin_family = AF_INET; + si4->sin_port = htons(port); + sizeof_sa = sizeof(struct sockaddr_in); + } + } + + for(;;) { + if(bind(sockfd, sock, sizeof_sa) >= 0) { + /* we succeeded to bind */ + struct Curl_sockaddr_storage add; + curl_socklen_t size = sizeof(add); + memset(&add, 0, sizeof(struct Curl_sockaddr_storage)); + if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) { + data->state.os_errno = error = SOCKERRNO; + failf(data, "getsockname() failed with errno %d: %s", + error, Curl_strerror(conn, error)); + return CURLE_INTERFACE_FAILED; + } + infof(data, "Local port: %hu\n", port); + conn->bits.bound = TRUE; + return CURLE_OK; + } + + if(--portnum > 0) { + infof(data, "Bind to local port %hu failed, trying next\n", port); + port++; /* try next port */ + /* We re-use/clobber the port variable here below */ + if(sock->sa_family == AF_INET) + si4->sin_port = ntohs(port); +#ifdef ENABLE_IPV6 + else + si6->sin6_port = ntohs(port); +#endif + } + else + break; + } + + data->state.os_errno = error = SOCKERRNO; + failf(data, "bind failed with errno %d: %s", + error, Curl_strerror(conn, error)); + + return CURLE_INTERFACE_FAILED; +} + +/* + * verifyconnect() returns TRUE if the connect really has happened. + */ +static bool verifyconnect(curl_socket_t sockfd, int *error) +{ + bool rc = TRUE; +#ifdef SO_ERROR + int err = 0; + curl_socklen_t errSize = sizeof(err); + +#ifdef WIN32 + /* + * In October 2003 we effectively nullified this function on Windows due to + * problems with it using all CPU in multi-threaded cases. + * + * In May 2004, we bring it back to offer more info back on connect failures. + * Gisle Vanem could reproduce the former problems with this function, but + * could avoid them by adding this SleepEx() call below: + * + * "I don't have Rational Quantify, but the hint from his post was + * ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe + * just Sleep(0) would be enough?) would release whatever + * mutex/critical-section the ntdll call is waiting on. + * + * Someone got to verify this on Win-NT 4.0, 2000." + */ + +#ifdef _WIN32_WCE + Sleep(0); +#else + SleepEx(0, FALSE); +#endif + +#endif + + if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize)) + err = SOCKERRNO; +#ifdef _WIN32_WCE + /* Old WinCE versions don't support SO_ERROR */ + if(WSAENOPROTOOPT == err) { + SET_SOCKERRNO(0); + err = 0; + } +#endif +#ifdef __minix + /* Minix 3.1.x doesn't support getsockopt on UDP sockets */ + if(EBADIOCTL == err) { + SET_SOCKERRNO(0); + err = 0; + } +#endif + if((0 == err) || (EISCONN == err)) + /* we are connected, awesome! */ + rc = TRUE; + else + /* This wasn't a successful connect */ + rc = FALSE; + if(error) + *error = err; +#else + (void)sockfd; + if(error) + *error = SOCKERRNO; +#endif + return rc; +} + +/* Used within the multi interface. Try next IP address, return TRUE if no + more address exists or error */ +static CURLcode trynextip(struct connectdata *conn, + int sockindex, + bool *connected) +{ + curl_socket_t sockfd; + Curl_addrinfo *ai; + + /* First clean up after the failed socket. + Don't close it yet to ensure that the next IP's socket gets a different + file descriptor, which can prevent bugs when the curl_multi_socket_action + interface is used with certain select() replacements such as kqueue. */ + curl_socket_t fd_to_close = conn->sock[sockindex]; + conn->sock[sockindex] = CURL_SOCKET_BAD; + *connected = FALSE; + + if(sockindex != FIRSTSOCKET) { + Curl_closesocket(conn, fd_to_close); + return CURLE_COULDNT_CONNECT; /* no next */ + } + + /* try the next address */ + ai = conn->ip_addr->ai_next; + + while(ai) { + CURLcode res = singleipconnect(conn, ai, 0L, &sockfd, connected); + if(res) + return res; + if(sockfd != CURL_SOCKET_BAD) { + /* store the new socket descriptor */ + conn->sock[sockindex] = sockfd; + conn->ip_addr = ai; + Curl_closesocket(conn, fd_to_close); + return CURLE_OK; + } + ai = ai->ai_next; + } + Curl_closesocket(conn, fd_to_close); + return CURLE_COULDNT_CONNECT; +} + +/* Copies connection info into the session handle to make it available + when the session handle is no longer associated with a connection. */ +void Curl_persistconninfo(struct connectdata *conn) +{ + memcpy(conn->data->info.conn_primary_ip, conn->primary_ip, MAX_IPADR_LEN); + memcpy(conn->data->info.conn_local_ip, conn->local_ip, MAX_IPADR_LEN); + conn->data->info.conn_primary_port = conn->primary_port; + conn->data->info.conn_local_port = conn->local_port; +} + +/* retrieves ip address and port from a sockaddr structure */ +static bool getaddressinfo(struct sockaddr* sa, char* addr, + long* port) +{ + unsigned short us_port; + struct sockaddr_in* si = NULL; +#ifdef ENABLE_IPV6 + struct sockaddr_in6* si6 = NULL; +#endif +#if defined(HAVE_SYS_UN_H) && defined(AF_UNIX) + struct sockaddr_un* su = NULL; +#endif + + switch (sa->sa_family) { + case AF_INET: + si = (struct sockaddr_in*) sa; + if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, + addr, MAX_IPADR_LEN)) { + us_port = ntohs(si->sin_port); + *port = us_port; + return TRUE; + } + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + si6 = (struct sockaddr_in6*)sa; + if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, + addr, MAX_IPADR_LEN)) { + us_port = ntohs(si6->sin6_port); + *port = us_port; + return TRUE; + } + break; +#endif +#if defined(HAVE_SYS_UN_H) && defined(AF_UNIX) + case AF_UNIX: + su = (struct sockaddr_un*)sa; + snprintf(addr, MAX_IPADR_LEN, "%s", su->sun_path); + *port = 0; + return TRUE; +#endif + default: + break; + } + + addr[0] = '\0'; + *port = 0; + + return FALSE; +} + +/* retrieves the start/end point information of a socket of an established + connection */ +void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd) +{ + int error; + curl_socklen_t len; + struct Curl_sockaddr_storage ssrem; + struct Curl_sockaddr_storage ssloc; + struct SessionHandle *data = conn->data; + + if(!conn->bits.reuse) { + + len = sizeof(struct Curl_sockaddr_storage); + if(getpeername(sockfd, (struct sockaddr*) &ssrem, &len)) { + error = SOCKERRNO; + failf(data, "getpeername() failed with errno %d: %s", + error, Curl_strerror(conn, error)); + return; + } + + len = sizeof(struct Curl_sockaddr_storage); + if(getsockname(sockfd, (struct sockaddr*) &ssloc, &len)) { + error = SOCKERRNO; + failf(data, "getsockname() failed with errno %d: %s", + error, Curl_strerror(conn, error)); + return; + } + + if(!getaddressinfo((struct sockaddr*)&ssrem, + conn->primary_ip, &conn->primary_port)) { + error = ERRNO; + failf(data, "ssrem inet_ntop() failed with errno %d: %s", + error, Curl_strerror(conn, error)); + return; + } + + if(!getaddressinfo((struct sockaddr*)&ssloc, + conn->local_ip, &conn->local_port)) { + error = ERRNO; + failf(data, "ssloc inet_ntop() failed with errno %d: %s", + error, Curl_strerror(conn, error)); + return; + } + + } + + /* persist connection info in session handle */ + Curl_persistconninfo(conn); +} + +/* + * Curl_is_connected() is used from the multi interface to check if the + * firstsocket has connected. + */ + +CURLcode Curl_is_connected(struct connectdata *conn, + int sockindex, + bool *connected) +{ + int rc; + struct SessionHandle *data = conn->data; + CURLcode code = CURLE_OK; + curl_socket_t sockfd = conn->sock[sockindex]; + long allow = DEFAULT_CONNECT_TIMEOUT; + int error = 0; + struct timeval now; + + DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); + + *connected = FALSE; /* a very negative world view is best */ + + if(conn->bits.tcpconnect[sockindex]) { + /* we are connected already! */ + *connected = TRUE; + return CURLE_OK; + } + + now = Curl_tvnow(); + + /* figure out how long time we have left to connect */ + allow = Curl_timeleft(data, &now, TRUE); + + if(allow < 0) { + /* time-out, bail out, go home */ + failf(data, "Connection time-out"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* check for connect without timeout as we want to return immediately */ + rc = waitconnect(conn, sockfd, 0); + if(WAITCONN_TIMEOUT == rc) { + if(curlx_tvdiff(now, conn->connecttime) >= conn->timeoutms_per_addr) { + infof(data, "After %ldms connect time, move on!\n", + conn->timeoutms_per_addr); + goto next; + } + + /* not an error, but also no connection yet */ + return code; + } + + if(WAITCONN_CONNECTED == rc) { + if(verifyconnect(sockfd, &error)) { + /* we are connected with TCP, awesome! */ + + /* see if we need to do any proxy magic first once we connected */ + code = Curl_connected_proxy(conn); + if(code) + return code; + + conn->bits.tcpconnect[sockindex] = TRUE; + *connected = TRUE; + if(sockindex == FIRSTSOCKET) + Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ + Curl_verboseconnect(conn); + Curl_updateconninfo(conn, sockfd); + + return CURLE_OK; + } + /* nope, not connected for real */ + } + else { + /* nope, not connected */ + if(WAITCONN_FDSET_ERROR == rc) { + (void)verifyconnect(sockfd, &error); + infof(data, "%s\n",Curl_strerror(conn, error)); + } + else + infof(data, "Connection failed\n"); + } + + /* + * The connection failed here, we should attempt to connect to the "next + * address" for the given host. But first remember the latest error. + */ + if(error) { + data->state.os_errno = error; + SET_SOCKERRNO(error); + } + next: + + conn->timeoutms_per_addr = conn->ip_addr->ai_next == NULL ? + allow : allow / 2; + code = trynextip(conn, sockindex, connected); + + if(code) { + error = SOCKERRNO; + data->state.os_errno = error; + failf(data, "Failed connect to %s:%ld; %s", + conn->host.name, conn->port, Curl_strerror(conn, error)); + } + + return code; +} + +static void tcpnodelay(struct connectdata *conn, + curl_socket_t sockfd) +{ +#ifdef TCP_NODELAY + struct SessionHandle *data= conn->data; + curl_socklen_t onoff = (curl_socklen_t) data->set.tcp_nodelay; + int level = IPPROTO_TCP; + +#if 0 + /* The use of getprotobyname() is disabled since it isn't thread-safe on + numerous systems. On these getprotobyname_r() should be used instead, but + that exists in at least one 4 arg version and one 5 arg version, and + since the proto number rarely changes anyway we now just use the hard + coded number. The "proper" fix would need a configure check for the + correct function much in the same style the gethostbyname_r versions are + detected. */ + struct protoent *pe = getprotobyname("tcp"); + if(pe) + level = pe->p_proto; +#endif + + if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, + sizeof(onoff)) < 0) + infof(data, "Could not set TCP_NODELAY: %s\n", + Curl_strerror(conn, SOCKERRNO)); + else + infof(data,"TCP_NODELAY set\n"); +#else + (void)conn; + (void)sockfd; +#endif +} + +#ifdef SO_NOSIGPIPE +/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when + sending data to a dead peer (instead of relying on the 4th argument to send + being MSG_NOSIGNAL). Possibly also existing and in use on other BSD + systems? */ +static void nosigpipe(struct connectdata *conn, + curl_socket_t sockfd) +{ + struct SessionHandle *data= conn->data; + int onoff = 1; + if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, + sizeof(onoff)) < 0) + infof(data, "Could not set SO_NOSIGPIPE: %s\n", + Curl_strerror(conn, SOCKERRNO)); +} +#else +#define nosigpipe(x,y) Curl_nop_stmt +#endif + +#ifdef USE_WINSOCK +/* When you run a program that uses the Windows Sockets API, you may + experience slow performance when you copy data to a TCP server. + + http://support.microsoft.com/kb/823764 + + Work-around: Make the Socket Send Buffer Size Larger Than the Program Send + Buffer Size + +*/ +void Curl_sndbufset(curl_socket_t sockfd) +{ + int val = CURL_MAX_WRITE_SIZE + 32; + int curval = 0; + int curlen = sizeof(curval); + + if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0) + if(curval > val) + return; + + setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val)); +} +#endif + + +/* + * singleipconnect() + * + * Note that even on connect fail it returns CURLE_OK, but with 'sock' set to + * CURL_SOCKET_BAD. Other errors will however return proper errors. + * + * singleipconnect() connects to the given IP only, and it may return without + * having connected if used from the multi interface. + */ +static CURLcode +singleipconnect(struct connectdata *conn, + const Curl_addrinfo *ai, + long timeout_ms, + curl_socket_t *sockp, + bool *connected) +{ + struct Curl_sockaddr_ex addr; + int rc; + int error = 0; + bool isconnected = FALSE; + struct SessionHandle *data = conn->data; + curl_socket_t sockfd; + CURLcode res = CURLE_OK; + + *sockp = CURL_SOCKET_BAD; + *connected = FALSE; /* default is not connected */ + + res = Curl_socket(conn, ai, &addr, &sockfd); + if(res) + /* Failed to create the socket, but still return OK since we signal the + lack of socket as well. This allows the parent function to keep looping + over alternative addresses/socket families etc. */ + return CURLE_OK; + + /* store remote address and port used in this connection attempt */ + if(!getaddressinfo((struct sockaddr*)&addr.sa_addr, + conn->primary_ip, &conn->primary_port)) { + /* malformed address or bug in inet_ntop, try next address */ + error = ERRNO; + failf(data, "sa_addr inet_ntop() failed with errno %d: %s", + error, Curl_strerror(conn, error)); + Curl_closesocket(conn, sockfd); + return CURLE_OK; + } + memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN); + infof(data, " Trying %s...\n", conn->ip_addr_str); + + Curl_persistconninfo(conn); + + if(data->set.tcp_nodelay) + tcpnodelay(conn, sockfd); + + nosigpipe(conn, sockfd); + + Curl_sndbufset(sockfd); + + if(data->set.tcp_keepalive) + tcpkeepalive(data, sockfd); + + if(data->set.fsockopt) { + /* activate callback for setting socket options */ + error = data->set.fsockopt(data->set.sockopt_client, + sockfd, + CURLSOCKTYPE_IPCXN); + + if(error == CURL_SOCKOPT_ALREADY_CONNECTED) + isconnected = TRUE; + else if(error) { + Curl_closesocket(conn, sockfd); /* close the socket and bail out */ + return CURLE_ABORTED_BY_CALLBACK; + } + } + + /* possibly bind the local end to an IP, interface or port */ + res = bindlocal(conn, sockfd, addr.family); + if(res) { + Curl_closesocket(conn, sockfd); /* close socket and bail out */ + return res; + } + + /* set socket non-blocking */ + curlx_nonblock(sockfd, TRUE); + + /* Connect TCP sockets, bind UDP */ + if(!isconnected && (conn->socktype == SOCK_STREAM)) { + rc = connect(sockfd, &addr.sa_addr, addr.addrlen); + if(-1 == rc) + error = SOCKERRNO; + conn->connecttime = Curl_tvnow(); + if(conn->num_addr > 1) + Curl_expire(data, conn->timeoutms_per_addr); + } + else + rc = 0; + + if(-1 == rc) { + switch (error) { + case EINPROGRESS: + case EWOULDBLOCK: +#if defined(EAGAIN) +#if (EAGAIN) != (EWOULDBLOCK) + /* On some platforms EAGAIN and EWOULDBLOCK are the + * same value, and on others they are different, hence + * the odd #if + */ + case EAGAIN: +#endif +#endif + rc = waitconnect(conn, sockfd, timeout_ms); + if(WAITCONN_ABORTED == rc) { + Curl_closesocket(conn, sockfd); + return CURLE_ABORTED_BY_CALLBACK; + } + break; + default: + /* unknown error, fallthrough and try another address! */ + failf(data, "Failed to connect to %s: %s", + conn->ip_addr_str, Curl_strerror(conn,error)); + data->state.os_errno = error; + break; + } + } + + /* The 'WAITCONN_TIMEOUT == rc' comes from the waitconnect(), and not from + connect(). We can be sure of this since connect() cannot return 1. */ + if((WAITCONN_TIMEOUT == rc) && + (data->state.used_interface == Curl_if_multi)) { + /* Timeout when running the multi interface */ + *sockp = sockfd; + return CURLE_OK; + } + + if(!isconnected) + isconnected = verifyconnect(sockfd, &error); + + if(!rc && isconnected) { + /* we are connected, awesome! */ + *connected = TRUE; /* this is a true connect */ + infof(data, "connected\n"); +#ifdef ENABLE_IPV6 + conn->bits.ipv6 = (addr.family == AF_INET6)?TRUE:FALSE; +#endif + + Curl_updateconninfo(conn, sockfd); + *sockp = sockfd; + return CURLE_OK; + } + else if(WAITCONN_TIMEOUT == rc) + infof(data, "Timeout\n"); + else { + data->state.os_errno = error; + infof(data, "%s\n", Curl_strerror(conn, error)); + } + + /* connect failed or timed out */ + Curl_closesocket(conn, sockfd); + + return CURLE_OK; +} + +/* + * TCP connect to the given host with timeout, proxy or remote doesn't matter. + * There might be more than one IP address to try out. Fill in the passed + * pointer with the connected socket. + */ + +CURLcode Curl_connecthost(struct connectdata *conn, /* context */ + const struct Curl_dns_entry *remotehost, + curl_socket_t *sockconn, /* the connected socket */ + Curl_addrinfo **addr, /* the one we used */ + bool *connected) /* really connected? */ +{ + struct SessionHandle *data = conn->data; + curl_socket_t sockfd = CURL_SOCKET_BAD; + Curl_addrinfo *ai; + Curl_addrinfo *curr_addr; + + struct timeval after; + struct timeval before = Curl_tvnow(); + + /************************************************************* + * Figure out what maximum time we have left + *************************************************************/ + long timeout_ms; + + DEBUGASSERT(sockconn); + *connected = FALSE; /* default to not connected */ + + /* get the timeout left */ + timeout_ms = Curl_timeleft(data, &before, TRUE); + + if(timeout_ms < 0) { + /* a precaution, no need to continue if time already is up */ + failf(data, "Connection time-out"); + return CURLE_OPERATION_TIMEDOUT; + } + + conn->num_addr = Curl_num_addresses(remotehost->addr); + + ai = remotehost->addr; + + /* Below is the loop that attempts to connect to all IP-addresses we + * know for the given host. One by one until one IP succeeds. + */ + + /* + * Connecting with a Curl_addrinfo chain + */ + for(curr_addr = ai; curr_addr; curr_addr = curr_addr->ai_next) { + CURLcode res; + + /* Max time for the next address */ + conn->timeoutms_per_addr = curr_addr->ai_next == NULL ? + timeout_ms : timeout_ms / 2; + + /* start connecting to the IP curr_addr points to */ + res = singleipconnect(conn, curr_addr, + /* don't hang when doing multi */ + (data->state.used_interface == Curl_if_multi)?0: + conn->timeoutms_per_addr, &sockfd, connected); + if(res) + return res; + + if(sockfd != CURL_SOCKET_BAD) + break; + + /* get a new timeout for next attempt */ + after = Curl_tvnow(); + timeout_ms -= Curl_tvdiff(after, before); + if(timeout_ms < 0) { + failf(data, "connect() timed out!"); + return CURLE_OPERATION_TIMEDOUT; + } + before = after; + } /* end of connect-to-each-address loop */ + + *sockconn = sockfd; /* the socket descriptor we've connected */ + + if(sockfd == CURL_SOCKET_BAD) { + /* no good connect was made */ + failf(data, "couldn't connect to %s at %s:%d", + conn->bits.proxy?"proxy":"host", + conn->bits.proxy?conn->proxy.name:conn->host.name, conn->port); + return CURLE_COULDNT_CONNECT; + } + + /* leave the socket in non-blocking mode */ + + /* store the address we use */ + if(addr) + *addr = curr_addr; + + data->info.numconnects++; /* to track the number of connections made */ + + return CURLE_OK; +} + +/* + * Used to extract socket and connectdata struct for the most recent + * transfer on the given SessionHandle. + * + * The returned socket will be CURL_SOCKET_BAD in case of failure! + */ +curl_socket_t Curl_getconnectinfo(struct SessionHandle *data, + struct connectdata **connp) +{ + curl_socket_t sockfd; + + DEBUGASSERT(data); + + if(data->state.lastconnect) { + struct connectdata *c = data->state.lastconnect; + if(connp) + /* only store this if the caller cares for it */ + *connp = c; + sockfd = c->sock[FIRSTSOCKET]; + /* we have a socket connected, let's determine if the server shut down */ + /* determine if ssl */ + if(c->ssl[FIRSTSOCKET].use) { + /* use the SSL context */ + if(!Curl_ssl_check_cxn(c)) + return CURL_SOCKET_BAD; /* FIN received */ + } +/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */ +#ifdef MSG_PEEK + else { + /* use the socket */ + char buf; + if(recv((RECV_TYPE_ARG1)c->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf, + (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) { + return CURL_SOCKET_BAD; /* FIN received */ + } + } +#endif + } + else + return CURL_SOCKET_BAD; + + return sockfd; +} + +/* + * Close a socket. + * + * 'conn' can be NULL, beware! + */ +int Curl_closesocket(struct connectdata *conn, + curl_socket_t sock) +{ + if(conn && conn->fclosesocket) { + if((sock == conn->sock[SECONDARYSOCKET]) && + conn->sock_accepted[SECONDARYSOCKET]) + /* if this socket matches the second socket, and that was created with + accept, then we MUST NOT call the callback but clear the accepted + status */ + conn->sock_accepted[SECONDARYSOCKET] = FALSE; + else + return conn->fclosesocket(conn->closesocket_client, sock); + } + return sclose(sock); +} + +/* + * Create a socket based on info from 'conn' and 'ai'. + * + * 'addr' should be a pointer to the correct struct to get data back, or NULL. + * 'sockfd' must be a pointer to a socket descriptor. + * + * If the open socket callback is set, used that! + * + */ +CURLcode Curl_socket(struct connectdata *conn, + const Curl_addrinfo *ai, + struct Curl_sockaddr_ex *addr, + curl_socket_t *sockfd) +{ + struct SessionHandle *data = conn->data; + struct Curl_sockaddr_ex dummy; + + if(!addr) + /* if the caller doesn't want info back, use a local temp copy */ + addr = &dummy; + + /* + * The Curl_sockaddr_ex structure is basically libcurl's external API + * curl_sockaddr structure with enough space available to directly hold + * any protocol-specific address structures. The variable declared here + * will be used to pass / receive data to/from the fopensocket callback + * if this has been set, before that, it is initialized from parameters. + */ + + addr->family = ai->ai_family; + addr->socktype = conn->socktype; + addr->protocol = conn->socktype==SOCK_DGRAM?IPPROTO_UDP:ai->ai_protocol; + addr->addrlen = ai->ai_addrlen; + + if(addr->addrlen > sizeof(struct Curl_sockaddr_storage)) + addr->addrlen = sizeof(struct Curl_sockaddr_storage); + memcpy(&addr->sa_addr, ai->ai_addr, addr->addrlen); + + if(data->set.fopensocket) + /* + * If the opensocket callback is set, all the destination address + * information is passed to the callback. Depending on this information the + * callback may opt to abort the connection, this is indicated returning + * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When + * the callback returns a valid socket the destination address information + * might have been changed and this 'new' address will actually be used + * here to connect. + */ + *sockfd = data->set.fopensocket(data->set.opensocket_client, + CURLSOCKTYPE_IPCXN, + (struct curl_sockaddr *)addr); + else + /* opensocket callback not set, so simply create the socket now */ + *sockfd = socket(addr->family, addr->socktype, addr->protocol); + + if(*sockfd == CURL_SOCKET_BAD) + /* no socket, no connection */ + return CURLE_COULDNT_CONNECT; + +#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + if(conn->scope && (addr->family == AF_INET6)) { + struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; + sa6->sin6_scope_id = conn->scope; + } +#endif + + return CURLE_OK; + +} diff --git a/lib/curl_content_encoding.c b/lib/curl_content_encoding.c new file mode 100644 index 000000000..6f4d1428a --- /dev/null +++ b/lib/curl_content_encoding.c @@ -0,0 +1,435 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_LIBZ + +#include "curl_urldata.h" +#include +#include "curl_sendf.h" +#include "curl_content_encoding.h" +#include "curl_memory.h" + +#include "curl_memdebug.h" + +/* Comment this out if zlib is always going to be at least ver. 1.2.0.4 + (doing so will reduce code size slightly). */ +#define OLD_ZLIB_SUPPORT 1 + +#define DSIZ CURL_MAX_WRITE_SIZE /* buffer size for decompressed data */ + +#define GZIP_MAGIC_0 0x1f +#define GZIP_MAGIC_1 0x8b + +/* gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xE0 /* bits 5..7: reserved */ + +static voidpf +zalloc_cb(voidpf opaque, unsigned int items, unsigned int size) +{ + (void) opaque; + /* not a typo, keep it calloc() */ + return (voidpf) calloc(items, size); +} + +static void +zfree_cb(voidpf opaque, voidpf ptr) +{ + (void) opaque; + free(ptr); +} + +static CURLcode +process_zlib_error(struct connectdata *conn, z_stream *z) +{ + struct SessionHandle *data = conn->data; + if(z->msg) + failf (data, "Error while processing content unencoding: %s", + z->msg); + else + failf (data, "Error while processing content unencoding: " + "Unknown failure within decompression software."); + + return CURLE_BAD_CONTENT_ENCODING; +} + +static CURLcode +exit_zlib(z_stream *z, zlibInitState *zlib_init, CURLcode result) +{ + inflateEnd(z); + *zlib_init = ZLIB_UNINIT; + return result; +} + +static CURLcode +inflate_stream(struct connectdata *conn, + struct SingleRequest *k) +{ + int allow_restart = 1; + z_stream *z = &k->z; /* zlib state structure */ + uInt nread = z->avail_in; + Bytef *orig_in = z->next_in; + int status; /* zlib status */ + CURLcode result = CURLE_OK; /* Curl_client_write status */ + char *decomp; /* Put the decompressed data here. */ + + /* Dynamically allocate a buffer for decompression because it's uncommonly + large to hold on the stack */ + decomp = malloc(DSIZ); + if(decomp == NULL) { + return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); + } + + /* because the buffer size is fixed, iteratively decompress and transfer to + the client via client_write. */ + for(;;) { + /* (re)set buffer for decompressed output for every iteration */ + z->next_out = (Bytef *)decomp; + z->avail_out = DSIZ; + + status = inflate(z, Z_SYNC_FLUSH); + if(status == Z_OK || status == Z_STREAM_END) { + allow_restart = 0; + if((DSIZ - z->avail_out) && (!k->ignorebody)) { + result = Curl_client_write(conn, CLIENTWRITE_BODY, decomp, + DSIZ - z->avail_out); + /* if !CURLE_OK, clean up, return */ + if(result) { + free(decomp); + return exit_zlib(z, &k->zlib_init, result); + } + } + + /* Done? clean up, return */ + if(status == Z_STREAM_END) { + free(decomp); + if(inflateEnd(z) == Z_OK) + return exit_zlib(z, &k->zlib_init, result); + else + return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + } + + /* Done with these bytes, exit */ + + /* status is always Z_OK at this point! */ + if(z->avail_in == 0) { + free(decomp); + return result; + } + } + else if(allow_restart && status == Z_DATA_ERROR) { + /* some servers seem to not generate zlib headers, so this is an attempt + to fix and continue anyway */ + + (void) inflateEnd(z); /* don't care about the return code */ + if(inflateInit2(z, -MAX_WBITS) != Z_OK) { + free(decomp); + return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + } + z->next_in = orig_in; + z->avail_in = nread; + allow_restart = 0; + continue; + } + else { /* Error; exit loop, handle below */ + free(decomp); + return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + } + } + /* Will never get here */ +} + +CURLcode +Curl_unencode_deflate_write(struct connectdata *conn, + struct SingleRequest *k, + ssize_t nread) +{ + z_stream *z = &k->z; /* zlib state structure */ + + /* Initialize zlib? */ + if(k->zlib_init == ZLIB_UNINIT) { + memset(z, 0, sizeof(z_stream)); + z->zalloc = (alloc_func)zalloc_cb; + z->zfree = (free_func)zfree_cb; + + if(inflateInit(z) != Z_OK) + return process_zlib_error(conn, z); + k->zlib_init = ZLIB_INIT; + } + + /* Set the compressed input when this function is called */ + z->next_in = (Bytef *)k->str; + z->avail_in = (uInt)nread; + + /* Now uncompress the data */ + return inflate_stream(conn, k); +} + +#ifdef OLD_ZLIB_SUPPORT +/* Skip over the gzip header */ +static enum { + GZIP_OK, + GZIP_BAD, + GZIP_UNDERFLOW +} check_gzip_header(unsigned char const *data, ssize_t len, ssize_t *headerlen) +{ + int method, flags; + const ssize_t totallen = len; + + /* The shortest header is 10 bytes */ + if(len < 10) + return GZIP_UNDERFLOW; + + if((data[0] != GZIP_MAGIC_0) || (data[1] != GZIP_MAGIC_1)) + return GZIP_BAD; + + method = data[2]; + flags = data[3]; + + if(method != Z_DEFLATED || (flags & RESERVED) != 0) { + /* Can't handle this compression method or unknown flag */ + return GZIP_BAD; + } + + /* Skip over time, xflags, OS code and all previous bytes */ + len -= 10; + data += 10; + + if(flags & EXTRA_FIELD) { + ssize_t extra_len; + + if(len < 2) + return GZIP_UNDERFLOW; + + extra_len = (data[1] << 8) | data[0]; + + if(len < (extra_len+2)) + return GZIP_UNDERFLOW; + + len -= (extra_len + 2); + data += (extra_len + 2); + } + + if(flags & ORIG_NAME) { + /* Skip over NUL-terminated file name */ + while(len && *data) { + --len; + ++data; + } + if(!len || *data) + return GZIP_UNDERFLOW; + + /* Skip over the NUL */ + --len; + ++data; + } + + if(flags & COMMENT) { + /* Skip over NUL-terminated comment */ + while(len && *data) { + --len; + ++data; + } + if(!len || *data) + return GZIP_UNDERFLOW; + + /* Skip over the NUL */ + --len; + } + + if(flags & HEAD_CRC) { + if(len < 2) + return GZIP_UNDERFLOW; + + len -= 2; + } + + *headerlen = totallen - len; + return GZIP_OK; +} +#endif + +CURLcode +Curl_unencode_gzip_write(struct connectdata *conn, + struct SingleRequest *k, + ssize_t nread) +{ + z_stream *z = &k->z; /* zlib state structure */ + + /* Initialize zlib? */ + if(k->zlib_init == ZLIB_UNINIT) { + memset(z, 0, sizeof(z_stream)); + z->zalloc = (alloc_func)zalloc_cb; + z->zfree = (free_func)zfree_cb; + + if(strcmp(zlibVersion(), "1.2.0.4") >= 0) { + /* zlib ver. >= 1.2.0.4 supports transparent gzip decompressing */ + if(inflateInit2(z, MAX_WBITS+32) != Z_OK) { + return process_zlib_error(conn, z); + } + k->zlib_init = ZLIB_INIT_GZIP; /* Transparent gzip decompress state */ + } + else { + /* we must parse the gzip header ourselves */ + if(inflateInit2(z, -MAX_WBITS) != Z_OK) { + return process_zlib_error(conn, z); + } + k->zlib_init = ZLIB_INIT; /* Initial call state */ + } + } + + if(k->zlib_init == ZLIB_INIT_GZIP) { + /* Let zlib handle the gzip decompression entirely */ + z->next_in = (Bytef *)k->str; + z->avail_in = (uInt)nread; + /* Now uncompress the data */ + return inflate_stream(conn, k); + } + +#ifndef OLD_ZLIB_SUPPORT + /* Support for old zlib versions is compiled away and we are running with + an old version, so return an error. */ + return exit_zlib(z, &k->zlib_init, CURLE_FUNCTION_NOT_FOUND); + +#else + /* This next mess is to get around the potential case where there isn't + * enough data passed in to skip over the gzip header. If that happens, we + * malloc a block and copy what we have then wait for the next call. If + * there still isn't enough (this is definitely a worst-case scenario), we + * make the block bigger, copy the next part in and keep waiting. + * + * This is only required with zlib versions < 1.2.0.4 as newer versions + * can handle the gzip header themselves. + */ + + switch (k->zlib_init) { + /* Skip over gzip header? */ + case ZLIB_INIT: + { + /* Initial call state */ + ssize_t hlen; + + switch (check_gzip_header((unsigned char *)k->str, nread, &hlen)) { + case GZIP_OK: + z->next_in = (Bytef *)k->str + hlen; + z->avail_in = (uInt)(nread - hlen); + k->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ + break; + + case GZIP_UNDERFLOW: + /* We need more data so we can find the end of the gzip header. It's + * possible that the memory block we malloc here will never be freed if + * the transfer abruptly aborts after this point. Since it's unlikely + * that circumstances will be right for this code path to be followed in + * the first place, and it's even more unlikely for a transfer to fail + * immediately afterwards, it should seldom be a problem. + */ + z->avail_in = (uInt)nread; + z->next_in = malloc(z->avail_in); + if(z->next_in == NULL) { + return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); + } + memcpy(z->next_in, k->str, z->avail_in); + k->zlib_init = ZLIB_GZIP_HEADER; /* Need more gzip header data state */ + /* We don't have any data to inflate yet */ + return CURLE_OK; + + case GZIP_BAD: + default: + return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + } + + } + break; + + case ZLIB_GZIP_HEADER: + { + /* Need more gzip header data state */ + ssize_t hlen; + unsigned char *oldblock = z->next_in; + + z->avail_in += (uInt)nread; + z->next_in = realloc(z->next_in, z->avail_in); + if(z->next_in == NULL) { + free(oldblock); + return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); + } + /* Append the new block of data to the previous one */ + memcpy(z->next_in + z->avail_in - nread, k->str, nread); + + switch (check_gzip_header(z->next_in, z->avail_in, &hlen)) { + case GZIP_OK: + /* This is the zlib stream data */ + free(z->next_in); + /* Don't point into the malloced block since we just freed it */ + z->next_in = (Bytef *)k->str + hlen + nread - z->avail_in; + z->avail_in = (uInt)(z->avail_in - hlen); + k->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ + break; + + case GZIP_UNDERFLOW: + /* We still don't have any data to inflate! */ + return CURLE_OK; + + case GZIP_BAD: + default: + free(z->next_in); + return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + } + + } + break; + + case ZLIB_GZIP_INFLATING: + default: + /* Inflating stream state */ + z->next_in = (Bytef *)k->str; + z->avail_in = (uInt)nread; + break; + } + + if(z->avail_in == 0) { + /* We don't have any data to inflate; wait until next time */ + return CURLE_OK; + } + + /* We've parsed the header, now uncompress the data */ + return inflate_stream(conn, k); +#endif +} + +void Curl_unencode_cleanup(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct SingleRequest *k = &data->req; + z_stream *z = &k->z; + if(k->zlib_init != ZLIB_UNINIT) + (void) exit_zlib(z, &k->zlib_init, CURLE_OK); +} + +#endif /* HAVE_LIBZ */ diff --git a/lib/curl_cookie.c b/lib/curl_cookie.c new file mode 100644 index 000000000..90ee884bb --- /dev/null +++ b/lib/curl_cookie.c @@ -0,0 +1,1163 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +/*** + + +RECEIVING COOKIE INFORMATION +============================ + +struct CookieInfo *cookie_init(char *file); + + Inits a cookie struct to store data in a local file. This is always + called before any cookies are set. + +int cookies_set(struct CookieInfo *cookie, char *cookie_line); + + The 'cookie_line' parameter is a full "Set-cookie:" line as + received from a server. + + The function need to replace previously stored lines that this new + line superceeds. + + It may remove lines that are expired. + + It should return an indication of success/error. + + +SENDING COOKIE INFORMATION +========================== + +struct Cookies *cookie_getlist(struct CookieInfo *cookie, + char *host, char *path, bool secure); + + For a given host and path, return a linked list of cookies that + the client should send to the server if used now. The secure + boolean informs the cookie if a secure connection is achieved or + not. + + It shall only return cookies that haven't expired. + + +Example set of cookies: + + Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure + Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/ftgw; secure + Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/; secure + Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/; secure + Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/; secure + Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT; + domain=.fidelity.com; path=/; secure + Set-cookie: + Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday, + 13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure +****/ + + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + +#define _MPRINTF_REPLACE +#include + +#include "curl_urldata.h" +#include "curl_cookie.h" +#include "curl_strequal.h" +#include "curl_strtok.h" +#include "curl_sendf.h" +#include "curl_memory.h" +#include "curl_share.h" +#include "curl_strtoofft.h" +#include "curl_rawstr.h" +#include "curl_memrchr.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +static void freecookie(struct Cookie *co) +{ + if(co->expirestr) + free(co->expirestr); + if(co->domain) + free(co->domain); + if(co->path) + free(co->path); + if(co->name) + free(co->name); + if(co->value) + free(co->value); + if(co->maxage) + free(co->maxage); + if(co->version) + free(co->version); + + free(co); +} + +static bool tailmatch(const char *little, const char *bigone) +{ + size_t littlelen = strlen(little); + size_t biglen = strlen(bigone); + + if(littlelen > biglen) + return FALSE; + + return Curl_raw_equal(little, bigone+biglen-littlelen) ? TRUE : FALSE; +} + +/* + * Load cookies from all given cookie files (CURLOPT_COOKIEFILE). + */ +void Curl_cookie_loadfiles(struct SessionHandle *data) +{ + struct curl_slist *list = data->change.cookielist; + if(list) { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + while(list) { + data->cookies = Curl_cookie_init(data, + list->data, + data->cookies, + data->set.cookiesession); + list = list->next; + } + curl_slist_free_all(data->change.cookielist); /* clean up list */ + data->change.cookielist = NULL; /* don't do this again! */ + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } +} + +/* + * strstore() makes a strdup() on the 'newstr' and if '*str' is non-NULL + * that will be freed before the allocated string is stored there. + * + * It is meant to easily replace strdup() + */ +static void strstore(char **str, const char *newstr) +{ + if(*str) + free(*str); + *str = strdup(newstr); +} + + +/**************************************************************************** + * + * Curl_cookie_add() + * + * Add a single cookie line to the cookie keeping object. + * + ***************************************************************************/ + +struct Cookie * +Curl_cookie_add(struct SessionHandle *data, + /* The 'data' pointer here may be NULL at times, and thus + must only be used very carefully for things that can deal + with data being NULL. Such as infof() and similar */ + + struct CookieInfo *c, + bool httpheader, /* TRUE if HTTP header-style line */ + char *lineptr, /* first character of the line */ + const char *domain, /* default domain */ + const char *path) /* full path used when this cookie is set, + used to get default path for the cookie + unless set */ +{ + struct Cookie *clist; + char name[MAX_NAME]; + struct Cookie *co; + struct Cookie *lastc=NULL; + time_t now = time(NULL); + bool replace_old = FALSE; + bool badcookie = FALSE; /* cookies are good by default. mmmmm yummy */ + +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; +#endif + + /* First, alloc and init a new struct for it */ + co = calloc(1, sizeof(struct Cookie)); + if(!co) + return NULL; /* bail out if we're this low on memory */ + + if(httpheader) { + /* This line was read off a HTTP-header */ + const char *ptr; + const char *semiptr; + char *what; + + what = malloc(MAX_COOKIE_LINE); + if(!what) { + free(co); + return NULL; + } + + semiptr=strchr(lineptr, ';'); /* first, find a semicolon */ + + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + + ptr = lineptr; + do { + /* we have a = pair or a stand-alone word here */ + name[0]=what[0]=0; /* init the buffers */ + if(1 <= sscanf(ptr, "%" MAX_NAME_TXT "[^;\r\n =]=%" + MAX_COOKIE_LINE_TXT "[^;\r\n]", + name, what)) { + /* Use strstore() below to properly deal with received cookie + headers that have the same string property set more than once, + and then we use the last one. */ + const char *whatptr; + bool done = FALSE; + bool sep; + size_t len=strlen(what); + const char *endofn = &ptr[ strlen(name) ]; + + /* skip trailing spaces in name */ + while(*endofn && ISBLANK(*endofn)) + endofn++; + + /* name ends with a '=' ? */ + sep = (*endofn == '=')?TRUE:FALSE; + + /* Strip off trailing whitespace from the 'what' */ + while(len && ISBLANK(what[len-1])) { + what[len-1]=0; + len--; + } + + /* Skip leading whitespace from the 'what' */ + whatptr=what; + while(*whatptr && ISBLANK(*whatptr)) + whatptr++; + + if(!len) { + /* this was a "=" with no content, and we must allow + 'secure' and 'httponly' specified this weirdly */ + done = TRUE; + if(Curl_raw_equal("secure", name)) + co->secure = TRUE; + else if(Curl_raw_equal("httponly", name)) + co->httponly = TRUE; + else if(sep) + /* there was a '=' so we're not done parsing this field */ + done = FALSE; + } + if(done) + ; + else if(Curl_raw_equal("path", name)) { + strstore(&co->path, whatptr); + if(!co->path) { + badcookie = TRUE; /* out of memory bad */ + break; + } + } + else if(Curl_raw_equal("domain", name)) { + /* note that this name may or may not have a preceding dot, but + we don't care about that, we treat the names the same anyway */ + + const char *domptr=whatptr; + const char *nextptr; + int dotcount=1; + + /* Count the dots, we need to make sure that there are enough + of them. */ + + if('.' == whatptr[0]) + /* don't count the initial dot, assume it */ + domptr++; + + do { + nextptr = strchr(domptr, '.'); + if(nextptr) { + if(domptr != nextptr) + dotcount++; + domptr = nextptr+1; + } + } while(nextptr); + + /* The original Netscape cookie spec defined that this domain name + MUST have three dots (or two if one of the seven holy TLDs), + but it seems that these kinds of cookies are in use "out there" + so we cannot be that strict. I've therefore lowered the check + to not allow less than two dots. */ + + if(dotcount < 2) { + /* Received and skipped a cookie with a domain using too few + dots. */ + badcookie=TRUE; /* mark this as a bad cookie */ + infof(data, "skipped cookie with illegal dotcount domain: %s\n", + whatptr); + } + else { + /* Now, we make sure that our host is within the given domain, + or the given domain is not valid and thus cannot be set. */ + + if('.' == whatptr[0]) + whatptr++; /* ignore preceding dot */ + + if(!domain || tailmatch(whatptr, domain)) { + const char *tailptr=whatptr; + if(tailptr[0] == '.') + tailptr++; + strstore(&co->domain, tailptr); /* don't prefix w/dots + internally */ + if(!co->domain) { + badcookie = TRUE; + break; + } + co->tailmatch=TRUE; /* we always do that if the domain name was + given */ + } + else { + /* we did not get a tailmatch and then the attempted set domain + is not a domain to which the current host belongs. Mark as + bad. */ + badcookie=TRUE; + infof(data, "skipped cookie with bad tailmatch domain: %s\n", + whatptr); + } + } + } + else if(Curl_raw_equal("version", name)) { + strstore(&co->version, whatptr); + if(!co->version) { + badcookie = TRUE; + break; + } + } + else if(Curl_raw_equal("max-age", name)) { + /* Defined in RFC2109: + + Optional. The Max-Age attribute defines the lifetime of the + cookie, in seconds. The delta-seconds value is a decimal non- + negative integer. After delta-seconds seconds elapse, the + client should discard the cookie. A value of zero means the + cookie should be discarded immediately. + + */ + strstore(&co->maxage, whatptr); + if(!co->maxage) { + badcookie = TRUE; + break; + } + co->expires = + strtol((*co->maxage=='\"')?&co->maxage[1]:&co->maxage[0],NULL,10) + + (long)now; + } + else if(Curl_raw_equal("expires", name)) { + strstore(&co->expirestr, whatptr); + if(!co->expirestr) { + badcookie = TRUE; + break; + } + /* Note that if the date couldn't get parsed for whatever reason, + the cookie will be treated as a session cookie */ + co->expires = curl_getdate(what, &now); + + /* Session cookies have expires set to 0 so if we get that back + from the date parser let's add a second to make it a + non-session cookie */ + if(co->expires == 0) + co->expires = 1; + else if(co->expires < 0) + co->expires = 0; + } + else if(!co->name) { + co->name = strdup(name); + co->value = strdup(whatptr); + if(!co->name || !co->value) { + badcookie = TRUE; + break; + } + } + /* + else this is the second (or more) name we don't know + about! */ + } + else { + /* this is an "illegal" = pair */ + } + + if(!semiptr || !*semiptr) { + /* we already know there are no more cookies */ + semiptr = NULL; + continue; + } + + ptr=semiptr+1; + while(*ptr && ISBLANK(*ptr)) + ptr++; + semiptr=strchr(ptr, ';'); /* now, find the next semicolon */ + + if(!semiptr && *ptr) + /* There are no more semicolons, but there's a final name=value pair + coming up */ + semiptr=strchr(ptr, '\0'); + } while(semiptr); + + if(!badcookie && !co->domain) { + if(domain) { + /* no domain was given in the header line, set the default */ + co->domain=strdup(domain); + if(!co->domain) + badcookie = TRUE; + } + } + + if(!badcookie && !co->path && path) { + /* No path was given in the header line, set the default. + Note that the passed-in path to this function MAY have a '?' and + following part that MUST not be stored as part of the path. */ + char *queryp = strchr(path, '?'); + + /* queryp is where the interesting part of the path ends, so now we + want to the find the last */ + char *endslash; + if(!queryp) + endslash = strrchr(path, '/'); + else + endslash = memrchr(path, '/', (size_t)(queryp - path)); + if(endslash) { + size_t pathlen = (size_t)(endslash-path+1); /* include ending slash */ + co->path=malloc(pathlen+1); /* one extra for the zero byte */ + if(co->path) { + memcpy(co->path, path, pathlen); + co->path[pathlen]=0; /* zero terminate */ + } + else + badcookie = TRUE; + } + } + + free(what); + + if(badcookie || !co->name) { + /* we didn't get a cookie name or a bad one, + this is an illegal line, bail out */ + freecookie(co); + return NULL; + } + + } + else { + /* This line is NOT a HTTP header style line, we do offer support for + reading the odd netscape cookies-file format here */ + char *ptr; + char *firstptr; + char *tok_buf=NULL; + int fields; + + /* IE introduced HTTP-only cookies to prevent XSS attacks. Cookies + marked with httpOnly after the domain name are not accessible + from javascripts, but since curl does not operate at javascript + level, we include them anyway. In Firefox's cookie files, these + lines are preceded with #HttpOnly_ and then everything is + as usual, so we skip 10 characters of the line.. + */ + if(strncmp(lineptr, "#HttpOnly_", 10) == 0) { + lineptr += 10; + co->httponly = TRUE; + } + + if(lineptr[0]=='#') { + /* don't even try the comments */ + free(co); + return NULL; + } + /* strip off the possible end-of-line characters */ + ptr=strchr(lineptr, '\r'); + if(ptr) + *ptr=0; /* clear it */ + ptr=strchr(lineptr, '\n'); + if(ptr) + *ptr=0; /* clear it */ + + firstptr=strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */ + + /* Here's a quick check to eliminate normal HTTP-headers from this */ + if(!firstptr || strchr(firstptr, ':')) { + free(co); + return NULL; + } + + /* Now loop through the fields and init the struct we already have + allocated */ + for(ptr=firstptr, fields=0; ptr && !badcookie; + ptr=strtok_r(NULL, "\t", &tok_buf), fields++) { + switch(fields) { + case 0: + if(ptr[0]=='.') /* skip preceding dots */ + ptr++; + co->domain = strdup(ptr); + if(!co->domain) + badcookie = TRUE; + break; + case 1: + /* This field got its explanation on the 23rd of May 2001 by + Andrés García: + + flag: A TRUE/FALSE value indicating if all machines within a given + domain can access the variable. This value is set automatically by + the browser, depending on the value you set for the domain. + + As far as I can see, it is set to true when the cookie says + .domain.com and to false when the domain is complete www.domain.com + */ + co->tailmatch = Curl_raw_equal(ptr, "TRUE")?TRUE:FALSE; + break; + case 2: + /* It turns out, that sometimes the file format allows the path + field to remain not filled in, we try to detect this and work + around it! Andrés García made us aware of this... */ + if(strcmp("TRUE", ptr) && strcmp("FALSE", ptr)) { + /* only if the path doesn't look like a boolean option! */ + co->path = strdup(ptr); + if(!co->path) + badcookie = TRUE; + break; + } + /* this doesn't look like a path, make one up! */ + co->path = strdup("/"); + if(!co->path) + badcookie = TRUE; + fields++; /* add a field and fall down to secure */ + /* FALLTHROUGH */ + case 3: + co->secure = Curl_raw_equal(ptr, "TRUE")?TRUE:FALSE; + break; + case 4: + co->expires = curlx_strtoofft(ptr, NULL, 10); + break; + case 5: + co->name = strdup(ptr); + if(!co->name) + badcookie = TRUE; + break; + case 6: + co->value = strdup(ptr); + if(!co->value) + badcookie = TRUE; + break; + } + } + if(6 == fields) { + /* we got a cookie with blank contents, fix it */ + co->value = strdup(""); + if(!co->value) + badcookie = TRUE; + else + fields++; + } + + if(!badcookie && (7 != fields)) + /* we did not find the sufficient number of fields */ + badcookie = TRUE; + + if(badcookie) { + freecookie(co); + return NULL; + } + + } + + if(!c->running && /* read from a file */ + c->newsession && /* clean session cookies */ + !co->expires) { /* this is a session cookie since it doesn't expire! */ + freecookie(co); + return NULL; + } + + co->livecookie = c->running; + + /* now, we have parsed the incoming line, we must now check if this + superceeds an already existing cookie, which it may if the previous have + the same domain and path as this */ + + clist = c->cookies; + replace_old = FALSE; + while(clist) { + if(Curl_raw_equal(clist->name, co->name)) { + /* the names are identical */ + + if(clist->domain && co->domain) { + if(Curl_raw_equal(clist->domain, co->domain)) + /* The domains are identical */ + replace_old=TRUE; + } + else if(!clist->domain && !co->domain) + replace_old = TRUE; + + if(replace_old) { + /* the domains were identical */ + + if(clist->path && co->path) { + if(Curl_raw_equal(clist->path, co->path)) { + replace_old = TRUE; + } + else + replace_old = FALSE; + } + else if(!clist->path && !co->path) + replace_old = TRUE; + else + replace_old = FALSE; + + } + + if(replace_old && !co->livecookie && clist->livecookie) { + /* Both cookies matched fine, except that the already present + cookie is "live", which means it was set from a header, while + the new one isn't "live" and thus only read from a file. We let + live cookies stay alive */ + + /* Free the newcomer and get out of here! */ + freecookie(co); + return NULL; + } + + if(replace_old) { + co->next = clist->next; /* get the next-pointer first */ + + /* then free all the old pointers */ + free(clist->name); + if(clist->value) + free(clist->value); + if(clist->domain) + free(clist->domain); + if(clist->path) + free(clist->path); + if(clist->expirestr) + free(clist->expirestr); + + if(clist->version) + free(clist->version); + if(clist->maxage) + free(clist->maxage); + + *clist = *co; /* then store all the new data */ + + free(co); /* free the newly alloced memory */ + co = clist; /* point to the previous struct instead */ + + /* We have replaced a cookie, now skip the rest of the list but + make sure the 'lastc' pointer is properly set */ + do { + lastc = clist; + clist = clist->next; + } while(clist); + break; + } + } + lastc = clist; + clist = clist->next; + } + + if(c->running) + /* Only show this when NOT reading the cookies from a file */ + infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, " + "expire %" FORMAT_OFF_T "\n", + replace_old?"Replaced":"Added", co->name, co->value, + co->domain, co->path, co->expires); + + if(!replace_old) { + /* then make the last item point on this new one */ + if(lastc) + lastc->next = co; + else + c->cookies = co; + } + + c->numcookies++; /* one more cookie in the jar */ + return co; +} + +/***************************************************************************** + * + * Curl_cookie_init() + * + * Inits a cookie struct to read data from a local file. This is always + * called before any cookies are set. File may be NULL. + * + * If 'newsession' is TRUE, discard all "session cookies" on read from file. + * + ****************************************************************************/ +struct CookieInfo *Curl_cookie_init(struct SessionHandle *data, + const char *file, + struct CookieInfo *inc, + bool newsession) +{ + struct CookieInfo *c; + FILE *fp; + bool fromfile=TRUE; + + if(NULL == inc) { + /* we didn't get a struct, create one */ + c = calloc(1, sizeof(struct CookieInfo)); + if(!c) + return NULL; /* failed to get memory */ + c->filename = strdup(file?file:"none"); /* copy the name just in case */ + } + else { + /* we got an already existing one, use that */ + c = inc; + } + c->running = FALSE; /* this is not running, this is init */ + + if(file && strequal(file, "-")) { + fp = stdin; + fromfile=FALSE; + } + else if(file && !*file) { + /* points to a "" string */ + fp = NULL; + } + else + fp = file?fopen(file, "r"):NULL; + + c->newsession = newsession; /* new session? */ + + if(fp) { + char *lineptr; + bool headerline; + + char *line = malloc(MAX_COOKIE_LINE); + if(line) { + while(fgets(line, MAX_COOKIE_LINE, fp)) { + if(checkprefix("Set-Cookie:", line)) { + /* This is a cookie line, get it! */ + lineptr=&line[11]; + headerline=TRUE; + } + else { + lineptr=line; + headerline=FALSE; + } + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + + Curl_cookie_add(data, c, headerline, lineptr, NULL, NULL); + } + free(line); /* free the line buffer */ + } + if(fromfile) + fclose(fp); + } + + c->running = TRUE; /* now, we're running */ + + return c; +} + +/* sort this so that the longest path gets before the shorter path */ +static int cookie_sort(const void *p1, const void *p2) +{ + struct Cookie *c1 = *(struct Cookie **)p1; + struct Cookie *c2 = *(struct Cookie **)p2; + + size_t l1 = c1->path?strlen(c1->path):0; + size_t l2 = c2->path?strlen(c2->path):0; + + return (l2 > l1) ? 1 : (l2 < l1) ? -1 : 0 ; +} + +/***************************************************************************** + * + * Curl_cookie_getlist() + * + * For a given host and path, return a linked list of cookies that the + * client should send to the server if used now. The secure boolean informs + * the cookie if a secure connection is achieved or not. + * + * It shall only return cookies that haven't expired. + * + ****************************************************************************/ + +struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, + const char *host, const char *path, + bool secure) +{ + struct Cookie *newco; + struct Cookie *co; + time_t now = time(NULL); + struct Cookie *mainco=NULL; + size_t matches = 0; + + if(!c || !c->cookies) + return NULL; /* no cookie struct or no cookies in the struct */ + + co = c->cookies; + + while(co) { + /* only process this cookie if it is not expired or had no expire + date AND that if the cookie requires we're secure we must only + continue if we are! */ + if((!co->expires || (co->expires > now)) && + (co->secure?secure:TRUE)) { + + /* now check if the domain is correct */ + if(!co->domain || + (co->tailmatch && tailmatch(co->domain, host)) || + (!co->tailmatch && Curl_raw_equal(host, co->domain)) ) { + /* the right part of the host matches the domain stuff in the + cookie data */ + + /* now check the left part of the path with the cookies path + requirement */ + if(!co->path || + /* not using checkprefix() because matching should be + case-sensitive */ + !strncmp(co->path, path, strlen(co->path)) ) { + + /* and now, we know this is a match and we should create an + entry for the return-linked-list */ + + newco = malloc(sizeof(struct Cookie)); + if(newco) { + /* first, copy the whole source cookie: */ + memcpy(newco, co, sizeof(struct Cookie)); + + /* then modify our next */ + newco->next = mainco; + + /* point the main to us */ + mainco = newco; + + matches++; + } + else { + fail: + /* failure, clear up the allocated chain and return NULL */ + while(mainco) { + co = mainco->next; + free(mainco); + mainco = co; + } + + return NULL; + } + } + } + } + co = co->next; + } + + if(matches) { + /* Now we need to make sure that if there is a name appearing more than + once, the longest specified path version comes first. To make this + the swiftest way, we just sort them all based on path length. */ + struct Cookie **array; + size_t i; + + /* alloc an array and store all cookie pointers */ + array = malloc(sizeof(struct Cookie *) * matches); + if(!array) + goto fail; + + co = mainco; + + for(i=0; co; co = co->next) + array[i++] = co; + + /* now sort the cookie pointers in path length order */ + qsort(array, matches, sizeof(struct Cookie *), cookie_sort); + + /* remake the linked list order according to the new order */ + + mainco = array[0]; /* start here */ + for(i=0; inext = array[i+1]; + array[matches-1]->next = NULL; /* terminate the list */ + + free(array); /* remove the temporary data again */ + } + + return mainco; /* return the new list */ +} + +/***************************************************************************** + * + * Curl_cookie_clearall() + * + * Clear all existing cookies and reset the counter. + * + ****************************************************************************/ +void Curl_cookie_clearall(struct CookieInfo *cookies) +{ + if(cookies) { + Curl_cookie_freelist(cookies->cookies, TRUE); + cookies->cookies = NULL; + cookies->numcookies = 0; + } +} + +/***************************************************************************** + * + * Curl_cookie_freelist() + * + * Free a list of cookies previously returned by Curl_cookie_getlist(); + * + * The 'cookiestoo' argument tells this function whether to just free the + * list or actually also free all cookies within the list as well. + * + ****************************************************************************/ + +void Curl_cookie_freelist(struct Cookie *co, bool cookiestoo) +{ + struct Cookie *next; + if(co) { + while(co) { + next = co->next; + if(cookiestoo) + freecookie(co); + else + free(co); /* we only free the struct since the "members" are all just + pointed out in the main cookie list! */ + co = next; + } + } +} + + +/***************************************************************************** + * + * Curl_cookie_clearsess() + * + * Free all session cookies in the cookies list. + * + ****************************************************************************/ +void Curl_cookie_clearsess(struct CookieInfo *cookies) +{ + struct Cookie *first, *curr, *next, *prev = NULL; + + if(!cookies || !cookies->cookies) + return; + + first = curr = prev = cookies->cookies; + + for(; curr; curr = next) { + next = curr->next; + if(!curr->expires) { + if(first == curr) + first = next; + + if(prev == curr) + prev = next; + else + prev->next = next; + + freecookie(curr); + cookies->numcookies--; + } + else + prev = curr; + } + + cookies->cookies = first; +} + + +/***************************************************************************** + * + * Curl_cookie_cleanup() + * + * Free a "cookie object" previous created with cookie_init(). + * + ****************************************************************************/ +void Curl_cookie_cleanup(struct CookieInfo *c) +{ + struct Cookie *co; + struct Cookie *next; + if(c) { + if(c->filename) + free(c->filename); + co = c->cookies; + + while(co) { + next = co->next; + freecookie(co); + co = next; + } + free(c); /* free the base struct as well */ + } +} + +/* get_netscape_format() + * + * Formats a string for Netscape output file, w/o a newline at the end. + * + * Function returns a char * to a formatted line. Has to be free()d +*/ +static char *get_netscape_format(const struct Cookie *co) +{ + return aprintf( + "%s" /* httponly preamble */ + "%s%s\t" /* domain */ + "%s\t" /* tailmatch */ + "%s\t" /* path */ + "%s\t" /* secure */ + "%" FORMAT_OFF_T "\t" /* expires */ + "%s\t" /* name */ + "%s", /* value */ + co->httponly?"#HttpOnly_":"", + /* Make sure all domains are prefixed with a dot if they allow + tailmatching. This is Mozilla-style. */ + (co->tailmatch && co->domain && co->domain[0] != '.')? ".":"", + co->domain?co->domain:"unknown", + co->tailmatch?"TRUE":"FALSE", + co->path?co->path:"/", + co->secure?"TRUE":"FALSE", + co->expires, + co->name, + co->value?co->value:""); +} + +/* + * cookie_output() + * + * Writes all internally known cookies to the specified file. Specify + * "-" as file name to write to stdout. + * + * The function returns non-zero on write failure. + */ +static int cookie_output(struct CookieInfo *c, const char *dumphere) +{ + struct Cookie *co; + FILE *out; + bool use_stdout=FALSE; + + if((NULL == c) || (0 == c->numcookies)) + /* If there are no known cookies, we don't write or even create any + destination file */ + return 0; + + if(strequal("-", dumphere)) { + /* use stdout */ + out = stdout; + use_stdout=TRUE; + } + else { + out = fopen(dumphere, "w"); + if(!out) + return 1; /* failure */ + } + + if(c) { + char *format_ptr; + + fputs("# Netscape HTTP Cookie File\n" + "# http://curl.haxx.se/docs/http-cookies.html\n" + "# This file was generated by libcurl! Edit at your own risk.\n\n", + out); + co = c->cookies; + + while(co) { + format_ptr = get_netscape_format(co); + if(format_ptr == NULL) { + fprintf(out, "#\n# Fatal libcurl error\n"); + if(!use_stdout) + fclose(out); + return 1; + } + fprintf(out, "%s\n", format_ptr); + free(format_ptr); + co=co->next; + } + } + + if(!use_stdout) + fclose(out); + + return 0; +} + +struct curl_slist *Curl_cookie_list(struct SessionHandle *data) +{ + struct curl_slist *list = NULL; + struct curl_slist *beg; + struct Cookie *c; + char *line; + + if((data->cookies == NULL) || + (data->cookies->numcookies == 0)) + return NULL; + + c = data->cookies->cookies; + + while(c) { + /* fill the list with _all_ the cookies we know */ + line = get_netscape_format(c); + if(!line) { + curl_slist_free_all(list); + return NULL; + } + beg = curl_slist_append(list, line); + free(line); + if(!beg) { + curl_slist_free_all(list); + return NULL; + } + list = beg; + c = c->next; + } + + return list; +} + +void Curl_flush_cookies(struct SessionHandle *data, int cleanup) +{ + if(data->set.str[STRING_COOKIEJAR]) { + if(data->change.cookielist) { + /* If there is a list of cookie files to read, do it first so that + we have all the told files read before we write the new jar. + Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */ + Curl_cookie_loadfiles(data); + } + + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + + /* if we have a destination file for all the cookies to get dumped to */ + if(cookie_output(data->cookies, data->set.str[STRING_COOKIEJAR])) + infof(data, "WARNING: failed to save cookies in %s\n", + data->set.str[STRING_COOKIEJAR]); + } + else { + if(cleanup && data->change.cookielist) { + /* since nothing is written, we can just free the list of cookie file + names */ + curl_slist_free_all(data->change.cookielist); /* clean up list */ + data->change.cookielist = NULL; + } + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + } + + if(cleanup && (!data->share || (data->cookies != data->share->cookies))) { + Curl_cookie_cleanup(data->cookies); + } + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); +} + +#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */ diff --git a/lib/curl_cyassl.c b/lib/curl_cyassl.c new file mode 100644 index 000000000..32f1cfe5c --- /dev/null +++ b/lib/curl_cyassl.c @@ -0,0 +1,611 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +/* + * Source file for all CyaSSL-specific code for the TLS/SSL layer. No code + * but curl_sslgen.c should ever call or use these functions. + * + */ + +#include "curl_setup.h" + +#ifdef USE_CYASSL + +#ifdef HAVE_LIMITS_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_inet_pton.h" +#include "curl_cyassl.h" +#include "curl_sslgen.h" +#include "curl_parsedate.h" +#include "curl_connect.h" /* for the connect timeout */ +#include "curl_select.h" +#include "curl_rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" +#include +#include + + +static Curl_recv cyassl_recv; +static Curl_send cyassl_send; + + +static int do_file_type(const char *type) +{ + if(!type || !type[0]) + return SSL_FILETYPE_PEM; + if(Curl_raw_equal(type, "PEM")) + return SSL_FILETYPE_PEM; + if(Curl_raw_equal(type, "DER")) + return SSL_FILETYPE_ASN1; + return -1; +} + +/* + * This function loads all the client/CA certificates and CRLs. Setup the TLS + * layer and do all necessary magic. + */ +static CURLcode +cyassl_connect_step1(struct connectdata *conn, + int sockindex) +{ + struct SessionHandle *data = conn->data; + struct ssl_connect_data* conssl = &conn->ssl[sockindex]; + SSL_METHOD* req_method = NULL; + void* ssl_sessionid = NULL; + curl_socket_t sockfd = conn->sock[sockindex]; + + if(conssl->state == ssl_connection_complete) + return CURLE_OK; + + /* CyaSSL doesn't support SSLv2 */ + if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) { + failf(data, "CyaSSL does not support SSLv2"); + return CURLE_SSL_CONNECT_ERROR; + } + + /* check to see if we've been told to use an explicit SSL/TLS version */ + switch(data->set.ssl.version) { + case CURL_SSLVERSION_DEFAULT: + /* we try to figure out version */ + req_method = SSLv23_client_method(); + break; + case CURL_SSLVERSION_TLSv1: + req_method = TLSv1_client_method(); + break; + case CURL_SSLVERSION_SSLv3: + req_method = SSLv3_client_method(); + break; + default: + req_method = TLSv1_client_method(); + } + + if(!req_method) { + failf(data, "SSL: couldn't create a method!"); + return CURLE_OUT_OF_MEMORY; + } + + if(conssl->ctx) + SSL_CTX_free(conssl->ctx); + conssl->ctx = SSL_CTX_new(req_method); + + if(!conssl->ctx) { + failf(data, "SSL: couldn't create a context!"); + return CURLE_OUT_OF_MEMORY; + } + +#ifndef NO_FILESYSTEM + /* load trusted cacert */ + if(data->set.str[STRING_SSL_CAFILE]) { + if(!SSL_CTX_load_verify_locations(conssl->ctx, + data->set.str[STRING_SSL_CAFILE], + data->set.str[STRING_SSL_CAPATH])) { + if(data->set.ssl.verifypeer) { + /* Fail if we insiste on successfully verifying the server. */ + failf(data,"error setting certificate verify locations:\n" + " CAfile: %s\n CApath: %s", + data->set.str[STRING_SSL_CAFILE]? + data->set.str[STRING_SSL_CAFILE]: "none", + data->set.str[STRING_SSL_CAPATH]? + data->set.str[STRING_SSL_CAPATH] : "none"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + /* Just continue with a warning if no strict certificate + verification is required. */ + infof(data, "error setting certificate verify locations," + " continuing anyway:\n"); + } + } + else { + /* Everything is fine. */ + infof(data, "successfully set certificate verify locations:\n"); + } + infof(data, + " CAfile: %s\n" + " CApath: %s\n", + data->set.str[STRING_SSL_CAFILE] ? data->set.str[STRING_SSL_CAFILE]: + "none", + data->set.str[STRING_SSL_CAPATH] ? data->set.str[STRING_SSL_CAPATH]: + "none"); + } + + /* Load the client certificate, and private key */ + if(data->set.str[STRING_CERT] && data->set.str[STRING_KEY]) { + int file_type = do_file_type(data->set.str[STRING_CERT_TYPE]); + + if(SSL_CTX_use_certificate_file(conssl->ctx, data->set.str[STRING_CERT], + file_type) != 1) { + failf(data, "unable to use client certificate (no key or wrong pass" + " phrase?)"); + return CURLE_SSL_CONNECT_ERROR; + } + + file_type = do_file_type(data->set.str[STRING_KEY_TYPE]); + if(SSL_CTX_use_PrivateKey_file(conssl->ctx, data->set.str[STRING_KEY], + file_type) != 1) { + failf(data, "unable to set private key"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#else + if(CyaSSL_no_filesystem_verify(conssl->ctx)!= SSL_SUCCESS) { + return CURLE_SSL_CONNECT_ERROR; + } +#endif /* NO_FILESYSTEM */ + + /* SSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ + SSL_CTX_set_verify(conssl->ctx, + data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE, + NULL); + + /* Let's make an SSL structure */ + if(conssl->handle) + SSL_free(conssl->handle); + conssl->handle = SSL_new(conssl->ctx); + if(!conssl->handle) { + failf(data, "SSL: couldn't create a context (handle)!"); + return CURLE_OUT_OF_MEMORY; + } + + /* Check if there's a cached ID we can/should use here! */ + if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) { + /* we got a session id, use it! */ + if(!SSL_set_session(conssl->handle, ssl_sessionid)) { + failf(data, "SSL: SSL_set_session failed: %s", + ERR_error_string(SSL_get_error(conssl->handle, 0),NULL)); + return CURLE_SSL_CONNECT_ERROR; + } + /* Informational message */ + infof (data, "SSL re-using session ID\n"); + } + + /* pass the raw socket into the SSL layer */ + if(!SSL_set_fd(conssl->handle, (int)sockfd)) { + failf(data, "SSL: SSL_set_fd failed"); + return CURLE_SSL_CONNECT_ERROR; + } + + conssl->connecting_state = ssl_connect_2; + return CURLE_OK; +} + + +static CURLcode +cyassl_connect_step2(struct connectdata *conn, + int sockindex) +{ + int ret = -1; + struct SessionHandle *data = conn->data; + struct ssl_connect_data* conssl = &conn->ssl[sockindex]; + + infof(data, "CyaSSL: Connecting to %s:%d\n", + conn->host.name, conn->remote_port); + + conn->recv[sockindex] = cyassl_recv; + conn->send[sockindex] = cyassl_send; + + /* Enable RFC2818 checks */ + if(data->set.ssl.verifyhost) { + ret = CyaSSL_check_domain_name(conssl->handle, conn->host.name); + if(ret == SSL_FAILURE) + return CURLE_OUT_OF_MEMORY; + } + + ret = SSL_connect(conssl->handle); + if(ret != 1) { + char error_buffer[80]; + int detail = SSL_get_error(conssl->handle, ret); + + if(SSL_ERROR_WANT_READ == detail) { + conssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + else if(SSL_ERROR_WANT_WRITE == detail) { + conssl->connecting_state = ssl_connect_2_writing; + return CURLE_OK; + } + /* There is no easy way to override only the CN matching. + * This will enable the override of both mismatching SubjectAltNames + * as also mismatching CN fields */ + else if(DOMAIN_NAME_MISMATCH == detail) { +#if 1 + failf(data, "\tsubject alt name(s) or common name do not match \"%s\"\n", + conn->host.dispname); + return CURLE_PEER_FAILED_VERIFICATION; +#else + /* When the CyaSSL_check_domain_name() is used and you desire to continue + * on a DOMAIN_NAME_MISMATCH, i.e. 'data->set.ssl.verifyhost == 0', + * CyaSSL version 2.4.0 will fail with an INCOMPLETE_DATA error. The only + * way to do this is currently to switch the CyaSSL_check_domain_name() + * in and out based on the 'data->set.ssl.verifyhost' value. */ + if(data->set.ssl.verifyhost) { + failf(data, + "\tsubject alt name(s) or common name do not match \"%s\"\n", + conn->host.dispname); + return CURLE_PEER_FAILED_VERIFICATION; + } + else { + infof(data, + "\tsubject alt name(s) and/or common name do not match \"%s\"\n", + conn->host.dispname); + return CURLE_OK; + } +#endif + } + else { + failf(data, "SSL_connect failed with error %d: %s", detail, + ERR_error_string(detail, error_buffer)); + return CURLE_SSL_CONNECT_ERROR; + } + } + + conssl->connecting_state = ssl_connect_3; + infof(data, "SSL connected\n"); + + return CURLE_OK; +} + + +static CURLcode +cyassl_connect_step3(struct connectdata *conn, + int sockindex) +{ + CURLcode retcode = CURLE_OK; + void *old_ssl_sessionid=NULL; + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + int incache; + SSL_SESSION *our_ssl_sessionid; + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + + our_ssl_sessionid = SSL_get_session(connssl->handle); + + incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL)); + if(incache) { + if(old_ssl_sessionid != our_ssl_sessionid) { + infof(data, "old SSL session ID is stale, removing\n"); + Curl_ssl_delsessionid(conn, old_ssl_sessionid); + incache = FALSE; + } + } + if(!incache) { + retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid, + 0 /* unknown size */); + if(retcode) { + failf(data, "failed to store ssl session"); + return retcode; + } + } + + connssl->connecting_state = ssl_connect_done; + + return retcode; +} + + +static ssize_t cyassl_send(struct connectdata *conn, + int sockindex, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + char error_buffer[80]; + int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; + int rc = SSL_write(conn->ssl[sockindex].handle, mem, memlen); + + if(rc < 0) { + int err = SSL_get_error(conn->ssl[sockindex].handle, rc); + + switch(err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* there's data pending, re-invoke SSL_write() */ + *curlcode = CURLE_AGAIN; + return -1; + default: + failf(conn->data, "SSL write: %s, errno %d", + ERR_error_string(err, error_buffer), + SOCKERRNO); + *curlcode = CURLE_SEND_ERROR; + return -1; + } + } + return rc; +} + +void Curl_cyassl_close_all(struct SessionHandle *data) +{ + (void)data; +} + +void Curl_cyassl_close(struct connectdata *conn, int sockindex) +{ + struct ssl_connect_data *conssl = &conn->ssl[sockindex]; + + if(conssl->handle) { + (void)SSL_shutdown(conssl->handle); + SSL_free (conssl->handle); + conssl->handle = NULL; + } + if(conssl->ctx) { + SSL_CTX_free (conssl->ctx); + conssl->ctx = NULL; + } +} + +static ssize_t cyassl_recv(struct connectdata *conn, + int num, + char *buf, + size_t buffersize, + CURLcode *curlcode) +{ + char error_buffer[80]; + int buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; + int nread = SSL_read(conn->ssl[num].handle, buf, buffsize); + + if(nread < 0) { + int err = SSL_get_error(conn->ssl[num].handle, nread); + + switch(err) { + case SSL_ERROR_ZERO_RETURN: /* no more data */ + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* there's data pending, re-invoke SSL_read() */ + *curlcode = CURLE_AGAIN; + return -1; + default: + failf(conn->data, "SSL read: %s, errno %d", + ERR_error_string(err, error_buffer), + SOCKERRNO); + *curlcode = CURLE_RECV_ERROR; + return -1; + } + } + return nread; +} + + +void Curl_cyassl_session_free(void *ptr) +{ + (void)ptr; + /* CyaSSL reuses sessions on own, no free */ +} + + +size_t Curl_cyassl_version(char *buffer, size_t size) +{ +#ifdef CYASSL_VERSION + return snprintf(buffer, size, "CyaSSL/%s", CYASSL_VERSION); +#else + return snprintf(buffer, size, "CyaSSL/%s", "<1.8.8"); +#endif +} + + +int Curl_cyassl_init(void) +{ + if(CyaSSL_Init() == 0) + return 1; + + return -1; +} + + +bool Curl_cyassl_data_pending(const struct connectdata* conn, int connindex) +{ + if(conn->ssl[connindex].handle) /* SSL is in use */ + return (0 != SSL_pending(conn->ssl[connindex].handle)) ? TRUE : FALSE; + else + return FALSE; +} + + +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +int Curl_cyassl_shutdown(struct connectdata *conn, int sockindex) +{ + int retval = 0; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + if(connssl->handle) { + SSL_free (connssl->handle); + connssl->handle = NULL; + } + return retval; +} + + +static CURLcode +cyassl_connect_common(struct connectdata *conn, + int sockindex, + bool nonblocking, + bool *done) +{ + CURLcode retcode; + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + curl_socket_t sockfd = conn->sock[sockindex]; + long timeout_ms; + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1==connssl->connecting_state) { + /* Find out how much more time we're allowed */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + retcode = cyassl_connect_step1(conn, sockindex); + if(retcode) + return retcode; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading + || connssl->connecting_state == ssl_connect_2_writing) { + + curl_socket_t writefd = ssl_connect_2_writing== + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading== + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if + * this connection is part of a multi handle and this loop would + * execute again. This permits the owner of a multi handle to + * abort a connection attempt before step2 has completed while + * ensuring that a client using select() or epoll() will always + * have a valid fdset to wait on. + */ + retcode = cyassl_connect_step2(conn, sockindex); + if(retcode || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return retcode; + + } /* repeat step2 until all transactions are done. */ + + if(ssl_connect_3==connssl->connecting_state) { + retcode = cyassl_connect_step3(conn, sockindex); + if(retcode) + return retcode; + } + + if(ssl_connect_done==connssl->connecting_state) { + connssl->state = ssl_connection_complete; + conn->recv[sockindex] = cyassl_recv; + conn->send[sockindex] = cyassl_send; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + + +CURLcode +Curl_cyassl_connect_nonblocking(struct connectdata *conn, + int sockindex, + bool *done) +{ + return cyassl_connect_common(conn, sockindex, TRUE, done); +} + + +CURLcode +Curl_cyassl_connect(struct connectdata *conn, + int sockindex) +{ + CURLcode retcode; + bool done = FALSE; + + retcode = cyassl_connect_common(conn, sockindex, FALSE, &done); + if(retcode) + return retcode; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +#endif diff --git a/lib/curl_dict.c b/lib/curl_dict.c new file mode 100644 index 000000000..114ef7c72 --- /dev/null +++ b/lib/curl_dict.c @@ -0,0 +1,284 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_DICT + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#include "curl_urldata.h" +#include +#include "curl_transfer.h" +#include "curl_sendf.h" + +#include "curl_progress.h" +#include "curl_strequal.h" +#include "curl_dict.h" +#include "curl_rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + * Forward declarations. + */ + +static CURLcode dict_do(struct connectdata *conn, bool *done); + +/* + * DICT protocol handler. + */ + +const struct Curl_handler Curl_handler_dict = { + "DICT", /* scheme */ + ZERO_NULL, /* setup_connection */ + dict_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_DICT, /* defport */ + CURLPROTO_DICT, /* protocol */ + PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ +}; + +static char *unescape_word(struct SessionHandle *data, const char *inputbuff) +{ + char *newp; + char *dictp; + char *ptr; + int len; + char byte; + int olen=0; + + newp = curl_easy_unescape(data, inputbuff, 0, &len); + if(!newp) + return NULL; + + dictp = malloc(((size_t)len)*2 + 1); /* add one for terminating zero */ + if(dictp) { + /* According to RFC2229 section 2.2, these letters need to be escaped with + \[letter] */ + for(ptr = newp; + (byte = *ptr) != 0; + ptr++) { + if((byte <= 32) || (byte == 127) || + (byte == '\'') || (byte == '\"') || (byte == '\\')) { + dictp[olen++] = '\\'; + } + dictp[olen++] = byte; + } + dictp[olen]=0; + + free(newp); + } + return dictp; +} + +static CURLcode dict_do(struct connectdata *conn, bool *done) +{ + char *word; + char *eword; + char *ppath; + char *database = NULL; + char *strategy = NULL; + char *nthdef = NULL; /* This is not part of the protocol, but required + by RFC 2229 */ + CURLcode result=CURLE_OK; + struct SessionHandle *data=conn->data; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + + char *path = data->state.path; + curl_off_t *bytecount = &data->req.bytecount; + + *done = TRUE; /* unconditionally */ + + if(conn->bits.user_passwd) { + /* AUTH is missing */ + } + + if(Curl_raw_nequal(path, DICT_MATCH, sizeof(DICT_MATCH)-1) || + Curl_raw_nequal(path, DICT_MATCH2, sizeof(DICT_MATCH2)-1) || + Curl_raw_nequal(path, DICT_MATCH3, sizeof(DICT_MATCH3)-1)) { + + word = strchr(path, ':'); + if(word) { + word++; + database = strchr(word, ':'); + if(database) { + *database++ = (char)0; + strategy = strchr(database, ':'); + if(strategy) { + *strategy++ = (char)0; + nthdef = strchr(strategy, ':'); + if(nthdef) { + *nthdef = (char)0; + } + } + } + } + + if((word == NULL) || (*word == (char)0)) { + infof(data, "lookup word is missing\n"); + word=(char *)"default"; + } + if((database == NULL) || (*database == (char)0)) { + database = (char *)"!"; + } + if((strategy == NULL) || (*strategy == (char)0)) { + strategy = (char *)"."; + } + + eword = unescape_word(data, word); + if(!eword) + return CURLE_OUT_OF_MEMORY; + + result = Curl_sendf(sockfd, conn, + "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" + "MATCH " + "%s " /* database */ + "%s " /* strategy */ + "%s\r\n" /* word */ + "QUIT\r\n", + + database, + strategy, + eword + ); + + free(eword); + + if(result) { + failf(data, "Failed sending DICT request"); + return result; + } + Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, + -1, NULL); /* no upload */ + } + else if(Curl_raw_nequal(path, DICT_DEFINE, sizeof(DICT_DEFINE)-1) || + Curl_raw_nequal(path, DICT_DEFINE2, sizeof(DICT_DEFINE2)-1) || + Curl_raw_nequal(path, DICT_DEFINE3, sizeof(DICT_DEFINE3)-1)) { + + word = strchr(path, ':'); + if(word) { + word++; + database = strchr(word, ':'); + if(database) { + *database++ = (char)0; + nthdef = strchr(database, ':'); + if(nthdef) { + *nthdef = (char)0; + } + } + } + + if((word == NULL) || (*word == (char)0)) { + infof(data, "lookup word is missing\n"); + word=(char *)"default"; + } + if((database == NULL) || (*database == (char)0)) { + database = (char *)"!"; + } + + eword = unescape_word(data, word); + if(!eword) + return CURLE_OUT_OF_MEMORY; + + result = Curl_sendf(sockfd, conn, + "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" + "DEFINE " + "%s " /* database */ + "%s\r\n" /* word */ + "QUIT\r\n", + database, + eword); + + free(eword); + + if(result) { + failf(data, "Failed sending DICT request"); + return result; + } + Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, + -1, NULL); /* no upload */ + } + else { + + ppath = strchr(path, '/'); + if(ppath) { + int i; + + ppath++; + for(i = 0; ppath[i]; i++) { + if(ppath[i] == ':') + ppath[i] = ' '; + } + result = Curl_sendf(sockfd, conn, + "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" + "%s\r\n" + "QUIT\r\n", ppath); + if(result) { + failf(data, "Failed sending DICT request"); + return result; + } + + Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, -1, NULL); + } + } + + return CURLE_OK; +} +#endif /*CURL_DISABLE_DICT*/ diff --git a/lib/curl_easy.c b/lib/curl_easy.c new file mode 100644 index 000000000..a2181cc4d --- /dev/null +++ b/lib/curl_easy.c @@ -0,0 +1,897 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include "curl_strequal.h" +#include "curl_urldata.h" +#include +#include "curl_transfer.h" +#include "curl_sslgen.h" +#include "curl_url.h" +#include "curl_getinfo.h" +#include "curl_hostip.h" +#include "curl_share.h" +#include "curl_strdup.h" +#include "curl_memory.h" +#include "curl_progress.h" +#include "curl_easyif.h" +#include "curl_select.h" +#include "curl_sendf.h" /* for failf function prototype */ +#include "curl_ntlm.h" +#include "curl_connect.h" /* for Curl_getconnectinfo */ +#include "curl_slist.h" +#include "curl_amigaos.h" +#include "curl_rand.h" +#include "curl_non_ascii.h" +#include "curl_warnless.h" +#include "curl_conncache.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* win32_cleanup() is for win32 socket cleanup functionality, the opposite + of win32_init() */ +static void win32_cleanup(void) +{ +#ifdef USE_WINSOCK + WSACleanup(); +#endif +#ifdef USE_WINDOWS_SSPI + Curl_sspi_global_cleanup(); +#endif +} + +/* win32_init() performs win32 socket initialization to properly setup the + stack to allow networking */ +static CURLcode win32_init(void) +{ +#ifdef USE_WINSOCK + WORD wVersionRequested; + WSADATA wsaData; + int res; + +#if defined(ENABLE_IPV6) && (USE_WINSOCK < 2) + Error IPV6_requires_winsock2 +#endif + + wVersionRequested = MAKEWORD(USE_WINSOCK, USE_WINSOCK); + + res = WSAStartup(wVersionRequested, &wsaData); + + if(res != 0) + /* Tell the user that we couldn't find a useable */ + /* winsock.dll. */ + return CURLE_FAILED_INIT; + + /* Confirm that the Windows Sockets DLL supports what we need.*/ + /* Note that if the DLL supports versions greater */ + /* than wVersionRequested, it will still return */ + /* wVersionRequested in wVersion. wHighVersion contains the */ + /* highest supported version. */ + + if(LOBYTE( wsaData.wVersion ) != LOBYTE(wVersionRequested) || + HIBYTE( wsaData.wVersion ) != HIBYTE(wVersionRequested) ) { + /* Tell the user that we couldn't find a useable */ + + /* winsock.dll. */ + WSACleanup(); + return CURLE_FAILED_INIT; + } + /* The Windows Sockets DLL is acceptable. Proceed. */ +#elif defined(USE_LWIPSOCK) + lwip_init(); +#endif + +#ifdef USE_WINDOWS_SSPI + { + CURLcode err = Curl_sspi_global_init(); + if(err != CURLE_OK) + return err; + } +#endif + + return CURLE_OK; +} + +#ifdef USE_LIBIDN +/* + * Initialise use of IDNA library. + * It falls back to ASCII if $CHARSET isn't defined. This doesn't work for + * idna_to_ascii_lz(). + */ +static void idna_init (void) +{ +#ifdef WIN32 + char buf[60]; + UINT cp = GetACP(); + + if(!getenv("CHARSET") && cp > 0) { + snprintf(buf, sizeof(buf), "CHARSET=cp%u", cp); + putenv(buf); + } +#else + /* to do? */ +#endif +} +#endif /* USE_LIBIDN */ + +/* true globals -- for curl_global_init() and curl_global_cleanup() */ +static unsigned int initialized; +static long init_flags; + +/* + * strdup (and other memory functions) is redefined in complicated + * ways, but at this point it must be defined as the system-supplied strdup + * so the callback pointer is initialized correctly. + */ +#if defined(_WIN32_WCE) +#define system_strdup _strdup +#elif !defined(HAVE_STRDUP) +#define system_strdup curlx_strdup +#else +#define system_strdup strdup +#endif + +#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) +# pragma warning(disable:4232) /* MSVC extension, dllimport identity */ +#endif + +#ifndef __SYMBIAN32__ +/* + * If a memory-using function (like curl_getenv) is used before + * curl_global_init() is called, we need to have these pointers set already. + */ +curl_malloc_callback Curl_cmalloc = (curl_malloc_callback)malloc; +curl_free_callback Curl_cfree = (curl_free_callback)free; +curl_realloc_callback Curl_crealloc = (curl_realloc_callback)realloc; +curl_strdup_callback Curl_cstrdup = (curl_strdup_callback)system_strdup; +curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc; +#else +/* + * Symbian OS doesn't support initialization to code in writeable static data. + * Initialization will occur in the curl_global_init() call. + */ +curl_malloc_callback Curl_cmalloc; +curl_free_callback Curl_cfree; +curl_realloc_callback Curl_crealloc; +curl_strdup_callback Curl_cstrdup; +curl_calloc_callback Curl_ccalloc; +#endif + +#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) +# pragma warning(default:4232) /* MSVC extension, dllimport identity */ +#endif + +/** + * curl_global_init() globally initializes cURL given a bitwise set of the + * different features of what to initialize. + */ +CURLcode curl_global_init(long flags) +{ + if(initialized++) + return CURLE_OK; + + /* Setup the default memory functions here (again) */ + Curl_cmalloc = (curl_malloc_callback)malloc; + Curl_cfree = (curl_free_callback)free; + Curl_crealloc = (curl_realloc_callback)realloc; + Curl_cstrdup = (curl_strdup_callback)system_strdup; + Curl_ccalloc = (curl_calloc_callback)calloc; + + if(flags & CURL_GLOBAL_SSL) + if(!Curl_ssl_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_ssl_init failed\n")); + return CURLE_FAILED_INIT; + } + + if(flags & CURL_GLOBAL_WIN32) + if(win32_init() != CURLE_OK) { + DEBUGF(fprintf(stderr, "Error: win32_init failed\n")); + return CURLE_FAILED_INIT; + } + +#ifdef __AMIGA__ + if(!Curl_amiga_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_amiga_init failed\n")); + return CURLE_FAILED_INIT; + } +#endif + +#ifdef NETWARE + if(netware_init()) { + DEBUGF(fprintf(stderr, "Warning: LONG namespace not available\n")); + } +#endif + +#ifdef USE_LIBIDN + idna_init(); +#endif + + if(Curl_resolver_global_init() != CURLE_OK) { + DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n")); + return CURLE_FAILED_INIT; + } + +#if defined(USE_LIBSSH2) && defined(HAVE_LIBSSH2_INIT) + if(libssh2_init(0)) { + DEBUGF(fprintf(stderr, "Error: libssh2_init failed\n")); + return CURLE_FAILED_INIT; + } +#endif + + init_flags = flags; + + /* Preset pseudo-random number sequence. */ + + Curl_srand(); + + return CURLE_OK; +} + +/* + * curl_global_init_mem() globally initializes cURL and also registers the + * user provided callback routines. + */ +CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, + curl_free_callback f, curl_realloc_callback r, + curl_strdup_callback s, curl_calloc_callback c) +{ + CURLcode code = CURLE_OK; + + /* Invalid input, return immediately */ + if(!m || !f || !r || !s || !c) + return CURLE_FAILED_INIT; + + /* Already initialized, don't do it again */ + if(initialized) + return CURLE_OK; + + /* Call the actual init function first */ + code = curl_global_init(flags); + if(code == CURLE_OK) { + Curl_cmalloc = m; + Curl_cfree = f; + Curl_cstrdup = s; + Curl_crealloc = r; + Curl_ccalloc = c; + } + + return code; +} + +/** + * curl_global_cleanup() globally cleanups cURL, uses the value of + * "init_flags" to determine what needs to be cleaned up and what doesn't. + */ +void curl_global_cleanup(void) +{ + if(!initialized) + return; + + if(--initialized) + return; + + Curl_global_host_cache_dtor(); + + if(init_flags & CURL_GLOBAL_SSL) + Curl_ssl_cleanup(); + + Curl_resolver_global_cleanup(); + + if(init_flags & CURL_GLOBAL_WIN32) + win32_cleanup(); + + Curl_amiga_cleanup(); + +#if defined(USE_LIBSSH2) && defined(HAVE_LIBSSH2_EXIT) + (void)libssh2_exit(); +#endif + + init_flags = 0; +} + +/* + * curl_easy_init() is the external interface to alloc, setup and init an + * easy handle that is returned. If anything goes wrong, NULL is returned. + */ +CURL *curl_easy_init(void) +{ + CURLcode res; + struct SessionHandle *data; + + /* Make sure we inited the global SSL stuff */ + if(!initialized) { + res = curl_global_init(CURL_GLOBAL_DEFAULT); + if(res) { + /* something in the global init failed, return nothing */ + DEBUGF(fprintf(stderr, "Error: curl_global_init failed\n")); + return NULL; + } + } + + /* We use curl_open() with undefined URL so far */ + res = Curl_open(&data); + if(res != CURLE_OK) { + DEBUGF(fprintf(stderr, "Error: Curl_open failed\n")); + return NULL; + } + + return data; +} + +/* + * curl_easy_setopt() is the external interface for setting options on an + * easy handle. + */ + +#undef curl_easy_setopt +CURLcode curl_easy_setopt(CURL *curl, CURLoption tag, ...) +{ + va_list arg; + struct SessionHandle *data = curl; + CURLcode ret; + + if(!curl) + return CURLE_BAD_FUNCTION_ARGUMENT; + + va_start(arg, tag); + + ret = Curl_setopt(data, tag, arg); + + va_end(arg); + return ret; +} + +#ifdef CURL_MULTIEASY +/*************************************************************************** + * This function is still only for testing purposes. It makes a great way + * to run the full test suite on the multi interface instead of the easy one. + *************************************************************************** + * + * The *new* curl_easy_perform() is the external interface that performs a + * transfer previously setup. + * + * Wrapper-function that: creates a multi handle, adds the easy handle to it, + * runs curl_multi_perform() until the transfer is done, then detaches the + * easy handle, destroys the multi handle and returns the easy handle's return + * code. This will make everything internally use and assume multi interface. + */ +CURLcode curl_easy_perform(CURL *easy) +{ + CURLM *multi; + CURLMcode mcode; + CURLcode code = CURLE_OK; + int still_running; + struct timeval timeout; + int rc; + CURLMsg *msg; + fd_set fdread; + fd_set fdwrite; + fd_set fdexcep; + int maxfd; + + if(!easy) + return CURLE_BAD_FUNCTION_ARGUMENT; + + multi = curl_multi_init(); + if(!multi) + return CURLE_OUT_OF_MEMORY; + + mcode = curl_multi_add_handle(multi, easy); + if(mcode) { + curl_multi_cleanup(multi); + if(mcode == CURLM_OUT_OF_MEMORY) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_FAILED_INIT; + } + + /* we start some action by calling perform right away */ + + do { + while(CURLM_CALL_MULTI_PERFORM == + curl_multi_perform(multi, &still_running)); + + if(!still_running) + break; + + FD_ZERO(&fdread); + FD_ZERO(&fdwrite); + FD_ZERO(&fdexcep); + + /* timeout once per second */ + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + /* Old deprecated style: get file descriptors from the transfers */ + curl_multi_fdset(multi, &fdread, &fdwrite, &fdexcep, &maxfd); + rc = Curl_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout); + + /* The way is to extract the sockets and wait for them without using + select. This whole alternative version should probably rather use the + curl_multi_socket() approach. */ + + if(rc == -1) + /* select error */ + break; + + /* timeout or data to send/receive => loop! */ + } while(still_running); + + msg = curl_multi_info_read(multi, &rc); + if(msg) + code = msg->data.result; + + mcode = curl_multi_remove_handle(multi, easy); + /* what to do if it fails? */ + + mcode = curl_multi_cleanup(multi); + /* what to do if it fails? */ + + return code; +} +#else +/* + * curl_easy_perform() is the external interface that performs a transfer + * previously setup. + */ +CURLcode curl_easy_perform(CURL *curl) +{ + struct SessionHandle *data = (struct SessionHandle *)curl; + + if(!data) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(! (data->share && data->share->hostcache)) { + /* this handle is not using a shared dns cache */ + + if(data->set.global_dns_cache && + (data->dns.hostcachetype != HCACHE_GLOBAL)) { + /* global dns cache was requested but still isn't */ + struct curl_hash *ptr; + + if(data->dns.hostcachetype == HCACHE_PRIVATE) { + /* if the current cache is private, kill it first */ + Curl_hash_destroy(data->dns.hostcache); + data->dns.hostcachetype = HCACHE_NONE; + data->dns.hostcache = NULL; + } + + ptr = Curl_global_host_cache_init(); + if(ptr) { + /* only do this if the global cache init works */ + data->dns.hostcache = ptr; + data->dns.hostcachetype = HCACHE_GLOBAL; + } + } + + if(!data->dns.hostcache) { + data->dns.hostcachetype = HCACHE_PRIVATE; + data->dns.hostcache = Curl_mk_dnscache(); + + if(!data->dns.hostcache) + /* While we possibly could survive and do good without a host cache, + the fact that creating it failed indicates that things are truly + screwed up and we should bail out! */ + return CURLE_OUT_OF_MEMORY; + } + + } + + if(!data->state.conn_cache) { + /* Oops, no connection cache, create one */ + data->state.conn_cache = Curl_conncache_init(CONNCACHE_PRIVATE); + if(!data->state.conn_cache) + return CURLE_OUT_OF_MEMORY; + } + + return Curl_perform(data); +} +#endif + +/* + * curl_easy_cleanup() is the external interface to cleaning/freeing the given + * easy handle. + */ +void curl_easy_cleanup(CURL *curl) +{ + struct SessionHandle *data = (struct SessionHandle *)curl; + + if(!data) + return; + + Curl_close(data); +} + +/* + * Store a pointed to the multi handle within the easy handle's data struct. + */ +void Curl_easy_addmulti(struct SessionHandle *data, + void *multi) +{ + data->multi = multi; + if(multi == NULL) + /* the association is cleared, mark the easy handle as not used by an + interface */ + data->state.used_interface = Curl_if_none; +} + +void Curl_easy_initHandleData(struct SessionHandle *data) +{ + memset(&data->req, 0, sizeof(struct SingleRequest)); + + data->req.maxdownload = -1; +} + +/* + * curl_easy_getinfo() is an external interface that allows an app to retrieve + * information from a performed transfer and similar. + */ +#undef curl_easy_getinfo +CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...) +{ + va_list arg; + void *paramp; + struct SessionHandle *data = (struct SessionHandle *)curl; + + va_start(arg, info); + paramp = va_arg(arg, void *); + + return Curl_getinfo(data, info, paramp); +} + +/* + * curl_easy_duphandle() is an external interface to allow duplication of a + * given input easy handle. The returned handle will be a new working handle + * with all options set exactly as the input source handle. + */ +CURL *curl_easy_duphandle(CURL *incurl) +{ + struct SessionHandle *data=(struct SessionHandle *)incurl; + + struct SessionHandle *outcurl = calloc(1, sizeof(struct SessionHandle)); + if(NULL == outcurl) + goto fail; + + /* + * We setup a few buffers we need. We should probably make them + * get setup on-demand in the code, as that would probably decrease + * the likeliness of us forgetting to init a buffer here in the future. + */ + outcurl->state.headerbuff = malloc(HEADERSIZE); + if(!outcurl->state.headerbuff) + goto fail; + outcurl->state.headersize = HEADERSIZE; + + /* copy all userdefined values */ + if(Curl_dupset(outcurl, data) != CURLE_OK) + goto fail; + + /* the connection cache is setup on demand */ + outcurl->state.conn_cache = NULL; + + outcurl->state.lastconnect = NULL; + + outcurl->progress.flags = data->progress.flags; + outcurl->progress.callback = data->progress.callback; + + if(data->cookies) { + /* If cookies are enabled in the parent handle, we enable them + in the clone as well! */ + outcurl->cookies = Curl_cookie_init(data, + data->cookies->filename, + outcurl->cookies, + data->set.cookiesession); + if(!outcurl->cookies) + goto fail; + } + + /* duplicate all values in 'change' */ + if(data->change.cookielist) { + outcurl->change.cookielist = + Curl_slist_duplicate(data->change.cookielist); + if(!outcurl->change.cookielist) + goto fail; + } + + if(data->change.url) { + outcurl->change.url = strdup(data->change.url); + if(!outcurl->change.url) + goto fail; + outcurl->change.url_alloc = TRUE; + } + + if(data->change.referer) { + outcurl->change.referer = strdup(data->change.referer); + if(!outcurl->change.referer) + goto fail; + outcurl->change.referer_alloc = TRUE; + } + + /* Clone the resolver handle, if present, for the new handle */ + if(Curl_resolver_duphandle(&outcurl->state.resolver, + data->state.resolver) != CURLE_OK) + goto fail; + + Curl_convert_setup(outcurl); + + Curl_easy_initHandleData(outcurl); + + outcurl->magic = CURLEASY_MAGIC_NUMBER; + + /* we reach this point and thus we are OK */ + + return outcurl; + + fail: + + if(outcurl) { + curl_slist_free_all(outcurl->change.cookielist); + outcurl->change.cookielist = NULL; + Curl_safefree(outcurl->state.headerbuff); + Curl_safefree(outcurl->change.url); + Curl_safefree(outcurl->change.referer); + Curl_freeset(outcurl); + free(outcurl); + } + + return NULL; +} + +/* + * curl_easy_reset() is an external interface that allows an app to re- + * initialize a session handle to the default values. + */ +void curl_easy_reset(CURL *curl) +{ + struct SessionHandle *data = (struct SessionHandle *)curl; + + Curl_safefree(data->state.pathbuffer); + + data->state.path = NULL; + + Curl_safefree(data->state.proto.generic); + + /* zero out UserDefined data: */ + Curl_freeset(data); + memset(&data->set, 0, sizeof(struct UserDefined)); + (void)Curl_init_userdefined(&data->set); + + /* zero out Progress data: */ + memset(&data->progress, 0, sizeof(struct Progress)); + + /* init Handle data */ + Curl_easy_initHandleData(data); + + data->progress.flags |= PGRS_HIDE; + data->state.current_speed = -1; /* init to negative == impossible */ +} + +/* + * curl_easy_pause() allows an application to pause or unpause a specific + * transfer and direction. This function sets the full new state for the + * current connection this easy handle operates on. + * + * NOTE: if you have the receiving paused and you call this function to remove + * the pausing, you may get your write callback called at this point. + * + * Action is a bitmask consisting of CURLPAUSE_* bits in curl/curl.h + */ +CURLcode curl_easy_pause(CURL *curl, int action) +{ + struct SessionHandle *data = (struct SessionHandle *)curl; + struct SingleRequest *k = &data->req; + CURLcode result = CURLE_OK; + + /* first switch off both pause bits */ + int newstate = k->keepon &~ (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE); + + /* set the new desired pause bits */ + newstate |= ((action & CURLPAUSE_RECV)?KEEP_RECV_PAUSE:0) | + ((action & CURLPAUSE_SEND)?KEEP_SEND_PAUSE:0); + + /* put it back in the keepon */ + k->keepon = newstate; + + if(!(newstate & KEEP_RECV_PAUSE) && data->state.tempwrite) { + /* we have a buffer for sending that we now seem to be able to deliver + since the receive pausing is lifted! */ + + /* get the pointer, type and length in local copies since the function may + return PAUSE again and then we'll get a new copy allocted and stored in + the tempwrite variables */ + char *tempwrite = data->state.tempwrite; + char *freewrite = tempwrite; /* store this pointer to free it later */ + size_t tempsize = data->state.tempwritesize; + int temptype = data->state.tempwritetype; + size_t chunklen; + + /* clear tempwrite here just to make sure it gets cleared if there's no + further use of it, and make sure we don't clear it after the function + invoke as it may have been set to a new value by then */ + data->state.tempwrite = NULL; + + /* since the write callback API is define to never exceed + CURL_MAX_WRITE_SIZE bytes in a single call, and since we may in fact + have more data than that in our buffer here, we must loop sending the + data in multiple calls until there's no data left or we get another + pause returned. + + A tricky part is that the function we call will "buffer" the data + itself when it pauses on a particular buffer, so we may need to do some + extra trickery if we get a pause return here. + */ + do { + chunklen = (tempsize > CURL_MAX_WRITE_SIZE)?CURL_MAX_WRITE_SIZE:tempsize; + + result = Curl_client_write(data->state.current_conn, + temptype, tempwrite, chunklen); + if(result) + /* failures abort the loop at once */ + break; + + if(data->state.tempwrite && (tempsize - chunklen)) { + /* Ouch, the reading is again paused and the block we send is now + "cached". If this is the final chunk we can leave it like this, but + if we have more chunks that are cached after this, we need to free + the newly cached one and put back a version that is truly the entire + contents that is saved for later + */ + char *newptr; + + /* note that tempsize is still the size as before the callback was + used, and thus the whole piece of data to keep */ + newptr = realloc(data->state.tempwrite, tempsize); + + if(!newptr) { + free(data->state.tempwrite); /* free old area */ + data->state.tempwrite = NULL; + result = CURLE_OUT_OF_MEMORY; + /* tempwrite will be freed further down */ + break; + } + data->state.tempwrite = newptr; /* store new pointer */ + memcpy(newptr, tempwrite, tempsize); + data->state.tempwritesize = tempsize; /* store new size */ + /* tempwrite will be freed further down */ + break; /* go back to pausing until further notice */ + } + else { + tempsize -= chunklen; /* left after the call above */ + tempwrite += chunklen; /* advance the pointer */ + } + + } while((result == CURLE_OK) && tempsize); + + free(freewrite); /* this is unconditionally no longer used */ + } + + return result; +} + + +static CURLcode easy_connection(struct SessionHandle *data, + curl_socket_t *sfd, + struct connectdata **connp) +{ + if(data == NULL) + return CURLE_BAD_FUNCTION_ARGUMENT; + + /* only allow these to be called on handles with CURLOPT_CONNECT_ONLY */ + if(!data->set.connect_only) { + failf(data, "CONNECT_ONLY is required!"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + + *sfd = Curl_getconnectinfo(data, connp); + + if(*sfd == CURL_SOCKET_BAD) { + failf(data, "Failed to get recent socket"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + + return CURLE_OK; +} + +/* + * Receives data from the connected socket. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + * Returns CURLE_OK on success, error code on error. + */ +CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) +{ + curl_socket_t sfd; + CURLcode ret; + ssize_t n1; + struct connectdata *c; + struct SessionHandle *data = (struct SessionHandle *)curl; + + ret = easy_connection(data, &sfd, &c); + if(ret) + return ret; + + *n = 0; + ret = Curl_read(c, sfd, buffer, buflen, &n1); + + if(ret != CURLE_OK) + return ret; + + *n = (size_t)n1; + + return CURLE_OK; +} + +/* + * Sends data over the connected socket. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + */ +CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, + size_t *n) +{ + curl_socket_t sfd; + CURLcode ret; + ssize_t n1; + struct connectdata *c = NULL; + struct SessionHandle *data = (struct SessionHandle *)curl; + + ret = easy_connection(data, &sfd, &c); + if(ret) + return ret; + + *n = 0; + ret = Curl_write(c, sfd, buffer, buflen, &n1); + + if(n1 == -1) + return CURLE_SEND_ERROR; + + /* detect EAGAIN */ + if((CURLE_OK == ret) && (0 == n1)) + return CURLE_AGAIN; + + *n = (size_t)n1; + + return ret; +} diff --git a/lib/curl_escape.c b/lib/curl_escape.c new file mode 100644 index 000000000..c0ed571bd --- /dev/null +++ b/lib/curl_escape.c @@ -0,0 +1,233 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +/* Escape and unescape URL encoding in strings. The functions return a new + * allocated string or NULL if an error occurred. */ + +#include "curl_setup.h" + +#include + +#include "curl_memory.h" +#include "curl_urldata.h" +#include "curl_warnless.h" +#include "curl_non_ascii.h" +#include "curl_escape.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* Portable character check (remember EBCDIC). Do not use isalnum() because + its behavior is altered by the current locale. + See http://tools.ietf.org/html/rfc3986#section-2.3 +*/ +static bool Curl_isunreserved(unsigned char in) +{ + switch (in) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '-': case '.': case '_': case '~': + return TRUE; + default: + break; + } + return FALSE; +} + +/* for ABI-compatibility with previous versions */ +char *curl_escape(const char *string, int inlength) +{ + return curl_easy_escape(NULL, string, inlength); +} + +/* for ABI-compatibility with previous versions */ +char *curl_unescape(const char *string, int length) +{ + return curl_easy_unescape(NULL, string, length, NULL); +} + +char *curl_easy_escape(CURL *handle, const char *string, int inlength) +{ + size_t alloc = (inlength?(size_t)inlength:strlen(string))+1; + char *ns; + char *testing_ptr = NULL; + unsigned char in; /* we need to treat the characters unsigned */ + size_t newlen = alloc; + size_t strindex=0; + size_t length; + CURLcode res; + + ns = malloc(alloc); + if(!ns) + return NULL; + + length = alloc-1; + while(length--) { + in = *string; + + if(Curl_isunreserved(in)) + /* just copy this */ + ns[strindex++]=in; + else { + /* encode it */ + newlen += 2; /* the size grows with two, since this'll become a %XX */ + if(newlen > alloc) { + alloc *= 2; + testing_ptr = realloc(ns, alloc); + if(!testing_ptr) { + free( ns ); + return NULL; + } + else { + ns = testing_ptr; + } + } + + res = Curl_convert_to_network(handle, &in, 1); + if(res) { + /* Curl_convert_to_network calls failf if unsuccessful */ + free(ns); + return NULL; + } + + snprintf(&ns[strindex], 4, "%%%02X", in); + + strindex+=3; + } + string++; + } + ns[strindex]=0; /* terminate it */ + return ns; +} + +/* + * Curl_urldecode() URL decodes the given string. + * + * Optionally detects control characters (byte codes lower than 32) in the + * data and rejects such data. + * + * Returns a pointer to a malloced string in *ostring with length given in + * *olen. If length == 0, the length is assumed to be strlen(string). + * + */ +CURLcode Curl_urldecode(struct SessionHandle *data, + const char *string, size_t length, + char **ostring, size_t *olen, + bool reject_ctrl) +{ + size_t alloc = (length?length:strlen(string))+1; + char *ns = malloc(alloc); + unsigned char in; + size_t strindex=0; + unsigned long hex; + CURLcode res; + + if(!ns) + return CURLE_OUT_OF_MEMORY; + + while(--alloc > 0) { + in = *string; + if(('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { + /* this is two hexadecimal digits following a '%' */ + char hexstr[3]; + char *ptr; + hexstr[0] = string[1]; + hexstr[1] = string[2]; + hexstr[2] = 0; + + hex = strtoul(hexstr, &ptr, 16); + + in = curlx_ultouc(hex); /* this long is never bigger than 255 anyway */ + + res = Curl_convert_from_network(data, &in, 1); + if(res) { + /* Curl_convert_from_network calls failf if unsuccessful */ + free(ns); + return res; + } + + string+=2; + alloc-=2; + } + if(reject_ctrl && (in < 0x20)) { + free(ns); + return CURLE_URL_MALFORMAT; + } + + ns[strindex++] = in; + string++; + } + ns[strindex]=0; /* terminate it */ + + if(olen) + /* store output size */ + *olen = strindex; + + if(ostring) + /* store output string */ + *ostring = ns; + + return CURLE_OK; +} + +/* + * Unescapes the given URL escaped string of given length. Returns a + * pointer to a malloced string with length given in *olen. + * If length == 0, the length is assumed to be strlen(string). + * If olen == NULL, no output length is stored. + */ +char *curl_easy_unescape(CURL *handle, const char *string, int length, + int *olen) +{ + char *str = NULL; + size_t inputlen = length; + size_t outputlen; + CURLcode res = Curl_urldecode(handle, string, inputlen, &str, &outputlen, + FALSE); + if(res) + return NULL; + if(olen) + *olen = curlx_uztosi(outputlen); + return str; +} + +/* For operating systems/environments that use different malloc/free + systems for the app and for this library, we provide a free that uses + the library's memory system */ +void curl_free(void *p) +{ + if(p) + free(p); +} diff --git a/lib/curl_file.c b/lib/curl_file.c new file mode 100644 index 000000000..6ea2bd769 --- /dev/null +++ b/lib/curl_file.c @@ -0,0 +1,591 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_FILE + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#include "curl_strtoofft.h" +#include "curl_urldata.h" +#include +#include "curl_progress.h" +#include "curl_sendf.h" +#include "curl_escape.h" +#include "curl_file.h" +#include "curl_speedcheck.h" +#include "curl_getinfo.h" +#include "curl_transfer.h" +#include "curl_url.h" +#include "curl_memory.h" +#include "curl_parsedate.h" /* for the week day and month names */ +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \ + defined(__SYMBIAN32__) +#define DOS_FILESYSTEM 1 +#endif + +#ifdef OPEN_NEEDS_ARG3 +# define open_readonly(p,f) open((p),(f),(0)) +#else +# define open_readonly(p,f) open((p),(f)) +#endif + +/* + * Forward declarations. + */ + +static CURLcode file_do(struct connectdata *, bool *done); +static CURLcode file_done(struct connectdata *conn, + CURLcode status, bool premature); +static CURLcode file_connect(struct connectdata *conn, bool *done); +static CURLcode file_disconnect(struct connectdata *conn, + bool dead_connection); + + +/* + * FILE scheme handler. + */ + +const struct Curl_handler Curl_handler_file = { + "FILE", /* scheme */ + ZERO_NULL, /* setup_connection */ + file_do, /* do_it */ + file_done, /* done */ + ZERO_NULL, /* do_more */ + file_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + file_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + 0, /* defport */ + CURLPROTO_FILE, /* protocol */ + PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */ +}; + + + /* + Check if this is a range download, and if so, set the internal variables + properly. This code is copied from the FTP implementation and might as + well be factored out. + */ +static CURLcode file_range(struct connectdata *conn) +{ + curl_off_t from, to; + curl_off_t totalsize=-1; + char *ptr; + char *ptr2; + struct SessionHandle *data = conn->data; + + if(data->state.use_range && data->state.range) { + from=curlx_strtoofft(data->state.range, &ptr, 0); + while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) + ptr++; + to=curlx_strtoofft(ptr, &ptr2, 0); + if(ptr == ptr2) { + /* we didn't get any digit */ + to=-1; + } + if((-1 == to) && (from>=0)) { + /* X - */ + data->state.resume_from = from; + DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n", + from)); + } + else if(from < 0) { + /* -Y */ + data->req.maxdownload = -from; + data->state.resume_from = from; + DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n", + -from)); + } + else { + /* X-Y */ + totalsize = to-from; + data->req.maxdownload = totalsize+1; /* include last byte */ + data->state.resume_from = from; + DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T + " getting %" FORMAT_OFF_T " bytes\n", + from, data->req.maxdownload)); + } + DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T + " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n", + from, to, data->req.maxdownload)); + } + else + data->req.maxdownload = -1; + return CURLE_OK; +} + +/* + * file_connect() gets called from Curl_protocol_connect() to allow us to + * do protocol-specific actions at connect-time. We emulate a + * connect-then-transfer protocol and "connect" to the file here + */ +static CURLcode file_connect(struct connectdata *conn, bool *done) +{ + struct SessionHandle *data = conn->data; + char *real_path; + struct FILEPROTO *file; + int fd; +#ifdef DOS_FILESYSTEM + int i; + char *actual_path; +#endif + + /* If there already is a protocol-specific struct allocated for this + sessionhandle, deal with it */ + Curl_reset_reqproto(conn); + + real_path = curl_easy_unescape(data, data->state.path, 0, NULL); + if(!real_path) + return CURLE_OUT_OF_MEMORY; + + if(!data->state.proto.file) { + file = calloc(1, sizeof(struct FILEPROTO)); + if(!file) { + free(real_path); + return CURLE_OUT_OF_MEMORY; + } + data->state.proto.file = file; + } + else { + /* file is not a protocol that can deal with "persistancy" */ + file = data->state.proto.file; + Curl_safefree(file->freepath); + file->path = NULL; + if(file->fd != -1) + close(file->fd); + file->fd = -1; + } + +#ifdef DOS_FILESYSTEM + /* If the first character is a slash, and there's + something that looks like a drive at the beginning of + the path, skip the slash. If we remove the initial + slash in all cases, paths without drive letters end up + relative to the current directory which isn't how + browsers work. + + Some browsers accept | instead of : as the drive letter + separator, so we do too. + + On other platforms, we need the slash to indicate an + absolute pathname. On Windows, absolute paths start + with a drive letter. + */ + actual_path = real_path; + if((actual_path[0] == '/') && + actual_path[1] && + (actual_path[2] == ':' || actual_path[2] == '|')) { + actual_path[2] = ':'; + actual_path++; + } + + /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */ + for(i=0; actual_path[i] != '\0'; ++i) + if(actual_path[i] == '/') + actual_path[i] = '\\'; + + fd = open_readonly(actual_path, O_RDONLY|O_BINARY); + file->path = actual_path; +#else + fd = open_readonly(real_path, O_RDONLY); + file->path = real_path; +#endif + file->freepath = real_path; /* free this when done */ + + file->fd = fd; + if(!data->set.upload && (fd == -1)) { + failf(data, "Couldn't open file %s", data->state.path); + file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE); + return CURLE_FILE_COULDNT_READ_FILE; + } + *done = TRUE; + + return CURLE_OK; +} + +static CURLcode file_done(struct connectdata *conn, + CURLcode status, bool premature) +{ + struct FILEPROTO *file = conn->data->state.proto.file; + (void)status; /* not used */ + (void)premature; /* not used */ + + if(file) { + Curl_safefree(file->freepath); + file->path = NULL; + if(file->fd != -1) + close(file->fd); + file->fd = -1; + } + + return CURLE_OK; +} + +static CURLcode file_disconnect(struct connectdata *conn, + bool dead_connection) +{ + struct FILEPROTO *file = conn->data->state.proto.file; + (void)dead_connection; /* not used */ + + if(file) { + Curl_safefree(file->freepath); + file->path = NULL; + if(file->fd != -1) + close(file->fd); + file->fd = -1; + } + + return CURLE_OK; +} + +#ifdef DOS_FILESYSTEM +#define DIRSEP '\\' +#else +#define DIRSEP '/' +#endif + +static CURLcode file_upload(struct connectdata *conn) +{ + struct FILEPROTO *file = conn->data->state.proto.file; + const char *dir = strchr(file->path, DIRSEP); + int fd; + int mode; + CURLcode res=CURLE_OK; + struct SessionHandle *data = conn->data; + char *buf = data->state.buffer; + size_t nread; + size_t nwrite; + curl_off_t bytecount = 0; + struct timeval now = Curl_tvnow(); + struct_stat file_stat; + const char* buf2; + + /* + * Since FILE: doesn't do the full init, we need to provide some extra + * assignments here. + */ + conn->fread_func = data->set.fread_func; + conn->fread_in = data->set.in; + conn->data->req.upload_fromhere = buf; + + if(!dir) + return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ + + if(!dir[1]) + return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ + +#ifdef O_BINARY +#define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY +#else +#define MODE_DEFAULT O_WRONLY|O_CREAT +#endif + + if(data->state.resume_from) + mode = MODE_DEFAULT|O_APPEND; + else + mode = MODE_DEFAULT|O_TRUNC; + + fd = open(file->path, mode, conn->data->set.new_file_perms); + if(fd < 0) { + failf(data, "Can't open %s for writing", file->path); + return CURLE_WRITE_ERROR; + } + + if(-1 != data->set.infilesize) + /* known size of data to "upload" */ + Curl_pgrsSetUploadSize(data, data->set.infilesize); + + /* treat the negative resume offset value as the case of "-" */ + if(data->state.resume_from < 0) { + if(fstat(fd, &file_stat)) { + close(fd); + failf(data, "Can't get the size of %s", file->path); + return CURLE_WRITE_ERROR; + } + else + data->state.resume_from = (curl_off_t)file_stat.st_size; + } + + while(res == CURLE_OK) { + int readcount; + res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount); + if(res) + break; + + if(readcount <= 0) /* fix questionable compare error. curlvms */ + break; + + nread = (size_t)readcount; + + /*skip bytes before resume point*/ + if(data->state.resume_from) { + if((curl_off_t)nread <= data->state.resume_from ) { + data->state.resume_from -= nread; + nread = 0; + buf2 = buf; + } + else { + buf2 = buf + data->state.resume_from; + nread -= (size_t)data->state.resume_from; + data->state.resume_from = 0; + } + } + else + buf2 = buf; + + /* write the data to the target */ + nwrite = write(fd, buf2, nread); + if(nwrite != nread) { + res = CURLE_SEND_ERROR; + break; + } + + bytecount += nread; + + Curl_pgrsSetUploadCounter(data, bytecount); + + if(Curl_pgrsUpdate(conn)) + res = CURLE_ABORTED_BY_CALLBACK; + else + res = Curl_speedcheck(data, now); + } + if(!res && Curl_pgrsUpdate(conn)) + res = CURLE_ABORTED_BY_CALLBACK; + + close(fd); + + return res; +} + +/* + * file_do() is the protocol-specific function for the do-phase, separated + * from the connect-phase above. Other protocols merely setup the transfer in + * the do-phase, to have it done in the main transfer loop but since some + * platforms we support don't allow select()ing etc on file handles (as + * opposed to sockets) we instead perform the whole do-operation in this + * function. + */ +static CURLcode file_do(struct connectdata *conn, bool *done) +{ + /* This implementation ignores the host name in conformance with + RFC 1738. Only local files (reachable via the standard file system) + are supported. This means that files on remotely mounted directories + (via NFS, Samba, NT sharing) can be accessed through a file:// URL + */ + CURLcode res = CURLE_OK; + struct_stat statbuf; /* struct_stat instead of struct stat just to allow the + Windows version to have a different struct without + having to redefine the simple word 'stat' */ + curl_off_t expected_size=0; + bool fstated=FALSE; + ssize_t nread; + struct SessionHandle *data = conn->data; + char *buf = data->state.buffer; + curl_off_t bytecount = 0; + int fd; + struct timeval now = Curl_tvnow(); + + *done = TRUE; /* unconditionally */ + + Curl_initinfo(data); + Curl_pgrsStartNow(data); + + if(data->set.upload) + return file_upload(conn); + + /* get the fd from the connection phase */ + fd = conn->data->state.proto.file->fd; + + /* VMS: This only works reliable for STREAMLF files */ + if(-1 != fstat(fd, &statbuf)) { + /* we could stat it, then read out the size */ + expected_size = statbuf.st_size; + /* and store the modification time */ + data->info.filetime = (long)statbuf.st_mtime; + fstated = TRUE; + } + + if(fstated && !data->state.range && data->set.timecondition) { + if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) { + *done = TRUE; + return CURLE_OK; + } + } + + /* If we have selected NOBODY and HEADER, it means that we only want file + information. Which for FILE can't be much more than the file size and + date. */ + if(data->set.opt_no_body && data->set.include_header && fstated) { + CURLcode result; + snprintf(buf, sizeof(data->state.buffer), + "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size); + result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); + if(result) + return result; + + result = Curl_client_write(conn, CLIENTWRITE_BOTH, + (char *)"Accept-ranges: bytes\r\n", 0); + if(result) + return result; + + if(fstated) { + time_t filetime = (time_t)statbuf.st_mtime; + struct tm buffer; + const struct tm *tm = &buffer; + result = Curl_gmtime(filetime, &buffer); + if(result) + return result; + + /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ + snprintf(buf, BUFSIZE-1, + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); + } + /* if we fstat()ed the file, set the file size to make it available post- + transfer */ + if(fstated) + Curl_pgrsSetDownloadSize(data, expected_size); + return result; + } + + /* Check whether file range has been specified */ + file_range(conn); + + /* Adjust the start offset in case we want to get the N last bytes + * of the stream iff the filesize could be determined */ + if(data->state.resume_from < 0) { + if(!fstated) { + failf(data, "Can't get the size of file."); + return CURLE_READ_ERROR; + } + else + data->state.resume_from += (curl_off_t)statbuf.st_size; + } + + if(data->state.resume_from <= expected_size) + expected_size -= data->state.resume_from; + else { + failf(data, "failed to resume file:// transfer"); + return CURLE_BAD_DOWNLOAD_RESUME; + } + + /* A high water mark has been specified so we obey... */ + if(data->req.maxdownload > 0) + expected_size = data->req.maxdownload; + + if(fstated && (expected_size == 0)) + return CURLE_OK; + + /* The following is a shortcut implementation of file reading + this is both more efficient than the former call to download() and + it avoids problems with select() and recv() on file descriptors + in Winsock */ + if(fstated) + Curl_pgrsSetDownloadSize(data, expected_size); + + if(data->state.resume_from) { + if(data->state.resume_from != + lseek(fd, data->state.resume_from, SEEK_SET)) + return CURLE_BAD_DOWNLOAD_RESUME; + } + + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + + while(res == CURLE_OK) { + /* Don't fill a whole buffer if we want less than all data */ + size_t bytestoread = + (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ? + curlx_sotouz(expected_size) : BUFSIZE - 1; + + nread = read(fd, buf, bytestoread); + + if(nread > 0) + buf[nread] = 0; + + if(nread <= 0 || expected_size == 0) + break; + + bytecount += nread; + expected_size -= nread; + + res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread); + if(res) + return res; + + Curl_pgrsSetDownloadCounter(data, bytecount); + + if(Curl_pgrsUpdate(conn)) + res = CURLE_ABORTED_BY_CALLBACK; + else + res = Curl_speedcheck(data, now); + } + if(Curl_pgrsUpdate(conn)) + res = CURLE_ABORTED_BY_CALLBACK; + + return res; +} + +#endif diff --git a/lib/curl_fileinfo.c b/lib/curl_fileinfo.c new file mode 100644 index 000000000..433c709b5 --- /dev/null +++ b/lib/curl_fileinfo.c @@ -0,0 +1,54 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2010-2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_strdup.h" +#include "curl_fileinfo.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +struct curl_fileinfo *Curl_fileinfo_alloc(void) +{ + struct curl_fileinfo *tmp = malloc(sizeof(struct curl_fileinfo)); + if(!tmp) + return NULL; + memset(tmp, 0, sizeof(struct curl_fileinfo)); + return tmp; +} + +void Curl_fileinfo_dtor(void *user, void *element) +{ + struct curl_fileinfo *finfo = element; + (void) user; + if(!finfo) + return; + + Curl_safefree(finfo->b_data); + + free(finfo); +} diff --git a/lib/curl_formdata.c b/lib/curl_formdata.c new file mode 100644 index 000000000..c7d85c4b4 --- /dev/null +++ b/lib/curl_formdata.c @@ -0,0 +1,1494 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +/* Length of the random boundary string. */ +#define BOUNDARY_LENGTH 40 + +#if !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY) + +#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME) +#include +#endif + +#include "curl_urldata.h" /* for struct SessionHandle */ +#include "curl_formdata.h" +#include "curl_rand.h" +#include "curl_strequal.h" +#include "curl_memory.h" +#include "curl_sendf.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#endif /* !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY) */ + +#ifndef CURL_DISABLE_HTTP + +#ifndef HAVE_BASENAME +static char *Curl_basename(char *path); +#define basename(x) Curl_basename((x)) +#endif + +static size_t readfromfile(struct Form *form, char *buffer, size_t size); + +/* What kind of Content-Type to use on un-specified files with unrecognized + extensions. */ +#define HTTPPOST_CONTENTTYPE_DEFAULT "application/octet-stream" + +#define FORM_FILE_SEPARATOR ',' +#define FORM_TYPE_SEPARATOR ';' + +/*************************************************************************** + * + * AddHttpPost() + * + * Adds a HttpPost structure to the list, if parent_post is given becomes + * a subpost of parent_post instead of a direct list element. + * + * Returns newly allocated HttpPost on success and NULL if malloc failed. + * + ***************************************************************************/ +static struct curl_httppost * +AddHttpPost(char *name, size_t namelength, + char *value, size_t contentslength, + char *buffer, size_t bufferlength, + char *contenttype, + long flags, + struct curl_slist* contentHeader, + char *showfilename, char *userp, + struct curl_httppost *parent_post, + struct curl_httppost **httppost, + struct curl_httppost **last_post) +{ + struct curl_httppost *post; + post = calloc(1, sizeof(struct curl_httppost)); + if(post) { + post->name = name; + post->namelength = (long)(name?(namelength?namelength:strlen(name)):0); + post->contents = value; + post->contentslength = (long)contentslength; + post->buffer = buffer; + post->bufferlength = (long)bufferlength; + post->contenttype = contenttype; + post->contentheader = contentHeader; + post->showfilename = showfilename; + post->userp = userp, + post->flags = flags; + } + else + return NULL; + + if(parent_post) { + /* now, point our 'more' to the original 'more' */ + post->more = parent_post->more; + + /* then move the original 'more' to point to ourselves */ + parent_post->more = post; + } + else { + /* make the previous point to this */ + if(*last_post) + (*last_post)->next = post; + else + (*httppost) = post; + + (*last_post) = post; + } + return post; +} + +/*************************************************************************** + * + * AddFormInfo() + * + * Adds a FormInfo structure to the list presented by parent_form_info. + * + * Returns newly allocated FormInfo on success and NULL if malloc failed/ + * parent_form_info is NULL. + * + ***************************************************************************/ +static FormInfo * AddFormInfo(char *value, + char *contenttype, + FormInfo *parent_form_info) +{ + FormInfo *form_info; + form_info = calloc(1, sizeof(struct FormInfo)); + if(form_info) { + if(value) + form_info->value = value; + if(contenttype) + form_info->contenttype = contenttype; + form_info->flags = HTTPPOST_FILENAME; + } + else + return NULL; + + if(parent_form_info) { + /* now, point our 'more' to the original 'more' */ + form_info->more = parent_form_info->more; + + /* then move the original 'more' to point to ourselves */ + parent_form_info->more = form_info; + } + + return form_info; +} + +/*************************************************************************** + * + * ContentTypeForFilename() + * + * Provides content type for filename if one of the known types (else + * (either the prevtype or the default is returned). + * + * Returns some valid contenttype for filename. + * + ***************************************************************************/ +static const char * ContentTypeForFilename (const char *filename, + const char *prevtype) +{ + const char *contenttype = NULL; + unsigned int i; + /* + * No type was specified, we scan through a few well-known + * extensions and pick the first we match! + */ + struct ContentType { + char extension[6]; + const char *type; + }; + static const struct ContentType ctts[]={ + {".gif", "image/gif"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".txt", "text/plain"}, + {".html", "text/html"}, + {".xml", "application/xml"} + }; + + if(prevtype) + /* default to the previously set/used! */ + contenttype = prevtype; + else + contenttype = HTTPPOST_CONTENTTYPE_DEFAULT; + + if(filename) { /* in case a NULL was passed in */ + for(i=0; i= strlen(ctts[i].extension)) { + if(strequal(filename + + strlen(filename) - strlen(ctts[i].extension), + ctts[i].extension)) { + contenttype = ctts[i].type; + break; + } + } + } + } + /* we have a contenttype by now */ + return contenttype; +} + +/*************************************************************************** + * + * memdup() + * + * Copies the 'source' data to a newly allocated buffer buffer (that is + * returned). Uses buffer_length if not null, else uses strlen to determine + * the length of the buffer to be copied + * + * Returns the new pointer or NULL on failure. + * + ***************************************************************************/ +static char *memdup(const char *src, size_t buffer_length) +{ + size_t length; + bool add = FALSE; + char *buffer; + + if(buffer_length) + length = buffer_length; + else if(src) { + length = strlen(src); + add = TRUE; + } + else + /* no length and a NULL src pointer! */ + return strdup(""); + + buffer = malloc(length+add); + if(!buffer) + return NULL; /* fail */ + + memcpy(buffer, src, length); + + /* if length unknown do null termination */ + if(add) + buffer[length] = '\0'; + + return buffer; +} + +/*************************************************************************** + * + * FormAdd() + * + * Stores a formpost parameter and builds the appropriate linked list. + * + * Has two principal functionalities: using files and byte arrays as + * post parts. Byte arrays are either copied or just the pointer is stored + * (as the user requests) while for files only the filename and not the + * content is stored. + * + * While you may have only one byte array for each name, multiple filenames + * are allowed (and because of this feature CURLFORM_END is needed after + * using CURLFORM_FILE). + * + * Examples: + * + * Simple name/value pair with copied contents: + * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", + * CURLFORM_COPYCONTENTS, "value", CURLFORM_END); + * + * name/value pair where only the content pointer is remembered: + * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", + * CURLFORM_PTRCONTENTS, ptr, CURLFORM_CONTENTSLENGTH, 10, CURLFORM_END); + * (if CURLFORM_CONTENTSLENGTH is missing strlen () is used) + * + * storing a filename (CONTENTTYPE is optional!): + * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", + * CURLFORM_FILE, "filename1", CURLFORM_CONTENTTYPE, "plain/text", + * CURLFORM_END); + * + * storing multiple filenames: + * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", + * CURLFORM_FILE, "filename1", CURLFORM_FILE, "filename2", CURLFORM_END); + * + * Returns: + * CURL_FORMADD_OK on success + * CURL_FORMADD_MEMORY if the FormInfo allocation fails + * CURL_FORMADD_OPTION_TWICE if one option is given twice for one Form + * CURL_FORMADD_NULL if a null pointer was given for a char + * CURL_FORMADD_MEMORY if the allocation of a FormInfo struct failed + * CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used + * CURL_FORMADD_INCOMPLETE if the some FormInfo is not complete (or error) + * CURL_FORMADD_MEMORY if a HttpPost struct cannot be allocated + * CURL_FORMADD_MEMORY if some allocation for string copying failed. + * CURL_FORMADD_ILLEGAL_ARRAY if an illegal option is used in an array + * + ***************************************************************************/ + +static +CURLFORMcode FormAdd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + va_list params) +{ + FormInfo *first_form, *current_form, *form = NULL; + CURLFORMcode return_value = CURL_FORMADD_OK; + const char *prevtype = NULL; + struct curl_httppost *post = NULL; + CURLformoption option; + struct curl_forms *forms = NULL; + char *array_value=NULL; /* value read from an array */ + + /* This is a state variable, that if TRUE means that we're parsing an + array that we got passed to us. If FALSE we're parsing the input + va_list arguments. */ + bool array_state = FALSE; + + /* + * We need to allocate the first struct to fill in. + */ + first_form = calloc(1, sizeof(struct FormInfo)); + if(!first_form) + return CURL_FORMADD_MEMORY; + + current_form = first_form; + + /* + * Loop through all the options set. Break if we have an error to report. + */ + while(return_value == CURL_FORMADD_OK) { + + /* first see if we have more parts of the array param */ + if(array_state && forms) { + /* get the upcoming option from the given array */ + option = forms->option; + array_value = (char *)forms->value; + + forms++; /* advance this to next entry */ + if(CURLFORM_END == option) { + /* end of array state */ + array_state = FALSE; + continue; + } + } + else { + /* This is not array-state, get next option */ + option = va_arg(params, CURLformoption); + if(CURLFORM_END == option) + break; + } + + switch (option) { + case CURLFORM_ARRAY: + if(array_state) + /* we don't support an array from within an array */ + return_value = CURL_FORMADD_ILLEGAL_ARRAY; + else { + forms = va_arg(params, struct curl_forms *); + if(forms) + array_state = TRUE; + else + return_value = CURL_FORMADD_NULL; + } + break; + + /* + * Set the Name property. + */ + case CURLFORM_PTRNAME: +#ifdef CURL_DOES_CONVERSIONS + /* Treat CURLFORM_PTR like CURLFORM_COPYNAME so that libcurl will copy + * the data in all cases so that we'll have safe memory for the eventual + * conversion. + */ +#else + current_form->flags |= HTTPPOST_PTRNAME; /* fall through */ +#endif + case CURLFORM_COPYNAME: + if(current_form->name) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + char *name = array_state? + array_value:va_arg(params, char *); + if(name) + current_form->name = name; /* store for the moment */ + else + return_value = CURL_FORMADD_NULL; + } + break; + case CURLFORM_NAMELENGTH: + if(current_form->namelength) + return_value = CURL_FORMADD_OPTION_TWICE; + else + current_form->namelength = + array_state?(size_t)array_value:(size_t)va_arg(params, long); + break; + + /* + * Set the contents property. + */ + case CURLFORM_PTRCONTENTS: + current_form->flags |= HTTPPOST_PTRCONTENTS; /* fall through */ + case CURLFORM_COPYCONTENTS: + if(current_form->value) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + char *value = + array_state?array_value:va_arg(params, char *); + if(value) + current_form->value = value; /* store for the moment */ + else + return_value = CURL_FORMADD_NULL; + } + break; + case CURLFORM_CONTENTSLENGTH: + if(current_form->contentslength) + return_value = CURL_FORMADD_OPTION_TWICE; + else + current_form->contentslength = + array_state?(size_t)array_value:(size_t)va_arg(params, long); + break; + + /* Get contents from a given file name */ + case CURLFORM_FILECONTENT: + if(current_form->flags != 0) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + const char *filename = array_state? + array_value:va_arg(params, char *); + if(filename) { + current_form->value = strdup(filename); + if(!current_form->value) + return_value = CURL_FORMADD_MEMORY; + else { + current_form->flags |= HTTPPOST_READFILE; + current_form->value_alloc = TRUE; + } + } + else + return_value = CURL_FORMADD_NULL; + } + break; + + /* We upload a file */ + case CURLFORM_FILE: + { + const char *filename = array_state?array_value: + va_arg(params, char *); + + if(current_form->value) { + if(current_form->flags & HTTPPOST_FILENAME) { + if(filename) { + char *fname = strdup(filename); + if(!fname) + return_value = CURL_FORMADD_MEMORY; + else { + form = AddFormInfo(fname, NULL, current_form); + if(!form) { + Curl_safefree(fname); + return_value = CURL_FORMADD_MEMORY; + } + else { + form->value_alloc = TRUE; + current_form = form; + form = NULL; + } + } + } + else + return_value = CURL_FORMADD_NULL; + } + else + return_value = CURL_FORMADD_OPTION_TWICE; + } + else { + if(filename) { + current_form->value = strdup(filename); + if(!current_form->value) + return_value = CURL_FORMADD_MEMORY; + else { + current_form->flags |= HTTPPOST_FILENAME; + current_form->value_alloc = TRUE; + } + } + else + return_value = CURL_FORMADD_NULL; + } + break; + } + + case CURLFORM_BUFFERPTR: + current_form->flags |= HTTPPOST_PTRBUFFER|HTTPPOST_BUFFER; + if(current_form->buffer) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + char *buffer = + array_state?array_value:va_arg(params, char *); + if(buffer) { + current_form->buffer = buffer; /* store for the moment */ + current_form->value = buffer; /* make it non-NULL to be accepted + as fine */ + } + else + return_value = CURL_FORMADD_NULL; + } + break; + + case CURLFORM_BUFFERLENGTH: + if(current_form->bufferlength) + return_value = CURL_FORMADD_OPTION_TWICE; + else + current_form->bufferlength = + array_state?(size_t)array_value:(size_t)va_arg(params, long); + break; + + case CURLFORM_STREAM: + current_form->flags |= HTTPPOST_CALLBACK; + if(current_form->userp) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + char *userp = + array_state?array_value:va_arg(params, char *); + if(userp) { + current_form->userp = userp; + current_form->value = userp; /* this isn't strictly true but we + derive a value from this later on + and we need this non-NULL to be + accepted as a fine form part */ + } + else + return_value = CURL_FORMADD_NULL; + } + break; + + case CURLFORM_CONTENTTYPE: + { + const char *contenttype = + array_state?array_value:va_arg(params, char *); + if(current_form->contenttype) { + if(current_form->flags & HTTPPOST_FILENAME) { + if(contenttype) { + char *type = strdup(contenttype); + if(!type) + return_value = CURL_FORMADD_MEMORY; + else { + form = AddFormInfo(NULL, type, current_form); + if(!form) { + Curl_safefree(type); + return_value = CURL_FORMADD_MEMORY; + } + else { + form->contenttype_alloc = TRUE; + current_form = form; + form = NULL; + } + } + } + else + return_value = CURL_FORMADD_NULL; + } + else + return_value = CURL_FORMADD_OPTION_TWICE; + } + else { + if(contenttype) { + current_form->contenttype = strdup(contenttype); + if(!current_form->contenttype) + return_value = CURL_FORMADD_MEMORY; + else + current_form->contenttype_alloc = TRUE; + } + else + return_value = CURL_FORMADD_NULL; + } + break; + } + case CURLFORM_CONTENTHEADER: + { + /* this "cast increases required alignment of target type" but + we consider it OK anyway */ + struct curl_slist* list = array_state? + (struct curl_slist*)array_value: + va_arg(params, struct curl_slist*); + + if(current_form->contentheader) + return_value = CURL_FORMADD_OPTION_TWICE; + else + current_form->contentheader = list; + + break; + } + case CURLFORM_FILENAME: + case CURLFORM_BUFFER: + { + const char *filename = array_state?array_value: + va_arg(params, char *); + if(current_form->showfilename) + return_value = CURL_FORMADD_OPTION_TWICE; + else { + current_form->showfilename = strdup(filename); + if(!current_form->showfilename) + return_value = CURL_FORMADD_MEMORY; + else + current_form->showfilename_alloc = TRUE; + } + break; + } + default: + return_value = CURL_FORMADD_UNKNOWN_OPTION; + break; + } + } + + if(CURL_FORMADD_OK != return_value) { + /* On error, free allocated fields for all nodes of the FormInfo linked + list without deallocating nodes. List nodes are deallocated later on */ + FormInfo *ptr; + for(ptr = first_form; ptr != NULL; ptr = ptr->more) { + if(ptr->name_alloc) { + Curl_safefree(ptr->name); + ptr->name_alloc = FALSE; + } + if(ptr->value_alloc) { + Curl_safefree(ptr->value); + ptr->value_alloc = FALSE; + } + if(ptr->contenttype_alloc) { + Curl_safefree(ptr->contenttype); + ptr->contenttype_alloc = FALSE; + } + if(ptr->showfilename_alloc) { + Curl_safefree(ptr->showfilename); + ptr->showfilename_alloc = FALSE; + } + } + } + + if(CURL_FORMADD_OK == return_value) { + /* go through the list, check for completeness and if everything is + * alright add the HttpPost item otherwise set return_value accordingly */ + + post = NULL; + for(form = first_form; + form != NULL; + form = form->more) { + if(((!form->name || !form->value) && !post) || + ( (form->contentslength) && + (form->flags & HTTPPOST_FILENAME) ) || + ( (form->flags & HTTPPOST_FILENAME) && + (form->flags & HTTPPOST_PTRCONTENTS) ) || + + ( (!form->buffer) && + (form->flags & HTTPPOST_BUFFER) && + (form->flags & HTTPPOST_PTRBUFFER) ) || + + ( (form->flags & HTTPPOST_READFILE) && + (form->flags & HTTPPOST_PTRCONTENTS) ) + ) { + return_value = CURL_FORMADD_INCOMPLETE; + break; + } + else { + if(((form->flags & HTTPPOST_FILENAME) || + (form->flags & HTTPPOST_BUFFER)) && + !form->contenttype ) { + /* our contenttype is missing */ + form->contenttype + = strdup(ContentTypeForFilename(form->value, prevtype)); + if(!form->contenttype) { + return_value = CURL_FORMADD_MEMORY; + break; + } + form->contenttype_alloc = TRUE; + } + if(!(form->flags & HTTPPOST_PTRNAME) && + (form == first_form) ) { + /* Note that there's small risk that form->name is NULL here if the + app passed in a bad combo, so we better check for that first. */ + if(form->name) + /* copy name (without strdup; possibly contains null characters) */ + form->name = memdup(form->name, form->namelength); + if(!form->name) { + return_value = CURL_FORMADD_MEMORY; + break; + } + form->name_alloc = TRUE; + } + if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | + HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | + HTTPPOST_CALLBACK)) ) { + /* copy value (without strdup; possibly contains null characters) */ + form->value = memdup(form->value, form->contentslength); + if(!form->value) { + return_value = CURL_FORMADD_MEMORY; + break; + } + form->value_alloc = TRUE; + } + post = AddHttpPost(form->name, form->namelength, + form->value, form->contentslength, + form->buffer, form->bufferlength, + form->contenttype, form->flags, + form->contentheader, form->showfilename, + form->userp, + post, httppost, + last_post); + + if(!post) { + return_value = CURL_FORMADD_MEMORY; + break; + } + + if(form->contenttype) + prevtype = form->contenttype; + } + } + if(CURL_FORMADD_OK != return_value) { + /* On error, free allocated fields for nodes of the FormInfo linked + list which are not already owned by the httppost linked list + without deallocating nodes. List nodes are deallocated later on */ + FormInfo *ptr; + for(ptr = form; ptr != NULL; ptr = ptr->more) { + if(ptr->name_alloc) { + Curl_safefree(ptr->name); + ptr->name_alloc = FALSE; + } + if(ptr->value_alloc) { + Curl_safefree(ptr->value); + ptr->value_alloc = FALSE; + } + if(ptr->contenttype_alloc) { + Curl_safefree(ptr->contenttype); + ptr->contenttype_alloc = FALSE; + } + if(ptr->showfilename_alloc) { + Curl_safefree(ptr->showfilename); + ptr->showfilename_alloc = FALSE; + } + } + } + } + + /* Always deallocate FormInfo linked list nodes without touching node + fields given that these have either been deallocated or are owned + now by the httppost linked list */ + while(first_form) { + FormInfo *ptr = first_form->more; + Curl_safefree(first_form); + first_form = ptr; + } + + return return_value; +} + +/* + * curl_formadd() is a public API to add a section to the multipart formpost. + * + * @unittest: 1308 + */ + +CURLFORMcode curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + ...) +{ + va_list arg; + CURLFORMcode result; + va_start(arg, last_post); + result = FormAdd(httppost, last_post, arg); + va_end(arg); + return result; +} + +/* + * AddFormData() adds a chunk of data to the FormData linked list. + * + * size is incremented by the chunk length, unless it is NULL + */ +static CURLcode AddFormData(struct FormData **formp, + enum formtype type, + const void *line, + size_t length, + curl_off_t *size) +{ + struct FormData *newform = malloc(sizeof(struct FormData)); + if(!newform) + return CURLE_OUT_OF_MEMORY; + newform->next = NULL; + + if(type <= FORM_CONTENT) { + /* we make it easier for plain strings: */ + if(!length) + length = strlen((char *)line); + + newform->line = malloc(length+1); + if(!newform->line) { + free(newform); + return CURLE_OUT_OF_MEMORY; + } + memcpy(newform->line, line, length); + newform->length = length; + newform->line[length]=0; /* zero terminate for easier debugging */ + } + else + /* For callbacks and files we don't have any actual data so we just keep a + pointer to whatever this points to */ + newform->line = (char *)line; + + newform->type = type; + + if(*formp) { + (*formp)->next = newform; + *formp = newform; + } + else + *formp = newform; + + if(size) { + if(type != FORM_FILE) + /* for static content as well as callback data we add the size given + as input argument */ + *size += length; + else { + /* Since this is a file to be uploaded here, add the size of the actual + file */ + if(!strequal("-", newform->line)) { + struct_stat file; + if(!stat(newform->line, &file)) { + *size += file.st_size; + } + } + } + } + return CURLE_OK; +} + +/* + * AddFormDataf() adds printf()-style formatted data to the formdata chain. + */ + +static CURLcode AddFormDataf(struct FormData **formp, + curl_off_t *size, + const char *fmt, ...) +{ + char s[4096]; + va_list ap; + va_start(ap, fmt); + vsnprintf(s, sizeof(s), fmt, ap); + va_end(ap); + + return AddFormData(formp, FORM_DATA, s, 0, size); +} + +/* + * Curl_formclean() is used from curl_http.c, this cleans a built FormData + * linked list + */ +void Curl_formclean(struct FormData **form_ptr) +{ + struct FormData *next, *form; + + form = *form_ptr; + if(!form) + return; + + do { + next=form->next; /* the following form line */ + if(form->type <= FORM_CONTENT) + free(form->line); /* free the line */ + free(form); /* free the struct */ + + } while((form = next) != NULL); /* continue */ + + *form_ptr = NULL; +} + +/* + * curl_formget() + * Serialize a curl_httppost struct. + * Returns 0 on success. + * + * @unittest: 1308 + */ +int curl_formget(struct curl_httppost *form, void *arg, + curl_formget_callback append) +{ + CURLcode rc; + curl_off_t size; + struct FormData *data, *ptr; + + rc = Curl_getformdata(NULL, &data, form, NULL, &size); + if(rc != CURLE_OK) + return (int)rc; + + for(ptr = data; ptr; ptr = ptr->next) { + if((ptr->type == FORM_FILE) || (ptr->type == FORM_CALLBACK)) { + char buffer[8192]; + size_t nread; + struct Form temp; + + Curl_FormInit(&temp, ptr); + + do { + nread = readfromfile(&temp, buffer, sizeof(buffer)); + if((nread == (size_t) -1) || + (nread > sizeof(buffer)) || + (nread != append(arg, buffer, nread))) { + if(temp.fp) + fclose(temp.fp); + Curl_formclean(&data); + return -1; + } + } while(nread); + } + else { + if(ptr->length != append(arg, ptr->line, ptr->length)) { + Curl_formclean(&data); + return -1; + } + } + } + Curl_formclean(&data); + return 0; +} + +/* + * curl_formfree() is an external function to free up a whole form post + * chain + */ +void curl_formfree(struct curl_httppost *form) +{ + struct curl_httppost *next; + + if(!form) + /* no form to free, just get out of this */ + return; + + do { + next=form->next; /* the following form line */ + + /* recurse to sub-contents */ + if(form->more) + curl_formfree(form->more); + + if(!(form->flags & HTTPPOST_PTRNAME) && form->name) + free(form->name); /* free the name */ + if(!(form->flags & + (HTTPPOST_PTRCONTENTS|HTTPPOST_BUFFER|HTTPPOST_CALLBACK)) && + form->contents) + free(form->contents); /* free the contents */ + if(form->contenttype) + free(form->contenttype); /* free the content type */ + if(form->showfilename) + free(form->showfilename); /* free the faked file name */ + free(form); /* free the struct */ + + } while((form = next) != NULL); /* continue */ +} + +#ifndef HAVE_BASENAME +/* + (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 + Edition) + + The basename() function shall take the pathname pointed to by path and + return a pointer to the final component of the pathname, deleting any + trailing '/' characters. + + If the string pointed to by path consists entirely of the '/' character, + basename() shall return a pointer to the string "/". If the string pointed + to by path is exactly "//", it is implementation-defined whether '/' or "//" + is returned. + + If path is a null pointer or points to an empty string, basename() shall + return a pointer to the string ".". + + The basename() function may modify the string pointed to by path, and may + return a pointer to static storage that may then be overwritten by a + subsequent call to basename(). + + The basename() function need not be reentrant. A function that is not + required to be reentrant is not required to be thread-safe. + +*/ +static char *Curl_basename(char *path) +{ + /* Ignore all the details above for now and make a quick and simple + implementaion here */ + char *s1; + char *s2; + + s1=strrchr(path, '/'); + s2=strrchr(path, '\\'); + + if(s1 && s2) { + path = (s1 > s2? s1 : s2)+1; + } + else if(s1) + path = s1 + 1; + else if(s2) + path = s2 + 1; + + return path; +} +#endif + +static char *strippath(const char *fullfile) +{ + char *filename; + char *base; + filename = strdup(fullfile); /* duplicate since basename() may ruin the + buffer it works on */ + if(!filename) + return NULL; + base = strdup(basename(filename)); + + free(filename); /* free temporary buffer */ + + return base; /* returns an allocated string or NULL ! */ +} + +/* + * Curl_getformdata() converts a linked list of "meta data" into a complete + * (possibly huge) multipart formdata. The input list is in 'post', while the + * output resulting linked lists gets stored in '*finalform'. *sizep will get + * the total size of the whole POST. + * A multipart/form_data content-type is built, unless a custom content-type + * is passed in 'custom_content_type'. + * + * This function will not do a failf() for the potential memory failures but + * should for all other errors it spots. Just note that this function MAY get + * a NULL pointer in the 'data' argument. + */ + +CURLcode Curl_getformdata(struct SessionHandle *data, + struct FormData **finalform, + struct curl_httppost *post, + const char *custom_content_type, + curl_off_t *sizep) +{ + struct FormData *form = NULL; + struct FormData *firstform; + struct curl_httppost *file; + CURLcode result = CURLE_OK; + + curl_off_t size = 0; /* support potentially ENORMOUS formposts */ + char *boundary; + char *fileboundary = NULL; + struct curl_slist* curList; + + *finalform = NULL; /* default form is empty */ + + if(!post) + return result; /* no input => no output! */ + + boundary = Curl_FormBoundary(); + if(!boundary) + return CURLE_OUT_OF_MEMORY; + + /* Make the first line of the output */ + result = AddFormDataf(&form, NULL, + "%s; boundary=%s\r\n", + custom_content_type?custom_content_type: + "Content-Type: multipart/form-data", + boundary); + + if(result) { + Curl_safefree(boundary); + return result; + } + /* we DO NOT include that line in the total size of the POST, since it'll be + part of the header! */ + + firstform = form; + + do { + + if(size) { + result = AddFormDataf(&form, &size, "\r\n"); + if(result) + break; + } + + /* boundary */ + result = AddFormDataf(&form, &size, "--%s\r\n", boundary); + if(result) + break; + + /* Maybe later this should be disabled when a custom_content_type is + passed, since Content-Disposition is not meaningful for all multipart + types. + */ + result = AddFormDataf(&form, &size, + "Content-Disposition: form-data; name=\""); + if(result) + break; + + result = AddFormData(&form, FORM_DATA, post->name, post->namelength, + &size); + if(result) + break; + + result = AddFormDataf(&form, &size, "\""); + if(result) + break; + + if(post->more) { + /* If used, this is a link to more file names, we must then do + the magic to include several files with the same field name */ + + Curl_safefree(fileboundary); + fileboundary = Curl_FormBoundary(); + if(!fileboundary) { + result = CURLE_OUT_OF_MEMORY; + break; + } + + result = AddFormDataf(&form, &size, + "\r\nContent-Type: multipart/mixed," + " boundary=%s\r\n", + fileboundary); + if(result) + break; + } + + file = post; + + do { + + /* If 'showfilename' is set, that is a faked name passed on to us + to use to in the formpost. If that is not set, the actually used + local file name should be added. */ + + if(post->more) { + /* if multiple-file */ + char *filebasename = NULL; + if(!file->showfilename) { + filebasename = strippath(file->contents); + if(!filebasename) { + result = CURLE_OUT_OF_MEMORY; + break; + } + } + + result = AddFormDataf(&form, &size, + "\r\n--%s\r\nContent-Disposition: " + "attachment; filename=\"%s\"", + fileboundary, + (file->showfilename?file->showfilename: + filebasename)); + Curl_safefree(filebasename); + if(result) + break; + } + else if(post->flags & (HTTPPOST_FILENAME|HTTPPOST_BUFFER| + HTTPPOST_CALLBACK)) { + /* it should be noted that for the HTTPPOST_FILENAME and + HTTPPOST_CALLBACK cases the ->showfilename struct member is always + assigned at this point */ + if(post->showfilename || (post->flags & HTTPPOST_FILENAME)) { + char *filebasename= + (!post->showfilename)?strippath(post->contents):NULL; + + result = AddFormDataf(&form, &size, + "; filename=\"%s\"", + (post->showfilename?post->showfilename: + filebasename)); + Curl_safefree(filebasename); + } + + if(result) + break; + } + + if(file->contenttype) { + /* we have a specified type */ + result = AddFormDataf(&form, &size, + "\r\nContent-Type: %s", + file->contenttype); + if(result) + break; + } + + curList = file->contentheader; + while(curList) { + /* Process the additional headers specified for this form */ + result = AddFormDataf( &form, &size, "\r\n%s", curList->data ); + if(result) + break; + curList = curList->next; + } + if(result) + break; + + result = AddFormDataf(&form, &size, "\r\n\r\n"); + if(result) + break; + + if((post->flags & HTTPPOST_FILENAME) || + (post->flags & HTTPPOST_READFILE)) { + /* we should include the contents from the specified file */ + FILE *fileread; + + fileread = strequal("-", file->contents)? + stdin:fopen(file->contents, "rb"); /* binary read for win32 */ + + /* + * VMS: This only allows for stream files on VMS. Stream files are + * OK, as are FIXED & VAR files WITHOUT implied CC For implied CC, + * every record needs to have a \n appended & 1 added to SIZE + */ + + if(fileread) { + if(fileread != stdin) { + /* close the file */ + fclose(fileread); + /* add the file name only - for later reading from this */ + result = AddFormData(&form, FORM_FILE, file->contents, 0, &size); + } + else { + /* When uploading from stdin, we can't know the size of the file, + * thus must read the full file as before. We *could* use chunked + * transfer-encoding, but that only works for HTTP 1.1 and we + * can't be sure we work with such a server. + */ + size_t nread; + char buffer[512]; + while((nread = fread(buffer, 1, sizeof(buffer), fileread)) != 0) { + result = AddFormData(&form, FORM_CONTENT, buffer, nread, &size); + if(result) + break; + } + } + } + else { + if(data) + failf(data, "couldn't open file \"%s\"", file->contents); + *finalform = NULL; + result = CURLE_READ_ERROR; + } + } + else if(post->flags & HTTPPOST_BUFFER) + /* include contents of buffer */ + result = AddFormData(&form, FORM_CONTENT, post->buffer, + post->bufferlength, &size); + else if(post->flags & HTTPPOST_CALLBACK) + /* the contents should be read with the callback and the size + is set with the contentslength */ + result = AddFormData(&form, FORM_CALLBACK, post->userp, + post->contentslength, &size); + else + /* include the contents we got */ + result = AddFormData(&form, FORM_CONTENT, post->contents, + post->contentslength, &size); + + file = file->more; + } while(file && !result); /* for each specified file for this field */ + + if(result) + break; + + if(post->more) { + /* this was a multiple-file inclusion, make a termination file + boundary: */ + result = AddFormDataf(&form, &size, + "\r\n--%s--", + fileboundary); + if(result) + break; + } + + } while((post = post->next) != NULL); /* for each field */ + + /* end-boundary for everything */ + if(CURLE_OK == result) + result = AddFormDataf(&form, &size, + "\r\n--%s--\r\n", + boundary); + + if(result) { + Curl_formclean(&firstform); + Curl_safefree(fileboundary); + Curl_safefree(boundary); + return result; + } + + *sizep = size; + + Curl_safefree(fileboundary); + Curl_safefree(boundary); + + *finalform = firstform; + + return result; +} + +/* + * Curl_FormInit() inits the struct 'form' points to with the 'formdata' + * and resets the 'sent' counter. + */ +int Curl_FormInit(struct Form *form, struct FormData *formdata ) +{ + if(!formdata) + return 1; /* error */ + + form->data = formdata; + form->sent = 0; + form->fp = NULL; + form->fread_func = ZERO_NULL; + + return 0; +} + +/* + * readfromfile() + * + * The read callback that this function may use can return a value larger than + * 'size' (which then this function returns) that indicates a problem and it + * must be properly dealt with + */ +static size_t readfromfile(struct Form *form, char *buffer, + size_t size) +{ + size_t nread; + bool callback = (form->data->type == FORM_CALLBACK)?TRUE:FALSE; + + if(callback) { + if(form->fread_func == ZERO_NULL) + return 0; + else + nread = form->fread_func(buffer, 1, size, form->data->line); + } + else { + if(!form->fp) { + /* this file hasn't yet been opened */ + form->fp = fopen(form->data->line, "rb"); /* b is for binary */ + if(!form->fp) + return (size_t)-1; /* failure */ + } + nread = fread(buffer, 1, size, form->fp); + } + if(!nread) { + /* this is the last chunk from the file, move on */ + if(form->fp) { + fclose(form->fp); + form->fp = NULL; + } + form->data = form->data->next; + } + + return nread; +} + +/* + * Curl_FormReader() is the fread() emulation function that will be used to + * deliver the formdata to the transfer loop and then sent away to the peer. + */ +size_t Curl_FormReader(char *buffer, + size_t size, + size_t nitems, + FILE *mydata) +{ + struct Form *form; + size_t wantedsize; + size_t gotsize = 0; + + form=(struct Form *)mydata; + + wantedsize = size * nitems; + + if(!form->data) + return 0; /* nothing, error, empty */ + + if((form->data->type == FORM_FILE) || + (form->data->type == FORM_CALLBACK)) { + gotsize = readfromfile(form, buffer, wantedsize); + + if(gotsize) + /* If positive or -1, return. If zero, continue! */ + return gotsize; + } + do { + + if((form->data->length - form->sent ) > wantedsize - gotsize) { + + memcpy(buffer + gotsize , form->data->line + form->sent, + wantedsize - gotsize); + + form->sent += wantedsize-gotsize; + + return wantedsize; + } + + memcpy(buffer+gotsize, + form->data->line + form->sent, + (form->data->length - form->sent) ); + gotsize += form->data->length - form->sent; + + form->sent = 0; + + form->data = form->data->next; /* advance */ + + } while(form->data && (form->data->type < FORM_CALLBACK)); + /* If we got an empty line and we have more data, we proceed to the next + line immediately to avoid returning zero before we've reached the end. */ + + return gotsize; +} + +/* + * Curl_formpostheader() returns the first line of the formpost, the + * request-header part (which is not part of the request-body like the rest of + * the post). + */ +char *Curl_formpostheader(void *formp, size_t *len) +{ + char *header; + struct Form *form=(struct Form *)formp; + + if(!form->data) + return 0; /* nothing, ERROR! */ + + header = form->data->line; + *len = form->data->length; + + form->data = form->data->next; /* advance */ + + return header; +} + +#else /* CURL_DISABLE_HTTP */ +CURLFORMcode curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + ...) +{ + (void)httppost; + (void)last_post; + return CURL_FORMADD_DISABLED; +} + +int curl_formget(struct curl_httppost *form, void *arg, + curl_formget_callback append) +{ + (void) form; + (void) arg; + (void) append; + return CURL_FORMADD_DISABLED; +} + +void curl_formfree(struct curl_httppost *form) +{ + (void)form; + /* does nothing HTTP is disabled */ +} + +#endif /* CURL_DISABLE_HTTP */ + +#if !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY) + +/* + * Curl_FormBoundary() creates a suitable boundary string and returns an + * allocated one. This is also used by SSL-code so it must be present even + * if HTTP is disabled! + */ +char *Curl_FormBoundary(void) +{ + char *retstring; + size_t i; + + static const char table16[]="0123456789abcdef"; + + retstring = malloc(BOUNDARY_LENGTH+1); + + if(!retstring) + return NULL; /* failed */ + + strcpy(retstring, "----------------------------"); + + for(i=strlen(retstring); i, et al. + * + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_FTP + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_if2ip.h" +#include "curl_hostip.h" +#include "curl_progress.h" +#include "curl_transfer.h" +#include "curl_escape.h" +#include "curl_http.h" /* for HTTP proxy tunnel stuff */ +#include "curl_socks.h" +#include "curl_ftp.h" +#include "curl_fileinfo.h" +#include "curl_ftplistparser.h" + +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) +#include "curl_krb4.h" +#endif + +#include "curl_strtoofft.h" +#include "curl_strequal.h" +#include "curl_sslgen.h" +#include "curl_connect.h" +#include "curl_strerror.h" +#include "curl_inet_ntop.h" +#include "curl_inet_pton.h" +#include "curl_select.h" +#include "curl_parsedate.h" /* for the week day and month names */ +#include "curl_sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "curl_multiif.h" +#include "curl_url.h" +#include "curl_rawstr.h" +#include "curl_speedcheck.h" +#include "curl_warnless.h" +#include "curl_http_proxy.h" +#include "curl_non_ascii.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#ifndef INET_ADDRSTRLEN +#define INET_ADDRSTRLEN 16 +#endif + +#ifdef CURL_DISABLE_VERBOSE_STRINGS +#define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt +#endif + +/* Local API functions */ +static void state(struct connectdata *conn, + ftpstate newstate); +static CURLcode ftp_sendquote(struct connectdata *conn, + struct curl_slist *quote); +static CURLcode ftp_quit(struct connectdata *conn); +static CURLcode ftp_parse_url_path(struct connectdata *conn); +static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done); +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void ftp_pasv_verbose(struct connectdata *conn, + Curl_addrinfo *ai, + char *newhost, /* ascii version */ + int port); +#endif +static CURLcode ftp_state_post_rest(struct connectdata *conn); +static CURLcode ftp_state_post_cwd(struct connectdata *conn); +static CURLcode ftp_state_quote(struct connectdata *conn, + bool init, ftpstate instate); +static CURLcode ftp_nb_type(struct connectdata *conn, + bool ascii, ftpstate newstate); +static int ftp_need_type(struct connectdata *conn, + bool ascii); +static CURLcode ftp_do(struct connectdata *conn, bool *done); +static CURLcode ftp_done(struct connectdata *conn, + CURLcode, bool premature); +static CURLcode ftp_connect(struct connectdata *conn, bool *done); +static CURLcode ftp_disconnect(struct connectdata *conn, bool dead_connection); +static CURLcode ftp_do_more(struct connectdata *conn, bool *completed); +static CURLcode ftp_multi_statemach(struct connectdata *conn, bool *done); +static int ftp_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks); +static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks); +static CURLcode ftp_doing(struct connectdata *conn, + bool *dophase_done); +static CURLcode ftp_setup_connection(struct connectdata * conn); + +static CURLcode init_wc_data(struct connectdata *conn); +static CURLcode wc_statemach(struct connectdata *conn); + +static void wc_data_dtor(void *ptr); + +static CURLcode ftp_state_post_retr_size(struct connectdata *conn, + curl_off_t filesize); + +static CURLcode ftp_readresp(curl_socket_t sockfd, + struct pingpong *pp, + int *ftpcode, + size_t *size); + +/* easy-to-use macro: */ +#define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z)) != CURLE_OK) \ + return result +#define PPSENDF(x,y,z) if((result = Curl_pp_sendf(x,y,z)) != CURLE_OK) \ + return result + + +/* + * FTP protocol handler. + */ + +const struct Curl_handler Curl_handler_ftp = { + "FTP", /* scheme */ + ftp_setup_connection, /* setup_connection */ + ftp_do, /* do_it */ + ftp_done, /* done */ + ftp_do_more, /* do_more */ + ftp_connect, /* connect_it */ + ftp_multi_statemach, /* connecting */ + ftp_doing, /* doing */ + ftp_getsock, /* proto_getsock */ + ftp_getsock, /* doing_getsock */ + ftp_domore_getsock, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_FTP, /* defport */ + CURLPROTO_FTP, /* protocol */ + PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD + | PROTOPT_NOURLQUERY /* flags */ +}; + + +#ifdef USE_SSL +/* + * FTPS protocol handler. + */ + +const struct Curl_handler Curl_handler_ftps = { + "FTPS", /* scheme */ + ftp_setup_connection, /* setup_connection */ + ftp_do, /* do_it */ + ftp_done, /* done */ + ftp_do_more, /* do_more */ + ftp_connect, /* connect_it */ + ftp_multi_statemach, /* connecting */ + ftp_doing, /* doing */ + ftp_getsock, /* proto_getsock */ + ftp_getsock, /* doing_getsock */ + ftp_domore_getsock, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_FTPS, /* defport */ + CURLPROTO_FTP | CURLPROTO_FTPS, /* protocol */ + PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION | + PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY /* flags */ +}; +#endif + +#ifndef CURL_DISABLE_HTTP +/* + * HTTP-proxyed FTP protocol handler. + */ + +static const struct Curl_handler Curl_handler_ftp_proxy = { + "FTP", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_FTP, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; + + +#ifdef USE_SSL +/* + * HTTP-proxyed FTPS protocol handler. + */ + +static const struct Curl_handler Curl_handler_ftps_proxy = { + "FTPS", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_FTPS, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; +#endif +#endif + + +/* + * NOTE: back in the old days, we added code in the FTP code that made NOBODY + * requests on files respond with headers passed to the client/stdout that + * looked like HTTP ones. + * + * This approach is not very elegant, it causes confusion and is error-prone. + * It is subject for removal at the next (or at least a future) soname bump. + * Until then you can test the effects of the removal by undefining the + * following define named CURL_FTP_HTTPSTYLE_HEAD. + */ +#define CURL_FTP_HTTPSTYLE_HEAD 1 + +static void freedirs(struct ftp_conn *ftpc) +{ + int i; + if(ftpc->dirs) { + for(i=0; i < ftpc->dirdepth; i++) { + if(ftpc->dirs[i]) { + free(ftpc->dirs[i]); + ftpc->dirs[i]=NULL; + } + } + free(ftpc->dirs); + ftpc->dirs = NULL; + ftpc->dirdepth = 0; + } + if(ftpc->file) { + free(ftpc->file); + ftpc->file = NULL; + } +} + +/* Returns non-zero if the given string contains CR (\r) or LF (\n), + which are not allowed within RFC 959 . + Note: The input string is in the client's encoding which might + not be ASCII, so escape sequences \r & \n must be used instead + of hex values 0x0d & 0x0a. +*/ +static bool isBadFtpString(const char *string) +{ + return ((NULL != strchr(string, '\r')) || + (NULL != strchr(string, '\n'))) ? TRUE : FALSE; +} + +/*********************************************************************** + * + * AcceptServerConnect() + * + * After connection request is received from the server this function is + * called to accept the connection and close the listening socket + * + */ +static CURLcode AcceptServerConnect(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + curl_socket_t sock = conn->sock[SECONDARYSOCKET]; + curl_socket_t s = CURL_SOCKET_BAD; +#ifdef ENABLE_IPV6 + struct Curl_sockaddr_storage add; +#else + struct sockaddr_in add; +#endif + curl_socklen_t size = (curl_socklen_t) sizeof(add); + + if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { + size = sizeof(add); + + s=accept(sock, (struct sockaddr *) &add, &size); + } + Curl_closesocket(conn, sock); /* close the first socket */ + + if(CURL_SOCKET_BAD == s) { + failf(data, "Error accept()ing server connect"); + return CURLE_FTP_PORT_FAILED; + } + infof(data, "Connection accepted from server\n"); + + conn->sock[SECONDARYSOCKET] = s; + curlx_nonblock(s, TRUE); /* enable non-blocking */ + conn->sock_accepted[SECONDARYSOCKET] = TRUE; + + if(data->set.fsockopt) { + int error = 0; + + /* activate callback for setting socket options */ + error = data->set.fsockopt(data->set.sockopt_client, + s, + CURLSOCKTYPE_ACCEPT); + + if(error) { + Curl_closesocket(conn, s); /* close the socket and bail out */ + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; + return CURLE_ABORTED_BY_CALLBACK; + } + } + + return CURLE_OK; + +} + +/* + * ftp_timeleft_accept() returns the amount of milliseconds left allowed for + * waiting server to connect. If the value is negative, the timeout time has + * already elapsed. + * + * The start time is stored in progress.t_acceptdata - as set with + * Curl_pgrsTime(..., TIMER_STARTACCEPT); + * + */ +static long ftp_timeleft_accept(struct SessionHandle *data) +{ + long timeout_ms = DEFAULT_ACCEPT_TIMEOUT; + long other; + struct timeval now; + + if(data->set.accepttimeout > 0) + timeout_ms = data->set.accepttimeout; + + now = Curl_tvnow(); + + /* check if the generic timeout possibly is set shorter */ + other = Curl_timeleft(data, &now, FALSE); + if(other && (other < timeout_ms)) + /* note that this also works fine for when other happens to be negative + due to it already having elapsed */ + timeout_ms = other; + else { + /* subtract elapsed time */ + timeout_ms -= Curl_tvdiff(now, data->progress.t_acceptdata); + if(!timeout_ms) + /* avoid returning 0 as that means no timeout! */ + return -1; + } + + return timeout_ms; +} + + +/*********************************************************************** + * + * ReceivedServerConnect() + * + * After allowing server to connect to us from data port, this function + * checks both data connection for connection establishment and ctrl + * connection for a negative response regarding a failure in connecting + * + */ +static CURLcode ReceivedServerConnect(struct connectdata* conn, bool* received) +{ + struct SessionHandle *data = conn->data; + curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; + curl_socket_t data_sock = conn->sock[SECONDARYSOCKET]; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + int result; + long timeout_ms; + ssize_t nread; + int ftpcode; + + *received = FALSE; + + timeout_ms = ftp_timeleft_accept(data); + infof(data, "Checking for server connect\n"); + if(timeout_ms < 0) { + /* if a timeout was already reached, bail out */ + failf(data, "Accept timeout occurred while waiting server connect"); + return CURLE_FTP_ACCEPT_TIMEOUT; + } + + /* First check whether there is a cached response from server */ + if(pp->cache_size && pp->cache && pp->cache[0] > '3') { + /* Data connection could not be established, let's return */ + infof(data, "There is negative response in cache while serv connect\n"); + Curl_GetFTPResponse(&nread, conn, &ftpcode); + return CURLE_FTP_ACCEPT_FAILED; + } + + result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); + + /* see if the connection request is already here */ + switch (result) { + case -1: /* error */ + /* let's die here */ + failf(data, "Error while waiting for server connect"); + return CURLE_FTP_ACCEPT_FAILED; + case 0: /* Server connect is not received yet */ + break; /* loop */ + default: + + if(result & CURL_CSELECT_IN2) { + infof(data, "Ready to accept data connection from server\n"); + *received = TRUE; + } + else if(result & CURL_CSELECT_IN) { + infof(data, "Ctrl conn has data while waiting for data conn\n"); + Curl_GetFTPResponse(&nread, conn, &ftpcode); + + if(ftpcode/100 > 3) + return CURLE_FTP_ACCEPT_FAILED; + + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + break; + } /* switch() */ + + return CURLE_OK; +} + + +/*********************************************************************** + * + * InitiateTransfer() + * + * After connection from server is accepted this function is called to + * setup transfer parameters and initiate the data transfer. + * + */ +static CURLcode InitiateTransfer(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct FTP *ftp = data->state.proto.ftp; + CURLcode result = CURLE_OK; + + if(conn->ssl[SECONDARYSOCKET].use) { + /* since we only have a plaintext TCP connection here, we must now + * do the TLS stuff */ + infof(data, "Doing the SSL/TLS handshake on the data stream\n"); + result = Curl_ssl_connect(conn, SECONDARYSOCKET); + if(result) + return result; + } + + if(conn->proto.ftpc.state_saved == FTP_STOR) { + *(ftp->bytecountp)=0; + + /* When we know we're uploading a specified file, we can get the file + size prior to the actual upload. */ + + Curl_pgrsSetUploadSize(data, data->set.infilesize); + + /* set the SO_SNDBUF for the secondary socket for those who need it */ + Curl_sndbufset(conn->sock[SECONDARYSOCKET]); + + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ + SECONDARYSOCKET, ftp->bytecountp); + } + else { + /* FTP download: */ + Curl_setup_transfer(conn, SECONDARYSOCKET, + conn->proto.ftpc.retr_size_saved, FALSE, + ftp->bytecountp, -1, NULL); /* no upload here */ + } + + conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ + state(conn, FTP_STOP); + + return CURLE_OK; +} + +/*********************************************************************** + * + * AllowServerConnect() + * + * When we've issue the PORT command, we have told the server to connect + * to us. This function + * - will sit and wait here until the server has connected for easy interface + * - will check whether data connection is established if so it is accepted + * for multi interface + * + */ +static CURLcode AllowServerConnect(struct connectdata *conn, bool *connected) +{ + struct SessionHandle *data = conn->data; + long timeout_ms; + long interval_ms; + CURLcode ret = CURLE_OK; + + *connected = FALSE; + infof(data, "Preparing for accepting server on data port\n"); + + /* Save the time we start accepting server connect */ + Curl_pgrsTime(data, TIMER_STARTACCEPT); + + for(;;) { + timeout_ms = ftp_timeleft_accept(data); + if(timeout_ms < 0) { + /* if a timeout was already reached, bail out */ + failf(data, "Accept timeout occurred while waiting server connect"); + return CURLE_FTP_ACCEPT_TIMEOUT; + } + + /* see if the connection request is already here */ + ret = ReceivedServerConnect(conn, connected); + if(ret) + return ret; + + if(*connected) { + ret = AcceptServerConnect(conn); + if(ret) + return ret; + + ret = InitiateTransfer(conn); + if(ret) + return ret; + + break; /* connection is accepted, break the loop */ + } + else { + if(data->state.used_interface == Curl_if_easy) { + interval_ms = 1000; + if(timeout_ms < interval_ms) + interval_ms = timeout_ms; + + /* sleep for 1 second and then continue */ + Curl_socket_ready(CURL_SOCKET_BAD, CURL_SOCKET_BAD, interval_ms); + } + else { + /* Add timeout to multi handle and break out of the loop */ + if(ret == CURLE_OK && *connected == FALSE) { + if(data->set.accepttimeout > 0) + Curl_expire(data, data->set.accepttimeout); + else + Curl_expire(data, DEFAULT_ACCEPT_TIMEOUT); + } + + break; /* connection was not accepted immediately */ + } + } + } + + return ret; +} + +/* macro to check for a three-digit ftp status code at the start of the + given string */ +#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ + ISDIGIT(line[2])) + +/* macro to check for the last line in an FTP server response */ +#define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3])) + +static int ftp_endofresp(struct pingpong *pp, + int *code) +{ + char *line = pp->linestart_resp; + size_t len = pp->nread_resp; + + if((len > 3) && LASTLINE(line)) { + *code = curlx_sltosi(strtol(line, NULL, 10)); + return 1; + } + return 0; +} + +static CURLcode ftp_readresp(curl_socket_t sockfd, + struct pingpong *pp, + int *ftpcode, /* return the ftp-code if done */ + size_t *size) /* size of the response */ +{ + struct connectdata *conn = pp->conn; + struct SessionHandle *data = conn->data; +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + char * const buf = data->state.buffer; +#endif + CURLcode result = CURLE_OK; + int code; + + result = Curl_pp_readresp(sockfd, pp, &code, size); + +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + /* handle the security-oriented responses 6xx ***/ + /* FIXME: some errorchecking perhaps... ***/ + switch(code) { + case 631: + code = Curl_sec_read_msg(conn, buf, PROT_SAFE); + break; + case 632: + code = Curl_sec_read_msg(conn, buf, PROT_PRIVATE); + break; + case 633: + code = Curl_sec_read_msg(conn, buf, PROT_CONFIDENTIAL); + break; + default: + /* normal ftp stuff we pass through! */ + break; + } +#endif + + /* store the latest code for later retrieval */ + data->info.httpcode=code; + + if(ftpcode) + *ftpcode = code; + + if(421 == code) { + /* 421 means "Service not available, closing control connection." and FTP + * servers use it to signal that idle session timeout has been exceeded. + * If we ignored the response, it could end up hanging in some cases. + * + * This response code can come at any point so having it treated + * generically is a good idea. + */ + infof(data, "We got a 421 - timeout!\n"); + state(conn, FTP_STOP); + return CURLE_OPERATION_TIMEDOUT; + } + + return result; +} + +/* --- parse FTP server responses --- */ + +/* + * Curl_GetFTPResponse() is a BLOCKING function to read the full response + * from a server after a command. + * + */ + +CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ + struct connectdata *conn, + int *ftpcode) /* return the ftp-code */ +{ + /* + * We cannot read just one byte per read() and then go back to select() as + * the OpenSSL read() doesn't grok that properly. + * + * Alas, read as much as possible, split up into lines, use the ending + * line in a response or continue reading. */ + + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + long timeout; /* timeout in milliseconds */ + long interval_ms; + struct SessionHandle *data = conn->data; + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + size_t nread; + int cache_skip=0; + int value_to_be_ignored=0; + + if(ftpcode) + *ftpcode = 0; /* 0 for errors */ + else + /* make the pointer point to something for the rest of this function */ + ftpcode = &value_to_be_ignored; + + *nreadp=0; + + while(!*ftpcode && !result) { + /* check and reset timeout value every lap */ + timeout = Curl_pp_state_timeout(pp); + + if(timeout <=0 ) { + failf(data, "FTP response timeout"); + return CURLE_OPERATION_TIMEDOUT; /* already too little time */ + } + + interval_ms = 1000; /* use 1 second timeout intervals */ + if(timeout < interval_ms) + interval_ms = timeout; + + /* + * Since this function is blocking, we need to wait here for input on the + * connection and only then we call the response reading function. We do + * timeout at least every second to make the timeout check run. + * + * A caution here is that the ftp_readresp() function has a cache that may + * contain pieces of a response from the previous invoke and we need to + * make sure we don't just wait for input while there is unhandled data in + * that cache. But also, if the cache is there, we call ftp_readresp() and + * the cache wasn't good enough to continue we must not just busy-loop + * around this function. + * + */ + + if(pp->cache && (cache_skip < 2)) { + /* + * There's a cache left since before. We then skipping the wait for + * socket action, unless this is the same cache like the previous round + * as then the cache was deemed not enough to act on and we then need to + * wait for more data anyway. + */ + } + else { + switch (Curl_socket_ready(sockfd, CURL_SOCKET_BAD, interval_ms)) { + case -1: /* select() error, stop reading */ + failf(data, "FTP response aborted due to select/poll error: %d", + SOCKERRNO); + return CURLE_RECV_ERROR; + + case 0: /* timeout */ + if(Curl_pgrsUpdate(conn)) + return CURLE_ABORTED_BY_CALLBACK; + continue; /* just continue in our loop for the timeout duration */ + + default: /* for clarity */ + break; + } + } + result = ftp_readresp(sockfd, pp, ftpcode, &nread); + if(result) + break; + + if(!nread && pp->cache) + /* bump cache skip counter as on repeated skips we must wait for more + data */ + cache_skip++; + else + /* when we got data or there is no cache left, we reset the cache skip + counter */ + cache_skip=0; + + *nreadp += nread; + + } /* while there's buffer left and loop is requested */ + + pp->pending_resp = FALSE; + + return result; +} + +/* This is the ONLY way to change FTP state! */ +static void state(struct connectdata *conn, + ftpstate newstate) +{ +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[]={ + "STOP", + "WAIT220", + "AUTH", + "USER", + "PASS", + "ACCT", + "PBSZ", + "PROT", + "CCC", + "PWD", + "SYST", + "NAMEFMT", + "QUOTE", + "RETR_PREQUOTE", + "STOR_PREQUOTE", + "POSTQUOTE", + "CWD", + "MKD", + "MDTM", + "TYPE", + "LIST_TYPE", + "RETR_TYPE", + "STOR_TYPE", + "SIZE", + "RETR_SIZE", + "STOR_SIZE", + "REST", + "RETR_REST", + "PORT", + "PRET", + "PASV", + "LIST", + "RETR", + "STOR", + "QUIT" + }; +#endif + struct ftp_conn *ftpc = &conn->proto.ftpc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + if(ftpc->state != newstate) + infof(conn->data, "FTP %p state change from %s to %s\n", + ftpc, names[ftpc->state], names[newstate]); +#endif + ftpc->state = newstate; +} + +static CURLcode ftp_state_user(struct connectdata *conn) +{ + CURLcode result; + struct FTP *ftp = conn->data->state.proto.ftp; + /* send USER */ + PPSENDF(&conn->proto.ftpc.pp, "USER %s", ftp->user?ftp->user:""); + + state(conn, FTP_USER); + conn->data->state.ftp_trying_alternative = FALSE; + + return CURLE_OK; +} + +static CURLcode ftp_state_pwd(struct connectdata *conn) +{ + CURLcode result; + + /* send PWD to discover our entry point */ + PPSENDF(&conn->proto.ftpc.pp, "PWD", NULL); + state(conn, FTP_PWD); + + return CURLE_OK; +} + +/* For the FTP "protocol connect" and "doing" phases only */ +static int ftp_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks); +} + +/* For the FTP "DO_MORE" phase only */ +static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(!numsocks) + return GETSOCK_BLANK; + + /* When in DO_MORE state, we could be either waiting for us to connect to a + remote site, or we could wait for that site to connect to us. Or just + handle ordinary commands. + + When waiting for a connect, we will be in FTP_STOP state and then we wait + for the secondary socket to become writeable. If we're in another state, + we're still handling commands on the control (primary) connection. + + */ + + switch(ftpc->state) { + case FTP_STOP: + break; + default: + return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks); + } + + socks[0] = conn->sock[SECONDARYSOCKET]; + if(ftpc->wait_data_conn) { + socks[1] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0) | GETSOCK_READSOCK(1); + } + + return GETSOCK_READSOCK(0); +} + +/* This is called after the FTP_QUOTE state is passed. + + ftp_state_cwd() sends the range of CWD commands to the server to change to + the correct directory. It may also need to send MKD commands to create + missing ones, if that option is enabled. +*/ +static CURLcode ftp_state_cwd(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(ftpc->cwddone) + /* already done and fine */ + result = ftp_state_post_cwd(conn); + else { + ftpc->count2 = 0; /* count2 counts failed CWDs */ + + /* count3 is set to allow a MKD to fail once. In the case when first CWD + fails and then MKD fails (due to another session raced it to create the + dir) this then allows for a second try to CWD to it */ + ftpc->count3 = (conn->data->set.ftp_create_missing_dirs==2)?1:0; + + if(conn->bits.reuse && ftpc->entrypath) { + /* This is a re-used connection. Since we change directory to where the + transfer is taking place, we must first get back to the original dir + where we ended up after login: */ + ftpc->count1 = 0; /* we count this as the first path, then we add one + for all upcoming ones in the ftp->dirs[] array */ + PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->entrypath); + state(conn, FTP_CWD); + } + else { + if(ftpc->dirdepth) { + ftpc->count1 = 1; + /* issue the first CWD, the rest is sent when the CWD responses are + received... */ + PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->dirs[ftpc->count1 -1]); + state(conn, FTP_CWD); + } + else { + /* No CWD necessary */ + result = ftp_state_post_cwd(conn); + } + } + } + return result; +} + +typedef enum { + EPRT, + PORT, + DONE +} ftpport; + +static CURLcode ftp_state_use_port(struct connectdata *conn, + ftpport fcmd) /* start with this */ + +{ + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct SessionHandle *data=conn->data; + curl_socket_t portsock= CURL_SOCKET_BAD; + char myhost[256] = ""; + + struct Curl_sockaddr_storage ss; + Curl_addrinfo *res, *ai; + curl_socklen_t sslen; + char hbuf[NI_MAXHOST]; + struct sockaddr *sa=(struct sockaddr *)&ss; + struct sockaddr_in * const sa4 = (void *)sa; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 * const sa6 = (void *)sa; +#endif + char tmp[1024]; + static const char mode[][5] = { "EPRT", "PORT" }; + int rc; + int error; + char *host = NULL; + char *string_ftpport = data->set.str[STRING_FTPPORT]; + struct Curl_dns_entry *h=NULL; + unsigned short port_min = 0; + unsigned short port_max = 0; + unsigned short port; + bool possibly_non_local = TRUE; + + char *addr = NULL; + + /* Step 1, figure out what is requested, + * accepted format : + * (ipv4|ipv6|domain|interface)?(:port(-range)?)? + */ + + if(data->set.str[STRING_FTPPORT] && + (strlen(data->set.str[STRING_FTPPORT]) > 1)) { + +#ifdef ENABLE_IPV6 + size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ? + INET6_ADDRSTRLEN : strlen(string_ftpport); +#else + size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ? + INET_ADDRSTRLEN : strlen(string_ftpport); +#endif + char *ip_start = string_ftpport; + char *ip_end = NULL; + char *port_start = NULL; + char *port_sep = NULL; + + addr = calloc(addrlen+1, 1); + if(!addr) + return CURLE_OUT_OF_MEMORY; + +#ifdef ENABLE_IPV6 + if(*string_ftpport == '[') { + /* [ipv6]:port(-range) */ + ip_start = string_ftpport + 1; + if((ip_end = strchr(string_ftpport, ']')) != NULL ) + strncpy(addr, ip_start, ip_end - ip_start); + } + else +#endif + if(*string_ftpport == ':') { + /* :port */ + ip_end = string_ftpport; + } + else if((ip_end = strchr(string_ftpport, ':')) != NULL) { + /* either ipv6 or (ipv4|domain|interface):port(-range) */ +#ifdef ENABLE_IPV6 + if(Curl_inet_pton(AF_INET6, string_ftpport, sa6) == 1) { + /* ipv6 */ + port_min = port_max = 0; + strcpy(addr, string_ftpport); + ip_end = NULL; /* this got no port ! */ + } + else +#endif + /* (ipv4|domain|interface):port(-range) */ + strncpy(addr, string_ftpport, ip_end - ip_start ); + } + else + /* ipv4|interface */ + strcpy(addr, string_ftpport); + + /* parse the port */ + if(ip_end != NULL) { + if((port_start = strchr(ip_end, ':')) != NULL) { + port_min = curlx_ultous(strtoul(port_start+1, NULL, 10)); + if((port_sep = strchr(port_start, '-')) != NULL) { + port_max = curlx_ultous(strtoul(port_sep + 1, NULL, 10)); + } + else + port_max = port_min; + } + } + + /* correct errors like: + * :1234-1230 + * :-4711 , in this case port_min is (unsigned)-1, + * therefore port_min > port_max for all cases + * but port_max = (unsigned)-1 + */ + if(port_min > port_max ) + port_min = port_max = 0; + + + if(*addr != '\0') { + /* attempt to get the address of the given interface name */ + if(!Curl_if2ip(conn->ip_addr->ai_family, addr, + hbuf, sizeof(hbuf))) + /* not an interface, use the given string as host name instead */ + host = addr; + else + host = hbuf; /* use the hbuf for host name */ + } + else + /* there was only a port(-range) given, default the host */ + host = NULL; + } /* data->set.ftpport */ + + if(!host) { + /* not an interface and not a host name, get default by extracting + the IP from the control connection */ + + sslen = sizeof(ss); + if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, SOCKERRNO) ); + Curl_safefree(addr); + return CURLE_FTP_PORT_FAILED; + } + switch(sa->sa_family) { +#ifdef ENABLE_IPV6 + case AF_INET6: + Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); + break; +#endif + default: + Curl_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, sizeof(hbuf)); + break; + } + host = hbuf; /* use this host name */ + possibly_non_local = FALSE; /* we know it is local now */ + } + + /* resolv ip/host to ip */ + rc = Curl_resolv(conn, host, 0, &h); + if(rc == CURLRESOLV_PENDING) + (void)Curl_resolver_wait_resolv(conn, &h); + if(h) { + res = h->addr; + /* when we return from this function, we can forget about this entry + to we can unlock it now already */ + Curl_resolv_unlock(data, h); + } /* (h) */ + else + res = NULL; /* failure! */ + + if(res == NULL) { + failf(data, "failed to resolve the address provided to PORT: %s", host); + Curl_safefree(addr); + return CURLE_FTP_PORT_FAILED; + } + + Curl_safefree(addr); + host = NULL; + + /* step 2, create a socket for the requested address */ + + portsock = CURL_SOCKET_BAD; + error = 0; + for(ai = res; ai; ai = ai->ai_next) { + result = Curl_socket(conn, ai, NULL, &portsock); + if(result) { + error = SOCKERRNO; + continue; + } + break; + } + if(!ai) { + failf(data, "socket failure: %s", Curl_strerror(conn, error)); + return CURLE_FTP_PORT_FAILED; + } + + /* step 3, bind to a suitable local address */ + + memcpy(sa, ai->ai_addr, ai->ai_addrlen); + sslen = ai->ai_addrlen; + + for(port = port_min; port <= port_max;) { + if(sa->sa_family == AF_INET) + sa4->sin_port = htons(port); +#ifdef ENABLE_IPV6 + else + sa6->sin6_port = htons(port); +#endif + /* Try binding the given address. */ + if(bind(portsock, sa, sslen) ) { + /* It failed. */ + error = SOCKERRNO; + if(possibly_non_local && (error == EADDRNOTAVAIL)) { + /* The requested bind address is not local. Use the address used for + * the control connection instead and restart the port loop + */ + + infof(data, "bind(port=%hu) on non-local address failed: %s\n", port, + Curl_strerror(conn, error) ); + + sslen = sizeof(ss); + if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, SOCKERRNO) ); + Curl_closesocket(conn, portsock); + return CURLE_FTP_PORT_FAILED; + } + port = port_min; + possibly_non_local = FALSE; /* don't try this again */ + continue; + } + else if(error != EADDRINUSE && error != EACCES) { + failf(data, "bind(port=%hu) failed: %s", port, + Curl_strerror(conn, error) ); + Curl_closesocket(conn, portsock); + return CURLE_FTP_PORT_FAILED; + } + } + else + break; + + port++; + } + + /* maybe all ports were in use already*/ + if(port > port_max) { + failf(data, "bind() failed, we ran out of ports!"); + Curl_closesocket(conn, portsock); + return CURLE_FTP_PORT_FAILED; + } + + /* get the name again after the bind() so that we can extract the + port number it uses now */ + sslen = sizeof(ss); + if(getsockname(portsock, (struct sockaddr *)sa, &sslen)) { + failf(data, "getsockname() failed: %s", + Curl_strerror(conn, SOCKERRNO) ); + Curl_closesocket(conn, portsock); + return CURLE_FTP_PORT_FAILED; + } + + /* step 4, listen on the socket */ + + if(listen(portsock, 1)) { + failf(data, "socket failure: %s", Curl_strerror(conn, SOCKERRNO)); + Curl_closesocket(conn, portsock); + return CURLE_FTP_PORT_FAILED; + } + + /* step 5, send the proper FTP command */ + + /* get a plain printable version of the numerical address to work with + below */ + Curl_printable_address(ai, myhost, sizeof(myhost)); + +#ifdef ENABLE_IPV6 + if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) + /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the + request and enable EPRT again! */ + conn->bits.ftp_use_eprt = TRUE; +#endif + + for(; fcmd != DONE; fcmd++) { + + if(!conn->bits.ftp_use_eprt && (EPRT == fcmd)) + /* if disabled, goto next */ + continue; + + if((PORT == fcmd) && sa->sa_family != AF_INET) + /* PORT is ipv4 only */ + continue; + + switch (sa->sa_family) { + case AF_INET: + port = ntohs(sa4->sin_port); + break; +#ifdef ENABLE_IPV6 + case AF_INET6: + port = ntohs(sa6->sin6_port); + break; +#endif + default: + continue; /* might as well skip this */ + } + + if(EPRT == fcmd) { + /* + * Two fine examples from RFC2428; + * + * EPRT |1|132.235.1.2|6275| + * + * EPRT |2|1080::8:800:200C:417A|5282| + */ + + result = Curl_pp_sendf(&ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd], + sa->sa_family == AF_INET?1:2, + myhost, port); + if(result) { + failf(data, "Failure sending EPRT command: %s", + curl_easy_strerror(result)); + Curl_closesocket(conn, portsock); + /* don't retry using PORT */ + ftpc->count1 = PORT; + /* bail out */ + state(conn, FTP_STOP); + return result; + } + break; + } + else if(PORT == fcmd) { + char *source = myhost; + char *dest = tmp; + + /* translate x.x.x.x to x,x,x,x */ + while(source && *source) { + if(*source == '.') + *dest=','; + else + *dest = *source; + dest++; + source++; + } + *dest = 0; + snprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff)); + + result = Curl_pp_sendf(&ftpc->pp, "%s %s", mode[fcmd], tmp); + if(result) { + failf(data, "Failure sending PORT command: %s", + curl_easy_strerror(result)); + Curl_closesocket(conn, portsock); + /* bail out */ + state(conn, FTP_STOP); + return result; + } + break; + } + } + + /* store which command was sent */ + ftpc->count1 = fcmd; + + /* we set the secondary socket variable to this for now, it is only so that + the cleanup function will close it in case we fail before the true + secondary stuff is made */ + if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) + Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = portsock; + + /* this tcpconnect assignment below is a hackish work-around to make the + multi interface with active FTP work - as it will not wait for a + (passive) connect in Curl_is_connected(). + + The *proper* fix is to make sure that the active connection from the + server is done in a non-blocking way. Currently, it is still BLOCKING. + */ + conn->bits.tcpconnect[SECONDARYSOCKET] = TRUE; + + state(conn, FTP_PORT); + return result; +} + +static CURLcode ftp_state_use_pasv(struct connectdata *conn) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result = CURLE_OK; + /* + Here's the excecutive summary on what to do: + + PASV is RFC959, expect: + 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) + + LPSV is RFC1639, expect: + 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) + + EPSV is RFC2428, expect: + 229 Entering Extended Passive Mode (|||port|) + + */ + + static const char mode[][5] = { "EPSV", "PASV" }; + int modeoff; + +#ifdef PF_INET6 + if(!conn->bits.ftp_use_epsv && conn->bits.ipv6) + /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the + request and enable EPSV again! */ + conn->bits.ftp_use_epsv = TRUE; +#endif + + modeoff = conn->bits.ftp_use_epsv?0:1; + + PPSENDF(&ftpc->pp, "%s", mode[modeoff]); + + ftpc->count1 = modeoff; + state(conn, FTP_PASV); + infof(conn->data, "Connect data stream passively\n"); + + return result; +} + +/* REST is the last command in the chain of commands when a "head"-like + request is made. Thus, if an actual transfer is to be made this is where + we take off for real. */ +static CURLcode ftp_state_post_rest(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->data->state.proto.ftp; + struct SessionHandle *data = conn->data; + + if(ftp->transfer != FTPTRANSFER_BODY) { + /* doesn't transfer any data */ + + /* still possibly do PRE QUOTE jobs */ + state(conn, FTP_RETR_PREQUOTE); + result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE); + } + else if(data->set.ftp_use_port) { + /* We have chosen to use the PORT (or similar) command */ + result = ftp_state_use_port(conn, EPRT); + } + else { + /* We have chosen (this is default) to use the PASV (or similar) command */ + if(data->set.ftp_use_pret) { + /* The user has requested that we send a PRET command + to prepare the server for the upcoming PASV */ + if(!conn->proto.ftpc.file) { + PPSENDF(&conn->proto.ftpc.pp, "PRET %s", + data->set.str[STRING_CUSTOMREQUEST]? + data->set.str[STRING_CUSTOMREQUEST]: + (data->set.ftp_list_only?"NLST":"LIST")); + } + else if(data->set.upload) { + PPSENDF(&conn->proto.ftpc.pp, "PRET STOR %s", conn->proto.ftpc.file); + } + else { + PPSENDF(&conn->proto.ftpc.pp, "PRET RETR %s", conn->proto.ftpc.file); + } + state(conn, FTP_PRET); + } + else { + result = ftp_state_use_pasv(conn); + } + } + return result; +} + +static CURLcode ftp_state_post_size(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if((ftp->transfer != FTPTRANSFER_BODY) && ftpc->file) { + /* if a "head"-like request is being made (on a file) */ + + /* Determine if server can respond to REST command and therefore + whether it supports range */ + PPSENDF(&conn->proto.ftpc.pp, "REST %d", 0); + + state(conn, FTP_REST); + } + else + result = ftp_state_post_rest(conn); + + return result; +} + +static CURLcode ftp_state_post_type(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if((ftp->transfer == FTPTRANSFER_INFO) && ftpc->file) { + /* if a "head"-like request is being made (on a file) */ + + /* we know ftpc->file is a valid pointer to a file name */ + PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); + + state(conn, FTP_SIZE); + } + else + result = ftp_state_post_size(conn); + + return result; +} + +static CURLcode ftp_state_post_listtype(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + /* If this output is to be machine-parsed, the NLST command might be better + to use, since the LIST command output is not specified or standard in any + way. It has turned out that the NLST list output is not the same on all + servers either... */ + + /* + if FTPFILE_NOCWD was specified, we are currently in + the user's home directory, so we should add the path + as argument for the LIST / NLST / or custom command. + Whether the server will support this, is uncertain. + + The other ftp_filemethods will CWD into dir/dir/ first and + then just do LIST (in that case: nothing to do here) + */ + char *cmd,*lstArg,*slashPos; + + lstArg = NULL; + if((data->set.ftp_filemethod == FTPFILE_NOCWD) && + data->state.path && + data->state.path[0] && + strchr(data->state.path,'/')) { + + lstArg = strdup(data->state.path); + if(!lstArg) + return CURLE_OUT_OF_MEMORY; + + /* Check if path does not end with /, as then we cut off the file part */ + if(lstArg[strlen(lstArg) - 1] != '/') { + + /* chop off the file part if format is dir/dir/file */ + slashPos = strrchr(lstArg,'/'); + if(slashPos) + *(slashPos+1) = '\0'; + } + } + + cmd = aprintf( "%s%s%s", + data->set.str[STRING_CUSTOMREQUEST]? + data->set.str[STRING_CUSTOMREQUEST]: + (data->set.ftp_list_only?"NLST":"LIST"), + lstArg? " ": "", + lstArg? lstArg: "" ); + + if(!cmd) { + if(lstArg) + free(lstArg); + return CURLE_OUT_OF_MEMORY; + } + + result = Curl_pp_sendf(&conn->proto.ftpc.pp, "%s", cmd); + + if(lstArg) + free(lstArg); + + free(cmd); + + if(result != CURLE_OK) + return result; + + state(conn, FTP_LIST); + + return result; +} + +static CURLcode ftp_state_post_retrtype(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + /* We've sent the TYPE, now we must send the list of prequote strings */ + + result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE); + + return result; +} + +static CURLcode ftp_state_post_stortype(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + /* We've sent the TYPE, now we must send the list of prequote strings */ + + result = ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE); + + return result; +} + +static CURLcode ftp_state_post_mdtm(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->data->state.proto.ftp; + struct SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + /* If we have selected NOBODY and HEADER, it means that we only want file + information. Which in FTP can't be much more than the file size and + date. */ + if(data->set.opt_no_body && ftpc->file && + ftp_need_type(conn, data->set.prefer_ascii)) { + /* The SIZE command is _not_ RFC 959 specified, and therefor many servers + may not support it! It is however the only way we have to get a file's + size! */ + + ftp->transfer = FTPTRANSFER_INFO; + /* this means no actual transfer will be made */ + + /* Some servers return different sizes for different modes, and thus we + must set the proper type before we check the size */ + result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_TYPE); + if(result) + return result; + } + else + result = ftp_state_post_type(conn); + + return result; +} + +/* This is called after the CWD commands have been done in the beginning of + the DO phase */ +static CURLcode ftp_state_post_cwd(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + /* Requested time of file or time-depended transfer? */ + if((data->set.get_filetime || data->set.timecondition) && ftpc->file) { + + /* we have requested to get the modified-time of the file, this is a white + spot as the MDTM is not mentioned in RFC959 */ + PPSENDF(&ftpc->pp, "MDTM %s", ftpc->file); + + state(conn, FTP_MDTM); + } + else + result = ftp_state_post_mdtm(conn); + + return result; +} + + +/* This is called after the TYPE and possible quote commands have been sent */ +static CURLcode ftp_state_ul_setup(struct connectdata *conn, + bool sizechecked) +{ + CURLcode result = CURLE_OK; + struct FTP *ftp = conn->data->state.proto.ftp; + struct SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + int seekerr = CURL_SEEKFUNC_OK; + + if((data->state.resume_from && !sizechecked) || + ((data->state.resume_from > 0) && sizechecked)) { + /* we're about to continue the uploading of a file */ + /* 1. get already existing file's size. We use the SIZE command for this + which may not exist in the server! The SIZE command is not in + RFC959. */ + + /* 2. This used to set REST. But since we can do append, we + don't another ftp command. We just skip the source file + offset and then we APPEND the rest on the file instead */ + + /* 3. pass file-size number of bytes in the source file */ + /* 4. lower the infilesize counter */ + /* => transfer as usual */ + + if(data->state.resume_from < 0 ) { + /* Got no given size to start from, figure it out */ + PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); + state(conn, FTP_STOR_SIZE); + return result; + } + + /* enable append */ + data->set.ftp_append = TRUE; + + /* Let's read off the proper amount of bytes from the input. */ + if(conn->seek_func) { + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_FTP_COULDNT_USE_REST; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + else { + curl_off_t passed=0; + do { + size_t readthisamountnow = + (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? + BUFSIZE : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + conn->fread_func(data->state.buffer, 1, readthisamountnow, + conn->fread_in); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + return CURLE_FTP_COULDNT_USE_REST; + } + } while(passed < data->state.resume_from); + } + } + /* now, decrease the size of the read */ + if(data->set.infilesize>0) { + data->set.infilesize -= data->state.resume_from; + + if(data->set.infilesize <= 0) { + infof(data, "File already completely uploaded\n"); + + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + /* Set ->transfer so that we won't get any error in + * ftp_done() because we didn't transfer anything! */ + ftp->transfer = FTPTRANSFER_NONE; + + state(conn, FTP_STOP); + return CURLE_OK; + } + } + /* we've passed, proceed as normal */ + } /* resume_from */ + + PPSENDF(&ftpc->pp, data->set.ftp_append?"APPE %s":"STOR %s", + ftpc->file); + + state(conn, FTP_STOR); + + return result; +} + +static CURLcode ftp_state_quote(struct connectdata *conn, + bool init, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct FTP *ftp = data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + bool quote=FALSE; + struct curl_slist *item; + + switch(instate) { + case FTP_QUOTE: + default: + item = data->set.quote; + break; + case FTP_RETR_PREQUOTE: + case FTP_STOR_PREQUOTE: + item = data->set.prequote; + break; + case FTP_POSTQUOTE: + item = data->set.postquote; + break; + } + + /* + * This state uses: + * 'count1' to iterate over the commands to send + * 'count2' to store wether to allow commands to fail + */ + + if(init) + ftpc->count1 = 0; + else + ftpc->count1++; + + if(item) { + int i = 0; + + /* Skip count1 items in the linked list */ + while((i< ftpc->count1) && item) { + item = item->next; + i++; + } + if(item) { + char *cmd = item->data; + if(cmd[0] == '*') { + cmd++; + ftpc->count2 = 1; /* the sent command is allowed to fail */ + } + else + ftpc->count2 = 0; /* failure means cancel operation */ + + PPSENDF(&ftpc->pp, "%s", cmd); + state(conn, instate); + quote = TRUE; + } + } + + if(!quote) { + /* No more quote to send, continue to ... */ + switch(instate) { + case FTP_QUOTE: + default: + result = ftp_state_cwd(conn); + break; + case FTP_RETR_PREQUOTE: + if(ftp->transfer != FTPTRANSFER_BODY) + state(conn, FTP_STOP); + else { + if(ftpc->known_filesize != -1) { + Curl_pgrsSetDownloadSize(data, ftpc->known_filesize); + result = ftp_state_post_retr_size(conn, ftpc->known_filesize); + } + else { + PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); + state(conn, FTP_RETR_SIZE); + } + } + break; + case FTP_STOR_PREQUOTE: + result = ftp_state_ul_setup(conn, FALSE); + break; + case FTP_POSTQUOTE: + break; + } + } + + return result; +} + +/* called from ftp_state_pasv_resp to switch to PASV in case of EPSV + problems */ +static CURLcode ftp_epsv_disable(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + infof(conn->data, "got positive EPSV response, but can't connect. " + "Disabling EPSV\n"); + /* disable it for next transfer */ + conn->bits.ftp_use_epsv = FALSE; + conn->data->state.errorbuf = FALSE; /* allow error message to get + rewritten */ + PPSENDF(&conn->proto.ftpc.pp, "PASV", NULL); + conn->proto.ftpc.count1++; + /* remain in the FTP_PASV state */ + return result; +} + +static CURLcode ftp_state_pasv_resp(struct connectdata *conn, + int ftpcode) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result; + struct SessionHandle *data=conn->data; + Curl_addrinfo *conninfo; + struct Curl_dns_entry *addr=NULL; + int rc; + unsigned short connectport; /* the local port connect() should use! */ + unsigned short newport=0; /* remote port */ + bool connected; + + /* newhost must be able to hold a full IP-style address in ASCII, which + in the IPv6 case means 5*8-1 = 39 letters */ +#define NEWHOST_BUFSIZE 48 + char newhost[NEWHOST_BUFSIZE]; + char *str=&data->state.buffer[4]; /* start on the first letter */ + + if((ftpc->count1 == 0) && + (ftpcode == 229)) { + /* positive EPSV response */ + char *ptr = strchr(str, '('); + if(ptr) { + unsigned int num; + char separator[4]; + ptr++; + if(5 == sscanf(ptr, "%c%c%c%u%c", + &separator[0], + &separator[1], + &separator[2], + &num, + &separator[3])) { + const char sep1 = separator[0]; + int i; + + /* The four separators should be identical, or else this is an oddly + formatted reply and we bail out immediately. */ + for(i=1; i<4; i++) { + if(separator[i] != sep1) { + ptr=NULL; /* set to NULL to signal error */ + break; + } + } + if(ptr) { + newport = (unsigned short)(num & 0xffff); + + if(conn->bits.tunnel_proxy || + conn->proxytype == CURLPROXY_SOCKS5 || + conn->proxytype == CURLPROXY_SOCKS5_HOSTNAME || + conn->proxytype == CURLPROXY_SOCKS4 || + conn->proxytype == CURLPROXY_SOCKS4A) + /* proxy tunnel -> use other host info because ip_addr_str is the + proxy address not the ftp host */ + snprintf(newhost, sizeof(newhost), "%s", conn->host.name); + else + /* use the same IP we are already connected to */ + snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str); + } + } + else + ptr=NULL; + } + if(!ptr) { + failf(data, "Weirdly formatted EPSV reply"); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + } + else if((ftpc->count1 == 1) && + (ftpcode == 227)) { + /* positive PASV response */ + int ip[4]; + int port[2]; + + /* + * Scan for a sequence of six comma-separated numbers and use them as + * IP+port indicators. + * + * Found reply-strings include: + * "227 Entering Passive Mode (127,0,0,1,4,51)" + * "227 Data transfer will passively listen to 127,0,0,1,4,51" + * "227 Entering passive mode. 127,0,0,1,4,51" + */ + while(*str) { + if(6 == sscanf(str, "%d,%d,%d,%d,%d,%d", + &ip[0], &ip[1], &ip[2], &ip[3], + &port[0], &port[1])) + break; + str++; + } + + if(!*str) { + failf(data, "Couldn't interpret the 227-response"); + return CURLE_FTP_WEIRD_227_FORMAT; + } + + /* we got OK from server */ + if(data->set.ftp_skip_ip) { + /* told to ignore the remotely given IP but instead use the one we used + for the control connection */ + infof(data, "Skips %d.%d.%d.%d for data connection, uses %s instead\n", + ip[0], ip[1], ip[2], ip[3], + conn->ip_addr_str); + if(conn->bits.tunnel_proxy || + conn->proxytype == CURLPROXY_SOCKS5 || + conn->proxytype == CURLPROXY_SOCKS5_HOSTNAME || + conn->proxytype == CURLPROXY_SOCKS4 || + conn->proxytype == CURLPROXY_SOCKS4A) + /* proxy tunnel -> use other host info because ip_addr_str is the + proxy address not the ftp host */ + snprintf(newhost, sizeof(newhost), "%s", conn->host.name); + else + snprintf(newhost, sizeof(newhost), "%s", conn->ip_addr_str); + } + else + snprintf(newhost, sizeof(newhost), + "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + newport = (unsigned short)(((port[0]<<8) + port[1]) & 0xffff); + } + else if(ftpc->count1 == 0) { + /* EPSV failed, move on to PASV */ + + /* disable it for next transfer */ + conn->bits.ftp_use_epsv = FALSE; + infof(data, "disabling EPSV usage\n"); + + PPSENDF(&ftpc->pp, "PASV", NULL); + ftpc->count1++; + /* remain in the FTP_PASV state */ + return result; + } + else { + failf(data, "Bad PASV/EPSV response: %03d", ftpcode); + return CURLE_FTP_WEIRD_PASV_REPLY; + } + + if(data->set.str[STRING_PROXY] && *data->set.str[STRING_PROXY]) { + /* + * This is a tunnel through a http proxy and we need to connect to the + * proxy again here. + * + * We don't want to rely on a former host lookup that might've expired + * now, instead we remake the lookup here and now! + */ + rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr); + if(rc == CURLRESOLV_PENDING) + /* BLOCKING, ignores the return code but 'addr' will be NULL in + case of failure */ + (void)Curl_resolver_wait_resolv(conn, &addr); + + connectport = + (unsigned short)conn->port; /* we connect to the proxy's port */ + + if(!addr) { + failf(data, "Can't resolve proxy host %s:%hu", + conn->proxy.name, connectport); + return CURLE_FTP_CANT_GET_HOST; + } + } + else { + /* normal, direct, ftp connection */ + rc = Curl_resolv(conn, newhost, newport, &addr); + if(rc == CURLRESOLV_PENDING) + /* BLOCKING */ + (void)Curl_resolver_wait_resolv(conn, &addr); + + connectport = newport; /* we connect to the remote port */ + + if(!addr) { + failf(data, "Can't resolve new host %s:%hu", newhost, connectport); + return CURLE_FTP_CANT_GET_HOST; + } + } + + result = Curl_connecthost(conn, + addr, + &conn->sock[SECONDARYSOCKET], + &conninfo, + &connected); + + Curl_resolv_unlock(data, addr); /* we're done using this address */ + + if(result) { + if(ftpc->count1 == 0 && ftpcode == 229) + return ftp_epsv_disable(conn); + + return result; + } + + conn->bits.tcpconnect[SECONDARYSOCKET] = connected; + + /* + * When this is used from the multi interface, this might've returned with + * the 'connected' set to FALSE and thus we are now awaiting a non-blocking + * connect to connect and we should not be "hanging" here waiting. + */ + + if(data->set.verbose) + /* this just dumps information about this second connection */ + ftp_pasv_verbose(conn, conninfo, newhost, connectport); + + switch(conn->proxytype) { + /* FIX: this MUST wait for a proper connect first if 'connected' is + * FALSE */ + case CURLPROXY_SOCKS5: + case CURLPROXY_SOCKS5_HOSTNAME: + result = Curl_SOCKS5(conn->proxyuser, conn->proxypasswd, newhost, newport, + SECONDARYSOCKET, conn); + break; + case CURLPROXY_SOCKS4: + result = Curl_SOCKS4(conn->proxyuser, newhost, newport, + SECONDARYSOCKET, conn, FALSE); + break; + case CURLPROXY_SOCKS4A: + result = Curl_SOCKS4(conn->proxyuser, newhost, newport, + SECONDARYSOCKET, conn, TRUE); + break; + case CURLPROXY_HTTP: + case CURLPROXY_HTTP_1_0: + /* do nothing here. handled later. */ + break; + default: + failf(data, "unknown proxytype option given"); + result = CURLE_COULDNT_CONNECT; + break; + } + + if(result) { + if(ftpc->count1 == 0 && ftpcode == 229) + return ftp_epsv_disable(conn); + return result; + } + + if(conn->bits.tunnel_proxy && conn->bits.httpproxy) { + /* FIX: this MUST wait for a proper connect first if 'connected' is + * FALSE */ + + /* BLOCKING */ + /* We want "seamless" FTP operations through HTTP proxy tunnel */ + + /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member + * conn->proto.http; we want FTP through HTTP and we have to change the + * member temporarily for connecting to the HTTP proxy. After + * Curl_proxyCONNECT we have to set back the member to the original struct + * FTP pointer + */ + struct HTTP http_proxy; + struct FTP *ftp_save = data->state.proto.ftp; + memset(&http_proxy, 0, sizeof(http_proxy)); + data->state.proto.http = &http_proxy; + + result = Curl_proxyCONNECT(conn, SECONDARYSOCKET, newhost, newport); + + data->state.proto.ftp = ftp_save; + + if(result) + return result; + } + + conn->bits.tcpconnect[SECONDARYSOCKET] = TRUE; + + state(conn, FTP_STOP); /* this phase is completed */ + + return result; +} + +static CURLcode ftp_state_port_resp(struct connectdata *conn, + int ftpcode) +{ + struct SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + ftpport fcmd = (ftpport)ftpc->count1; + CURLcode result = CURLE_OK; + + if(ftpcode != 200) { + /* the command failed */ + + if(EPRT == fcmd) { + infof(data, "disabling EPRT usage\n"); + conn->bits.ftp_use_eprt = FALSE; + } + fcmd++; + + if(fcmd == DONE) { + failf(data, "Failed to do PORT"); + result = CURLE_FTP_PORT_FAILED; + } + else + /* try next */ + result = ftp_state_use_port(conn, fcmd); + } + else { + infof(data, "Connect data stream actively\n"); + state(conn, FTP_STOP); /* end of DO phase */ + } + + return result; +} + +static CURLcode ftp_state_mdtm_resp(struct connectdata *conn, + int ftpcode) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data=conn->data; + struct FTP *ftp = data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + switch(ftpcode) { + case 213: + { + /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the + last .sss part is optional and means fractions of a second */ + int year, month, day, hour, minute, second; + char *buf = data->state.buffer; + if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d", + &year, &month, &day, &hour, &minute, &second)) { + /* we have a time, reformat it */ + time_t secs=time(NULL); + /* using the good old yacc/bison yuck */ + snprintf(buf, sizeof(conn->data->state.buffer), + "%04d%02d%02d %02d:%02d:%02d GMT", + year, month, day, hour, minute, second); + /* now, convert this into a time() value: */ + data->info.filetime = (long)curl_getdate(buf, &secs); + } + +#ifdef CURL_FTP_HTTPSTYLE_HEAD + /* If we asked for a time of the file and we actually got one as well, + we "emulate" a HTTP-style header in our output. */ + + if(data->set.opt_no_body && + ftpc->file && + data->set.get_filetime && + (data->info.filetime>=0) ) { + time_t filetime = (time_t)data->info.filetime; + struct tm buffer; + const struct tm *tm = &buffer; + + result = Curl_gmtime(filetime, &buffer); + if(result) + return result; + + /* format: "Tue, 15 Nov 1994 12:45:26" */ + snprintf(buf, BUFSIZE-1, + "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); + if(result) + return result; + } /* end of a ridiculous amount of conditionals */ +#endif + } + break; + default: + infof(data, "unsupported MDTM reply format\n"); + break; + case 550: /* "No such file or directory" */ + failf(data, "Given file does not exist"); + result = CURLE_FTP_COULDNT_RETR_FILE; + break; + } + + if(data->set.timecondition) { + if((data->info.filetime > 0) && (data->set.timevalue > 0)) { + switch(data->set.timecondition) { + case CURL_TIMECOND_IFMODSINCE: + default: + if(data->info.filetime <= data->set.timevalue) { + infof(data, "The requested document is not new enough\n"); + ftp->transfer = FTPTRANSFER_NONE; /* mark to not transfer data */ + data->info.timecond = TRUE; + state(conn, FTP_STOP); + return CURLE_OK; + } + break; + case CURL_TIMECOND_IFUNMODSINCE: + if(data->info.filetime > data->set.timevalue) { + infof(data, "The requested document is not old enough\n"); + ftp->transfer = FTPTRANSFER_NONE; /* mark to not transfer data */ + data->info.timecond = TRUE; + state(conn, FTP_STOP); + return CURLE_OK; + } + break; + } /* switch */ + } + else { + infof(data, "Skipping time comparison\n"); + } + } + + if(!result) + result = ftp_state_post_mdtm(conn); + + return result; +} + +static CURLcode ftp_state_type_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data=conn->data; + + if(ftpcode/100 != 2) { + /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a + successful 'TYPE I'. While that is not as RFC959 says, it is still a + positive response code and we allow that. */ + failf(data, "Couldn't set desired mode"); + return CURLE_FTP_COULDNT_SET_TYPE; + } + if(ftpcode != 200) + infof(data, "Got a %03d response code instead of the assumed 200\n", + ftpcode); + + if(instate == FTP_TYPE) + result = ftp_state_post_type(conn); + else if(instate == FTP_LIST_TYPE) + result = ftp_state_post_listtype(conn); + else if(instate == FTP_RETR_TYPE) + result = ftp_state_post_retrtype(conn); + else if(instate == FTP_STOR_TYPE) + result = ftp_state_post_stortype(conn); + + return result; +} + +static CURLcode ftp_state_post_retr_size(struct connectdata *conn, + curl_off_t filesize) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data=conn->data; + struct FTP *ftp = data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(data->set.max_filesize && (filesize > data->set.max_filesize)) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + ftp->downloadsize = filesize; + + if(data->state.resume_from) { + /* We always (attempt to) get the size of downloads, so it is done before + this even when not doing resumes. */ + if(filesize == -1) { + infof(data, "ftp server doesn't support SIZE\n"); + /* We couldn't get the size and therefore we can't know if there really + is a part of the file left to get, although the server will just + close the connection when we start the connection so it won't cause + us any harm, just not make us exit as nicely. */ + } + else { + /* We got a file size report, so we check that there actually is a + part of the file left to get, or else we go home. */ + if(data->state.resume_from< 0) { + /* We're supposed to download the last abs(from) bytes */ + if(filesize < -data->state.resume_from) { + failf(data, "Offset (%" FORMAT_OFF_T + ") was beyond file size (%" FORMAT_OFF_T ")", + data->state.resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* convert to size to download */ + ftp->downloadsize = -data->state.resume_from; + /* download from where? */ + data->state.resume_from = filesize - ftp->downloadsize; + } + else { + if(filesize < data->state.resume_from) { + failf(data, "Offset (%" FORMAT_OFF_T + ") was beyond file size (%" FORMAT_OFF_T ")", + data->state.resume_from, filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* Now store the number of bytes we are expected to download */ + ftp->downloadsize = filesize-data->state.resume_from; + } + } + + if(ftp->downloadsize == 0) { + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + infof(data, "File already completely downloaded\n"); + + /* Set ->transfer so that we won't get any error in ftp_done() + * because we didn't transfer the any file */ + ftp->transfer = FTPTRANSFER_NONE; + state(conn, FTP_STOP); + return CURLE_OK; + } + + /* Set resume file transfer offset */ + infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T + "\n", data->state.resume_from); + + PPSENDF(&ftpc->pp, "REST %" FORMAT_OFF_T, data->state.resume_from); + + state(conn, FTP_RETR_REST); + + } + else { + /* no resume */ + PPSENDF(&ftpc->pp, "RETR %s", ftpc->file); + state(conn, FTP_RETR); + } + + return result; +} + +static CURLcode ftp_state_size_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data=conn->data; + curl_off_t filesize; + char *buf = data->state.buffer; + + /* get the size from the ascii string: */ + filesize = (ftpcode == 213)?curlx_strtoofft(buf+4, NULL, 0):-1; + + if(instate == FTP_SIZE) { +#ifdef CURL_FTP_HTTPSTYLE_HEAD + if(-1 != filesize) { + snprintf(buf, sizeof(data->state.buffer), + "Content-Length: %" FORMAT_OFF_T "\r\n", filesize); + result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); + if(result) + return result; + } +#endif + Curl_pgrsSetDownloadSize(data, filesize); + result = ftp_state_post_size(conn); + } + else if(instate == FTP_RETR_SIZE) { + Curl_pgrsSetDownloadSize(data, filesize); + result = ftp_state_post_retr_size(conn, filesize); + } + else if(instate == FTP_STOR_SIZE) { + data->state.resume_from = filesize; + result = ftp_state_ul_setup(conn, TRUE); + } + + return result; +} + +static CURLcode ftp_state_rest_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + switch(instate) { + case FTP_REST: + default: +#ifdef CURL_FTP_HTTPSTYLE_HEAD + if(ftpcode == 350) { + char buffer[24]= { "Accept-ranges: bytes\r\n" }; + result = Curl_client_write(conn, CLIENTWRITE_BOTH, buffer, 0); + if(result) + return result; + } +#endif + result = ftp_state_post_rest(conn); + break; + + case FTP_RETR_REST: + if(ftpcode != 350) { + failf(conn->data, "Couldn't use REST"); + result = CURLE_FTP_COULDNT_USE_REST; + } + else { + PPSENDF(&ftpc->pp, "RETR %s", ftpc->file); + state(conn, FTP_RETR); + } + break; + } + + return result; +} + +static CURLcode ftp_state_stor_resp(struct connectdata *conn, + int ftpcode, ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + if(ftpcode>=400) { + failf(data, "Failed FTP upload: %0d", ftpcode); + state(conn, FTP_STOP); + /* oops, we never close the sockets! */ + return CURLE_UPLOAD_FAILED; + } + + conn->proto.ftpc.state_saved = instate; + + /* PORT means we are now awaiting the server to connect to us. */ + if(data->set.ftp_use_port) { + bool connected; + + result = AllowServerConnect(conn, &connected); + if(result) + return result; + + if(!connected) { + struct ftp_conn *ftpc = &conn->proto.ftpc; + infof(data, "Data conn was not available immediately\n"); + ftpc->wait_data_conn = TRUE; + } + + return CURLE_OK; + } + else + return InitiateTransfer(conn); +} + +/* for LIST and RETR responses */ +static CURLcode ftp_state_get_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct FTP *ftp = data->state.proto.ftp; + char *buf = data->state.buffer; + + if((ftpcode == 150) || (ftpcode == 125)) { + + /* + A; + 150 Opening BINARY mode data connection for /etc/passwd (2241 + bytes). (ok, the file is being transferred) + + B: + 150 Opening ASCII mode data connection for /bin/ls + + C: + 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes). + + D: + 150 Opening ASCII mode data connection for [file] (0.0.0.0,0) (545 bytes) + + E: + 125 Data connection already open; Transfer starting. */ + + curl_off_t size=-1; /* default unknown size */ + + + /* + * It appears that there are FTP-servers that return size 0 for files when + * SIZE is used on the file while being in BINARY mode. To work around + * that (stupid) behavior, we attempt to parse the RETR response even if + * the SIZE returned size zero. + * + * Debugging help from Salvatore Sorrentino on February 26, 2003. + */ + + if((instate != FTP_LIST) && + !data->set.prefer_ascii && + (ftp->downloadsize < 1)) { + /* + * It seems directory listings either don't show the size or very + * often uses size 0 anyway. ASCII transfers may very well turn out + * that the transferred amount of data is not the same as this line + * tells, why using this number in those cases only confuses us. + * + * Example D above makes this parsing a little tricky */ + char *bytes; + bytes=strstr(buf, " bytes"); + if(bytes--) { + long in=(long)(bytes-buf); + /* this is a hint there is size information in there! ;-) */ + while(--in) { + /* scan for the left parenthesis and break there */ + if('(' == *bytes) + break; + /* skip only digits */ + if(!ISDIGIT(*bytes)) { + bytes=NULL; + break; + } + /* one more estep backwards */ + bytes--; + } + /* if we have nothing but digits: */ + if(bytes++) { + /* get the number! */ + size = curlx_strtoofft(bytes, NULL, 0); + } + } + } + else if(ftp->downloadsize > -1) + size = ftp->downloadsize; + + if(size > data->req.maxdownload && data->req.maxdownload > 0) + size = data->req.size = data->req.maxdownload; + else if((instate != FTP_LIST) && (data->set.prefer_ascii)) + size = -1; /* kludge for servers that understate ASCII mode file size */ + + infof(data, "Maxdownload = %" FORMAT_OFF_T "\n", data->req.maxdownload); + + if(instate != FTP_LIST) + infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size); + + /* FTP download: */ + conn->proto.ftpc.state_saved = instate; + conn->proto.ftpc.retr_size_saved = size; + + if(data->set.ftp_use_port) { + bool connected; + + result = AllowServerConnect(conn, &connected); + if(result) + return result; + + if(!connected) { + struct ftp_conn *ftpc = &conn->proto.ftpc; + infof(data, "Data conn was not available immediately\n"); + state(conn, FTP_STOP); + ftpc->wait_data_conn = TRUE; + } + } + else + return InitiateTransfer(conn); + } + else { + if((instate == FTP_LIST) && (ftpcode == 450)) { + /* simply no matching files in the dir listing */ + ftp->transfer = FTPTRANSFER_NONE; /* don't download anything */ + state(conn, FTP_STOP); /* this phase is over */ + } + else { + failf(data, "RETR response: %03d", ftpcode); + return instate == FTP_RETR && ftpcode == 550? + CURLE_REMOTE_FILE_NOT_FOUND: + CURLE_FTP_COULDNT_RETR_FILE; + } + } + + return result; +} + +/* after USER, PASS and ACCT */ +static CURLcode ftp_state_loggedin(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + +#ifdef HAVE_KRB4 + 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 + */ + if(conn->passwd && *conn->passwd) { + /* BLOCKING */ + result = Curl_krb_kauth(conn); + if(result) + return result; + } + } +#endif + if(conn->ssl[FIRSTSOCKET].use) { + /* PBSZ = PROTECTION BUFFER SIZE. + + The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: + + Specifically, the PROT command MUST be preceded by a PBSZ + command and a PBSZ command MUST be preceded by a successful + security data exchange (the TLS negotiation in this case) + + ... (and on page 8): + + Thus the PBSZ command must still be issued, but must have a + parameter of '0' to indicate that no buffering is taking place + and the data connection should not be encapsulated. + */ + PPSENDF(&conn->proto.ftpc.pp, "PBSZ %d", 0); + state(conn, FTP_PBSZ); + } + else { + result = ftp_state_pwd(conn); + } + return result; +} + +/* for USER and PASS responses */ +static CURLcode ftp_state_user_resp(struct connectdata *conn, + int ftpcode, + ftpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct FTP *ftp = data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + (void)instate; /* no use for this yet */ + + /* some need password anyway, and others just return 2xx ignored */ + if((ftpcode == 331) && (ftpc->state == FTP_USER)) { + /* 331 Password required for ... + (the server requires to send the user's password too) */ + PPSENDF(&ftpc->pp, "PASS %s", ftp->passwd?ftp->passwd:""); + state(conn, FTP_PASS); + } + else if(ftpcode/100 == 2) { + /* 230 User ... logged in. + (the user logged in with or without password) */ + result = ftp_state_loggedin(conn); + } + else if(ftpcode == 332) { + if(data->set.str[STRING_FTP_ACCOUNT]) { + PPSENDF(&ftpc->pp, "ACCT %s", data->set.str[STRING_FTP_ACCOUNT]); + state(conn, FTP_ACCT); + } + else { + failf(data, "ACCT requested but none available"); + result = CURLE_LOGIN_DENIED; + } + } + else { + /* All other response codes, like: + + 530 User ... access denied + (the server denies to log the specified user) */ + + if(conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] && + !conn->data->state.ftp_trying_alternative) { + /* Ok, USER failed. Let's try the supplied command. */ + PPSENDF(&conn->proto.ftpc.pp, "%s", + conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); + conn->data->state.ftp_trying_alternative = TRUE; + state(conn, FTP_USER); + result = CURLE_OK; + } + else { + failf(data, "Access denied: %03d", ftpcode); + result = CURLE_LOGIN_DENIED; + } + } + return result; +} + +/* for ACCT response */ +static CURLcode ftp_state_acct_resp(struct connectdata *conn, + int ftpcode) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + if(ftpcode != 230) { + failf(data, "ACCT rejected by server: %03d", ftpcode); + result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ + } + else + result = ftp_state_loggedin(conn); + + return result; +} + + +static CURLcode ftp_statemach_act(struct connectdata *conn) +{ + CURLcode result; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + struct SessionHandle *data=conn->data; + int ftpcode; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + static const char ftpauth[][4] = { "SSL", "TLS" }; + size_t nread = 0; + + if(pp->sendleft) + return Curl_pp_flushsend(pp); + + result = ftp_readresp(sock, pp, &ftpcode, &nread); + if(result) + return result; + + if(ftpcode) { + /* we have now received a full FTP server response */ + switch(ftpc->state) { + case FTP_WAIT220: + if(ftpcode != 220) { + failf(data, "Got a %03d ftp-server response when 220 was expected", + ftpcode); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + /* We have received a 220 response fine, now we proceed. */ +#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.str[STRING_KRB_LEVEL]); + + if(Curl_sec_login(conn) != CURLE_OK) + infof(data, "Logging in with password in cleartext!\n"); + else + infof(data, "Authentication successful\n"); + } +#endif + + if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { + /* We don't have a SSL/TLS connection yet, but FTPS is + requested. Try a FTPS connection now */ + + ftpc->count3=0; + switch(data->set.ftpsslauth) { + case CURLFTPAUTH_DEFAULT: + case CURLFTPAUTH_SSL: + ftpc->count2 = 1; /* add one to get next */ + ftpc->count1 = 0; + break; + case CURLFTPAUTH_TLS: + ftpc->count2 = -1; /* subtract one to get next */ + ftpc->count1 = 1; + break; + default: + failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", + (int)data->set.ftpsslauth); + return CURLE_UNKNOWN_OPTION; /* we don't know what to do */ + } + PPSENDF(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); + state(conn, FTP_AUTH); + } + else { + result = ftp_state_user(conn); + if(result) + return result; + } + + break; + + case FTP_AUTH: + /* we have gotten the response to a previous AUTH command */ + + /* RFC2228 (page 5) says: + * + * If the server is willing to accept the named security mechanism, + * and does not require any security data, it must respond with + * reply code 234/334. + */ + + if((ftpcode == 234) || (ftpcode == 334)) { + /* Curl_ssl_connect is BLOCKING */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(CURLE_OK == result) { + conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */ + result = ftp_state_user(conn); + } + } + else if(ftpc->count3 < 1) { + ftpc->count3++; + ftpc->count1 += ftpc->count2; /* get next attempt */ + result = Curl_pp_sendf(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); + /* remain in this same state */ + } + else { + if(data->set.use_ssl > CURLUSESSL_TRY) + /* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */ + result = CURLE_USE_SSL_FAILED; + else + /* ignore the failure and continue */ + result = ftp_state_user(conn); + } + + if(result) + return result; + break; + + case FTP_USER: + case FTP_PASS: + result = ftp_state_user_resp(conn, ftpcode, ftpc->state); + break; + + case FTP_ACCT: + result = ftp_state_acct_resp(conn, ftpcode); + break; + + case FTP_PBSZ: + PPSENDF(&ftpc->pp, "PROT %c", + data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); + state(conn, FTP_PROT); + + break; + + case FTP_PROT: + if(ftpcode/100 == 2) + /* We have enabled SSL for the data connection! */ + conn->ssl[SECONDARYSOCKET].use = + (data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE; + /* FTP servers typically responds with 500 if they decide to reject + our 'P' request */ + else if(data->set.use_ssl > CURLUSESSL_CONTROL) + /* we failed and bails out */ + return CURLE_USE_SSL_FAILED; + + if(data->set.ftp_ccc) { + /* CCC - Clear Command Channel + */ + PPSENDF(&ftpc->pp, "CCC", NULL); + state(conn, FTP_CCC); + } + else { + result = ftp_state_pwd(conn); + if(result) + return result; + } + break; + + case FTP_CCC: + if(ftpcode < 500) { + /* First shut down the SSL layer (note: this call will block) */ + result = Curl_ssl_shutdown(conn, FIRSTSOCKET); + + if(result) { + failf(conn->data, "Failed to clear the command channel (CCC)"); + return result; + } + } + + /* Then continue as normal */ + result = ftp_state_pwd(conn); + if(result) + return result; + break; + + case FTP_PWD: + if(ftpcode == 257) { + char *ptr=&data->state.buffer[4]; /* start on the first letter */ + char *dir; + char *store; + + dir = malloc(nread + 1); + if(!dir) + return CURLE_OUT_OF_MEMORY; + + /* Reply format is like + 257"" and the RFC959 + says + + The directory name can contain any character; embedded + double-quotes should be escaped by double-quotes (the + "quote-doubling" convention). + */ + if('\"' == *ptr) { + /* it started good */ + ptr++; + for(store = dir; *ptr;) { + if('\"' == *ptr) { + if('\"' == ptr[1]) { + /* "quote-doubling" */ + *store = ptr[1]; + ptr++; + } + else { + /* end of path */ + *store = '\0'; /* zero terminate */ + break; /* get out of this loop */ + } + } + else + *store = *ptr; + store++; + ptr++; + } + + /* If the path name does not look like an absolute path (i.e.: it + does not start with a '/'), we probably need some server-dependent + adjustments. For example, this is the case when connecting to + an OS400 FTP server: this server supports two name syntaxes, + the default one being incompatible with standard pathes. In + addition, this server switches automatically to the regular path + syntax when one is encountered in a command: this results in + having an entrypath in the wrong syntax when later used in CWD. + The method used here is to check the server OS: we do it only + if the path name looks strange to minimize overhead on other + systems. */ + + if(!ftpc->server_os && dir[0] != '/') { + + result = Curl_pp_sendf(&ftpc->pp, "SYST", NULL); + if(result != CURLE_OK) { + free(dir); + return result; + } + Curl_safefree(ftpc->entrypath); + ftpc->entrypath = dir; /* remember this */ + infof(data, "Entry path is '%s'\n", ftpc->entrypath); + /* also save it where getinfo can access it: */ + data->state.most_recent_ftp_entrypath = ftpc->entrypath; + state(conn, FTP_SYST); + break; + } + + Curl_safefree(ftpc->entrypath); + ftpc->entrypath = dir; /* remember this */ + infof(data, "Entry path is '%s'\n", ftpc->entrypath); + /* also save it where getinfo can access it: */ + data->state.most_recent_ftp_entrypath = ftpc->entrypath; + } + else { + /* couldn't get the path */ + free(dir); + infof(data, "Failed to figure out path\n"); + } + } + state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ + DEBUGF(infof(data, "protocol connect phase DONE\n")); + break; + + case FTP_SYST: + if(ftpcode == 215) { + char *ptr=&data->state.buffer[4]; /* start on the first letter */ + char *os; + char *store; + + os = malloc(nread + 1); + if(!os) + return CURLE_OUT_OF_MEMORY; + + /* Reply format is like + 215 + */ + while(*ptr == ' ') + ptr++; + for(store = os; *ptr && *ptr != ' ';) + *store++ = *ptr++; + *store = '\0'; /* zero terminate */ + + /* Check for special servers here. */ + + if(strequal(os, "OS/400")) { + /* Force OS400 name format 1. */ + result = Curl_pp_sendf(&ftpc->pp, "SITE NAMEFMT 1", NULL); + if(result != CURLE_OK) { + free(os); + return result; + } + /* remember target server OS */ + Curl_safefree(ftpc->server_os); + ftpc->server_os = os; + state(conn, FTP_NAMEFMT); + break; + } + else { + /* Nothing special for the target server. */ + /* remember target server OS */ + Curl_safefree(ftpc->server_os); + ftpc->server_os = os; + } + } + else { + /* Cannot identify server OS. Continue anyway and cross fingers. */ + } + + state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ + DEBUGF(infof(data, "protocol connect phase DONE\n")); + break; + + case FTP_NAMEFMT: + if(ftpcode == 250) { + /* Name format change successful: reload initial path. */ + ftp_state_pwd(conn); + break; + } + + state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ + DEBUGF(infof(data, "protocol connect phase DONE\n")); + break; + + case FTP_QUOTE: + case FTP_POSTQUOTE: + case FTP_RETR_PREQUOTE: + case FTP_STOR_PREQUOTE: + if((ftpcode >= 400) && !ftpc->count2) { + /* failure response code, and not allowed to fail */ + failf(conn->data, "QUOT command failed with %03d", ftpcode); + return CURLE_QUOTE_ERROR; + } + result = ftp_state_quote(conn, FALSE, ftpc->state); + if(result) + return result; + + break; + + case FTP_CWD: + if(ftpcode/100 != 2) { + /* failure to CWD there */ + if(conn->data->set.ftp_create_missing_dirs && + ftpc->count1 && !ftpc->count2) { + /* try making it */ + ftpc->count2++; /* counter to prevent CWD-MKD loops */ + PPSENDF(&ftpc->pp, "MKD %s", ftpc->dirs[ftpc->count1 - 1]); + state(conn, FTP_MKD); + } + else { + /* return failure */ + failf(data, "Server denied you to change to the given directory"); + ftpc->cwdfail = TRUE; /* don't remember this path as we failed + to enter it */ + return CURLE_REMOTE_ACCESS_DENIED; + } + } + else { + /* success */ + ftpc->count2=0; + if(++ftpc->count1 <= ftpc->dirdepth) { + /* send next CWD */ + PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]); + } + else { + result = ftp_state_post_cwd(conn); + if(result) + return result; + } + } + break; + + case FTP_MKD: + if((ftpcode/100 != 2) && !ftpc->count3--) { + /* failure to MKD the dir */ + failf(data, "Failed to MKD dir: %03d", ftpcode); + return CURLE_REMOTE_ACCESS_DENIED; + } + state(conn, FTP_CWD); + /* send CWD */ + PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]); + break; + + case FTP_MDTM: + result = ftp_state_mdtm_resp(conn, ftpcode); + break; + + case FTP_TYPE: + case FTP_LIST_TYPE: + case FTP_RETR_TYPE: + case FTP_STOR_TYPE: + result = ftp_state_type_resp(conn, ftpcode, ftpc->state); + break; + + case FTP_SIZE: + case FTP_RETR_SIZE: + case FTP_STOR_SIZE: + result = ftp_state_size_resp(conn, ftpcode, ftpc->state); + break; + + case FTP_REST: + case FTP_RETR_REST: + result = ftp_state_rest_resp(conn, ftpcode, ftpc->state); + break; + + case FTP_PRET: + if(ftpcode != 200) { + /* there only is this one standard OK return code. */ + failf(data, "PRET command not accepted: %03d", ftpcode); + return CURLE_FTP_PRET_FAILED; + } + result = ftp_state_use_pasv(conn); + break; + + case FTP_PASV: + result = ftp_state_pasv_resp(conn, ftpcode); + break; + + case FTP_PORT: + result = ftp_state_port_resp(conn, ftpcode); + break; + + case FTP_LIST: + case FTP_RETR: + result = ftp_state_get_resp(conn, ftpcode, ftpc->state); + break; + + case FTP_STOR: + result = ftp_state_stor_resp(conn, ftpcode, ftpc->state); + break; + + case FTP_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + state(conn, FTP_STOP); + break; + } + } /* if(ftpcode) */ + + return result; +} + + +/* called repeatedly until done from curl_multi.c */ +static CURLcode ftp_multi_statemach(struct connectdata *conn, + bool *done) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result = Curl_pp_multi_statemach(&ftpc->pp); + + /* Check for the state outside of the Curl_socket_ready() return code checks + since at times we are in fact already in this state when this function + gets called. */ + *done = (ftpc->state == FTP_STOP) ? TRUE : FALSE; + + return result; +} + +static CURLcode ftp_easy_statemach(struct connectdata *conn) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + CURLcode result = CURLE_OK; + + while(ftpc->state != FTP_STOP) { + result = Curl_pp_easy_statemach(pp); + if(result) + break; + } + + return result; +} + +/* + * Allocate and initialize the struct FTP for the current SessionHandle. If + * need be. + */ + +#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \ + defined(__OPTIMIZE__) && defined(__unix__) && defined(__i386__) + /* workaround icc 9.1 optimizer issue */ +#pragma optimize("", off) +#endif + +static CURLcode ftp_init(struct connectdata *conn) +{ + struct FTP *ftp; + + if(NULL == conn->data->state.proto.ftp) { + conn->data->state.proto.ftp = malloc(sizeof(struct FTP)); + if(NULL == conn->data->state.proto.ftp) + return CURLE_OUT_OF_MEMORY; + } + + ftp = conn->data->state.proto.ftp; + + /* get some initial data into the ftp struct */ + ftp->bytecountp = &conn->data->req.bytecount; + ftp->transfer = FTPTRANSFER_BODY; + ftp->downloadsize = 0; + + /* No need to duplicate user+password, the connectdata struct won't change + during a session, but we re-init them here since on subsequent inits + since the conn struct may have changed or been replaced. + */ + ftp->user = conn->user; + ftp->passwd = conn->passwd; + if(isBadFtpString(ftp->user)) + return CURLE_URL_MALFORMAT; + if(isBadFtpString(ftp->passwd)) + return CURLE_URL_MALFORMAT; + + conn->proto.ftpc.known_filesize = -1; /* unknown size for now */ + + return CURLE_OK; +} + +#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \ + defined(__OPTIMIZE__) && defined(__unix__) && defined(__i386__) + /* workaround icc 9.1 optimizer issue */ +#pragma optimize("", on) +#endif + +/* + * ftp_connect() should do everything that is to be considered a part of + * the connection phase. + * + * The variable 'done' points to will be TRUE if the protocol-layer connect + * phase is done when this function returns, or FALSE is not. When called as + * a part of the easy interface, it will always be TRUE. + */ +static CURLcode ftp_connect(struct connectdata *conn, + bool *done) /* see description above */ +{ + CURLcode result; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct SessionHandle *data=conn->data; + struct pingpong *pp = &ftpc->pp; + + *done = FALSE; /* default to not done yet */ + + /* If there already is a protocol-specific struct allocated for this + sessionhandle, deal with it */ + Curl_reset_reqproto(conn); + + result = ftp_init(conn); + if(CURLE_OK != result) + return result; + + /* We always support persistent connections on ftp */ + conn->bits.close = FALSE; + + pp->response_time = RESP_TIMEOUT; /* set default response time-out */ + pp->statemach_act = ftp_statemach_act; + pp->endofresp = ftp_endofresp; + pp->conn = conn; + + if(conn->handler->flags & PROTOPT_SSL) { + /* BLOCKING */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(result) + return result; + } + + Curl_pp_init(pp); /* init the generic pingpong data */ + + /* When we connect, we start in the state where we await the 220 + response */ + state(conn, FTP_WAIT220); + + if(data->state.used_interface == Curl_if_multi) + result = ftp_multi_statemach(conn, done); + else { + result = ftp_easy_statemach(conn); + if(!result) + *done = TRUE; + } + + return result; +} + +/*********************************************************************** + * + * ftp_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode ftp_done(struct connectdata *conn, CURLcode status, + bool premature) +{ + struct SessionHandle *data = conn->data; + struct FTP *ftp = data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + ssize_t nread; + int ftpcode; + CURLcode result = CURLE_OK; + bool was_ctl_valid = ftpc->ctl_valid; + char *path; + const char *path_to_use = data->state.path; + + if(!ftp) + /* When the easy handle is removed from the multi while libcurl is still + * trying to resolve the host name, it seems that the ftp struct is not + * yet initialized, but the removal action calls Curl_done() which calls + * this function. So we simply return success if no ftp pointer is set. + */ + return CURLE_OK; + + switch(status) { + case CURLE_BAD_DOWNLOAD_RESUME: + case CURLE_FTP_WEIRD_PASV_REPLY: + case CURLE_FTP_PORT_FAILED: + case CURLE_FTP_ACCEPT_FAILED: + case CURLE_FTP_ACCEPT_TIMEOUT: + case CURLE_FTP_COULDNT_SET_TYPE: + case CURLE_FTP_COULDNT_RETR_FILE: + case CURLE_PARTIAL_FILE: + case CURLE_UPLOAD_FAILED: + case CURLE_REMOTE_ACCESS_DENIED: + case CURLE_FILESIZE_EXCEEDED: + case CURLE_REMOTE_FILE_NOT_FOUND: + case CURLE_WRITE_ERROR: + /* the connection stays alive fine even though this happened */ + /* fall-through */ + case CURLE_OK: /* doesn't affect the control connection's status */ + if(!premature) { + ftpc->ctl_valid = was_ctl_valid; + break; + } + /* until we cope better with prematurely ended requests, let them + * fallback as if in complete failure */ + default: /* by default, an error means the control connection is + wedged and should not be used anymore */ + ftpc->ctl_valid = FALSE; + ftpc->cwdfail = TRUE; /* set this TRUE to prevent us to remember the + current path, as this connection is going */ + conn->bits.close = TRUE; /* marked for closure */ + result = status; /* use the already set error code */ + break; + } + + /* now store a copy of the directory we are in */ + if(ftpc->prevpath) + free(ftpc->prevpath); + + if(data->set.wildcardmatch) { + if(data->set.chunk_end && ftpc->file) { + data->set.chunk_end(data->wildcard.customptr); + } + ftpc->known_filesize = -1; + } + + /* get the "raw" path */ + path = curl_easy_unescape(data, path_to_use, 0, NULL); + if(!path) { + /* out of memory, but we can limp along anyway (and should try to + * since we may already be in the out of memory cleanup path) */ + if(!result) + result = CURLE_OUT_OF_MEMORY; + ftpc->ctl_valid = FALSE; /* mark control connection as bad */ + conn->bits.close = TRUE; /* mark for connection closure */ + ftpc->prevpath = NULL; /* no path remembering */ + } + else { + size_t flen = ftpc->file?strlen(ftpc->file):0; /* file is "raw" already */ + size_t dlen = strlen(path)-flen; + if(!ftpc->cwdfail) { + if(dlen && (data->set.ftp_filemethod != FTPFILE_NOCWD)) { + ftpc->prevpath = path; + if(flen) + /* if 'path' is not the whole string */ + ftpc->prevpath[dlen]=0; /* terminate */ + } + else { + /* we never changed dir */ + ftpc->prevpath=strdup(""); + free(path); + } + if(ftpc->prevpath) + infof(data, "Remembering we are in dir \"%s\"\n", ftpc->prevpath); + } + else { + ftpc->prevpath = NULL; /* no path */ + free(path); + } + } + /* free the dir tree and file parts */ + freedirs(ftpc); + + /* shut down the socket to inform the server we're done */ + +#ifdef _WIN32_WCE + shutdown(conn->sock[SECONDARYSOCKET],2); /* SD_BOTH */ +#endif + + if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { + if(!result && ftpc->dont_check && data->req.maxdownload > 0) + /* partial download completed */ + result = Curl_pp_sendf(pp, "ABOR"); + if(result) { + failf(data, "Failure sending ABOR command: %s", + curl_easy_strerror(result)); + ftpc->ctl_valid = FALSE; /* mark control connection as bad */ + conn->bits.close = TRUE; /* mark for connection closure */ + } + + if(conn->ssl[SECONDARYSOCKET].use) { + /* The secondary socket is using SSL so we must close down that part + first before we close the socket for real */ + Curl_ssl_close(conn, SECONDARYSOCKET); + + /* Note that we keep "use" set to TRUE since that (next) connection is + still requested to use SSL */ + } + if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) { + Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; + conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE; + } + } + + if(!result && (ftp->transfer == FTPTRANSFER_BODY) && ftpc->ctl_valid && + pp->pending_resp && !premature) { + /* + * Let's see what the server says about the transfer we just performed, + * but lower the timeout as sometimes this connection has died while the + * data has been transferred. This happens when doing through NATs etc that + * abandon old silent connections. + */ + long old_time = pp->response_time; + + pp->response_time = 60*1000; /* give it only a minute for now */ + pp->response = Curl_tvnow(); /* timeout relative now */ + + result = Curl_GetFTPResponse(&nread, conn, &ftpcode); + + pp->response_time = old_time; /* set this back to previous value */ + + if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) { + failf(data, "control connection looks dead"); + ftpc->ctl_valid = FALSE; /* mark control connection as bad */ + conn->bits.close = TRUE; /* mark for closure */ + } + + if(result) + return result; + + if(ftpc->dont_check && data->req.maxdownload > 0) { + /* we have just sent ABOR and there is no reliable way to check if it was + * successful or not; we have to close the connection now */ + infof(data, "partial download completed, closing connection\n"); + conn->bits.close = TRUE; /* mark for closure */ + return result; + } + + if(!ftpc->dont_check) { + /* 226 Transfer complete, 250 Requested file action okay, completed. */ + if((ftpcode != 226) && (ftpcode != 250)) { + failf(data, "server did not report OK, got %d", ftpcode); + result = CURLE_PARTIAL_FILE; + } + } + } + + if(result || premature) + /* the response code from the transfer showed an error already so no + use checking further */ + ; + else if(data->set.upload) { + if((-1 != data->set.infilesize) && + (data->set.infilesize != *ftp->bytecountp) && + !data->set.crlf && + (ftp->transfer == FTPTRANSFER_BODY)) { + failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T + " out of %" FORMAT_OFF_T " bytes)", + *ftp->bytecountp, data->set.infilesize); + result = CURLE_PARTIAL_FILE; + } + } + else { + if((-1 != data->req.size) && + (data->req.size != *ftp->bytecountp) && +#ifdef CURL_DO_LINEEND_CONV + /* Most FTP servers don't adjust their file SIZE response for CRLFs, so + * we'll check to see if the discrepancy can be explained by the number + * of CRLFs we've changed to LFs. + */ + ((data->req.size + data->state.crlf_conversions) != + *ftp->bytecountp) && +#endif /* CURL_DO_LINEEND_CONV */ + (data->req.maxdownload != *ftp->bytecountp)) { + failf(data, "Received only partial file: %" FORMAT_OFF_T " bytes", + *ftp->bytecountp); + result = CURLE_PARTIAL_FILE; + } + else if(!ftpc->dont_check && + !*ftp->bytecountp && + (data->req.size>0)) { + failf(data, "No data was received!"); + result = CURLE_FTP_COULDNT_RETR_FILE; + } + } + + /* clear these for next connection */ + ftp->transfer = FTPTRANSFER_BODY; + ftpc->dont_check = FALSE; + + /* Send any post-transfer QUOTE strings? */ + if(!status && !result && !premature && data->set.postquote) + result = ftp_sendquote(conn, data->set.postquote); + + return result; +} + +/*********************************************************************** + * + * ftp_sendquote() + * + * Where a 'quote' means a list of custom commands to send to the server. + * The quote list is passed as an argument. + * + * BLOCKING + */ + +static +CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote) +{ + struct curl_slist *item; + ssize_t nread; + int ftpcode; + CURLcode result; + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + + item = quote; + while(item) { + if(item->data) { + char *cmd = item->data; + bool acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal FTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server reponds. */ + + if(cmd[0] == '*') { + cmd++; + acceptfail = TRUE; + } + + FTPSENDF(conn, "%s", cmd); + + pp->response = Curl_tvnow(); /* timeout relative now */ + + result = Curl_GetFTPResponse(&nread, conn, &ftpcode); + if(result) + return result; + + if(!acceptfail && (ftpcode >= 400)) { + failf(conn->data, "QUOT string not accepted: %s", cmd); + return CURLE_QUOTE_ERROR; + } + } + + item = item->next; + } + + return CURLE_OK; +} + +/*********************************************************************** + * + * ftp_need_type() + * + * Returns TRUE if we in the current situation should send TYPE + */ +static int ftp_need_type(struct connectdata *conn, + bool ascii_wanted) +{ + return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I'); +} + +/*********************************************************************** + * + * ftp_nb_type() + * + * Set TYPE. We only deal with ASCII or BINARY so this function + * sets one of them. + * If the transfer type is not sent, simulate on OK response in newstate + */ +static CURLcode ftp_nb_type(struct connectdata *conn, + bool ascii, ftpstate newstate) +{ + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result; + char want = (char)(ascii?'A':'I'); + + if(ftpc->transfertype == want) { + state(conn, newstate); + return ftp_state_type_resp(conn, 200, newstate); + } + + PPSENDF(&ftpc->pp, "TYPE %c", want); + state(conn, newstate); + + /* keep track of our current transfer type */ + ftpc->transfertype = want; + return CURLE_OK; +} + +/*************************************************************************** + * + * ftp_pasv_verbose() + * + * This function only outputs some informationals about this second connection + * when we've issued a PASV command before and thus we have connected to a + * possibly new IP address. + * + */ +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void +ftp_pasv_verbose(struct connectdata *conn, + Curl_addrinfo *ai, + char *newhost, /* ascii version */ + int port) +{ + char buf[256]; + Curl_printable_address(ai, buf, sizeof(buf)); + infof(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port); +} +#endif + +/* + Check if this is a range download, and if so, set the internal variables + properly. + */ + +static CURLcode ftp_range(struct connectdata *conn) +{ + curl_off_t from, to; + char *ptr; + char *ptr2; + struct SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(data->state.use_range && data->state.range) { + from=curlx_strtoofft(data->state.range, &ptr, 0); + while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) + ptr++; + to=curlx_strtoofft(ptr, &ptr2, 0); + if(ptr == ptr2) { + /* we didn't get any digit */ + to=-1; + } + if((-1 == to) && (from>=0)) { + /* X - */ + data->state.resume_from = from; + DEBUGF(infof(conn->data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", + from)); + } + else if(from < 0) { + /* -Y */ + data->req.maxdownload = -from; + data->state.resume_from = from; + DEBUGF(infof(conn->data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", + -from)); + } + else { + /* X-Y */ + data->req.maxdownload = (to-from)+1; /* include last byte */ + data->state.resume_from = from; + DEBUGF(infof(conn->data, "FTP RANGE from %" FORMAT_OFF_T + " getting %" FORMAT_OFF_T " bytes\n", + from, data->req.maxdownload)); + } + DEBUGF(infof(conn->data, "range-download from %" FORMAT_OFF_T + " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n", + from, to, data->req.maxdownload)); + ftpc->dont_check = TRUE; /* dont check for successful transfer */ + } + else + data->req.maxdownload = -1; + return CURLE_OK; +} + + +/* + * ftp_do_more() + * + * This function shall be called when the second FTP (data) connection is + * connected. + */ + +static CURLcode ftp_do_more(struct connectdata *conn, bool *complete) +{ + struct SessionHandle *data=conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + CURLcode result = CURLE_OK; + bool connected = FALSE; + + /* the ftp struct is inited in ftp_connect() */ + struct FTP *ftp = data->state.proto.ftp; + + *complete = FALSE; + + /* if the second connection isn't done yet, wait for it */ + if(!conn->bits.tcpconnect[SECONDARYSOCKET]) { + result = Curl_is_connected(conn, SECONDARYSOCKET, &connected); + + /* Ready to do more? */ + if(connected) { + DEBUGF(infof(data, "DO-MORE connected phase starts\n")); + } + else + return result; + } + + if((data->state.used_interface == Curl_if_multi) && + ftpc->state) { + /* multi interface and already in a state so skip the intial commands. + They are only done to kickstart the do_more state */ + result = ftp_multi_statemach(conn, complete); + + /* if we got an error or if we don't wait for a data connection return + immediately */ + if(result || (ftpc->wait_data_conn != TRUE)) + return result; + } + + if(ftp->transfer <= FTPTRANSFER_INFO) { + /* a transfer is about to take place, or if not a file name was given + so we'll do a SIZE on it later and then we need the right TYPE first */ + + if(ftpc->wait_data_conn == TRUE) { + bool serv_conned; + + result = ReceivedServerConnect(conn, &serv_conned); + if(result) + return result; /* Failed to accept data connection */ + + if(serv_conned) { + /* It looks data connection is established */ + result = AcceptServerConnect(conn); + ftpc->wait_data_conn = FALSE; + if(!result) + result = InitiateTransfer(conn); + + if(result) + return result; + } + } + else if(data->set.upload) { + result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_STOR_TYPE); + if(result) + return result; + } + else { + /* download */ + ftp->downloadsize = -1; /* unknown as of yet */ + + result = ftp_range(conn); + if(result) + ; + else if(data->set.ftp_list_only || !ftpc->file) { + /* The specified path ends with a slash, and therefore we think this + is a directory that is requested, use LIST. But before that we + need to set ASCII transfer mode. */ + + /* But only if a body transfer was requested. */ + if(ftp->transfer == FTPTRANSFER_BODY) { + result = ftp_nb_type(conn, TRUE, FTP_LIST_TYPE); + if(result) + return result; + } + /* otherwise just fall through */ + } + else { + result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_RETR_TYPE); + if(result) + return result; + } + } + if(data->state.used_interface == Curl_if_multi) { + result = ftp_multi_statemach(conn, complete); + + return result; + } + else + result = ftp_easy_statemach(conn); + } + + if((result == CURLE_OK) && (ftp->transfer != FTPTRANSFER_BODY)) + /* no data to transfer. FIX: it feels like a kludge to have this here + too! */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + if(!ftpc->wait_data_conn) { + /* no waiting for the data connection so this is now complete */ + *complete = TRUE; + DEBUGF(infof(data, "DO-MORE phase ends with %d\n", (int)result)); + } + + return result; +} + + + +/*********************************************************************** + * + * ftp_perform() + * + * This is the actual DO function for FTP. Get a file/directory according to + * the options previously setup. + */ + +static +CURLcode ftp_perform(struct connectdata *conn, + bool *connected, /* connect status after PASV / PORT */ + bool *dophase_done) +{ + /* this is FTP and no proxy */ + CURLcode result=CURLE_OK; + + DEBUGF(infof(conn->data, "DO phase starts\n")); + + if(conn->data->set.opt_no_body) { + /* requested no body means no transfer... */ + struct FTP *ftp = conn->data->state.proto.ftp; + ftp->transfer = FTPTRANSFER_INFO; + } + + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + result = ftp_state_quote(conn, TRUE, FTP_QUOTE); + if(result) + return result; + + /* run the state-machine */ + if(conn->data->state.used_interface == Curl_if_multi) + result = ftp_multi_statemach(conn, dophase_done); + else { + result = ftp_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect[FIRSTSOCKET]; + + if(*dophase_done) + DEBUGF(infof(conn->data, "DO phase is complete\n")); + + return result; +} + +static void wc_data_dtor(void *ptr) +{ + struct ftp_wc_tmpdata *tmp = ptr; + if(tmp) + Curl_ftp_parselist_data_free(&tmp->parser); + Curl_safefree(tmp); +} + +static CURLcode init_wc_data(struct connectdata *conn) +{ + char *last_slash; + char *path = conn->data->state.path; + struct WildcardData *wildcard = &(conn->data->wildcard); + CURLcode ret = CURLE_OK; + struct ftp_wc_tmpdata *ftp_tmp; + + last_slash = strrchr(conn->data->state.path, '/'); + if(last_slash) { + last_slash++; + if(last_slash[0] == '\0') { + wildcard->state = CURLWC_CLEAN; + ret = ftp_parse_url_path(conn); + return ret; + } + else { + wildcard->pattern = strdup(last_slash); + if(!wildcard->pattern) + return CURLE_OUT_OF_MEMORY; + last_slash[0] = '\0'; /* cut file from path */ + } + } + else { /* there is only 'wildcard pattern' or nothing */ + if(path[0]) { + wildcard->pattern = strdup(path); + if(!wildcard->pattern) + return CURLE_OUT_OF_MEMORY; + path[0] = '\0'; + } + else { /* only list */ + wildcard->state = CURLWC_CLEAN; + ret = ftp_parse_url_path(conn); + return ret; + } + } + + /* program continues only if URL is not ending with slash, allocate needed + resources for wildcard transfer */ + + /* allocate ftp protocol specific temporary wildcard data */ + ftp_tmp = calloc(1, sizeof(struct ftp_wc_tmpdata)); + if(!ftp_tmp) { + Curl_safefree(wildcard->pattern); + return CURLE_OUT_OF_MEMORY; + } + + /* INITIALIZE parselist structure */ + ftp_tmp->parser = Curl_ftp_parselist_data_alloc(); + if(!ftp_tmp->parser) { + Curl_safefree(wildcard->pattern); + Curl_safefree(ftp_tmp); + return CURLE_OUT_OF_MEMORY; + } + + wildcard->tmp = ftp_tmp; /* put it to the WildcardData tmp pointer */ + wildcard->tmp_dtor = wc_data_dtor; + + /* wildcard does not support NOCWD option (assert it?) */ + if(conn->data->set.ftp_filemethod == FTPFILE_NOCWD) + conn->data->set.ftp_filemethod = FTPFILE_MULTICWD; + + /* try to parse ftp url */ + ret = ftp_parse_url_path(conn); + if(ret) { + Curl_safefree(wildcard->pattern); + wildcard->tmp_dtor(wildcard->tmp); + wildcard->tmp_dtor = ZERO_NULL; + wildcard->tmp = NULL; + return ret; + } + + wildcard->path = strdup(conn->data->state.path); + if(!wildcard->path) { + Curl_safefree(wildcard->pattern); + wildcard->tmp_dtor(wildcard->tmp); + wildcard->tmp_dtor = ZERO_NULL; + wildcard->tmp = NULL; + return CURLE_OUT_OF_MEMORY; + } + + /* backup old write_function */ + ftp_tmp->backup.write_function = conn->data->set.fwrite_func; + /* parsing write function */ + conn->data->set.fwrite_func = Curl_ftp_parselist; + /* backup old file descriptor */ + ftp_tmp->backup.file_descriptor = conn->data->set.out; + /* let the writefunc callback know what curl pointer is working with */ + conn->data->set.out = conn; + + infof(conn->data, "Wildcard - Parsing started\n"); + return CURLE_OK; +} + +/* This is called recursively */ +static CURLcode wc_statemach(struct connectdata *conn) +{ + struct WildcardData * const wildcard = &(conn->data->wildcard); + CURLcode ret = CURLE_OK; + + switch (wildcard->state) { + case CURLWC_INIT: + ret = init_wc_data(conn); + if(wildcard->state == CURLWC_CLEAN) + /* only listing! */ + break; + else + wildcard->state = ret ? CURLWC_ERROR : CURLWC_MATCHING; + break; + + case CURLWC_MATCHING: { + /* In this state is LIST response successfully parsed, so lets restore + previous WRITEFUNCTION callback and WRITEDATA pointer */ + struct ftp_wc_tmpdata *ftp_tmp = wildcard->tmp; + conn->data->set.fwrite_func = ftp_tmp->backup.write_function; + conn->data->set.out = ftp_tmp->backup.file_descriptor; + ftp_tmp->backup.write_function = ZERO_NULL; + ftp_tmp->backup.file_descriptor = NULL; + wildcard->state = CURLWC_DOWNLOADING; + + if(Curl_ftp_parselist_geterror(ftp_tmp->parser)) { + /* error found in LIST parsing */ + wildcard->state = CURLWC_CLEAN; + return wc_statemach(conn); + } + else if(wildcard->filelist->size == 0) { + /* no corresponding file */ + wildcard->state = CURLWC_CLEAN; + return CURLE_REMOTE_FILE_NOT_FOUND; + } + return wc_statemach(conn); + } + + case CURLWC_DOWNLOADING: { + /* filelist has at least one file, lets get first one */ + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct curl_fileinfo *finfo = wildcard->filelist->head->ptr; + char *tmp_path = malloc(strlen(conn->data->state.path) + + strlen(finfo->filename) + 1); + if(!tmp_path) { + return CURLE_OUT_OF_MEMORY; + } + + tmp_path[0] = 0; + /* make full path to matched file */ + strcat(tmp_path, wildcard->path); + strcat(tmp_path, finfo->filename); + /* switch default "state.pathbuffer" and tmp_path, good to see + ftp_parse_url_path function to understand this trick */ + Curl_safefree(conn->data->state.pathbuffer); + conn->data->state.pathbuffer = tmp_path; + conn->data->state.path = tmp_path; + + infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename); + if(conn->data->set.chunk_bgn) { + long userresponse = conn->data->set.chunk_bgn( + finfo, wildcard->customptr, (int)wildcard->filelist->size); + switch(userresponse) { + case CURL_CHUNK_BGN_FUNC_SKIP: + infof(conn->data, "Wildcard - \"%s\" skipped by user\n", + finfo->filename); + wildcard->state = CURLWC_SKIP; + return wc_statemach(conn); + case CURL_CHUNK_BGN_FUNC_FAIL: + return CURLE_CHUNK_FAILED; + } + } + + if(finfo->filetype != CURLFILETYPE_FILE) { + wildcard->state = CURLWC_SKIP; + return wc_statemach(conn); + } + + if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) + ftpc->known_filesize = finfo->size; + + ret = ftp_parse_url_path(conn); + if(ret) { + return ret; + } + + /* we don't need the Curl_fileinfo of first file anymore */ + Curl_llist_remove(wildcard->filelist, wildcard->filelist->head, NULL); + + if(wildcard->filelist->size == 0) { /* remains only one file to down. */ + wildcard->state = CURLWC_CLEAN; + /* after that will be ftp_do called once again and no transfer + will be done because of CURLWC_CLEAN state */ + return CURLE_OK; + } + } break; + + case CURLWC_SKIP: { + if(conn->data->set.chunk_end) + conn->data->set.chunk_end(conn->data->wildcard.customptr); + Curl_llist_remove(wildcard->filelist, wildcard->filelist->head, NULL); + wildcard->state = (wildcard->filelist->size == 0) ? + CURLWC_CLEAN : CURLWC_DOWNLOADING; + return wc_statemach(conn); + } + + case CURLWC_CLEAN: { + struct ftp_wc_tmpdata *ftp_tmp = wildcard->tmp; + ret = CURLE_OK; + if(ftp_tmp) { + ret = Curl_ftp_parselist_geterror(ftp_tmp->parser); + } + wildcard->state = ret ? CURLWC_ERROR : CURLWC_DONE; + } break; + + case CURLWC_DONE: + case CURLWC_ERROR: + break; + } + + return ret; +} + +/*********************************************************************** + * + * ftp_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (ftp_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode ftp_do(struct connectdata *conn, bool *done) +{ + CURLcode retcode = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + *done = FALSE; /* default to false */ + ftpc->wait_data_conn = FALSE; /* default to no such wait */ + + /* + Since connections can be re-used between SessionHandles, this might be a + connection already existing but on a fresh SessionHandle struct so we must + make sure we have a good 'struct FTP' to play with. For new connections, + the struct FTP is allocated and setup in the ftp_connect() function. + */ + Curl_reset_reqproto(conn); + retcode = ftp_init(conn); + if(retcode) + return retcode; + + if(conn->data->set.wildcardmatch) { + retcode = wc_statemach(conn); + if(conn->data->wildcard.state == CURLWC_SKIP || + conn->data->wildcard.state == CURLWC_DONE) { + /* do not call ftp_regular_transfer */ + return CURLE_OK; + } + if(retcode) /* error, loop or skipping the file */ + return retcode; + } + else { /* no wildcard FSM needed */ + retcode = ftp_parse_url_path(conn); + if(retcode) + return retcode; + } + + retcode = ftp_regular_transfer(conn, done); + + return retcode; +} + + +CURLcode Curl_ftpsendf(struct connectdata *conn, + const char *fmt, ...) +{ + ssize_t bytes_written; +#define SBUF_SIZE 1024 + 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, SBUF_SIZE-3, fmt, ap); + va_end(ap); + + strcat(s, "\r\n"); /* append a trailing CRLF */ + + bytes_written=0; + write_len = strlen(s); + + res = Curl_convert_to_network(conn->data, s, write_len); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(res) + return(res); + + for(;;) { +#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) + DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); + conn->data_prot = data_sec; +#endif + + if(CURLE_OK != res) + break; + + if(conn->data->set.verbose) + Curl_debug(conn->data, CURLINFO_HEADER_OUT, + sptr, (size_t)bytes_written, conn); + + if(bytes_written != (ssize_t)write_len) { + write_len -= bytes_written; + sptr += bytes_written; + } + else + break; + } + + return res; +} + +/*********************************************************************** + * + * ftp_quit() + * + * This should be called before calling sclose() on an ftp control connection + * (not data connections). We should then wait for the response from the + * server before returning. The calling code should then try to close the + * connection. + * + */ +static CURLcode ftp_quit(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + if(conn->proto.ftpc.ctl_valid) { + result = Curl_pp_sendf(&conn->proto.ftpc.pp, "QUIT", NULL); + if(result) { + failf(conn->data, "Failure sending QUIT command: %s", + curl_easy_strerror(result)); + conn->proto.ftpc.ctl_valid = FALSE; /* mark control connection as bad */ + conn->bits.close = TRUE; /* mark for connection closure */ + state(conn, FTP_STOP); + return result; + } + + state(conn, FTP_QUIT); + + result = ftp_easy_statemach(conn); + } + + return result; +} + +/*********************************************************************** + * + * ftp_disconnect() + * + * Disconnect from an FTP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode ftp_disconnect(struct connectdata *conn, bool dead_connection) +{ + struct ftp_conn *ftpc= &conn->proto.ftpc; + struct pingpong *pp = &ftpc->pp; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to. + + ftp_quit() will check the state of ftp->ctl_valid. If it's ok it + will try to send the QUIT command, otherwise it will just return. + */ + if(dead_connection) + ftpc->ctl_valid = FALSE; + + /* The FTP session may or may not have been allocated/setup at this point! */ + (void)ftp_quit(conn); /* ignore errors on the QUIT */ + + if(ftpc->entrypath) { + struct SessionHandle *data = conn->data; + if(data->state.most_recent_ftp_entrypath == ftpc->entrypath) { + data->state.most_recent_ftp_entrypath = NULL; + } + free(ftpc->entrypath); + ftpc->entrypath = NULL; + } + + freedirs(ftpc); + if(ftpc->prevpath) { + free(ftpc->prevpath); + ftpc->prevpath = NULL; + } + if(ftpc->server_os) { + free(ftpc->server_os); + ftpc->server_os = NULL; + } + + Curl_pp_disconnect(pp); + +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + Curl_sec_end(conn); +#endif + + return CURLE_OK; +} + +/*********************************************************************** + * + * ftp_parse_url_path() + * + * Parse the URL path into separate path components. + * + */ +static +CURLcode ftp_parse_url_path(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + /* the ftp struct is already inited in ftp_connect() */ + struct FTP *ftp = data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + const char *slash_pos; /* position of the first '/' char in curpos */ + const char *path_to_use = data->state.path; + const char *cur_pos; + const char *filename = NULL; + + cur_pos = path_to_use; /* current position in path. point at the begin + of next path component */ + + ftpc->ctl_valid = FALSE; + ftpc->cwdfail = FALSE; + + switch(data->set.ftp_filemethod) { + case FTPFILE_NOCWD: + /* fastest, but less standard-compliant */ + + /* + The best time to check whether the path is a file or directory is right + here. so: + + the first condition in the if() right here, is there just in case + someone decides to set path to NULL one day + */ + if(data->state.path && + data->state.path[0] && + (data->state.path[strlen(data->state.path) - 1] != '/') ) + filename = data->state.path; /* this is a full file path */ + /* + ftpc->file is not used anywhere other than for operations on a file. + In other words, never for directory operations. + So we can safely leave filename as NULL here and use it as a + argument in dir/file decisions. + */ + break; + + case FTPFILE_SINGLECWD: + /* get the last slash */ + if(!path_to_use[0]) { + /* no dir, no file */ + ftpc->dirdepth = 0; + break; + } + slash_pos=strrchr(cur_pos, '/'); + if(slash_pos || !*cur_pos) { + ftpc->dirs = calloc(1, sizeof(ftpc->dirs[0])); + if(!ftpc->dirs) + return CURLE_OUT_OF_MEMORY; + + ftpc->dirs[0] = curl_easy_unescape(conn->data, slash_pos ? cur_pos : "/", + slash_pos ? + curlx_sztosi(slash_pos-cur_pos) : 1, + NULL); + if(!ftpc->dirs[0]) { + freedirs(ftpc); + return CURLE_OUT_OF_MEMORY; + } + ftpc->dirdepth = 1; /* we consider it to be a single dir */ + filename = slash_pos ? slash_pos+1 : cur_pos; /* rest is file name */ + } + else + filename = cur_pos; /* this is a file name only */ + break; + + default: /* allow pretty much anything */ + case FTPFILE_MULTICWD: + ftpc->dirdepth = 0; + ftpc->diralloc = 5; /* default dir depth to allocate */ + ftpc->dirs = calloc(ftpc->diralloc, sizeof(ftpc->dirs[0])); + if(!ftpc->dirs) + return CURLE_OUT_OF_MEMORY; + + /* we have a special case for listing the root dir only */ + if(strequal(path_to_use, "/")) { + cur_pos++; /* make it point to the zero byte */ + ftpc->dirs[0] = strdup("/"); + ftpc->dirdepth++; + } + else { + /* parse the URL path into separate path components */ + while((slash_pos = strchr(cur_pos, '/')) != NULL) { + /* 1 or 0 pointer offset to indicate absolute directory */ + ssize_t absolute_dir = ((cur_pos - data->state.path > 0) && + (ftpc->dirdepth == 0))?1:0; + + /* seek out the next path component */ + if(slash_pos-cur_pos) { + /* we skip empty path components, like "x//y" since the FTP command + CWD requires a parameter and a non-existent parameter a) doesn't + work on many servers and b) has no effect on the others. */ + int len = curlx_sztosi(slash_pos - cur_pos + absolute_dir); + ftpc->dirs[ftpc->dirdepth] = + curl_easy_unescape(conn->data, cur_pos - absolute_dir, len, NULL); + if(!ftpc->dirs[ftpc->dirdepth]) { /* run out of memory ... */ + failf(data, "no memory"); + freedirs(ftpc); + return CURLE_OUT_OF_MEMORY; + } + if(isBadFtpString(ftpc->dirs[ftpc->dirdepth])) { + free(ftpc->dirs[ftpc->dirdepth]); + freedirs(ftpc); + return CURLE_URL_MALFORMAT; + } + } + else { + cur_pos = slash_pos + 1; /* jump to the rest of the string */ + continue; + } + + cur_pos = slash_pos + 1; /* jump to the rest of the string */ + if(++ftpc->dirdepth >= ftpc->diralloc) { + /* enlarge array */ + char **bigger; + ftpc->diralloc *= 2; /* double the size each time */ + bigger = realloc(ftpc->dirs, ftpc->diralloc * sizeof(ftpc->dirs[0])); + if(!bigger) { + freedirs(ftpc); + return CURLE_OUT_OF_MEMORY; + } + ftpc->dirs = bigger; + } + } + } + filename = cur_pos; /* the rest is the file name */ + break; + } /* switch */ + + if(filename && *filename) { + ftpc->file = curl_easy_unescape(conn->data, filename, 0, NULL); + if(NULL == ftpc->file) { + freedirs(ftpc); + failf(data, "no memory"); + return CURLE_OUT_OF_MEMORY; + } + if(isBadFtpString(ftpc->file)) { + freedirs(ftpc); + return CURLE_URL_MALFORMAT; + } + } + else + ftpc->file=NULL; /* instead of point to a zero byte, we make it a NULL + pointer */ + + if(data->set.upload && !ftpc->file && (ftp->transfer == FTPTRANSFER_BODY)) { + /* We need a file name when uploading. Return error! */ + failf(data, "Uploading to a URL without a file name!"); + return CURLE_URL_MALFORMAT; + } + + ftpc->cwddone = FALSE; /* default to not done */ + + if(ftpc->prevpath) { + /* prevpath is "raw" so we convert the input path before we compare the + strings */ + int dlen; + char *path = curl_easy_unescape(conn->data, data->state.path, 0, &dlen); + if(!path) { + freedirs(ftpc); + return CURLE_OUT_OF_MEMORY; + } + + dlen -= ftpc->file?curlx_uztosi(strlen(ftpc->file)):0; + if((dlen == curlx_uztosi(strlen(ftpc->prevpath))) && + strnequal(path, ftpc->prevpath, dlen)) { + infof(data, "Request has same path as previous transfer\n"); + ftpc->cwddone = TRUE; + } + free(path); + } + + return CURLE_OK; +} + +/* call this when the DO phase has completed */ +static CURLcode ftp_dophase_done(struct connectdata *conn, + bool connected) +{ + struct FTP *ftp = conn->data->state.proto.ftp; + struct ftp_conn *ftpc = &conn->proto.ftpc; + + if(connected) { + bool completed; + CURLcode result = ftp_do_more(conn, &completed); + + if(result) { + if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { + /* close the second socket if it was created already */ + Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; + } + return result; + } + } + + if(ftp->transfer != FTPTRANSFER_BODY) + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + else if(!connected) + /* since we didn't connect now, we want do_more to get called */ + conn->bits.do_more = TRUE; + + ftpc->ctl_valid = TRUE; /* seems good */ + + return CURLE_OK; +} + +/* called from curl_multi.c while DOing */ +static CURLcode ftp_doing(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result = ftp_multi_statemach(conn, dophase_done); + + if(result) + DEBUGF(infof(conn->data, "DO phase failed\n")); + else if(*dophase_done) { + result = ftp_dophase_done(conn, FALSE /* not connected */); + + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + return result; +} + +/*********************************************************************** + * + * ftp_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + * + * ftp->ctl_valid starts out as FALSE, and gets set to TRUE if we reach the + * ftp_done() function without finding any major problem. + */ +static +CURLcode ftp_regular_transfer(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result=CURLE_OK; + bool connected=FALSE; + struct SessionHandle *data = conn->data; + struct ftp_conn *ftpc = &conn->proto.ftpc; + data->req.size = -1; /* make sure this is unknown at this point */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, 0); + Curl_pgrsSetDownloadSize(data, 0); + + ftpc->ctl_valid = TRUE; /* starts good */ + + result = ftp_perform(conn, + &connected, /* have we connected after PASV/PORT */ + dophase_done); /* all commands in the DO-phase done? */ + + if(CURLE_OK == result) { + + if(!*dophase_done) + /* the DO phase has not completed yet */ + return CURLE_OK; + + result = ftp_dophase_done(conn, connected); + if(result) + return result; + } + else + freedirs(ftpc); + + return result; +} + +static CURLcode ftp_setup_connection(struct connectdata * conn) +{ + struct SessionHandle *data = conn->data; + char * type; + char command; + + if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { + /* Unless we have asked to tunnel ftp operations through the proxy, we + switch and use HTTP operations only */ +#ifndef CURL_DISABLE_HTTP + if(conn->handler == &Curl_handler_ftp) + conn->handler = &Curl_handler_ftp_proxy; + else { +#ifdef USE_SSL + conn->handler = &Curl_handler_ftps_proxy; +#else + failf(data, "FTPS not supported!"); + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + /* + * We explicitly mark this connection as persistent here as we're doing + * FTP over HTTP and thus we accidentally avoid setting this value + * otherwise. + */ + conn->bits.close = FALSE; +#else + failf(data, "FTP over http proxy requires HTTP support built-in!"); + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + + data->state.path++; /* don't include the initial slash */ + data->state.slash_removed = TRUE; /* we've skipped the slash */ + + /* FTP URLs support an extension like ";type=" that + * we'll try to get now! */ + type = strstr(data->state.path, ";type="); + + if(!type) + type = strstr(conn->host.rawalloc, ";type="); + + if(type) { + *type = 0; /* it was in the middle of the hostname */ + command = Curl_raw_toupper(type[6]); + conn->bits.type_set = TRUE; + + switch (command) { + case 'A': /* ASCII mode */ + data->set.prefer_ascii = TRUE; + break; + + case 'D': /* directory mode */ + data->set.ftp_list_only = TRUE; + break; + + case 'I': /* binary mode */ + default: + /* switch off ASCII */ + data->set.prefer_ascii = FALSE; + break; + } + } + + return CURLE_OK; +} + +#endif /* CURL_DISABLE_FTP */ diff --git a/lib/curl_ftplistparser.c b/lib/curl_ftplistparser.c new file mode 100644 index 000000000..a1a7d5187 --- /dev/null +++ b/lib/curl_ftplistparser.c @@ -0,0 +1,1050 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +/** + * Now implemented: + * + * 1) UNIX version 1 + * drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog + * 2) UNIX version 2 + * drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog + * 3) UNIX version 3 + * drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog + * 4) UNIX symlink + * lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000 + * 5) DOS style + * 01-29-97 11:32PM prog + */ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_FTP + +#include + +#include "curl_urldata.h" +#include "curl_fileinfo.h" +#include "curl_llist.h" +#include "curl_strtoofft.h" +#include "curl_rawstr.h" +#include "curl_ftp.h" +#include "curl_ftplistparser.h" +#include "curl_fnmatch.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* allocs buffer which will contain one line of LIST command response */ +#define FTP_BUFFER_ALLOCSIZE 160 + +typedef enum { + PL_UNIX_TOTALSIZE = 0, + PL_UNIX_FILETYPE, + PL_UNIX_PERMISSION, + PL_UNIX_HLINKS, + PL_UNIX_USER, + PL_UNIX_GROUP, + PL_UNIX_SIZE, + PL_UNIX_TIME, + PL_UNIX_FILENAME, + PL_UNIX_SYMLINK +} pl_unix_mainstate; + +typedef union { + enum { + PL_UNIX_TOTALSIZE_INIT = 0, + PL_UNIX_TOTALSIZE_READING + } total_dirsize; + + enum { + PL_UNIX_HLINKS_PRESPACE = 0, + PL_UNIX_HLINKS_NUMBER + } hlinks; + + enum { + PL_UNIX_USER_PRESPACE = 0, + PL_UNIX_USER_PARSING + } user; + + enum { + PL_UNIX_GROUP_PRESPACE = 0, + PL_UNIX_GROUP_NAME + } group; + + enum { + PL_UNIX_SIZE_PRESPACE = 0, + PL_UNIX_SIZE_NUMBER + } size; + + enum { + PL_UNIX_TIME_PREPART1 = 0, + PL_UNIX_TIME_PART1, + PL_UNIX_TIME_PREPART2, + PL_UNIX_TIME_PART2, + PL_UNIX_TIME_PREPART3, + PL_UNIX_TIME_PART3 + } time; + + enum { + PL_UNIX_FILENAME_PRESPACE = 0, + PL_UNIX_FILENAME_NAME, + PL_UNIX_FILENAME_WINDOWSEOL + } filename; + + enum { + PL_UNIX_SYMLINK_PRESPACE = 0, + PL_UNIX_SYMLINK_NAME, + PL_UNIX_SYMLINK_PRETARGET1, + PL_UNIX_SYMLINK_PRETARGET2, + PL_UNIX_SYMLINK_PRETARGET3, + PL_UNIX_SYMLINK_PRETARGET4, + PL_UNIX_SYMLINK_TARGET, + PL_UNIX_SYMLINK_WINDOWSEOL + } symlink; +} pl_unix_substate; + +typedef enum { + PL_WINNT_DATE = 0, + PL_WINNT_TIME, + PL_WINNT_DIRORSIZE, + PL_WINNT_FILENAME +} pl_winNT_mainstate; + +typedef union { + enum { + PL_WINNT_TIME_PRESPACE = 0, + PL_WINNT_TIME_TIME + } time; + enum { + PL_WINNT_DIRORSIZE_PRESPACE = 0, + PL_WINNT_DIRORSIZE_CONTENT + } dirorsize; + enum { + PL_WINNT_FILENAME_PRESPACE = 0, + PL_WINNT_FILENAME_CONTENT, + PL_WINNT_FILENAME_WINEOL + } filename; +} pl_winNT_substate; + +/* This struct is used in wildcard downloading - for parsing LIST response */ +struct ftp_parselist_data { + enum { + OS_TYPE_UNKNOWN = 0, + OS_TYPE_UNIX, + OS_TYPE_WIN_NT + } os_type; + + union { + struct { + pl_unix_mainstate main; + pl_unix_substate sub; + } UNIX; + + struct { + pl_winNT_mainstate main; + pl_winNT_substate sub; + } NT; + } state; + + CURLcode error; + struct curl_fileinfo *file_data; + unsigned int item_length; + size_t item_offset; + struct { + size_t filename; + size_t user; + size_t group; + size_t time; + size_t perm; + size_t symlink_target; + } offsets; +}; + +struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void) +{ + return calloc(1, sizeof(struct ftp_parselist_data)); +} + + +void Curl_ftp_parselist_data_free(struct ftp_parselist_data **pl_data) +{ + if(*pl_data) + free(*pl_data); + *pl_data = NULL; +} + + +CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data) +{ + return pl_data->error; +} + + +#define FTP_LP_MALFORMATED_PERM 0x01000000 + +static int ftp_pl_get_permission(const char *str) +{ + int permissions = 0; + /* USER */ + if(str[0] == 'r') + permissions |= 1 << 8; + else if(str[0] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[1] == 'w') + permissions |= 1 << 7; + else if(str[1] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + + if(str[2] == 'x') + permissions |= 1 << 6; + else if(str[2] == 's') { + permissions |= 1 << 6; + permissions |= 1 << 11; + } + else if(str[2] == 'S') + permissions |= 1 << 11; + else if(str[2] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + /* GROUP */ + if(str[3] == 'r') + permissions |= 1 << 5; + else if(str[3] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[4] == 'w') + permissions |= 1 << 4; + else if(str[4] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[5] == 'x') + permissions |= 1 << 3; + else if(str[5] == 's') { + permissions |= 1 << 3; + permissions |= 1 << 10; + } + else if(str[5] == 'S') + permissions |= 1 << 10; + else if(str[5] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + /* others */ + if(str[6] == 'r') + permissions |= 1 << 2; + else if(str[6] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[7] == 'w') + permissions |= 1 << 1; + else if(str[7] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + if(str[8] == 'x') + permissions |= 1; + else if(str[8] == 't') { + permissions |= 1; + permissions |= 1 << 9; + } + else if(str[8] == 'T') + permissions |= 1 << 9; + else if(str[8] != '-') + permissions |= FTP_LP_MALFORMATED_PERM; + + return permissions; +} + +static void PL_ERROR(struct connectdata *conn, CURLcode err) +{ + struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp; + struct ftp_parselist_data *parser = tmpdata->parser; + if(parser->file_data) + Curl_fileinfo_dtor(NULL, parser->file_data); + parser->file_data = NULL; + parser->error = err; +} + +static bool ftp_pl_gettime(struct ftp_parselist_data *parser, char *string) +{ + (void)parser; + (void)string; + /* TODO + * There could be possible parse timestamp from server. Leaving unimplemented + * for now. + * If you want implement this, please add CURLFINFOFLAG_KNOWN_TIME flag to + * parser->file_data->flags + * + * Ftp servers are giving usually these formats: + * Apr 11 1998 (unknown time.. set it to 00:00:00?) + * Apr 11 12:21 (unknown year -> set it to NOW() time?) + * 08-05-09 02:49PM (ms-dos format) + * 20100421092538 -> for MLST/MLSD response + */ + + return FALSE; +} + +static CURLcode ftp_pl_insert_finfo(struct connectdata *conn, + struct curl_fileinfo *finfo) +{ + curl_fnmatch_callback compare; + struct WildcardData *wc = &conn->data->wildcard; + struct ftp_wc_tmpdata *tmpdata = wc->tmp; + struct curl_llist *llist = wc->filelist; + struct ftp_parselist_data *parser = tmpdata->parser; + bool add = TRUE; + + /* move finfo pointers to b_data */ + char *str = finfo->b_data; + finfo->filename = str + parser->offsets.filename; + finfo->strings.group = parser->offsets.group ? + str + parser->offsets.group : NULL; + finfo->strings.perm = parser->offsets.perm ? + str + parser->offsets.perm : NULL; + finfo->strings.target = parser->offsets.symlink_target ? + str + parser->offsets.symlink_target : NULL; + finfo->strings.time = str + parser->offsets.time; + finfo->strings.user = parser->offsets.user ? + str + parser->offsets.user : NULL; + + /* get correct fnmatch callback */ + compare = conn->data->set.fnmatch; + if(!compare) + compare = Curl_fnmatch; + + /* filter pattern-corresponding filenames */ + if(compare(conn->data->set.fnmatch_data, wc->pattern, + finfo->filename) == 0) { + /* discard symlink which is containing multiple " -> " */ + if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target && + (strstr(finfo->strings.target, " -> "))) { + add = FALSE; + } + } + else { + add = FALSE; + } + + if(add) { + if(!Curl_llist_insert_next(llist, llist->tail, finfo)) { + Curl_fileinfo_dtor(NULL, finfo); + tmpdata->parser->file_data = NULL; + return CURLE_OUT_OF_MEMORY; + } + } + else { + Curl_fileinfo_dtor(NULL, finfo); + } + + tmpdata->parser->file_data = NULL; + return CURLE_OK; +} + +size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, + void *connptr) +{ + size_t bufflen = size*nmemb; + struct connectdata *conn = (struct connectdata *)connptr; + struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp; + struct ftp_parselist_data *parser = tmpdata->parser; + struct curl_fileinfo *finfo; + unsigned long i = 0; + CURLcode rc; + + if(parser->error) { /* error in previous call */ + /* scenario: + * 1. call => OK.. + * 2. call => OUT_OF_MEMORY (or other error) + * 3. (last) call => is skipped RIGHT HERE and the error is hadled later + * in wc_statemach() + */ + return bufflen; + } + + if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) { + /* considering info about FILE response format */ + parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ? + OS_TYPE_WIN_NT : OS_TYPE_UNIX; + } + + while(i < bufflen) { /* FSM */ + + char c = buffer[i]; + if(!parser->file_data) { /* tmp file data is not allocated yet */ + parser->file_data = Curl_fileinfo_alloc(); + if(!parser->file_data) { + parser->error = CURLE_OUT_OF_MEMORY; + return bufflen; + } + parser->file_data->b_data = malloc(FTP_BUFFER_ALLOCSIZE); + if(!parser->file_data->b_data) { + PL_ERROR(conn, CURLE_OUT_OF_MEMORY); + return bufflen; + } + parser->file_data->b_size = FTP_BUFFER_ALLOCSIZE; + parser->item_offset = 0; + parser->item_length = 0; + } + + finfo = parser->file_data; + finfo->b_data[finfo->b_used++] = c; + + if(finfo->b_used >= finfo->b_size - 1) { + /* if it is important, extend buffer space for file data */ + char *tmp = realloc(finfo->b_data, + finfo->b_size + FTP_BUFFER_ALLOCSIZE); + if(tmp) { + finfo->b_size += FTP_BUFFER_ALLOCSIZE; + finfo->b_data = tmp; + } + else { + Curl_fileinfo_dtor(NULL, parser->file_data); + parser->file_data = NULL; + parser->error = CURLE_OUT_OF_MEMORY; + PL_ERROR(conn, CURLE_OUT_OF_MEMORY); + return bufflen; + } + } + + switch (parser->os_type) { + case OS_TYPE_UNIX: + switch (parser->state.UNIX.main) { + case PL_UNIX_TOTALSIZE: + switch(parser->state.UNIX.sub.total_dirsize) { + case PL_UNIX_TOTALSIZE_INIT: + if(c == 't') { + parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING; + parser->item_length++; + } + else { + parser->state.UNIX.main = PL_UNIX_FILETYPE; + /* start FSM again not considering size of directory */ + finfo->b_used = 0; + i--; + } + break; + case PL_UNIX_TOTALSIZE_READING: + parser->item_length++; + if(c == '\r') { + parser->item_length--; + finfo->b_used--; + } + else if(c == '\n') { + finfo->b_data[parser->item_length - 1] = 0; + if(strncmp("total ", finfo->b_data, 6) == 0) { + char *endptr = finfo->b_data+6; + /* here we can deal with directory size */ + while(ISSPACE(*endptr)) + endptr++; + if(*endptr != 0) { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + else { + parser->state.UNIX.main = PL_UNIX_FILETYPE; + finfo->b_used = 0; + } + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + break; + } + break; + case PL_UNIX_FILETYPE: + switch (c) { + case '-': + finfo->filetype = CURLFILETYPE_FILE; + break; + case 'd': + finfo->filetype = CURLFILETYPE_DIRECTORY; + break; + case 'l': + finfo->filetype = CURLFILETYPE_SYMLINK; + break; + case 'p': + finfo->filetype = CURLFILETYPE_NAMEDPIPE; + break; + case 's': + finfo->filetype = CURLFILETYPE_SOCKET; + break; + case 'c': + finfo->filetype = CURLFILETYPE_DEVICE_CHAR; + break; + case 'b': + finfo->filetype = CURLFILETYPE_DEVICE_BLOCK; + break; + case 'D': + finfo->filetype = CURLFILETYPE_DOOR; + break; + default: + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + parser->state.UNIX.main = PL_UNIX_PERMISSION; + parser->item_length = 0; + parser->item_offset = 1; + break; + case PL_UNIX_PERMISSION: + parser->item_length++; + if(parser->item_length <= 9) { + if(!strchr("rwx-tTsS", c)) { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + else if(parser->item_length == 10) { + unsigned int perm; + if(c != ' ') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + finfo->b_data[10] = 0; /* terminate permissions */ + perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset); + if(perm & FTP_LP_MALFORMATED_PERM) { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + parser->file_data->flags |= CURLFINFOFLAG_KNOWN_PERM; + parser->file_data->perm = perm; + parser->offsets.perm = parser->item_offset; + + parser->item_length = 0; + parser->state.UNIX.main = PL_UNIX_HLINKS; + parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE; + } + break; + case PL_UNIX_HLINKS: + switch(parser->state.UNIX.sub.hlinks) { + case PL_UNIX_HLINKS_PRESPACE: + if(c != ' ') { + if(c >= '0' && c <= '9') { + parser->item_offset = finfo->b_used - 1; + parser->item_length = 1; + parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + break; + case PL_UNIX_HLINKS_NUMBER: + parser->item_length ++; + if(c == ' ') { + char *p; + long int hlinks; + finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10); + if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) { + parser->file_data->flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT; + parser->file_data->hardlinks = hlinks; + } + parser->item_length = 0; + parser->item_offset = 0; + parser->state.UNIX.main = PL_UNIX_USER; + parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE; + } + else if(c < '0' || c > '9') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + } + break; + case PL_UNIX_USER: + switch(parser->state.UNIX.sub.user) { + case PL_UNIX_USER_PRESPACE: + if(c != ' ') { + parser->item_offset = finfo->b_used - 1; + parser->item_length = 1; + parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING; + } + break; + case PL_UNIX_USER_PARSING: + parser->item_length++; + if(c == ' ') { + finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.user = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_GROUP; + parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE; + parser->item_offset = 0; + parser->item_length = 0; + } + break; + } + break; + case PL_UNIX_GROUP: + switch(parser->state.UNIX.sub.group) { + case PL_UNIX_GROUP_PRESPACE: + if(c != ' ') { + parser->item_offset = finfo->b_used - 1; + parser->item_length = 1; + parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME; + } + break; + case PL_UNIX_GROUP_NAME: + parser->item_length++; + if(c == ' ') { + finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.group = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_SIZE; + parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE; + parser->item_offset = 0; + parser->item_length = 0; + } + break; + } + break; + case PL_UNIX_SIZE: + switch(parser->state.UNIX.sub.size) { + case PL_UNIX_SIZE_PRESPACE: + if(c != ' ') { + if(c >= '0' && c <= '9') { + parser->item_offset = finfo->b_used - 1; + parser->item_length = 1; + parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + break; + case PL_UNIX_SIZE_NUMBER: + parser->item_length++; + if(c == ' ') { + char *p; + curl_off_t fsize; + finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + fsize = curlx_strtoofft(finfo->b_data+parser->item_offset, &p, 10); + if(p[0] == '\0' && fsize != CURL_OFF_T_MAX && + fsize != CURL_OFF_T_MIN) { + parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE; + parser->file_data->size = fsize; + } + parser->item_length = 0; + parser->item_offset = 0; + parser->state.UNIX.main = PL_UNIX_TIME; + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1; + } + else if(!ISDIGIT(c)) { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + } + break; + case PL_UNIX_TIME: + switch(parser->state.UNIX.sub.time) { + case PL_UNIX_TIME_PREPART1: + if(c != ' ') { + if(ISALNUM(c)) { + parser->item_offset = finfo->b_used -1; + parser->item_length = 1; + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + break; + case PL_UNIX_TIME_PART1: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2; + } + else if(!ISALNUM(c) && c != '.') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + case PL_UNIX_TIME_PREPART2: + parser->item_length++; + if(c != ' ') { + if(ISALNUM(c)) { + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + break; + case PL_UNIX_TIME_PART2: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3; + } + else if(!ISALNUM(c) && c != '.') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + case PL_UNIX_TIME_PREPART3: + parser->item_length++; + if(c != ' ') { + if(ISALNUM(c)) { + parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + break; + case PL_UNIX_TIME_PART3: + parser->item_length++; + if(c == ' ') { + finfo->b_data[parser->item_offset + parser->item_length -1] = 0; + parser->offsets.time = parser->item_offset; + if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) { + parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME; + } + if(finfo->filetype == CURLFILETYPE_SYMLINK) { + parser->state.UNIX.main = PL_UNIX_SYMLINK; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE; + } + else { + parser->state.UNIX.main = PL_UNIX_FILENAME; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE; + } + } + else if(!ISALNUM(c) && c != '.' && c != ':') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + } + break; + case PL_UNIX_FILENAME: + switch(parser->state.UNIX.sub.filename) { + case PL_UNIX_FILENAME_PRESPACE: + if(c != ' ') { + parser->item_offset = finfo->b_used - 1; + parser->item_length = 1; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME; + } + break; + case PL_UNIX_FILENAME_NAME: + parser->item_length++; + if(c == '\r') { + parser->item_length--; + parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL; + } + else if(c == '\n') { + finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.filename = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_FILETYPE; + rc = ftp_pl_insert_finfo(conn, finfo); + if(rc) { + PL_ERROR(conn, rc); + return bufflen; + } + } + break; + case PL_UNIX_FILENAME_WINDOWSEOL: + if(c == '\n') { + finfo->b_data[parser->item_offset + parser->item_length] = 0; + parser->offsets.filename = parser->item_offset; + parser->state.UNIX.main = PL_UNIX_FILETYPE; + rc = ftp_pl_insert_finfo(conn, finfo); + if(rc) { + PL_ERROR(conn, rc); + return bufflen; + } + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + } + break; + case PL_UNIX_SYMLINK: + switch(parser->state.UNIX.sub.symlink) { + case PL_UNIX_SYMLINK_PRESPACE: + if(c != ' ') { + parser->item_offset = finfo->b_used - 1; + parser->item_length = 1; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_NAME: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1; + } + else if(c == '\r' || c == '\n') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + case PL_UNIX_SYMLINK_PRETARGET1: + parser->item_length++; + if(c == '-') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2; + } + else if(c == '\r' || c == '\n') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + else { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_PRETARGET2: + parser->item_length++; + if(c == '>') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3; + } + else if(c == '\r' || c == '\n') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + else { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_PRETARGET3: + parser->item_length++; + if(c == ' ') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4; + /* now place where is symlink following */ + finfo->b_data[parser->item_offset + parser->item_length - 4] = 0; + parser->offsets.filename = parser->item_offset; + parser->item_length = 0; + parser->item_offset = 0; + } + else if(c == '\r' || c == '\n') { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + else { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; + } + break; + case PL_UNIX_SYMLINK_PRETARGET4: + if(c != '\r' && c != '\n') { + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET; + parser->item_offset = finfo->b_used - 1; + parser->item_length = 1; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + case PL_UNIX_SYMLINK_TARGET: + parser->item_length ++; + if(c == '\r') { + parser->item_length --; + parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL; + } + else if(c == '\n') { + finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.symlink_target = parser->item_offset; + rc = ftp_pl_insert_finfo(conn, finfo); + if(rc) { + PL_ERROR(conn, rc); + return bufflen; + } + parser->state.UNIX.main = PL_UNIX_FILETYPE; + } + break; + case PL_UNIX_SYMLINK_WINDOWSEOL: + if(c == '\n') { + finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + parser->offsets.symlink_target = parser->item_offset; + rc = ftp_pl_insert_finfo(conn, finfo); + if(rc) { + PL_ERROR(conn, rc); + return bufflen; + } + parser->state.UNIX.main = PL_UNIX_FILETYPE; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + } + break; + } + break; + case OS_TYPE_WIN_NT: + switch(parser->state.NT.main) { + case PL_WINNT_DATE: + parser->item_length++; + if(parser->item_length < 9) { + if(!strchr("0123456789-", c)) { /* only simple control */ + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + else if(parser->item_length == 9) { + if(c == ' ') { + parser->state.NT.main = PL_WINNT_TIME; + parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + case PL_WINNT_TIME: + parser->item_length++; + switch(parser->state.NT.sub.time) { + case PL_WINNT_TIME_PRESPACE: + if(!ISSPACE(c)) { + parser->state.NT.sub.time = PL_WINNT_TIME_TIME; + } + break; + case PL_WINNT_TIME_TIME: + if(c == ' ') { + parser->offsets.time = parser->item_offset; + finfo->b_data[parser->item_offset + parser->item_length -1] = 0; + parser->state.NT.main = PL_WINNT_DIRORSIZE; + parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE; + parser->item_length = 0; + } + else if(!strchr("APM0123456789:", c)) { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + } + break; + case PL_WINNT_DIRORSIZE: + switch(parser->state.NT.sub.dirorsize) { + case PL_WINNT_DIRORSIZE_PRESPACE: + if(c == ' ') { + + } + else { + parser->item_offset = finfo->b_used - 1; + parser->item_length = 1; + parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT; + } + break; + case PL_WINNT_DIRORSIZE_CONTENT: + parser->item_length ++; + if(c == ' ') { + finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + if(strcmp("", finfo->b_data + parser->item_offset) == 0) { + finfo->filetype = CURLFILETYPE_DIRECTORY; + finfo->size = 0; + } + else { + char *endptr; + finfo->size = curlx_strtoofft(finfo->b_data + + parser->item_offset, + &endptr, 10); + if(!*endptr) { + if(finfo->size == CURL_OFF_T_MAX || + finfo->size == CURL_OFF_T_MIN) { + if(errno == ERANGE) { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + } + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + /* correct file type */ + parser->file_data->filetype = CURLFILETYPE_FILE; + } + + parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE; + parser->item_length = 0; + parser->state.NT.main = PL_WINNT_FILENAME; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + break; + } + break; + case PL_WINNT_FILENAME: + switch (parser->state.NT.sub.filename) { + case PL_WINNT_FILENAME_PRESPACE: + if(c != ' ') { + parser->item_offset = finfo->b_used -1; + parser->item_length = 1; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT; + } + break; + case PL_WINNT_FILENAME_CONTENT: + parser->item_length++; + if(c == '\r') { + parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL; + finfo->b_data[finfo->b_used - 1] = 0; + } + else if(c == '\n') { + parser->offsets.filename = parser->item_offset; + finfo->b_data[finfo->b_used - 1] = 0; + parser->offsets.filename = parser->item_offset; + rc = ftp_pl_insert_finfo(conn, finfo); + if(rc) { + PL_ERROR(conn, rc); + return bufflen; + } + parser->state.NT.main = PL_WINNT_DATE; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + break; + case PL_WINNT_FILENAME_WINEOL: + if(c == '\n') { + parser->offsets.filename = parser->item_offset; + rc = ftp_pl_insert_finfo(conn, finfo); + if(rc) { + PL_ERROR(conn, rc); + return bufflen; + } + parser->state.NT.main = PL_WINNT_DATE; + parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; + } + else { + PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); + return bufflen; + } + break; + } + break; + } + break; + default: + return bufflen+1; + } + + i++; + } + + return bufflen; +} + +#endif /* CURL_DISABLE_FTP */ diff --git a/lib/curl_getenv.c b/lib/curl_getenv.c new file mode 100644 index 000000000..cf8b03619 --- /dev/null +++ b/lib/curl_getenv.c @@ -0,0 +1,61 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef __VMS +#include +#endif + +#include +#include "curl_memory.h" + +#include "curl_memdebug.h" + +static +char *GetEnv(const char *variable) +{ +#ifdef _WIN32_WCE + return NULL; +#else +#ifdef WIN32 + char env[MAX_PATH]; /* MAX_PATH is from windef.h */ + char *temp = getenv(variable); + env[0] = '\0'; + if(temp != NULL) + ExpandEnvironmentStringsA(temp, env, sizeof(env)); + return (env[0] != '\0')?strdup(env):NULL; +#else + char *env = getenv(variable); +#ifdef __VMS + if(env && strcmp("HOME",variable) == 0) + env = decc_translate_vms(env); +#endif + return (env && env[0])?strdup(env):NULL; +#endif +#endif +} + +char *curl_getenv(const char *v) +{ + return GetEnv(v); +} diff --git a/lib/curl_getinfo.c b/lib/curl_getinfo.c new file mode 100644 index 000000000..0404c28ea --- /dev/null +++ b/lib/curl_getinfo.c @@ -0,0 +1,330 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#include "curl_urldata.h" +#include "curl_getinfo.h" + +#include "curl_memory.h" +#include "curl_sslgen.h" +#include "curl_connect.h" /* Curl_getconnectinfo() */ +#include "curl_progress.h" + +/* Make this the last #include */ +#include "curl_memdebug.h" + +/* + * This is supposed to be called in the beginning of a perform() session + * and should reset all session-info variables + */ +CURLcode Curl_initinfo(struct SessionHandle *data) +{ + struct Progress *pro = &data->progress; + struct PureInfo *info =&data->info; + + pro->t_nslookup = 0; + pro->t_connect = 0; + pro->t_appconnect = 0; + pro->t_pretransfer = 0; + pro->t_starttransfer = 0; + pro->timespent = 0; + pro->t_redirect = 0; + + info->httpcode = 0; + info->httpversion=0; + info->filetime=-1; /* -1 is an illegal time and thus means unknown */ + + if(info->contenttype) + free(info->contenttype); + info->contenttype = NULL; + + info->header_size = 0; + info->request_size = 0; + info->numconnects = 0; + + info->conn_primary_ip[0] = '\0'; + info->conn_local_ip[0] = '\0'; + info->conn_primary_port = 0; + info->conn_local_port = 0; + + return CURLE_OK; +} + +static CURLcode getinfo_char(struct SessionHandle *data, CURLINFO info, + char **param_charp) +{ + switch(info) { + case CURLINFO_EFFECTIVE_URL: + *param_charp = data->change.url?data->change.url:(char *)""; + break; + case CURLINFO_CONTENT_TYPE: + *param_charp = data->info.contenttype; + break; + case CURLINFO_PRIVATE: + *param_charp = (char *) data->set.private_data; + break; + case CURLINFO_FTP_ENTRY_PATH: + /* Return the entrypath string from the most recent connection. + This pointer was copied from the connectdata structure by FTP. + The actual string may be free()ed by subsequent libcurl calls so + it must be copied to a safer area before the next libcurl call. + Callers must never free it themselves. */ + *param_charp = data->state.most_recent_ftp_entrypath; + break; + case CURLINFO_REDIRECT_URL: + /* Return the URL this request would have been redirected to if that + option had been enabled! */ + *param_charp = data->info.wouldredirect; + break; + case CURLINFO_PRIMARY_IP: + /* Return the ip address of the most recent (primary) connection */ + *param_charp = data->info.conn_primary_ip; + break; + case CURLINFO_LOCAL_IP: + /* Return the source/local ip address of the most recent (primary) + connection */ + *param_charp = data->info.conn_local_ip; + break; + case CURLINFO_RTSP_SESSION_ID: + *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; + break; + + default: + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return CURLE_OK; +} + +static CURLcode getinfo_long(struct SessionHandle *data, CURLINFO info, + long *param_longp) +{ + curl_socket_t sockfd; + + union { + unsigned long *to_ulong; + long *to_long; + } lptr; + + switch(info) { + case CURLINFO_RESPONSE_CODE: + *param_longp = data->info.httpcode; + break; + case CURLINFO_HTTP_CONNECTCODE: + *param_longp = data->info.httpproxycode; + break; + case CURLINFO_FILETIME: + *param_longp = data->info.filetime; + break; + case CURLINFO_HEADER_SIZE: + *param_longp = data->info.header_size; + break; + case CURLINFO_REQUEST_SIZE: + *param_longp = data->info.request_size; + break; + case CURLINFO_SSL_VERIFYRESULT: + *param_longp = data->set.ssl.certverifyresult; + break; + case CURLINFO_REDIRECT_COUNT: + *param_longp = data->set.followlocation; + break; + case CURLINFO_HTTPAUTH_AVAIL: + lptr.to_long = param_longp; + *lptr.to_ulong = data->info.httpauthavail; + break; + case CURLINFO_PROXYAUTH_AVAIL: + lptr.to_long = param_longp; + *lptr.to_ulong = data->info.proxyauthavail; + break; + case CURLINFO_OS_ERRNO: + *param_longp = data->state.os_errno; + break; + case CURLINFO_NUM_CONNECTS: + *param_longp = data->info.numconnects; + break; + case CURLINFO_LASTSOCKET: + sockfd = Curl_getconnectinfo(data, NULL); + + /* note: this is not a good conversion for systems with 64 bit sockets and + 32 bit longs */ + if(sockfd != CURL_SOCKET_BAD) + *param_longp = (long)sockfd; + else + /* this interface is documented to return -1 in case of badness, which + may not be the same as the CURL_SOCKET_BAD value */ + *param_longp = -1; + break; + case CURLINFO_PRIMARY_PORT: + /* Return the (remote) port of the most recent (primary) connection */ + *param_longp = data->info.conn_primary_port; + break; + case CURLINFO_LOCAL_PORT: + /* Return the local port of the most recent (primary) connection */ + *param_longp = data->info.conn_local_port; + break; + case CURLINFO_CONDITION_UNMET: + /* return if the condition prevented the document to get transferred */ + *param_longp = data->info.timecond; + break; + case CURLINFO_RTSP_CLIENT_CSEQ: + *param_longp = data->state.rtsp_next_client_CSeq; + break; + case CURLINFO_RTSP_SERVER_CSEQ: + *param_longp = data->state.rtsp_next_server_CSeq; + break; + case CURLINFO_RTSP_CSEQ_RECV: + *param_longp = data->state.rtsp_CSeq_recv; + break; + + default: + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return CURLE_OK; +} + +static CURLcode getinfo_double(struct SessionHandle *data, CURLINFO info, + double *param_doublep) +{ + switch(info) { + case CURLINFO_TOTAL_TIME: + *param_doublep = data->progress.timespent; + break; + case CURLINFO_NAMELOOKUP_TIME: + *param_doublep = data->progress.t_nslookup; + break; + case CURLINFO_CONNECT_TIME: + *param_doublep = data->progress.t_connect; + break; + case CURLINFO_APPCONNECT_TIME: + *param_doublep = data->progress.t_appconnect; + break; + case CURLINFO_PRETRANSFER_TIME: + *param_doublep = data->progress.t_pretransfer; + break; + case CURLINFO_STARTTRANSFER_TIME: + *param_doublep = data->progress.t_starttransfer; + break; + case CURLINFO_SIZE_UPLOAD: + *param_doublep = (double)data->progress.uploaded; + break; + case CURLINFO_SIZE_DOWNLOAD: + *param_doublep = (double)data->progress.downloaded; + break; + case CURLINFO_SPEED_DOWNLOAD: + *param_doublep = (double)data->progress.dlspeed; + break; + case CURLINFO_SPEED_UPLOAD: + *param_doublep = (double)data->progress.ulspeed; + break; + case CURLINFO_CONTENT_LENGTH_DOWNLOAD: + *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? + (double)data->progress.size_dl:-1; + break; + case CURLINFO_CONTENT_LENGTH_UPLOAD: + *param_doublep = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? + (double)data->progress.size_ul:-1; + break; + case CURLINFO_REDIRECT_TIME: + *param_doublep = data->progress.t_redirect; + break; + + default: + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return CURLE_OK; +} + +static CURLcode getinfo_slist(struct SessionHandle *data, CURLINFO info, + struct curl_slist **param_slistp) +{ + union { + struct curl_certinfo * to_certinfo; + struct curl_slist * to_slist; + } ptr; + + switch(info) { + case CURLINFO_SSL_ENGINES: + *param_slistp = Curl_ssl_engines_list(data); + break; + case CURLINFO_COOKIELIST: + *param_slistp = Curl_cookie_list(data); + break; + case CURLINFO_CERTINFO: + /* Return the a pointer to the certinfo struct. Not really an slist + pointer but we can pretend it is here */ + ptr.to_certinfo = &data->info.certs; + *param_slistp = ptr.to_slist; + break; + + default: + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return CURLE_OK; +} + +CURLcode Curl_getinfo(struct SessionHandle *data, CURLINFO info, ...) +{ + va_list arg; + long *param_longp=NULL; + double *param_doublep=NULL; + char **param_charp=NULL; + struct curl_slist **param_slistp=NULL; + int type; + /* default return code is to error out! */ + CURLcode ret = CURLE_BAD_FUNCTION_ARGUMENT; + + if(!data) + return ret; + + va_start(arg, info); + + type = CURLINFO_TYPEMASK & (int)info; + switch(type) { + case CURLINFO_STRING: + param_charp = va_arg(arg, char **); + if(NULL != param_charp) + ret = getinfo_char(data, info, param_charp); + break; + case CURLINFO_LONG: + param_longp = va_arg(arg, long *); + if(NULL != param_longp) + ret = getinfo_long(data, info, param_longp); + break; + case CURLINFO_DOUBLE: + param_doublep = va_arg(arg, double *); + if(NULL != param_doublep) + ret = getinfo_double(data, info, param_doublep); + break; + case CURLINFO_SLIST: + param_slistp = va_arg(arg, struct curl_slist **); + if(NULL != param_slistp) + ret = getinfo_slist(data, info, param_slistp); + break; + default: + break; + } + + va_end(arg); + return ret; +} diff --git a/lib/curl_gopher.c b/lib/curl_gopher.c new file mode 100644 index 000000000..80fc18e8e --- /dev/null +++ b/lib/curl_gopher.c @@ -0,0 +1,169 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_GOPHER + +#include "curl_urldata.h" +#include +#include "curl_transfer.h" +#include "curl_sendf.h" + +#include "curl_progress.h" +#include "curl_strequal.h" +#include "curl_gopher.h" +#include "curl_rawstr.h" +#include "curl_select.h" +#include "curl_url.h" +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + * Forward declarations. + */ + +static CURLcode gopher_do(struct connectdata *conn, bool *done); + +/* + * Gopher protocol handler. + * This is also a nice simple template to build off for simple + * connect-command-download protocols. + */ + +const struct Curl_handler Curl_handler_gopher = { + "GOPHER", /* scheme */ + ZERO_NULL, /* setup_connection */ + gopher_do, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_GOPHER, /* defport */ + CURLPROTO_GOPHER, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +static CURLcode gopher_do(struct connectdata *conn, bool *done) +{ + CURLcode result=CURLE_OK; + struct SessionHandle *data=conn->data; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + + curl_off_t *bytecount = &data->req.bytecount; + char *path = data->state.path; + char *sel; + char *sel_org = NULL; + ssize_t amount, k; + + *done = TRUE; /* unconditionally */ + + /* Create selector. Degenerate cases: / and /1 => convert to "" */ + if(strlen(path) <= 2) + sel = (char *)""; + else { + char *newp; + size_t j, i; + int len; + + /* Otherwise, drop / and the first character (i.e., item type) ... */ + newp = path; + newp+=2; + + /* ... then turn ? into TAB for search servers, Veronica, etc. ... */ + j = strlen(newp); + for(i=0; i, et al. + * + * 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. + * + ***************************************************************************/ + +/* + * Source file for all GnuTLS-specific code for the TLS/SSL layer. No code + * but curl_sslgen.c should ever call or use these functions. + * + * Note: don't use the GnuTLS' *_t variable type names in this source code, + * since they were not present in 1.0.X. + */ + +#include "curl_setup.h" + +#ifdef USE_GNUTLS + +#include +#include + +#ifdef USE_GNUTLS_NETTLE +#include +#include +#else +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_inet_pton.h" +#include "curl_gtls.h" +#include "curl_sslgen.h" +#include "curl_parsedate.h" +#include "curl_connect.h" /* for the connect timeout */ +#include "curl_select.h" +#include "curl_rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + Some hackish cast macros based on: + http://library.gnome.org/devel/glib/unstable/glib-Type-Conversion-Macros.html +*/ +#ifndef GNUTLS_POINTER_TO_INT_CAST +#define GNUTLS_POINTER_TO_INT_CAST(p) ((int) (long) (p)) +#endif +#ifndef GNUTLS_INT_TO_POINTER_CAST +#define GNUTLS_INT_TO_POINTER_CAST(i) ((void*) (long) (i)) +#endif + +/* Enable GnuTLS debugging by defining GTLSDEBUG */ +/*#define GTLSDEBUG */ + +#ifdef GTLSDEBUG +static void tls_log_func(int level, const char *str) +{ + fprintf(stderr, "|<%d>| %s", level, str); +} +#endif +static bool gtls_inited = FALSE; + +#if defined(GNUTLS_VERSION_NUMBER) +# if (GNUTLS_VERSION_NUMBER >= 0x020c00) +# undef gnutls_transport_set_lowat +# define gnutls_transport_set_lowat(A,B) Curl_nop_stmt +# define USE_GNUTLS_PRIORITY_SET_DIRECT 1 +# endif +# if (GNUTLS_VERSION_NUMBER >= 0x020c03) +# define GNUTLS_MAPS_WINSOCK_ERRORS 1 +# endif +#endif + +/* + * Custom push and pull callback functions used by GNU TLS to read and write + * to the socket. These functions are simple wrappers to send() and recv() + * (although here using sread/swrite macros as defined by curl_setup_once.h). + * We use custom functions rather than the GNU TLS defaults because it allows + * us to get specific about the fourth "flags" argument, and to use arbitrary + * private data with gnutls_transport_set_ptr if we wish. + * + * When these custom push and pull callbacks fail, GNU TLS checks its own + * session-specific error variable, and when not set also its own global + * errno variable, in order to take appropriate action. GNU TLS does not + * require that the transport is actually a socket. This implies that for + * Windows builds these callbacks should ideally set the session-specific + * error variable using function gnutls_transport_set_errno or as a last + * resort global errno variable using gnutls_transport_set_global_errno, + * with a transport agnostic error value. This implies that some winsock + * error translation must take place in these callbacks. + * + * Paragraph above applies to GNU TLS versions older than 2.12.3, since + * this version GNU TLS does its own internal winsock error translation + * using system_errno() function. + */ + +#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) +# define gtls_EINTR 4 +# define gtls_EIO 5 +# define gtls_EAGAIN 11 +static int gtls_mapped_sockerrno(void) +{ + switch(SOCKERRNO) { + case WSAEWOULDBLOCK: + return gtls_EAGAIN; + case WSAEINTR: + return gtls_EINTR; + default: + break; + } + return gtls_EIO; +} +#endif + +static ssize_t Curl_gtls_push(void *s, const void *buf, size_t len) +{ + ssize_t ret = swrite(GNUTLS_POINTER_TO_INT_CAST(s), buf, len); +#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) + if(ret < 0) + gnutls_transport_set_global_errno(gtls_mapped_sockerrno()); +#endif + return ret; +} + +static ssize_t Curl_gtls_pull(void *s, void *buf, size_t len) +{ + ssize_t ret = sread(GNUTLS_POINTER_TO_INT_CAST(s), buf, len); +#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) + if(ret < 0) + gnutls_transport_set_global_errno(gtls_mapped_sockerrno()); +#endif + return ret; +} + +/* Curl_gtls_init() + * + * Global GnuTLS init, called from Curl_ssl_init(). This calls functions that + * are not thread-safe and thus this function itself is not thread-safe and + * must only be called from within curl_global_init() to keep the thread + * situation under control! + */ +int Curl_gtls_init(void) +{ + int ret = 1; + if(!gtls_inited) { + ret = gnutls_global_init()?0:1; +#ifdef GTLSDEBUG + gnutls_global_set_log_function(tls_log_func); + gnutls_global_set_log_level(2); +#endif + gtls_inited = TRUE; + } + return ret; +} + +int Curl_gtls_cleanup(void) +{ + if(gtls_inited) { + gnutls_global_deinit(); + gtls_inited = FALSE; + } + return 1; +} + +static void showtime(struct SessionHandle *data, + const char *text, + time_t stamp) +{ + struct tm buffer; + const struct tm *tm = &buffer; + CURLcode result = Curl_gmtime(stamp, &buffer); + if(result) + return; + + snprintf(data->state.buffer, + BUFSIZE, + "\t %s: %s, %02d %s %4d %02d:%02d:%02d GMT\n", + text, + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + infof(data, "%s\n", data->state.buffer); +} + +static gnutls_datum load_file (const char *file) +{ + FILE *f; + gnutls_datum loaded_file = { NULL, 0 }; + long filelen; + void *ptr; + + if(!(f = fopen(file, "r"))) + return loaded_file; + if(fseek(f, 0, SEEK_END) != 0 + || (filelen = ftell(f)) < 0 + || fseek(f, 0, SEEK_SET) != 0 + || !(ptr = malloc((size_t)filelen))) + goto out; + if(fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) { + free(ptr); + goto out; + } + + loaded_file.data = ptr; + loaded_file.size = (unsigned int)filelen; +out: + fclose(f); + return loaded_file; +} + +static void unload_file(gnutls_datum data) { + free(data.data); +} + + +/* this function does a SSL/TLS (re-)handshake */ +static CURLcode handshake(struct connectdata *conn, + int sockindex, + bool duringconnect, + bool nonblocking) +{ + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + gnutls_session session = conn->ssl[sockindex].session; + curl_socket_t sockfd = conn->sock[sockindex]; + long timeout_ms; + int rc; + int what; + + for(;;) { + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, duringconnect); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading + || connssl->connecting_state == ssl_connect_2_writing) { + + curl_socket_t writefd = ssl_connect_2_writing== + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading== + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_ready(readfd, writefd, + nonblocking?0: + timeout_ms?timeout_ms:1000); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) + return CURLE_OK; + else if(timeout_ms) { + /* timeout */ + failf(data, "SSL connection timeout at %ld", timeout_ms); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + rc = gnutls_handshake(session); + + if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { + connssl->connecting_state = + gnutls_record_get_direction(session)? + ssl_connect_2_writing:ssl_connect_2_reading; + continue; + if(nonblocking) + return CURLE_OK; + } + else if((rc < 0) && !gnutls_error_is_fatal(rc)) { + const char *strerr = NULL; + + if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { + int alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); + } + + if(strerr == NULL) + strerr = gnutls_strerror(rc); + + failf(data, "gnutls_handshake() warning: %s", strerr); + } + else if(rc < 0) { + const char *strerr = NULL; + + if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { + int alert = gnutls_alert_get(session); + strerr = gnutls_alert_get_name(alert); + } + + if(strerr == NULL) + strerr = gnutls_strerror(rc); + + failf(data, "gnutls_handshake() failed: %s", strerr); + return CURLE_SSL_CONNECT_ERROR; + } + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + return CURLE_OK; + } +} + +static gnutls_x509_crt_fmt do_file_type(const char *type) +{ + if(!type || !type[0]) + return GNUTLS_X509_FMT_PEM; + if(Curl_raw_equal(type, "PEM")) + return GNUTLS_X509_FMT_PEM; + if(Curl_raw_equal(type, "DER")) + return GNUTLS_X509_FMT_DER; + return -1; +} + +static CURLcode +gtls_connect_step1(struct connectdata *conn, + int sockindex) +{ +#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT + static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; +#endif + struct SessionHandle *data = conn->data; + gnutls_session session; + int rc; + void *ssl_sessionid; + size_t ssl_idsize; + bool sni = TRUE; /* default is SNI enabled */ +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + + if(conn->ssl[sockindex].state == ssl_connection_complete) + /* to make us tolerant against being called more than once for the + same connection */ + return CURLE_OK; + + if(!gtls_inited) + Curl_gtls_init(); + + /* GnuTLS only supports SSLv3 and TLSv1 */ + if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) { + failf(data, "GnuTLS does not support SSLv2"); + return CURLE_SSL_CONNECT_ERROR; + } + else if(data->set.ssl.version == CURL_SSLVERSION_SSLv3) + sni = FALSE; /* SSLv3 has no SNI */ + + /* allocate a cred struct */ + rc = gnutls_certificate_allocate_credentials(&conn->ssl[sockindex].cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { + infof(data, "Using TLS-SRP username: %s\n", data->set.ssl.username); + + rc = gnutls_srp_allocate_client_credentials( + &conn->ssl[sockindex].srp_client_cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_allocate_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_OUT_OF_MEMORY; + } + + rc = gnutls_srp_set_client_credentials(conn->ssl[sockindex]. + srp_client_cred, + data->set.ssl.username, + data->set.ssl.password); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_set_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } +#endif + + if(data->set.ssl.CAfile) { + /* set the trusted CA cert bundle file */ + gnutls_certificate_set_verify_flags(conn->ssl[sockindex].cred, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + + rc = gnutls_certificate_set_x509_trust_file(conn->ssl[sockindex].cred, + data->set.ssl.CAfile, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + infof(data, "error reading ca cert file %s (%s)\n", + data->set.ssl.CAfile, gnutls_strerror(rc)); + if(data->set.ssl.verifypeer) + return CURLE_SSL_CACERT_BADFILE; + } + else + infof(data, "found %d certificates in %s\n", + rc, data->set.ssl.CAfile); + } + + if(data->set.ssl.CRLfile) { + /* set the CRL list file */ + rc = gnutls_certificate_set_x509_crl_file(conn->ssl[sockindex].cred, + data->set.ssl.CRLfile, + GNUTLS_X509_FMT_PEM); + if(rc < 0) { + failf(data, "error reading crl file %s (%s)", + data->set.ssl.CRLfile, gnutls_strerror(rc)); + return CURLE_SSL_CRL_BADFILE; + } + else + infof(data, "found %d CRL in %s\n", + rc, data->set.ssl.CRLfile); + } + + /* Initialize TLS session as a client */ + rc = gnutls_init(&conn->ssl[sockindex].session, GNUTLS_CLIENT); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_init() failed: %d", rc); + return CURLE_SSL_CONNECT_ERROR; + } + + /* convenient assign */ + session = conn->ssl[sockindex].session; + + if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) && +#ifdef ENABLE_IPV6 + (0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) && +#endif + sni && + (gnutls_server_name_set(session, GNUTLS_NAME_DNS, conn->host.name, + strlen(conn->host.name)) < 0)) + infof(data, "WARNING: failed to configure server name indication (SNI) " + "TLS extension\n"); + + /* Use default priorities */ + rc = gnutls_set_default_priority(session); + if(rc != GNUTLS_E_SUCCESS) + return CURLE_SSL_CONNECT_ERROR; + + if(data->set.ssl.version == CURL_SSLVERSION_SSLv3) { +#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT + static const int protocol_priority[] = { GNUTLS_SSL3, 0 }; + rc = gnutls_protocol_set_priority(session, protocol_priority); +#else + const char *err; + /* the combination of the cipher ARCFOUR with SSL 3.0 and TLS 1.0 is not + vulnerable to attacks such as the BEAST, why this code now explicitly + asks for that + */ + rc = gnutls_priority_set_direct(session, + "NORMAL:-VERS-TLS-ALL:+VERS-SSL3.0:" + "-CIPHER-ALL:+ARCFOUR-128", + &err); +#endif + if(rc != GNUTLS_E_SUCCESS) + return CURLE_SSL_CONNECT_ERROR; + } + +#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT + /* Sets the priority on the certificate types supported by gnutls. Priority + is higher for types specified before others. After specifying the types + you want, you must append a 0. */ + rc = gnutls_certificate_type_set_priority(session, cert_type_priority); + if(rc != GNUTLS_E_SUCCESS) + return CURLE_SSL_CONNECT_ERROR; +#endif + + if(data->set.str[STRING_CERT]) { + if(gnutls_certificate_set_x509_key_file( + conn->ssl[sockindex].cred, + data->set.str[STRING_CERT], + data->set.str[STRING_KEY] ? + data->set.str[STRING_KEY] : data->set.str[STRING_CERT], + do_file_type(data->set.str[STRING_CERT_TYPE]) ) != + GNUTLS_E_SUCCESS) { + failf(data, "error reading X.509 key or certificate file"); + return CURLE_SSL_CONNECT_ERROR; + } + } + +#ifdef USE_TLS_SRP + /* put the credentials to the current session */ + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { + rc = gnutls_credentials_set(session, GNUTLS_CRD_SRP, + conn->ssl[sockindex].srp_client_cred); + if(rc != GNUTLS_E_SUCCESS) + failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); + } + else +#endif + rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, + conn->ssl[sockindex].cred); + + /* set the connection handle (file descriptor for the socket) */ + gnutls_transport_set_ptr(session, + GNUTLS_INT_TO_POINTER_CAST(conn->sock[sockindex])); + + /* register callback functions to send and receive data. */ + gnutls_transport_set_push_function(session, Curl_gtls_push); + gnutls_transport_set_pull_function(session, Curl_gtls_pull); + + /* lowat must be set to zero when using custom push and pull functions. */ + gnutls_transport_set_lowat(session, 0); + + /* This might be a reconnect, so we check for a session ID in the cache + to speed up things */ + + if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, &ssl_idsize)) { + /* we got a session id, use it! */ + gnutls_session_set_data(session, ssl_sessionid, ssl_idsize); + + /* Informational message */ + infof (data, "SSL re-using session ID\n"); + } + + return CURLE_OK; +} + +static Curl_recv gtls_recv; +static Curl_send gtls_send; + +static CURLcode +gtls_connect_step3(struct connectdata *conn, + int sockindex) +{ + unsigned int cert_list_size; + const gnutls_datum *chainp; + unsigned int verify_status; + gnutls_x509_crt x509_cert,x509_issuer; + gnutls_datum issuerp; + char certbuf[256]; /* big enough? */ + size_t size; + unsigned int algo; + unsigned int bits; + time_t certclock; + const char *ptr; + struct SessionHandle *data = conn->data; + gnutls_session session = conn->ssl[sockindex].session; + int rc; + int incache; + void *ssl_sessionid; + CURLcode result = CURLE_OK; + + /* This function will return the peer's raw certificate (chain) as sent by + the peer. These certificates are in raw format (DER encoded for + X.509). In case of a X.509 then a certificate list may be present. The + first certificate in the list is the peer's certificate, following the + issuer's certificate, then the issuer's issuer etc. */ + + chainp = gnutls_certificate_get_peers(session, &cert_list_size); + if(!chainp) { + if(data->set.ssl.verifypeer || + data->set.ssl.verifyhost || + data->set.ssl.issuercert) { +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP + && data->set.ssl.username != NULL + && !data->set.ssl.verifypeer + && gnutls_cipher_get(session)) { + /* no peer cert, but auth is ok if we have SRP user and cipher and no + peer verify */ + } + else { +#endif + failf(data, "failed to get server cert"); + return CURLE_PEER_FAILED_VERIFICATION; +#ifdef USE_TLS_SRP + } +#endif + } + infof(data, "\t common name: WARNING couldn't obtain\n"); + } + + if(data->set.ssl.verifypeer) { + /* This function will try to verify the peer's certificate and return its + status (trusted, invalid etc.). The value of status should be one or + more of the gnutls_certificate_status_t enumerated elements bitwise + or'd. To avoid denial of service attacks some default upper limits + regarding the certificate key size and chain size are set. To override + them use gnutls_certificate_set_verify_limits(). */ + + rc = gnutls_certificate_verify_peers2(session, &verify_status); + if(rc < 0) { + failf(data, "server cert verify failed: %d", rc); + return CURLE_SSL_CONNECT_ERROR; + } + + /* verify_status is a bitmask of gnutls_certificate_status bits */ + if(verify_status & GNUTLS_CERT_INVALID) { + if(data->set.ssl.verifypeer) { + failf(data, "server certificate verification failed. CAfile: %s " + "CRLfile: %s", data->set.ssl.CAfile?data->set.ssl.CAfile:"none", + data->set.ssl.CRLfile?data->set.ssl.CRLfile:"none"); + return CURLE_SSL_CACERT; + } + else + infof(data, "\t server certificate verification FAILED\n"); + } + else + infof(data, "\t server certificate verification OK\n"); + } + else { + infof(data, "\t server certificate verification SKIPPED\n"); + goto after_server_cert_verification; + } + + /* initialize an X.509 certificate structure. */ + gnutls_x509_crt_init(&x509_cert); + + /* convert the given DER or PEM encoded Certificate to the native + gnutls_x509_crt_t format */ + gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); + + if(data->set.ssl.issuercert) { + gnutls_x509_crt_init(&x509_issuer); + issuerp = load_file(data->set.ssl.issuercert); + gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); + rc = gnutls_x509_crt_check_issuer(x509_cert,x509_issuer); + unload_file(issuerp); + if(rc <= 0) { + failf(data, "server certificate issuer check failed (IssuerCert: %s)", + data->set.ssl.issuercert?data->set.ssl.issuercert:"none"); + return CURLE_SSL_ISSUER_ERROR; + } + infof(data,"\t server certificate issuer check OK (Issuer Cert: %s)\n", + data->set.ssl.issuercert?data->set.ssl.issuercert:"none"); + } + + size=sizeof(certbuf); + rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME, + 0, /* the first and only one */ + FALSE, + certbuf, + &size); + if(rc) { + infof(data, "error fetching CN from cert:%s\n", + gnutls_strerror(rc)); + } + + /* This function will check if the given certificate's subject matches the + given hostname. This is a basic implementation of the matching described + in RFC2818 (HTTPS), which takes into account wildcards, and the subject + alternative name PKIX extension. Returns non zero on success, and zero on + failure. */ + rc = gnutls_x509_crt_check_hostname(x509_cert, conn->host.name); + + if(!rc) { + if(data->set.ssl.verifyhost) { + failf(data, "SSL: certificate subject name (%s) does not match " + "target host name '%s'", certbuf, conn->host.dispname); + gnutls_x509_crt_deinit(x509_cert); + return CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, "\t common name: %s (does not match '%s')\n", + certbuf, conn->host.dispname); + } + else + infof(data, "\t common name: %s (matched)\n", certbuf); + + /* Check for time-based validity */ + certclock = gnutls_x509_crt_get_expiration_time(x509_cert); + + if(certclock == (time_t)-1) { + failf(data, "server cert expiration date verify failed"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(certclock < time(NULL)) { + if(data->set.ssl.verifypeer) { + failf(data, "server certificate expiration date has passed."); + return CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, "\t server certificate expiration date FAILED\n"); + } + else + infof(data, "\t server certificate expiration date OK\n"); + + certclock = gnutls_x509_crt_get_activation_time(x509_cert); + + if(certclock == (time_t)-1) { + failf(data, "server cert activation date verify failed"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(certclock > time(NULL)) { + if(data->set.ssl.verifypeer) { + failf(data, "server certificate not activated yet."); + return CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, "\t server certificate activation date FAILED\n"); + } + else + infof(data, "\t server certificate activation date OK\n"); + + /* Show: + + - ciphers used + - subject + - start date + - expire date + - common name + - issuer + + */ + + /* public key algorithm's parameters */ + algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits); + infof(data, "\t certificate public key: %s\n", + gnutls_pk_algorithm_get_name(algo)); + + /* version of the X.509 certificate. */ + infof(data, "\t certificate version: #%d\n", + gnutls_x509_crt_get_version(x509_cert)); + + + size = sizeof(certbuf); + gnutls_x509_crt_get_dn(x509_cert, certbuf, &size); + infof(data, "\t subject: %s\n", certbuf); + + certclock = gnutls_x509_crt_get_activation_time(x509_cert); + showtime(data, "start date", certclock); + + certclock = gnutls_x509_crt_get_expiration_time(x509_cert); + showtime(data, "expire date", certclock); + + size = sizeof(certbuf); + gnutls_x509_crt_get_issuer_dn(x509_cert, certbuf, &size); + infof(data, "\t issuer: %s\n", certbuf); + + gnutls_x509_crt_deinit(x509_cert); + +after_server_cert_verification: + + /* compression algorithm (if any) */ + ptr = gnutls_compression_get_name(gnutls_compression_get(session)); + /* the *_get_name() says "NULL" if GNUTLS_COMP_NULL is returned */ + infof(data, "\t compression: %s\n", ptr); + + /* the name of the cipher used. ie 3DES. */ + ptr = gnutls_cipher_get_name(gnutls_cipher_get(session)); + infof(data, "\t cipher: %s\n", ptr); + + /* the MAC algorithms name. ie SHA1 */ + ptr = gnutls_mac_get_name(gnutls_mac_get(session)); + infof(data, "\t MAC: %s\n", ptr); + + conn->ssl[sockindex].state = ssl_connection_complete; + conn->recv[sockindex] = gtls_recv; + conn->send[sockindex] = gtls_send; + + { + /* we always unconditionally get the session id here, as even if we + already got it from the cache and asked to use it in the connection, it + might've been rejected and then a new one is in use now and we need to + detect that. */ + void *connect_sessionid; + size_t connect_idsize; + + /* get the session ID data size */ + gnutls_session_get_data(session, NULL, &connect_idsize); + connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ + + if(connect_sessionid) { + /* extract session ID to the allocated buffer */ + gnutls_session_get_data(session, connect_sessionid, &connect_idsize); + + incache = !(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)); + if(incache) { + /* there was one before in the cache, so instead of risking that the + previous one was rejected, we just kill that and store the new */ + Curl_ssl_delsessionid(conn, ssl_sessionid); + } + + /* store this session id */ + result = Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize); + if(result) { + free(connect_sessionid); + result = CURLE_OUT_OF_MEMORY; + } + } + else + result = CURLE_OUT_OF_MEMORY; + } + + return result; +} + + +/* + * This function is called after the TCP connect has completed. Setup the TLS + * layer and do all necessary magic. + */ +/* We use connssl->connecting_state to keep track of the connection status; + there are three states: 'ssl_connect_1' (not started yet or complete), + 'ssl_connect_2_reading' (waiting for data from server), and + 'ssl_connect_2_writing' (waiting to be able to write). + */ +static CURLcode +gtls_connect_common(struct connectdata *conn, + int sockindex, + bool nonblocking, + bool *done) +{ + int rc; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + /* Initiate the connection, if not already done */ + if(ssl_connect_1==connssl->connecting_state) { + rc = gtls_connect_step1 (conn, sockindex); + if(rc) + return rc; + } + + rc = handshake(conn, sockindex, TRUE, nonblocking); + if(rc) + /* handshake() sets its own error message with failf() */ + return rc; + + /* Finish connecting once the handshake is done */ + if(ssl_connect_1==connssl->connecting_state) { + rc = gtls_connect_step3(conn, sockindex); + if(rc) + return rc; + } + + *done = ssl_connect_1==connssl->connecting_state; + + return CURLE_OK; +} + +CURLcode +Curl_gtls_connect_nonblocking(struct connectdata *conn, + int sockindex, + bool *done) +{ + return gtls_connect_common(conn, sockindex, TRUE, done); +} + +CURLcode +Curl_gtls_connect(struct connectdata *conn, + int sockindex) + +{ + CURLcode retcode; + bool done = FALSE; + + retcode = gtls_connect_common(conn, sockindex, FALSE, &done); + if(retcode) + return retcode; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +static ssize_t gtls_send(struct connectdata *conn, + int sockindex, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + ssize_t rc = gnutls_record_send(conn->ssl[sockindex].session, mem, len); + + if(rc < 0 ) { + *curlcode = (rc == GNUTLS_E_AGAIN) + ? CURLE_AGAIN + : CURLE_SEND_ERROR; + + rc = -1; + } + + return rc; +} + +void Curl_gtls_close_all(struct SessionHandle *data) +{ + /* FIX: make the OpenSSL code more generic and use parts of it here */ + (void)data; +} + +static void close_one(struct connectdata *conn, + int idx) +{ + if(conn->ssl[idx].session) { + gnutls_bye(conn->ssl[idx].session, GNUTLS_SHUT_RDWR); + gnutls_deinit(conn->ssl[idx].session); + conn->ssl[idx].session = NULL; + } + if(conn->ssl[idx].cred) { + gnutls_certificate_free_credentials(conn->ssl[idx].cred); + conn->ssl[idx].cred = NULL; + } +#ifdef USE_TLS_SRP + if(conn->ssl[idx].srp_client_cred) { + gnutls_srp_free_client_credentials(conn->ssl[idx].srp_client_cred); + conn->ssl[idx].srp_client_cred = NULL; + } +#endif +} + +void Curl_gtls_close(struct connectdata *conn, int sockindex) +{ + close_one(conn, sockindex); +} + +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +int Curl_gtls_shutdown(struct connectdata *conn, int sockindex) +{ + ssize_t result; + int retval = 0; + struct SessionHandle *data = conn->data; + int done = 0; + char buf[120]; + + /* This has only been tested on the proftpd server, and the mod_tls code + sends a close notify alert without waiting for a close notify alert in + response. Thus we wait for a close notify alert from the server, but + we do not send one. Let's hope other servers do the same... */ + + if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) + gnutls_bye(conn->ssl[sockindex].session, GNUTLS_SHUT_WR); + + if(conn->ssl[sockindex].session) { + while(!done) { + int what = Curl_socket_ready(conn->sock[sockindex], + CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); + if(what > 0) { + /* Something to read, let's do it and hope that it is the close + notify alert from the server */ + result = gnutls_record_recv(conn->ssl[sockindex].session, + buf, sizeof(buf)); + switch(result) { + case 0: + /* This is the expected response. There was no data but only + the close notify alert */ + done = 1; + break; + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED\n"); + break; + default: + retval = -1; + done = 1; + break; + } + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + done = 1; + break; + } + else { + /* anything that gets here is fatally bad */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + retval = -1; + done = 1; + } + } + gnutls_deinit(conn->ssl[sockindex].session); + } + gnutls_certificate_free_credentials(conn->ssl[sockindex].cred); + +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP + && data->set.ssl.username != NULL) + gnutls_srp_free_client_credentials(conn->ssl[sockindex].srp_client_cred); +#endif + + conn->ssl[sockindex].cred = NULL; + conn->ssl[sockindex].session = NULL; + + return retval; +} + +static ssize_t gtls_recv(struct connectdata *conn, /* connection data */ + int num, /* socketindex */ + char *buf, /* store read data here */ + size_t buffersize, /* max amount to read */ + CURLcode *curlcode) +{ + ssize_t ret; + + ret = gnutls_record_recv(conn->ssl[num].session, buf, buffersize); + if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { + *curlcode = CURLE_AGAIN; + return -1; + } + + if(ret == GNUTLS_E_REHANDSHAKE) { + /* BLOCKING call, this is bad but a work-around for now. Fixing this "the + proper way" takes a whole lot of work. */ + CURLcode rc = handshake(conn, num, FALSE, FALSE); + if(rc) + /* handshake() writes error message on its own */ + *curlcode = rc; + else + *curlcode = CURLE_AGAIN; /* then return as if this was a wouldblock */ + return -1; + } + + if(ret < 0) { + failf(conn->data, "GnuTLS recv error (%d): %s", + (int)ret, gnutls_strerror((int)ret)); + *curlcode = CURLE_RECV_ERROR; + return -1; + } + + return ret; +} + +void Curl_gtls_session_free(void *ptr) +{ + free(ptr); +} + +size_t Curl_gtls_version(char *buffer, size_t size) +{ + return snprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); +} + +int Curl_gtls_seed(struct SessionHandle *data) +{ + /* we have the "SSL is seeded" boolean static to prevent multiple + time-consuming seedings in vain */ + static bool ssl_seeded = FALSE; + + /* Quickly add a bit of entropy */ +#ifndef USE_GNUTLS_NETTLE + gcry_fast_random_poll(); +#endif + + if(!ssl_seeded || data->set.str[STRING_SSL_RANDOM_FILE] || + data->set.str[STRING_SSL_EGDSOCKET]) { + + /* TODO: to a good job seeding the RNG + This may involve the gcry_control function and these options: + GCRYCTL_SET_RANDOM_SEED_FILE + GCRYCTL_SET_RNDEGD_SOCKET + */ + ssl_seeded = TRUE; + } + return 0; +} + +void Curl_gtls_random(struct SessionHandle *data, + unsigned char *entropy, + size_t length) +{ +#if defined(USE_GNUTLS_NETTLE) + (void)data; + gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length); +#elif defined(USE_GNUTLS) + Curl_gtls_seed(data); /* Initiate the seed if not already done */ + gcry_randomize(entropy, length, GCRY_STRONG_RANDOM); +#endif +} + +void Curl_gtls_md5sum(unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *md5sum, /* output */ + size_t md5len) +{ +#if defined(USE_GNUTLS_NETTLE) + struct md5_ctx MD5pw; + md5_init(&MD5pw); + md5_update(&MD5pw, tmplen, tmp); + md5_digest(&MD5pw, md5len, md5sum); +#elif defined(USE_GNUTLS) + gcry_md_hd_t MD5pw; + gcry_md_open(&MD5pw, GCRY_MD_MD5, 0); + gcry_md_write(MD5pw, tmp, tmplen); + memcpy(md5sum, gcry_md_read (MD5pw, 0), md5len); + gcry_md_close(MD5pw); +#endif +} + +#endif /* USE_GNUTLS */ diff --git a/lib/curl_hash.c b/lib/curl_hash.c new file mode 100644 index 000000000..732dbcf73 --- /dev/null +++ b/lib/curl_hash.c @@ -0,0 +1,400 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_hash.h" +#include "curl_llist.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +static void +hash_element_dtor(void *user, void *element) +{ + struct curl_hash *h = (struct curl_hash *) user; + struct curl_hash_element *e = (struct curl_hash_element *) element; + + Curl_safefree(e->key); + + if(e->ptr) { + h->dtor(e->ptr); + e->ptr = NULL; + } + + e->key_len = 0; + + free(e); +} + +/* return 1 on error, 0 is fine */ +int +Curl_hash_init(struct curl_hash *h, + int slots, + hash_function hfunc, + comp_function comparator, + curl_hash_dtor dtor) +{ + int i; + + if(!slots || !hfunc || !comparator ||!dtor) { + return 1; /* failure */ + } + + h->hash_func = hfunc; + h->comp_func = comparator; + h->dtor = dtor; + h->size = 0; + h->slots = slots; + + h->table = malloc(slots * sizeof(struct curl_llist *)); + if(h->table) { + for(i = 0; i < slots; ++i) { + h->table[i] = Curl_llist_alloc((curl_llist_dtor) hash_element_dtor); + if(!h->table[i]) { + while(i--) { + Curl_llist_destroy(h->table[i], NULL); + h->table[i] = NULL; + } + free(h->table); + h->table = NULL; + h->slots = 0; + return 1; /* failure */ + } + } + return 0; /* fine */ + } + else { + h->slots = 0; + return 1; /* failure */ + } +} + +struct curl_hash * +Curl_hash_alloc(int slots, + hash_function hfunc, + comp_function comparator, + curl_hash_dtor dtor) +{ + struct curl_hash *h; + + if(!slots || !hfunc || !comparator ||!dtor) { + return NULL; /* failure */ + } + + h = malloc(sizeof(struct curl_hash)); + if(h) { + if(Curl_hash_init(h, slots, hfunc, comparator, dtor)) { + /* failure */ + free(h); + h = NULL; + } + } + + return h; +} + + + +static struct curl_hash_element * +mk_hash_element(const void *key, size_t key_len, const void *p) +{ + struct curl_hash_element *he = malloc(sizeof(struct curl_hash_element)); + + if(he) { + void *dupkey = malloc(key_len); + if(dupkey) { + /* copy the key */ + memcpy(dupkey, key, key_len); + + he->key = dupkey; + he->key_len = key_len; + he->ptr = (void *) p; + } + else { + /* failed to duplicate the key, free memory and fail */ + free(he); + he = NULL; + } + } + return he; +} + +#define FETCH_LIST(x,y,z) x->table[x->hash_func(y, z, x->slots)] + +/* Insert the data in the hash. If there already was a match in the hash, + * that data is replaced. + * + * @unittest: 1305 + */ +void * +Curl_hash_add(struct curl_hash *h, void *key, size_t key_len, void *p) +{ + struct curl_hash_element *he; + struct curl_llist_element *le; + struct curl_llist *l = FETCH_LIST (h, key, key_len); + + for(le = l->head; le; le = le->next) { + he = (struct curl_hash_element *) le->ptr; + if(h->comp_func(he->key, he->key_len, key, key_len)) { + Curl_llist_remove(l, le, (void *)h); + --h->size; + break; + } + } + + he = mk_hash_element(key, key_len, p); + if(he) { + if(Curl_llist_insert_next(l, l->tail, he)) { + ++h->size; + return p; /* return the new entry */ + } + /* + * Couldn't insert it, destroy the 'he' element and the key again. We + * don't call hash_element_dtor() since that would also call the + * "destructor" for the actual data 'p'. When we fail, we shall not touch + * that data. + */ + free(he->key); + free(he); + } + + return NULL; /* failure */ +} + +/* remove the identified hash entry, returns non-zero on failure */ +int Curl_hash_delete(struct curl_hash *h, void *key, size_t key_len) +{ + struct curl_llist_element *le; + struct curl_hash_element *he; + struct curl_llist *l = FETCH_LIST(h, key, key_len); + + for(le = l->head; le; le = le->next) { + he = le->ptr; + if(h->comp_func(he->key, he->key_len, key, key_len)) { + Curl_llist_remove(l, le, (void *) h); + --h->size; + return 0; + } + } + return 1; +} + +void * +Curl_hash_pick(struct curl_hash *h, void *key, size_t key_len) +{ + struct curl_llist_element *le; + struct curl_hash_element *he; + struct curl_llist *l; + + if(h) { + l = FETCH_LIST(h, key, key_len); + for(le = l->head; le; le = le->next) { + he = le->ptr; + if(h->comp_func(he->key, he->key_len, key, key_len)) { + return he->ptr; + } + } + } + + return NULL; +} + +#if defined(DEBUGBUILD) && defined(AGGRESIVE_TEST) +void +Curl_hash_apply(curl_hash *h, void *user, + void (*cb)(void *user, void *ptr)) +{ + struct curl_llist_element *le; + int i; + + for(i = 0; i < h->slots; ++i) { + for(le = (h->table[i])->head; + le; + le = le->next) { + curl_hash_element *el = le->ptr; + cb(user, el->ptr); + } + } +} +#endif + +void +Curl_hash_clean(struct curl_hash *h) +{ + int i; + + for(i = 0; i < h->slots; ++i) { + Curl_llist_destroy(h->table[i], (void *) h); + h->table[i] = NULL; + } + + Curl_safefree(h->table); + h->size = 0; + h->slots = 0; +} + +void +Curl_hash_clean_with_criterium(struct curl_hash *h, void *user, + int (*comp)(void *, void *)) +{ + struct curl_llist_element *le; + struct curl_llist_element *lnext; + struct curl_llist *list; + int i; + + if(!h) + return; + + for(i = 0; i < h->slots; ++i) { + list = h->table[i]; + le = list->head; /* get first list entry */ + while(le) { + struct curl_hash_element *he = le->ptr; + lnext = le->next; + /* ask the callback function if we shall remove this entry or not */ + if(comp(user, he->ptr)) { + Curl_llist_remove(list, le, (void *) h); + --h->size; /* one less entry in the hash now */ + } + le = lnext; + } + } +} + +void +Curl_hash_destroy(struct curl_hash *h) +{ + if(!h) + return; + + Curl_hash_clean(h); + + free(h); +} + +size_t Curl_hash_str(void* key, size_t key_length, size_t slots_num) +{ + const char* key_str = (const char *) key; + const char *end = key_str + key_length; + unsigned long h = 5381; + + while(key_str < end) { + h += h << 5; + h ^= (unsigned long) *key_str++; + } + + return (h % slots_num); +} + +size_t Curl_str_key_compare(void*k1, size_t key1_len, void*k2, size_t key2_len) +{ + char *key1 = (char *)k1; + char *key2 = (char *)k2; + + if(key1_len == key2_len && + *key1 == *key2 && + memcmp(key1, key2, key1_len) == 0) { + return 1; + } + + return 0; +} + +void Curl_hash_start_iterate(struct curl_hash *hash, + struct curl_hash_iterator *iter) +{ + iter->hash = hash; + iter->slot_index = 0; + iter->current_element = NULL; +} + +struct curl_hash_element * +Curl_hash_next_element(struct curl_hash_iterator *iter) +{ + int i; + struct curl_hash *h = iter->hash; + + /* Get the next element in the current list, if any */ + if(iter->current_element) + iter->current_element = iter->current_element->next; + + /* If we have reached the end of the list, find the next one */ + if(!iter->current_element) { + for(i = iter->slot_index;i < h->slots;i++) { + if(h->table[i]->head) { + iter->current_element = h->table[i]->head; + iter->slot_index = i+1; + break; + } + } + } + + if(iter->current_element) { + struct curl_hash_element *he = iter->current_element->ptr; + return he; + } + else { + iter->current_element = NULL; + return NULL; + } +} + +#if 0 /* useful function for debugging hashes and their contents */ +void Curl_hash_print(struct curl_hash *h, + void (*func)(void *)) +{ + struct curl_hash_iterator iter; + struct curl_hash_element *he; + int last_index = -1; + + if(!h) + return; + + fprintf(stderr, "=Hash dump=\n"); + + Curl_hash_start_iterate(h, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + if(iter.slot_index != last_index) { + fprintf(stderr, "index %d:", iter.slot_index); + if(last_index >= 0) { + fprintf(stderr, "\n"); + } + last_index = iter.slot_index; + } + + if(func) + func(he->ptr); + else + fprintf(stderr, " [%p]", he->ptr); + + he = Curl_hash_next_element(&iter); + } + fprintf(stderr, "\n"); +} +#endif diff --git a/lib/curl_hmac.c b/lib/curl_hmac.c new file mode 100644 index 000000000..692d27940 --- /dev/null +++ b/lib/curl_hmac.c @@ -0,0 +1,133 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + * RFC2104 Keyed-Hashing for Message Authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_CRYPTO_AUTH + +#include "curl_hmac.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + * Generic HMAC algorithm. + * + * This module computes HMAC digests based on any hash function. Parameters + * and computing procedures are set-up dynamically at HMAC computation + * context initialisation. + */ + +static const unsigned char hmac_ipad = 0x36; +static const unsigned char hmac_opad = 0x5C; + + + +HMAC_context * +Curl_HMAC_init(const HMAC_params * hashparams, + const unsigned char * key, + unsigned int keylen) +{ + size_t i; + HMAC_context * ctxt; + unsigned char * hkey; + unsigned char b; + + /* Create HMAC context. */ + i = sizeof *ctxt + 2 * hashparams->hmac_ctxtsize + + hashparams->hmac_resultlen; + ctxt = malloc(i); + + if(!ctxt) + return ctxt; + + ctxt->hmac_hash = hashparams; + ctxt->hmac_hashctxt1 = (void *) (ctxt + 1); + ctxt->hmac_hashctxt2 = (void *) ((char *) ctxt->hmac_hashctxt1 + + hashparams->hmac_ctxtsize); + + /* If the key is too long, replace it by its hash digest. */ + if(keylen > hashparams->hmac_maxkeylen) { + (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, key, keylen); + hkey = (unsigned char *) ctxt->hmac_hashctxt2 + hashparams->hmac_ctxtsize; + (*hashparams->hmac_hfinal)(hkey, ctxt->hmac_hashctxt1); + key = hkey; + keylen = hashparams->hmac_resultlen; + } + + /* Prime the two hash contexts with the modified key. */ + (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); + (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt2); + + for(i = 0; i < keylen; i++) { + b = (unsigned char)(*key ^ hmac_ipad); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &b, 1); + b = (unsigned char)(*key++ ^ hmac_opad); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &b, 1); + } + + for(; i < hashparams->hmac_maxkeylen; i++) { + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &hmac_ipad, 1); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &hmac_opad, 1); + } + + /* Done, return pointer to HMAC context. */ + return ctxt; +} + +int Curl_HMAC_update(HMAC_context * ctxt, + const unsigned char * data, + unsigned int len) +{ + /* Update first hash calculation. */ + (*ctxt->hmac_hash->hmac_hupdate)(ctxt->hmac_hashctxt1, data, len); + return 0; +} + + +int Curl_HMAC_final(HMAC_context * ctxt, unsigned char * result) +{ + const HMAC_params * hashparams = ctxt->hmac_hash; + + /* Do not get result if called with a null parameter: only release + storage. */ + + if(!result) + result = (unsigned char *) ctxt->hmac_hashctxt2 + + ctxt->hmac_hash->hmac_ctxtsize; + + (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt1); + (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, + result, hashparams->hmac_resultlen); + (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt2); + free((char *) ctxt); + return 0; +} + +#endif /* CURL_DISABLE_CRYPTO_AUTH */ diff --git a/lib/curl_hostasyn.c b/lib/curl_hostasyn.c new file mode 100644 index 000000000..0097b6cfd --- /dev/null +++ b/lib/curl_hostasyn.c @@ -0,0 +1,157 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef HAVE_PROCESS_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_hostip.h" +#include "curl_hash.h" +#include "curl_share.h" +#include "curl_strerror.h" +#include "curl_url.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/*********************************************************************** + * Only for builds using asynchronous name resolves + **********************************************************************/ +#ifdef CURLRES_ASYNCH + +/* + * Curl_addrinfo_callback() gets called by ares, gethostbyname_thread() + * or getaddrinfo_thread() when we got the name resolved (or not!). + * + * If the status argument is CURL_ASYNC_SUCCESS, this function takes + * ownership of the Curl_addrinfo passed, storing the resolved data + * in the DNS cache. + * + * The storage operation locks and unlocks the DNS cache. + */ +CURLcode Curl_addrinfo_callback(struct connectdata *conn, + int status, + struct Curl_addrinfo *ai) +{ + struct Curl_dns_entry *dns = NULL; + CURLcode rc = CURLE_OK; + + conn->async.status = status; + + if(CURL_ASYNC_SUCCESS == status) { + if(ai) { + struct SessionHandle *data = conn->data; + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + dns = Curl_cache_addr(data, ai, + conn->async.hostname, + conn->async.port); + if(!dns) { + /* failed to store, cleanup and return error */ + Curl_freeaddrinfo(ai); + rc = CURLE_OUT_OF_MEMORY; + } + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + } + else { + rc = CURLE_OUT_OF_MEMORY; + } + } + + conn->async.dns = dns; + + /* Set async.done TRUE last in this function since it may be used multi- + threaded and once this is TRUE the other thread may read fields from the + async struct */ + conn->async.done = TRUE; + + /* ipv4: The input hostent struct will be freed by ares when we return from + this function */ + return rc; +} + +/* Call this function after Curl_connect() has returned async=TRUE and + then a successful name resolve has been received. + + Note: this function disconnects and frees the conn data in case of + resolve failure */ +CURLcode Curl_async_resolved(struct connectdata *conn, + bool *protocol_done) +{ + CURLcode code; + + if(conn->async.dns) { + conn->dns_entry = conn->async.dns; + conn->async.dns = NULL; + } + + code = Curl_setup_conn(conn, protocol_done); + + if(code) + /* We're not allowed to return failure with memory left allocated + in the connectdata struct, free those here */ + Curl_disconnect(conn, FALSE); /* close the connection */ + + return code; +} + +/* + * Curl_getaddrinfo() is the generic low-level name resolve API within this + * source file. There are several versions of this function - for different + * name resolve layers (selected at build-time). They all take this same set + * of arguments + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + const char *hostname, + int port, + int *waitp) +{ + return Curl_resolver_getaddrinfo(conn, hostname, port, waitp); +} + +#endif /* CURLRES_ASYNCH */ diff --git a/lib/curl_hostcheck.c b/lib/curl_hostcheck.c new file mode 100644 index 000000000..a5bf8b02f --- /dev/null +++ b/lib/curl_hostcheck.c @@ -0,0 +1,96 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_SSLEAY) || defined(USE_AXTLS) +/* these two backends use functions from this file */ + +#include "curl_hostcheck.h" +#include "curl_rawstr.h" + +/* + * Match a hostname against a wildcard pattern. + * E.g. + * "foo.host.com" matches "*.host.com". + * + * We use the matching rule described in RFC6125, section 6.4.3. + * http://tools.ietf.org/html/rfc6125#section-6.4.3 + */ + +static int hostmatch(const char *hostname, const char *pattern) +{ + const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; + int wildcard_enabled; + size_t prefixlen, suffixlen; + pattern_wildcard = strchr(pattern, '*'); + if(pattern_wildcard == NULL) + return Curl_raw_equal(pattern, hostname) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; + + /* We require at least 2 dots in pattern to avoid too wide wildcard + match. */ + wildcard_enabled = 1; + pattern_label_end = strchr(pattern, '.'); + if(pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL || + pattern_wildcard > pattern_label_end || + Curl_raw_nequal(pattern, "xn--", 4)) { + wildcard_enabled = 0; + } + if(!wildcard_enabled) + return Curl_raw_equal(pattern, hostname) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; + + hostname_label_end = strchr(hostname, '.'); + if(hostname_label_end == NULL || + !Curl_raw_equal(pattern_label_end, hostname_label_end)) + return CURL_HOST_NOMATCH; + + /* The wildcard must match at least one character, so the left-most + label of the hostname is at least as large as the left-most label + of the pattern. */ + if(hostname_label_end - hostname < pattern_label_end - pattern) + return CURL_HOST_NOMATCH; + + prefixlen = pattern_wildcard - pattern; + suffixlen = pattern_label_end - (pattern_wildcard+1); + return Curl_raw_nequal(pattern, hostname, prefixlen) && + Curl_raw_nequal(pattern_wildcard+1, hostname_label_end - suffixlen, + suffixlen) ? + CURL_HOST_MATCH : CURL_HOST_NOMATCH; +} + +int Curl_cert_hostcheck(const char *match_pattern, const char *hostname) +{ + if(!match_pattern || !*match_pattern || + !hostname || !*hostname) /* sanity check */ + return 0; + + if(Curl_raw_equal(hostname, match_pattern)) /* trivial case */ + return 1; + + if(hostmatch(hostname,match_pattern) == CURL_HOST_MATCH) + return 1; + return 0; +} + +#endif /* SSLEAY or AXTLS */ diff --git a/lib/curl_hostip.c b/lib/curl_hostip.c new file mode 100644 index 000000000..7cc51f899 --- /dev/null +++ b/lib/curl_hostip.c @@ -0,0 +1,820 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef HAVE_SETJMP_H +#include +#endif +#ifdef HAVE_SIGNAL_H +#include +#endif + +#ifdef HAVE_PROCESS_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_hostip.h" +#include "curl_hash.h" +#include "curl_share.h" +#include "curl_strerror.h" +#include "curl_url.h" +#include "curl_inet_ntop.h" +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#if defined(CURLRES_SYNCH) && \ + defined(HAVE_ALARM) && defined(SIGALRM) && defined(HAVE_SIGSETJMP) +/* alarm-based timeouts can only be used with all the dependencies satisfied */ +#define USE_ALARM_TIMEOUT +#endif + +/* + * curl_hostip.c explained + * ======================= + * + * The main COMPILE-TIME DEFINES to keep in mind when reading the curl_host*.c + * source file are these: + * + * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use + * that. The host may not be able to resolve IPv6, but we don't really have to + * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4 + * defined. + * + * CURLRES_ARES - is defined if libcurl is built to use c-ares for + * asynchronous name resolves. This can be Windows or *nix. + * + * CURLRES_THREADED - is defined if libcurl is built to run under (native) + * Windows, and then the name resolve will be done in a new thread, and the + * supported API will be the same as for ares-builds. + * + * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If + * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is + * defined. + * + * The curl_host*.c sources files are split up like this: + * + * curl_hostip.c - method-independent resolver and utility functions + * curl_hostasyn.c - functions for asynchronous name resolves + * curl_hostsyn.c - functions for synchronous name resolves + * curl_hostip4.c - ipv4-specific functions + * curl_hostip6.c - ipv6-specific functions + * + * The two asynchronous name resolver backends are implemented in: + * curl_asyn_ares.c - functions for ares-using name resolves + * curl_asyn_thread.c - functions for threaded name resolves + + * The curl_hostip.h is the united header file for all this. It defines the + * CURLRES_* defines based on the config*.h and curl_setup.h defines. + */ + +/* These two symbols are for the global DNS cache */ +static struct curl_hash hostname_cache; +static int host_cache_initialized; + +static void freednsentry(void *freethis); + +/* + * Curl_global_host_cache_init() initializes and sets up a global DNS cache. + * Global DNS cache is general badness. Do not use. This will be removed in + * a future version. Use the share interface instead! + * + * Returns a struct curl_hash pointer on success, NULL on failure. + */ +struct curl_hash *Curl_global_host_cache_init(void) +{ + int rc = 0; + if(!host_cache_initialized) { + rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str, + Curl_str_key_compare, freednsentry); + if(!rc) + host_cache_initialized = 1; + } + return rc?NULL:&hostname_cache; +} + +/* + * Destroy and cleanup the global DNS cache + */ +void Curl_global_host_cache_dtor(void) +{ + if(host_cache_initialized) { + Curl_hash_clean(&hostname_cache); + host_cache_initialized = 0; + } +} + +/* + * Return # of adresses in a Curl_addrinfo struct + */ +int Curl_num_addresses(const Curl_addrinfo *addr) +{ + int i = 0; + while(addr) { + addr = addr->ai_next; + i++; + } + return i; +} + +/* + * Curl_printable_address() returns a printable version of the 1st address + * given in the 'ai' argument. The result will be stored in the buf that is + * bufsize bytes big. + * + * If the conversion fails, it returns NULL. + */ +const char * +Curl_printable_address(const Curl_addrinfo *ai, char *buf, size_t bufsize) +{ + const struct sockaddr_in *sa4; + const struct in_addr *ipaddr4; +#ifdef ENABLE_IPV6 + const struct sockaddr_in6 *sa6; + const struct in6_addr *ipaddr6; +#endif + + switch (ai->ai_family) { + case AF_INET: + sa4 = (const void *)ai->ai_addr; + ipaddr4 = &sa4->sin_addr; + return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, + bufsize); +#ifdef ENABLE_IPV6 + case AF_INET6: + sa6 = (const void *)ai->ai_addr; + ipaddr6 = &sa6->sin6_addr; + return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf, + bufsize); +#endif + default: + break; + } + return NULL; +} + +/* + * Return a hostcache id string for the provided host + port, to be used by + * the DNS caching. + */ +static char * +create_hostcache_id(const char *name, int port) +{ + /* create and return the new allocated entry */ + char *id = aprintf("%s:%d", name, port); + char *ptr = id; + if(ptr) { + /* lower case the name part */ + while(*ptr && (*ptr != ':')) { + *ptr = (char)TOLOWER(*ptr); + ptr++; + } + } + return id; +} + +struct hostcache_prune_data { + long cache_timeout; + time_t now; +}; + +/* + * This function is set as a callback to be called for every entry in the DNS + * cache when we want to prune old unused entries. + * + * Returning non-zero means remove the entry, return 0 to keep it in the + * cache. + */ +static int +hostcache_timestamp_remove(void *datap, void *hc) +{ + struct hostcache_prune_data *data = + (struct hostcache_prune_data *) datap; + struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; + + return (data->now - c->timestamp >= data->cache_timeout); +} + +/* + * Prune the DNS cache. This assumes that a lock has already been taken. + */ +static void +hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now) +{ + struct hostcache_prune_data user; + + user.cache_timeout = cache_timeout; + user.now = now; + + Curl_hash_clean_with_criterium(hostcache, + (void *) &user, + hostcache_timestamp_remove); +} + +/* + * Library-wide function for pruning the DNS cache. This function takes and + * returns the appropriate locks. + */ +void Curl_hostcache_prune(struct SessionHandle *data) +{ + time_t now; + + if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache) + /* cache forever means never prune, and NULL hostcache means + we can't do it */ + return; + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + time(&now); + + /* Remove outdated and unused entries from the hostcache */ + hostcache_prune(data->dns.hostcache, + data->set.dns_cache_timeout, + now); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +} + +/* + * Check if the entry should be pruned. Assumes a locked cache. + */ +static int +remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns) +{ + struct hostcache_prune_data user; + + if(!dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache) + /* cache forever means never prune, and NULL hostcache means + we can't do it */ + return 0; + + time(&user.now); + user.cache_timeout = data->set.dns_cache_timeout; + + if(!hostcache_timestamp_remove(&user,dns) ) + return 0; + + Curl_hash_clean_with_criterium(data->dns.hostcache, + (void *) &user, + hostcache_timestamp_remove); + + return 1; +} + + +#ifdef HAVE_SIGSETJMP +/* Beware this is a global and unique instance. This is used to store the + return address that we can jump back to from inside a signal handler. This + is not thread-safe stuff. */ +sigjmp_buf curl_jmpenv; +#endif + + +/* + * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. + * + * When calling Curl_resolv() has resulted in a response with a returned + * address, we call this function to store the information in the dns + * cache etc + * + * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. + */ +struct Curl_dns_entry * +Curl_cache_addr(struct SessionHandle *data, + Curl_addrinfo *addr, + const char *hostname, + int port) +{ + char *entry_id; + size_t entry_len; + struct Curl_dns_entry *dns; + struct Curl_dns_entry *dns2; + + /* Create an entry id, based upon the hostname and port */ + entry_id = create_hostcache_id(hostname, port); + /* If we can't create the entry id, fail */ + if(!entry_id) + return NULL; + entry_len = strlen(entry_id); + + /* Create a new cache entry */ + dns = calloc(1, sizeof(struct Curl_dns_entry)); + if(!dns) { + free(entry_id); + return NULL; + } + + dns->inuse = 0; /* init to not used */ + dns->addr = addr; /* this is the address(es) */ + time(&dns->timestamp); + if(dns->timestamp == 0) + dns->timestamp = 1; /* zero indicates that entry isn't in hash table */ + + /* Store the resolved data in our DNS cache. */ + dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1, + (void *)dns); + if(!dns2) { + free(dns); + free(entry_id); + return NULL; + } + + dns = dns2; + dns->inuse++; /* mark entry as in-use */ + + /* free the allocated entry_id */ + free(entry_id); + + return dns; +} + +/* + * Curl_resolv() is the main name resolve function within libcurl. It resolves + * a name and returns a pointer to the entry in the 'entry' argument (if one + * is provided). This function might return immediately if we're using asynch + * resolves. See the return codes. + * + * The cache entry we return will get its 'inuse' counter increased when this + * function is used. You MUST call Curl_resolv_unlock() later (when you're + * done using this struct) to decrease the counter again. + * + * In debug mode, we specifically test for an interface name "LocalHost" + * and resolve "localhost" instead as a means to permit test cases + * to connect to a local test server with any host name. + * + * Return codes: + * + * CURLRESOLV_ERROR (-1) = error, no pointer + * CURLRESOLV_RESOLVED (0) = OK, pointer provided + * CURLRESOLV_PENDING (1) = waiting for response, no pointer + */ + +int Curl_resolv(struct connectdata *conn, + const char *hostname, + int port, + struct Curl_dns_entry **entry) +{ + char *entry_id = NULL; + struct Curl_dns_entry *dns = NULL; + size_t entry_len; + struct SessionHandle *data = conn->data; + CURLcode result; + int rc = CURLRESOLV_ERROR; /* default to failure */ + + *entry = NULL; + + /* Create an entry id, based upon the hostname and port */ + entry_id = create_hostcache_id(hostname, port); + /* If we can't create the entry id, fail */ + if(!entry_id) + return rc; + + entry_len = strlen(entry_id); + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + /* See if its already in our dns cache */ + dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1); + + /* free the allocated entry_id again */ + free(entry_id); + + /* See whether the returned entry is stale. Done before we release lock */ + if(remove_entry_if_stale(data, dns)) + dns = NULL; /* the memory deallocation is being handled by the hash */ + + if(dns) { + dns->inuse++; /* we use it! */ + rc = CURLRESOLV_RESOLVED; + } + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) { + /* The entry was not in the cache. Resolve it to IP address */ + + Curl_addrinfo *addr; + int respwait; + + /* Check what IP specifics the app has requested and if we can provide it. + * If not, bail out. */ + if(!Curl_ipvalid(conn)) + return CURLRESOLV_ERROR; + + /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a + non-zero value indicating that we need to wait for the response to the + resolve call */ + addr = Curl_getaddrinfo(conn, +#ifdef DEBUGBUILD + (data->set.str[STRING_DEVICE] + && !strcmp(data->set.str[STRING_DEVICE], + "LocalHost"))?"localhost": +#endif + hostname, port, &respwait); + + if(!addr) { + if(respwait) { + /* the response to our resolve call will come asynchronously at + a later time, good or bad */ + /* First, check that we haven't received the info by now */ + result = Curl_resolver_is_resolved(conn, &dns); + if(result) /* error detected */ + return CURLRESOLV_ERROR; + if(dns) + rc = CURLRESOLV_RESOLVED; /* pointer provided */ + else + rc = CURLRESOLV_PENDING; /* no info yet */ + } + } + else { + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + /* we got a response, store it in the cache */ + dns = Curl_cache_addr(data, addr, hostname, port); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) + /* returned failure, bail out nicely */ + Curl_freeaddrinfo(addr); + else + rc = CURLRESOLV_RESOLVED; + } + } + + *entry = dns; + + return rc; +} + +#ifdef USE_ALARM_TIMEOUT +/* + * This signal handler jumps back into the main libcurl code and continues + * execution. This effectively causes the remainder of the application to run + * within a signal handler which is nonportable and could lead to problems. + */ +static +RETSIGTYPE alarmfunc(int sig) +{ + /* this is for "-ansi -Wall -pedantic" to stop complaining! (rabe) */ + (void)sig; + siglongjmp(curl_jmpenv, 1); + return; +} +#endif /* USE_ALARM_TIMEOUT */ + +/* + * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a + * timeout. This function might return immediately if we're using asynch + * resolves. See the return codes. + * + * The cache entry we return will get its 'inuse' counter increased when this + * function is used. You MUST call Curl_resolv_unlock() later (when you're + * done using this struct) to decrease the counter again. + * + * If built with a synchronous resolver and use of signals is not + * disabled by the application, then a nonzero timeout will cause a + * timeout after the specified number of milliseconds. Otherwise, timeout + * is ignored. + * + * Return codes: + * + * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired + * CURLRESOLV_ERROR (-1) = error, no pointer + * CURLRESOLV_RESOLVED (0) = OK, pointer provided + * CURLRESOLV_PENDING (1) = waiting for response, no pointer + */ + +int Curl_resolv_timeout(struct connectdata *conn, + const char *hostname, + int port, + struct Curl_dns_entry **entry, + long timeoutms) +{ +#ifdef USE_ALARM_TIMEOUT +#ifdef HAVE_SIGACTION + struct sigaction keep_sigact; /* store the old struct here */ + volatile bool keep_copysig = FALSE; /* wether old sigact has been saved */ + struct sigaction sigact; +#else +#ifdef HAVE_SIGNAL + void (*keep_sigact)(int); /* store the old handler here */ +#endif /* HAVE_SIGNAL */ +#endif /* HAVE_SIGACTION */ + volatile long timeout; + volatile unsigned int prev_alarm = 0; + struct SessionHandle *data = conn->data; +#endif /* USE_ALARM_TIMEOUT */ + int rc; + + *entry = NULL; + + if(timeoutms < 0) + /* got an already expired timeout */ + return CURLRESOLV_TIMEDOUT; + +#ifdef USE_ALARM_TIMEOUT + if(data->set.no_signal) + /* Ignore the timeout when signals are disabled */ + timeout = 0; + else + timeout = timeoutms; + + if(!timeout) + /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */ + return Curl_resolv(conn, hostname, port, entry); + + if(timeout < 1000) + /* The alarm() function only provides integer second resolution, so if + we want to wait less than one second we must bail out already now. */ + return CURLRESOLV_TIMEDOUT; + + /************************************************************* + * Set signal handler to catch SIGALRM + * Store the old value to be able to set it back later! + *************************************************************/ +#ifdef HAVE_SIGACTION + sigaction(SIGALRM, NULL, &sigact); + keep_sigact = sigact; + keep_copysig = TRUE; /* yes, we have a copy */ + sigact.sa_handler = alarmfunc; +#ifdef SA_RESTART + /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */ + sigact.sa_flags &= ~SA_RESTART; +#endif + /* now set the new struct */ + sigaction(SIGALRM, &sigact, NULL); +#else /* HAVE_SIGACTION */ + /* no sigaction(), revert to the much lamer signal() */ +#ifdef HAVE_SIGNAL + keep_sigact = signal(SIGALRM, alarmfunc); +#endif +#endif /* HAVE_SIGACTION */ + + /* alarm() makes a signal get sent when the timeout fires off, and that + will abort system calls */ + prev_alarm = alarm(curlx_sltoui(timeout/1000L)); + + /* This allows us to time-out from the name resolver, as the timeout + will generate a signal and we will siglongjmp() from that here. + This technique has problems (see alarmfunc). + This should be the last thing we do before calling Curl_resolv(), + as otherwise we'd have to worry about variables that get modified + before we invoke Curl_resolv() (and thus use "volatile"). */ + if(sigsetjmp(curl_jmpenv, 1)) { + /* this is coming from a siglongjmp() after an alarm signal */ + failf(data, "name lookup timed out"); + rc = CURLRESOLV_ERROR; + goto clean_up; + } + +#else +#ifndef CURLRES_ASYNCH + if(timeoutms) + infof(conn->data, "timeout on name lookup is not supported\n"); +#else + (void)timeoutms; /* timeoutms not used with an async resolver */ +#endif +#endif /* USE_ALARM_TIMEOUT */ + + /* Perform the actual name resolution. This might be interrupted by an + * alarm if it takes too long. + */ + rc = Curl_resolv(conn, hostname, port, entry); + +#ifdef USE_ALARM_TIMEOUT +clean_up: + + if(!prev_alarm) + /* deactivate a possibly active alarm before uninstalling the handler */ + alarm(0); + +#ifdef HAVE_SIGACTION + if(keep_copysig) { + /* we got a struct as it looked before, now put that one back nice + and clean */ + sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */ + } +#else +#ifdef HAVE_SIGNAL + /* restore the previous SIGALRM handler */ + signal(SIGALRM, keep_sigact); +#endif +#endif /* HAVE_SIGACTION */ + + /* switch back the alarm() to either zero or to what it was before minus + the time we spent until now! */ + if(prev_alarm) { + /* there was an alarm() set before us, now put it back */ + unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created); + + /* the alarm period is counted in even number of seconds */ + unsigned long alarm_set = prev_alarm - elapsed_ms/1000; + + if(!alarm_set || + ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) { + /* if the alarm time-left reached zero or turned "negative" (counted + with unsigned values), we should fire off a SIGALRM here, but we + won't, and zero would be to switch it off so we never set it to + less than 1! */ + alarm(1); + rc = CURLRESOLV_TIMEDOUT; + failf(data, "Previous alarm fired off!"); + } + else + alarm((unsigned int)alarm_set); + } +#endif /* USE_ALARM_TIMEOUT */ + + return rc; +} + +/* + * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been + * made, the struct may be destroyed due to pruning. It is important that only + * one unlock is made for each Curl_resolv() call. + */ +void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns) +{ + DEBUGASSERT(dns && (dns->inuse>0)); + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + dns->inuse--; + /* only free if nobody is using AND it is not in hostcache (timestamp == + 0) */ + if(dns->inuse == 0 && dns->timestamp == 0) { + Curl_freeaddrinfo(dns->addr); + free(dns); + } + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); +} + +/* + * File-internal: free a cache dns entry. + */ +static void freednsentry(void *freethis) +{ + struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis; + + /* mark the entry as not in hostcache */ + p->timestamp = 0; + if(p->inuse == 0) { + Curl_freeaddrinfo(p->addr); + free(p); + } +} + +/* + * Curl_mk_dnscache() creates a new DNS cache and returns the handle for it. + */ +struct curl_hash *Curl_mk_dnscache(void) +{ + return Curl_hash_alloc(7, Curl_hash_str, Curl_str_key_compare, freednsentry); +} + +static int hostcache_inuse(void *data, void *hc) +{ + struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; + + if(c->inuse == 1) + Curl_resolv_unlock(data, c); + + return 1; /* free all entries */ +} + +void Curl_hostcache_clean(struct SessionHandle *data) +{ + /* Entries added to the hostcache with the CURLOPT_RESOLVE function are + * still present in the cache with the inuse counter set to 1. Detect them + * and cleanup! + */ + Curl_hash_clean_with_criterium(data->dns.hostcache, data, hostcache_inuse); +} + +void Curl_hostcache_destroy(struct SessionHandle *data) +{ + Curl_hostcache_clean(data); + Curl_hash_destroy(data->dns.hostcache); + data->dns.hostcachetype = HCACHE_NONE; + data->dns.hostcache = NULL; +} + +CURLcode Curl_loadhostpairs(struct SessionHandle *data) +{ + struct curl_slist *hostp; + char hostname[256]; + char address[256]; + int port; + + for(hostp = data->change.resolve; hostp; hostp = hostp->next ) { + if(!hostp->data) + continue; + if(hostp->data[0] == '-') { + /* TODO: mark an entry for removal */ + } + else if(3 == sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port, + address)) { + struct Curl_dns_entry *dns; + Curl_addrinfo *addr; + char *entry_id; + size_t entry_len; + + addr = Curl_str2addr(address, port); + if(!addr) { + infof(data, "Resolve %s found illegal!\n", hostp->data); + continue; + } + + /* Create an entry id, based upon the hostname and port */ + entry_id = create_hostcache_id(hostname, port); + /* If we can't create the entry id, fail */ + if(!entry_id) { + Curl_freeaddrinfo(addr); + return CURLE_OUT_OF_MEMORY; + } + + entry_len = strlen(entry_id); + + if(data->share) + Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); + + /* See if its already in our dns cache */ + dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1); + + /* free the allocated entry_id again */ + free(entry_id); + + if(!dns) + /* if not in the cache already, put this host in the cache */ + dns = Curl_cache_addr(data, addr, hostname, port); + else + /* this is a duplicate, free it again */ + Curl_freeaddrinfo(addr); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) { + Curl_freeaddrinfo(addr); + return CURLE_OUT_OF_MEMORY; + } + infof(data, "Added %s:%d:%s to DNS cache\n", + hostname, port, address); + } + } + data->change.resolve = NULL; /* dealt with now */ + + return CURLE_OK; +} diff --git a/lib/curl_hostip4.c b/lib/curl_hostip4.c new file mode 100644 index 000000000..5b64b4673 --- /dev/null +++ b/lib/curl_hostip4.c @@ -0,0 +1,310 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef HAVE_PROCESS_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_hostip.h" +#include "curl_hash.h" +#include "curl_share.h" +#include "curl_strerror.h" +#include "curl_url.h" +#include "curl_inet_pton.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/*********************************************************************** + * Only for plain-ipv4 builds + **********************************************************************/ +#ifdef CURLRES_IPV4 /* plain ipv4 code coming up */ +/* + * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've + * been set and returns TRUE if they are OK. + */ +bool Curl_ipvalid(struct connectdata *conn) +{ + if(conn->ip_version == CURL_IPRESOLVE_V6) + /* an ipv6 address was requested and we can't get/use one */ + return FALSE; + + return TRUE; /* OK, proceed */ +} + +#ifdef CURLRES_SYNCH + +/* + * Curl_getaddrinfo() - the ipv4 synchronous version. + * + * The original code to this function was from the Dancer source code, written + * by Bjorn Reese, it has since been patched and modified considerably. + * + * gethostbyname_r() is the thread-safe version of the gethostbyname() + * function. When we build for plain IPv4, we attempt to use this + * function. There are _three_ different gethostbyname_r() versions, and we + * detect which one this platform supports in the configure script and set up + * the HAVE_GETHOSTBYNAME_R_3, HAVE_GETHOSTBYNAME_R_5 or + * HAVE_GETHOSTBYNAME_R_6 defines accordingly. Note that HAVE_GETADDRBYNAME + * has the corresponding rules. This is primarily on *nix. Note that some unix + * flavours have thread-safe versions of the plain gethostbyname() etc. + * + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + const char *hostname, + int port, + int *waitp) +{ + Curl_addrinfo *ai = NULL; + +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)conn; +#endif + + *waitp = 0; /* synchronous response only */ + + ai = Curl_ipv4_resolve_r(hostname, port); + if(!ai) + infof(conn->data, "Curl_ipv4_resolve_r failed for %s\n", hostname); + + return ai; +} +#endif /* CURLRES_SYNCH */ +#endif /* CURLRES_IPV4 */ + +#if defined(CURLRES_IPV4) && !defined(CURLRES_ARES) + +/* + * Curl_ipv4_resolve_r() - ipv4 threadsafe resolver function. + * + * This is used for both synchronous and asynchronous resolver builds, + * implying that only threadsafe code and function calls may be used. + * + */ +Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, + int port) +{ +#if !defined(HAVE_GETADDRINFO_THREADSAFE) && defined(HAVE_GETHOSTBYNAME_R_3) + int res; +#endif + Curl_addrinfo *ai = NULL; + struct hostent *h = NULL; + struct in_addr in; + struct hostent *buf = NULL; + + if(Curl_inet_pton(AF_INET, hostname, &in) > 0) + /* This is a dotted IP address 123.123.123.123-style */ + return Curl_ip2addr(AF_INET, &in, hostname, port); + +#if defined(HAVE_GETADDRINFO_THREADSAFE) + else { + struct addrinfo hints; + char sbuf[NI_MAXSERV]; + char *sbufptr = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_INET; + hints.ai_socktype = SOCK_STREAM; + if(port) { + snprintf(sbuf, sizeof(sbuf), "%d", port); + sbufptr = sbuf; + } + + (void)Curl_getaddrinfo_ex(hostname, sbufptr, &hints, &ai); + +#elif defined(HAVE_GETHOSTBYNAME_R) + /* + * gethostbyname_r() is the preferred resolve function for many platforms. + * Since there are three different versions of it, the following code is + * somewhat #ifdef-ridden. + */ + else { + int h_errnop; + + buf = calloc(1, CURL_HOSTENT_SIZE); + if(!buf) + return NULL; /* major failure */ + /* + * The clearing of the buffer is a workaround for a gethostbyname_r bug in + * qnx nto and it is also _required_ for some of these functions on some + * platforms. + */ + +#if defined(HAVE_GETHOSTBYNAME_R_5) + /* Solaris, IRIX and more */ + h = gethostbyname_r(hostname, + (struct hostent *)buf, + (char *)buf + sizeof(struct hostent), + CURL_HOSTENT_SIZE - sizeof(struct hostent), + &h_errnop); + + /* If the buffer is too small, it returns NULL and sets errno to + * ERANGE. The errno is thread safe if this is compiled with + * -D_REENTRANT as then the 'errno' variable is a macro defined to get + * used properly for threads. + */ + + if(h) { + ; + } + else +#elif defined(HAVE_GETHOSTBYNAME_R_6) + /* Linux */ + + (void)gethostbyname_r(hostname, + (struct hostent *)buf, + (char *)buf + sizeof(struct hostent), + CURL_HOSTENT_SIZE - sizeof(struct hostent), + &h, /* DIFFERENCE */ + &h_errnop); + /* Redhat 8, using glibc 2.2.93 changed the behavior. Now all of a + * sudden this function returns EAGAIN if the given buffer size is too + * small. Previous versions are known to return ERANGE for the same + * problem. + * + * This wouldn't be such a big problem if older versions wouldn't + * sometimes return EAGAIN on a common failure case. Alas, we can't + * assume that EAGAIN *or* ERANGE means ERANGE for any given version of + * glibc. + * + * For now, we do that and thus we may call the function repeatedly and + * fail for older glibc versions that return EAGAIN, until we run out of + * buffer size (step_size grows beyond CURL_HOSTENT_SIZE). + * + * If anyone has a better fix, please tell us! + * + * ------------------------------------------------------------------- + * + * On October 23rd 2003, Dan C dug up more details on the mysteries of + * gethostbyname_r() in glibc: + * + * In glibc 2.2.5 the interface is different (this has also been + * discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I can't + * explain, is that tests performed on glibc 2.2.4-34 and 2.2.4-32 + * (shipped/upgraded by Redhat 7.2) don't show this behavior! + * + * In this "buggy" version, the return code is -1 on error and 'errno' + * is set to the ERANGE or EAGAIN code. Note that 'errno' is not a + * thread-safe variable. + */ + + if(!h) /* failure */ +#elif defined(HAVE_GETHOSTBYNAME_R_3) + /* AIX, Digital Unix/Tru64, HPUX 10, more? */ + + /* For AIX 4.3 or later, we don't use gethostbyname_r() at all, because of + * the plain fact that it does not return unique full buffers on each + * call, but instead several of the pointers in the hostent structs will + * point to the same actual data! This have the unfortunate down-side that + * our caching system breaks down horribly. Luckily for us though, AIX 4.3 + * and more recent versions have a "completely thread-safe"[*] libc where + * all the data is stored in thread-specific memory areas making calls to + * the plain old gethostbyname() work fine even for multi-threaded + * programs. + * + * This AIX 4.3 or later detection is all made in the configure script. + * + * Troels Walsted Hansen helped us work this out on March 3rd, 2003. + * + * [*] = much later we've found out that it isn't at all "completely + * thread-safe", but at least the gethostbyname() function is. + */ + + if(CURL_HOSTENT_SIZE >= + (sizeof(struct hostent)+sizeof(struct hostent_data))) { + + /* August 22nd, 2000: Albert Chin-A-Young brought an updated version + * that should work! September 20: Richard Prescott worked on the buffer + * size dilemma. + */ + + res = gethostbyname_r(hostname, + (struct hostent *)buf, + (struct hostent_data *)((char *)buf + + sizeof(struct hostent))); + h_errnop = SOCKERRNO; /* we don't deal with this, but set it anyway */ + } + else + res = -1; /* failure, too smallish buffer size */ + + if(!res) { /* success */ + + h = buf; /* result expected in h */ + + /* This is the worst kind of the different gethostbyname_r() interfaces. + * Since we don't know how big buffer this particular lookup required, + * we can't realloc down the huge alloc without doing closer analysis of + * the returned data. Thus, we always use CURL_HOSTENT_SIZE for every + * name lookup. Fixing this would require an extra malloc() and then + * calling Curl_addrinfo_copy() that subsequent realloc()s down the new + * memory area to the actually used amount. + */ + } + else +#endif /* HAVE_...BYNAME_R_5 || HAVE_...BYNAME_R_6 || HAVE_...BYNAME_R_3 */ + { + h = NULL; /* set return code to NULL */ + free(buf); + } +#else /* HAVE_GETADDRINFO_THREADSAFE || HAVE_GETHOSTBYNAME_R */ + /* + * Here is code for platforms that don't have a thread safe + * getaddrinfo() nor gethostbyname_r() function or for which + * gethostbyname() is the preferred one. + */ + else { + h = gethostbyname((void*)hostname); +#endif /* HAVE_GETADDRINFO_THREADSAFE || HAVE_GETHOSTBYNAME_R */ + } + + if(h) { + ai = Curl_he2ai(h, port); + + if(buf) /* used a *_r() function */ + free(buf); + } + + return ai; +} +#endif /* defined(CURLRES_IPV4) && !defined(CURLRES_ARES) */ diff --git a/lib/curl_hostip6.c b/lib/curl_hostip6.c new file mode 100644 index 000000000..cfd6081c1 --- /dev/null +++ b/lib/curl_hostip6.c @@ -0,0 +1,224 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef HAVE_PROCESS_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_hostip.h" +#include "curl_hash.h" +#include "curl_share.h" +#include "curl_strerror.h" +#include "curl_url.h" +#include "curl_inet_pton.h" +#include "curl_connect.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/*********************************************************************** + * Only for ipv6-enabled builds + **********************************************************************/ +#ifdef CURLRES_IPV6 + + +#if defined(CURLDEBUG) && defined(HAVE_GETNAMEINFO) +/* These are strictly for memory tracing and are using the same style as the + * family otherwise present in curl_memdebug.c. I put these ones here since + * they require a bunch of structs I didn't want to include there. + */ + +/* + * For CURLRES_ARS, this should be written using ares_gethostbyaddr() + * (ignoring the fact c-ares doesn't return 'serv'). + */ + +int curl_dogetnameinfo(GETNAMEINFO_QUAL_ARG1 GETNAMEINFO_TYPE_ARG1 sa, + GETNAMEINFO_TYPE_ARG2 salen, + char *host, GETNAMEINFO_TYPE_ARG46 hostlen, + char *serv, GETNAMEINFO_TYPE_ARG46 servlen, + GETNAMEINFO_TYPE_ARG7 flags, + int line, const char *source) +{ + int res = (getnameinfo)(sa, salen, + host, hostlen, + serv, servlen, + flags); + if(0 == res) + /* success */ + curl_memlog("GETNAME %s:%d getnameinfo()\n", + source, line); + else + curl_memlog("GETNAME %s:%d getnameinfo() failed = %d\n", + source, line, res); + return res; +} +#endif /* defined(CURLDEBUG) && defined(HAVE_GETNAMEINFO) */ + +/* + * Curl_ipv6works() returns TRUE if ipv6 seems to work. + */ +bool Curl_ipv6works(void) +{ + /* the nature of most system is that IPv6 status doesn't come and go + during a program's lifetime so we only probe the first time and then we + have the info kept for fast re-use */ + static int ipv6_works = -1; + if(-1 == ipv6_works) { + /* probe to see if we have a working IPv6 stack */ + curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0); + if(s == CURL_SOCKET_BAD) + /* an ipv6 address was requested but we can't get/use one */ + ipv6_works = 0; + else { + ipv6_works = 1; + Curl_closesocket(NULL, s); + } + } + return (ipv6_works>0)?TRUE:FALSE; +} + +/* + * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've + * been set and returns TRUE if they are OK. + */ +bool Curl_ipvalid(struct connectdata *conn) +{ + if(conn->ip_version == CURL_IPRESOLVE_V6) + return Curl_ipv6works(); + return TRUE; +} + +#if defined(CURLRES_SYNCH) + +#ifdef DEBUG_ADDRINFO +static void dump_addrinfo(struct connectdata *conn, const Curl_addrinfo *ai) +{ + printf("dump_addrinfo:\n"); + for(; ai; ai = ai->ai_next) { + char buf[INET6_ADDRSTRLEN]; + + printf(" fam %2d, CNAME %s, ", + ai->ai_family, ai->ai_canonname ? ai->ai_canonname : ""); + if(Curl_printable_address(ai, buf, sizeof(buf))) + printf("%s\n", buf); + else + printf("failed; %s\n", Curl_strerror(conn, SOCKERRNO)); + } +} +#else +#define dump_addrinfo(x,y) Curl_nop_stmt +#endif + +/* + * Curl_getaddrinfo() when built ipv6-enabled (non-threading and + * non-ares version). + * + * Returns name information about the given hostname and port number. If + * successful, the 'addrinfo' is returned and the forth argument will point to + * memory we need to free after use. That memory *MUST* be freed with + * Curl_freeaddrinfo(), nothing else. + */ +Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, + const char *hostname, + int port, + int *waitp) +{ + struct addrinfo hints; + Curl_addrinfo *res; + int error; + char sbuf[NI_MAXSERV]; + char *sbufptr = NULL; + char addrbuf[128]; + int pf; + struct SessionHandle *data = conn->data; + + *waitp = 0; /* synchronous response only */ + + /* + * Check if a limited name resolve has been requested. + */ + switch(conn->ip_version) { + case CURL_IPRESOLVE_V4: + pf = PF_INET; + break; + case CURL_IPRESOLVE_V6: + pf = PF_INET6; + break; + default: + pf = PF_UNSPEC; + break; + } + + if((pf != PF_INET) && !Curl_ipv6works()) + /* the stack seems to be a non-ipv6 one */ + pf = PF_INET; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = pf; + hints.ai_socktype = conn->socktype; + + if((1 == Curl_inet_pton(AF_INET, hostname, addrbuf)) || + (1 == Curl_inet_pton(AF_INET6, hostname, addrbuf))) { + /* the given address is numerical only, prevent a reverse lookup */ + hints.ai_flags = AI_NUMERICHOST; + } + + if(port) { + snprintf(sbuf, sizeof(sbuf), "%d", port); + sbufptr=sbuf; + } + error = Curl_getaddrinfo_ex(hostname, sbufptr, &hints, &res); + if(error) { + infof(data, "getaddrinfo(3) failed for %s:%d\n", hostname, port); + return NULL; + } + + dump_addrinfo(conn, res); + + return res; +} +#endif /* CURLRES_SYNCH */ +#endif /* CURLRES_IPV6 */ + diff --git a/lib/curl_hostsyn.c b/lib/curl_hostsyn.c new file mode 100644 index 000000000..9a26f8d44 --- /dev/null +++ b/lib/curl_hostsyn.c @@ -0,0 +1,75 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#ifdef HAVE_PROCESS_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_hostip.h" +#include "curl_hash.h" +#include "curl_share.h" +#include "curl_strerror.h" +#include "curl_url.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/*********************************************************************** + * Only for builds using synchronous name resolves + **********************************************************************/ +#ifdef CURLRES_SYNCH + +/* + * Function provided by the resolver backend to set DNS servers to use. + */ +CURLcode Curl_set_dns_servers(struct SessionHandle *data, + char *servers) +{ + (void)data; + (void)servers; + return CURLE_NOT_BUILT_IN; + +} + +#endif /* truly sync */ diff --git a/lib/curl_http.c b/lib/curl_http.c new file mode 100644 index 000000000..420361c76 --- /dev/null +++ b/lib/curl_http.c @@ -0,0 +1,3506 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_HTTP + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include "curl_urldata.h" +#include +#include "curl_transfer.h" +#include "curl_sendf.h" +#include "curl_formdata.h" +#include "curl_progress.h" +#include "curl_base64.h" +#include "curl_cookie.h" +#include "curl_strequal.h" +#include "curl_sslgen.h" +#include "curl_http_digest.h" +#include "curl_ntlm.h" +#include "curl_ntlm_wb.h" +#include "curl_http_negotiate.h" +#include "curl_url.h" +#include "curl_share.h" +#include "curl_hostip.h" +#include "curl_http.h" +#include "curl_memory.h" +#include "curl_select.h" +#include "curl_parsedate.h" /* for the week day and month names */ +#include "curl_strtoofft.h" +#include "curl_multiif.h" +#include "curl_rawstr.h" +#include "curl_content_encoding.h" +#include "curl_http_proxy.h" +#include "curl_warnless.h" +#include "curl_non_ascii.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + * Forward declarations. + */ + +static int http_getsock_do(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); +static int http_should_fail(struct connectdata *conn); + +#ifdef USE_SSL +static CURLcode https_connecting(struct connectdata *conn, bool *done); +static int https_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); +#else +#define https_connecting(x,y) CURLE_COULDNT_CONNECT +#endif + +/* + * HTTP handler interface. + */ +const struct Curl_handler Curl_handler_http = { + "HTTP", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_HTTP, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +#ifdef USE_SSL +/* + * HTTPS handler interface. + */ +const struct Curl_handler Curl_handler_https = { + "HTTPS", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + Curl_http_connect, /* connect_it */ + https_connecting, /* connecting */ + ZERO_NULL, /* doing */ + https_getsock, /* proto_getsock */ + http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_HTTPS, /* defport */ + CURLPROTO_HTTP | CURLPROTO_HTTPS, /* protocol */ + PROTOPT_SSL /* flags */ +}; +#endif + + +/* + * checkheaders() checks the linked list of custom HTTP headers for a + * particular header (prefix). + * + * Returns a pointer to the first matching header or NULL if none matched. + */ +char *Curl_checkheaders(struct SessionHandle *data, const char *thisheader) +{ + struct curl_slist *head; + size_t thislen = strlen(thisheader); + + for(head = data->set.headers; head; head=head->next) { + if(Curl_raw_nequal(head->data, thisheader, thislen)) + return head->data; + } + return NULL; +} + +/* + * Strip off leading and trailing whitespace from the value in the + * given HTTP header line and return a strdupped copy. Returns NULL in + * case of allocation failure. Returns an empty string if the header value + * consists entirely of whitespace. + */ +static char *copy_header_value(const char *h) +{ + const char *start; + const char *end; + char *value; + size_t len; + + DEBUGASSERT(h); + + /* Find the end of the header name */ + while(*h && (*h != ':')) + ++h; + + if(*h) + /* Skip over colon */ + ++h; + + /* Find the first non-space letter */ + start = h; + while(*start && ISSPACE(*start)) + start++; + + /* data is in the host encoding so + use '\r' and '\n' instead of 0x0d and 0x0a */ + end = strchr(start, '\r'); + if(!end) + end = strchr(start, '\n'); + if(!end) + end = strchr(start, '\0'); + if(!end) + return NULL; + + /* skip all trailing space letters */ + while((end > start) && ISSPACE(*end)) + end--; + + /* get length of the type */ + len = end-start+1; + + value = malloc(len + 1); + if(!value) + return NULL; + + memcpy(value, start, len); + value[len] = 0; /* zero terminate */ + + return value; +} + +/* + * http_output_basic() sets up an Authorization: header (or the proxy version) + * for HTTP Basic authentication. + * + * Returns CURLcode. + */ +static CURLcode http_output_basic(struct connectdata *conn, bool proxy) +{ + size_t size = 0; + char *authorization = NULL; + struct SessionHandle *data = conn->data; + char **userp; + const char *user; + const char *pwd; + CURLcode error; + + if(proxy) { + userp = &conn->allocptr.proxyuserpwd; + user = conn->proxyuser; + pwd = conn->proxypasswd; + } + else { + userp = &conn->allocptr.userpwd; + user = conn->user; + pwd = conn->passwd; + } + + snprintf(data->state.buffer, sizeof(data->state.buffer), "%s:%s", user, pwd); + + error = Curl_base64_encode(data, + data->state.buffer, strlen(data->state.buffer), + &authorization, &size); + if(error) + return error; + + if(!authorization) + return CURLE_REMOTE_ACCESS_DENIED; + + Curl_safefree(*userp); + *userp = aprintf("%sAuthorization: Basic %s\r\n", + proxy?"Proxy-":"", + authorization); + free(authorization); + if(!*userp) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +/* pickoneauth() selects the most favourable authentication method from the + * ones available and the ones we want. + * + * return TRUE if one was picked + */ +static bool pickoneauth(struct auth *pick) +{ + bool picked; + /* only deal with authentication we want */ + unsigned long avail = pick->avail & pick->want; + picked = TRUE; + + /* The order of these checks is highly relevant, as this will be the order + of preference in case of the existence of multiple accepted types. */ + if(avail & CURLAUTH_GSSNEGOTIATE) + pick->picked = CURLAUTH_GSSNEGOTIATE; + else if(avail & CURLAUTH_DIGEST) + pick->picked = CURLAUTH_DIGEST; + else if(avail & CURLAUTH_NTLM) + pick->picked = CURLAUTH_NTLM; + else if(avail & CURLAUTH_NTLM_WB) + pick->picked = CURLAUTH_NTLM_WB; + else if(avail & CURLAUTH_BASIC) + pick->picked = CURLAUTH_BASIC; + else { + pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */ + picked = FALSE; + } + pick->avail = CURLAUTH_NONE; /* clear it here */ + + return picked; +} + +/* + * Curl_http_perhapsrewind() + * + * If we are doing POST or PUT { + * If we have more data to send { + * If we are doing NTLM { + * Keep sending since we must not disconnect + * } + * else { + * If there is more than just a little data left to send, close + * the current connection by force. + * } + * } + * If we have sent any data { + * If we don't have track of all the data { + * call app to tell it to rewind + * } + * else { + * rewind internally so that the operation can restart fine + * } + * } + * } + */ +static CURLcode http_perhapsrewind(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct HTTP *http = data->state.proto.http; + curl_off_t bytessent; + curl_off_t expectsend = -1; /* default is unknown */ + + if(!http) + /* If this is still NULL, we have not reach very far and we can safely + skip this rewinding stuff */ + return CURLE_OK; + + switch(data->set.httpreq) { + case HTTPREQ_GET: + case HTTPREQ_HEAD: + return CURLE_OK; + default: + break; + } + + bytessent = http->writebytecount; + + if(conn->bits.authneg) + /* This is a state where we are known to be negotiating and we don't send + any data then. */ + expectsend = 0; + else { + /* figure out how much data we are expected to send */ + switch(data->set.httpreq) { + case HTTPREQ_POST: + if(data->set.postfieldsize != -1) + expectsend = data->set.postfieldsize; + else if(data->set.postfields) + expectsend = (curl_off_t)strlen(data->set.postfields); + break; + case HTTPREQ_PUT: + if(data->set.infilesize != -1) + expectsend = data->set.infilesize; + break; + case HTTPREQ_POST_FORM: + expectsend = http->postsize; + break; + default: + break; + } + } + + conn->bits.rewindaftersend = FALSE; /* default */ + + if((expectsend == -1) || (expectsend > bytessent)) { + /* There is still data left to send */ + if((data->state.authproxy.picked == CURLAUTH_NTLM) || + (data->state.authhost.picked == CURLAUTH_NTLM) || + (data->state.authproxy.picked == CURLAUTH_NTLM_WB) || + (data->state.authhost.picked == CURLAUTH_NTLM_WB)) { + if(((expectsend - bytessent) < 2000) || + (conn->ntlm.state != NTLMSTATE_NONE) || + (conn->proxyntlm.state != NTLMSTATE_NONE)) { + /* The NTLM-negotiation has started *OR* there is just a little (<2K) + data left to send, keep on sending. */ + + /* rewind data when completely done sending! */ + if(!conn->bits.authneg) { + conn->bits.rewindaftersend = TRUE; + infof(data, "Rewind stream after send\n"); + } + + return CURLE_OK; + } + if(conn->bits.close) + /* this is already marked to get closed */ + return CURLE_OK; + + infof(data, "NTLM send, close instead of sending %" FORMAT_OFF_T + " bytes\n", (curl_off_t)(expectsend - bytessent)); + } + + /* This is not NTLM or many bytes left to send: close + */ + conn->bits.close = TRUE; + data->req.size = 0; /* don't download any more than 0 bytes */ + + /* There still is data left to send, but this connection is marked for + closure so we can safely do the rewind right now */ + } + + if(bytessent) + /* we rewind now at once since if we already sent something */ + return Curl_readrewind(conn); + + return CURLE_OK; +} + +/* + * Curl_http_auth_act() gets called when all HTTP headers have been received + * and it checks what authentication methods that are available and decides + * which one (if any) to use. It will set 'newurl' if an auth method was + * picked. + */ + +CURLcode Curl_http_auth_act(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + bool pickhost = FALSE; + bool pickproxy = FALSE; + CURLcode code = CURLE_OK; + + if(100 <= data->req.httpcode && 199 >= data->req.httpcode) + /* this is a transient response code, ignore */ + return CURLE_OK; + + if(data->state.authproblem) + return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK; + + if(conn->bits.user_passwd && + ((data->req.httpcode == 401) || + (conn->bits.authneg && data->req.httpcode < 300))) { + pickhost = pickoneauth(&data->state.authhost); + if(!pickhost) + data->state.authproblem = TRUE; + } + if(conn->bits.proxy_user_passwd && + ((data->req.httpcode == 407) || + (conn->bits.authneg && data->req.httpcode < 300))) { + pickproxy = pickoneauth(&data->state.authproxy); + if(!pickproxy) + data->state.authproblem = TRUE; + } + + if(pickhost || pickproxy) { + /* In case this is GSS auth, the newurl field is already allocated so + we must make sure to free it before allocating a new one. As figured + out in bug #2284386 */ + Curl_safefree(data->req.newurl); + data->req.newurl = strdup(data->change.url); /* clone URL */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + + if((data->set.httpreq != HTTPREQ_GET) && + (data->set.httpreq != HTTPREQ_HEAD) && + !conn->bits.rewindaftersend) { + code = http_perhapsrewind(conn); + if(code) + return code; + } + } + + else if((data->req.httpcode < 300) && + (!data->state.authhost.done) && + conn->bits.authneg) { + /* no (known) authentication available, + authentication is not "done" yet and + no authentication seems to be required and + we didn't try HEAD or GET */ + if((data->set.httpreq != HTTPREQ_GET) && + (data->set.httpreq != HTTPREQ_HEAD)) { + data->req.newurl = strdup(data->change.url); /* clone URL */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + data->state.authhost.done = TRUE; + } + } + if(http_should_fail(conn)) { + failf (data, "The requested URL returned error: %d", + data->req.httpcode); + code = CURLE_HTTP_RETURNED_ERROR; + } + + return code; +} + + +/* + * Output the correct authentication header depending on the auth type + * and whether or not it is to a proxy. + */ +static CURLcode +output_auth_headers(struct connectdata *conn, + struct auth *authstatus, + const char *request, + const char *path, + bool proxy) +{ + struct SessionHandle *data = conn->data; + const char *auth=NULL; + CURLcode result = CURLE_OK; +#ifdef USE_HTTP_NEGOTIATE + struct negotiatedata *negdata = proxy? + &data->state.proxyneg:&data->state.negotiate; +#endif + +#ifdef CURL_DISABLE_CRYPTO_AUTH + (void)request; + (void)path; +#endif + +#ifdef USE_HTTP_NEGOTIATE + negdata->state = GSS_AUTHNONE; + if((authstatus->picked == CURLAUTH_GSSNEGOTIATE) && + negdata->context && !GSS_ERROR(negdata->status)) { + auth="GSS-Negotiate"; + result = Curl_output_negotiate(conn, proxy); + if(result) + return result; + authstatus->done = TRUE; + negdata->state = GSS_AUTHSENT; + } + else +#endif +#ifdef USE_NTLM + if(authstatus->picked == CURLAUTH_NTLM) { + auth="NTLM"; + result = Curl_output_ntlm(conn, proxy); + if(result) + return result; + } + else +#endif +#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) + if(authstatus->picked == CURLAUTH_NTLM_WB) { + auth="NTLM_WB"; + result = Curl_output_ntlm_wb(conn, proxy); + if(result) + return result; + } + else +#endif +#ifndef CURL_DISABLE_CRYPTO_AUTH + if(authstatus->picked == CURLAUTH_DIGEST) { + auth="Digest"; + result = Curl_output_digest(conn, + proxy, + (const unsigned char *)request, + (const unsigned char *)path); + if(result) + return result; + } + else +#endif + if(authstatus->picked == CURLAUTH_BASIC) { + /* Basic */ + if((proxy && conn->bits.proxy_user_passwd && + !Curl_checkheaders(data, "Proxy-authorization:")) || + (!proxy && conn->bits.user_passwd && + !Curl_checkheaders(data, "Authorization:"))) { + auth="Basic"; + result = http_output_basic(conn, proxy); + if(result) + return result; + } + /* NOTE: this function should set 'done' TRUE, as the other auth + functions work that way */ + authstatus->done = TRUE; + } + + if(auth) { + infof(data, "%s auth using %s with user '%s'\n", + proxy?"Proxy":"Server", auth, + proxy?(conn->proxyuser?conn->proxyuser:""): + (conn->user?conn->user:"")); + authstatus->multi = (!authstatus->done) ? TRUE : FALSE; + } + else + authstatus->multi = FALSE; + + return CURLE_OK; +} + +/** + * Curl_http_output_auth() setups the authentication headers for the + * host/proxy and the correct authentication + * method. conn->data->state.authdone is set to TRUE when authentication is + * done. + * + * @param conn all information about the current connection + * @param request pointer to the request keyword + * @param path pointer to the requested path + * @param proxytunnel boolean if this is the request setting up a "proxy + * tunnel" + * + * @returns CURLcode + */ +CURLcode +Curl_http_output_auth(struct connectdata *conn, + const char *request, + const char *path, + bool proxytunnel) /* TRUE if this is the request setting + up the proxy tunnel */ +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct auth *authhost; + struct auth *authproxy; + + DEBUGASSERT(data); + + authhost = &data->state.authhost; + authproxy = &data->state.authproxy; + + if((conn->bits.httpproxy && conn->bits.proxy_user_passwd) || + conn->bits.user_passwd) + /* continue please */ ; + else { + authhost->done = TRUE; + authproxy->done = TRUE; + return CURLE_OK; /* no authentication with no user or password */ + } + + if(authhost->want && !authhost->picked) + /* The app has selected one or more methods, but none has been picked + so far by a server round-trip. Then we set the picked one to the + want one, and if this is one single bit it'll be used instantly. */ + authhost->picked = authhost->want; + + if(authproxy->want && !authproxy->picked) + /* The app has selected one or more methods, but none has been picked so + far by a proxy round-trip. Then we set the picked one to the want one, + and if this is one single bit it'll be used instantly. */ + authproxy->picked = authproxy->want; + +#ifndef CURL_DISABLE_PROXY + /* Send proxy authentication header if needed */ + if(conn->bits.httpproxy && + (conn->bits.tunnel_proxy == proxytunnel)) { + result = output_auth_headers(conn, authproxy, request, path, TRUE); + if(result) + return result; + } + else +#else + (void)proxytunnel; +#endif /* CURL_DISABLE_PROXY */ + /* we have no proxy so let's pretend we're done authenticating + with it */ + authproxy->done = TRUE; + + /* To prevent the user+password to get sent to other than the original + host due to a location-follow, we do some weirdo checks here */ + if(!data->state.this_is_a_follow || + conn->bits.netrc || + !data->state.first_host || + data->set.http_disable_hostname_check_before_authentication || + Curl_raw_equal(data->state.first_host, conn->host.name)) { + result = output_auth_headers(conn, authhost, request, path, FALSE); + } + else + authhost->done = TRUE; + + return result; +} + + +/* + * Curl_http_input_auth() deals with Proxy-Authenticate: and WWW-Authenticate: + * headers. They are dealt with both in the curl_transfer.c main loop and in + * the proxy CONNECT loop. + */ + +CURLcode Curl_http_input_auth(struct connectdata *conn, + int httpcode, + const char *header) /* the first non-space */ +{ + /* + * This resource requires authentication + */ + struct SessionHandle *data = conn->data; + + unsigned long *availp; + const char *start; + struct auth *authp; + + if(httpcode == 407) { + start = header+strlen("Proxy-authenticate:"); + availp = &data->info.proxyauthavail; + authp = &data->state.authproxy; + } + else { + start = header+strlen("WWW-Authenticate:"); + availp = &data->info.httpauthavail; + authp = &data->state.authhost; + } + + /* pass all white spaces */ + while(*start && ISSPACE(*start)) + start++; + + /* + * Here we check if we want the specific single authentication (using ==) and + * if we do, we initiate usage of it. + * + * If the provided authentication is wanted as one out of several accepted + * types (using &), we OR this authentication type to the authavail + * variable. + * + * Note: + * + * ->picked is first set to the 'want' value (one or more bits) before the + * request is sent, and then it is again set _after_ all response 401/407 + * headers have been received but then only to a single preferred method + * (bit). + * + */ + + while(*start) { +#ifdef USE_HTTP_NEGOTIATE + if(checkprefix("GSS-Negotiate", start) || + checkprefix("Negotiate", start)) { + int neg; + *availp |= CURLAUTH_GSSNEGOTIATE; + authp->avail |= CURLAUTH_GSSNEGOTIATE; + + if(authp->picked == CURLAUTH_GSSNEGOTIATE) { + if(data->state.negotiate.state == GSS_AUTHSENT) { + /* if we sent GSS authentication in the outgoing request and we get + this back, we're in trouble */ + infof(data, "Authentication problem. Ignoring this.\n"); + data->state.authproblem = TRUE; + } + else { + neg = Curl_input_negotiate(conn, (bool)(httpcode == 407), start); + if(neg == 0) { + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->change.url); + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + data->state.authproblem = FALSE; + /* we received GSS auth info and we dealt with it fine */ + data->state.negotiate.state = GSS_AUTHRECV; + } + else + data->state.authproblem = TRUE; + } + } + } + else +#endif +#ifdef USE_NTLM + /* NTLM support requires the SSL crypto libs */ + if(checkprefix("NTLM", start)) { + *availp |= CURLAUTH_NTLM; + authp->avail |= CURLAUTH_NTLM; + if(authp->picked == CURLAUTH_NTLM || + authp->picked == CURLAUTH_NTLM_WB) { + /* NTLM authentication is picked and activated */ + CURLcode ntlm = + Curl_input_ntlm(conn, (httpcode == 407)?TRUE:FALSE, start); + if(CURLE_OK == ntlm) { + data->state.authproblem = FALSE; +#ifdef NTLM_WB_ENABLED + if(authp->picked == CURLAUTH_NTLM_WB) { + *availp &= ~CURLAUTH_NTLM; + authp->avail &= ~CURLAUTH_NTLM; + *availp |= CURLAUTH_NTLM_WB; + authp->avail |= CURLAUTH_NTLM_WB; + + /* Get the challenge-message which will be passed to + * ntlm_auth for generating the type 3 message later */ + while(*start && ISSPACE(*start)) + start++; + if(checkprefix("NTLM", start)) { + start += strlen("NTLM"); + while(*start && ISSPACE(*start)) + start++; + if(*start) + if((conn->challenge_header = strdup(start)) == NULL) + return CURLE_OUT_OF_MEMORY; + } + } +#endif + } + else { + infof(data, "Authentication problem. Ignoring this.\n"); + data->state.authproblem = TRUE; + } + } + } + else +#endif +#ifndef CURL_DISABLE_CRYPTO_AUTH + if(checkprefix("Digest", start)) { + if((authp->avail & CURLAUTH_DIGEST) != 0) { + infof(data, "Ignoring duplicate digest auth header.\n"); + } + else { + CURLdigest dig; + *availp |= CURLAUTH_DIGEST; + authp->avail |= CURLAUTH_DIGEST; + + /* We call this function on input Digest headers even if Digest + * authentication isn't activated yet, as we need to store the + * incoming data from this header in case we are gonna use + * Digest. */ + dig = Curl_input_digest(conn, (httpcode == 407)?TRUE:FALSE, start); + + if(CURLDIGEST_FINE != dig) { + infof(data, "Authentication problem. Ignoring this.\n"); + data->state.authproblem = TRUE; + } + } + } + else +#endif + if(checkprefix("Basic", start)) { + *availp |= CURLAUTH_BASIC; + authp->avail |= CURLAUTH_BASIC; + if(authp->picked == CURLAUTH_BASIC) { + /* We asked for Basic authentication but got a 40X back + anyway, which basically means our name+password isn't + valid. */ + authp->avail = CURLAUTH_NONE; + infof(data, "Authentication problem. Ignoring this.\n"); + data->state.authproblem = TRUE; + } + } + + /* there may be multiple methods on one line, so keep reading */ + while(*start && *start != ',') /* read up to the next comma */ + start++; + if(*start == ',') /* if we're on a comma, skip it */ + start++; + while(*start && ISSPACE(*start)) + start++; + } + return CURLE_OK; +} + +/** + * http_should_fail() determines whether an HTTP response has gotten us + * into an error state or not. + * + * @param conn all information about the current connection + * + * @retval 0 communications should continue + * + * @retval 1 communications should not continue + */ +static int http_should_fail(struct connectdata *conn) +{ + struct SessionHandle *data; + int httpcode; + + DEBUGASSERT(conn); + data = conn->data; + DEBUGASSERT(data); + + httpcode = data->req.httpcode; + + /* + ** If we haven't been asked to fail on error, + ** don't fail. + */ + if(!data->set.http_fail_on_error) + return 0; + + /* + ** Any code < 400 is never terminal. + */ + if(httpcode < 400) + return 0; + + if(data->state.resume_from && + (data->set.httpreq==HTTPREQ_GET) && + (httpcode == 416)) { + /* "Requested Range Not Satisfiable", just proceed and + pretend this is no error */ + return 0; + } + + /* + ** Any code >= 400 that's not 401 or 407 is always + ** a terminal error + */ + if((httpcode != 401) && + (httpcode != 407)) + return 1; + + /* + ** All we have left to deal with is 401 and 407 + */ + DEBUGASSERT((httpcode == 401) || (httpcode == 407)); + + /* + ** Examine the current authentication state to see if this + ** is an error. The idea is for this function to get + ** called after processing all the headers in a response + ** message. So, if we've been to asked to authenticate a + ** particular stage, and we've done it, we're OK. But, if + ** we're already completely authenticated, it's not OK to + ** get another 401 or 407. + ** + ** It is possible for authentication to go stale such that + ** the client needs to reauthenticate. Once that info is + ** available, use it here. + */ + + /* + ** Either we're not authenticating, or we're supposed to + ** be authenticating something else. This is an error. + */ + if((httpcode == 401) && !conn->bits.user_passwd) + return TRUE; + if((httpcode == 407) && !conn->bits.proxy_user_passwd) + return TRUE; + + return data->state.authproblem; +} + +/* + * readmoredata() is a "fread() emulation" to provide POST and/or request + * data. It is used when a huge POST is to be made and the entire chunk wasn't + * sent in the first send(). This function will then be called from the + * curl_transfer.c loop when more data is to be sent to the peer. + * + * Returns the amount of bytes it filled the buffer with. + */ +static size_t readmoredata(char *buffer, + size_t size, + size_t nitems, + void *userp) +{ + struct connectdata *conn = (struct connectdata *)userp; + struct HTTP *http = conn->data->state.proto.http; + size_t fullsize = size * nitems; + + if(0 == http->postsize) + /* nothing to return */ + return 0; + + /* make sure that a HTTP request is never sent away chunked! */ + conn->data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; + + if(http->postsize <= (curl_off_t)fullsize) { + memcpy(buffer, http->postdata, (size_t)http->postsize); + fullsize = (size_t)http->postsize; + + if(http->backup.postsize) { + /* move backup data into focus and continue on that */ + http->postdata = http->backup.postdata; + http->postsize = http->backup.postsize; + conn->fread_func = http->backup.fread_func; + conn->fread_in = http->backup.fread_in; + + http->sending++; /* move one step up */ + + http->backup.postsize=0; + } + else + http->postsize = 0; + + return fullsize; + } + + memcpy(buffer, http->postdata, fullsize); + http->postdata += fullsize; + http->postsize -= fullsize; + + return fullsize; +} + +/* ------------------------------------------------------------------------- */ +/* add_buffer functions */ + +/* + * Curl_add_buffer_init() sets up and returns a fine buffer struct + */ +Curl_send_buffer *Curl_add_buffer_init(void) +{ + return calloc(1, sizeof(Curl_send_buffer)); +} + +/* + * Curl_add_buffer_send() sends a header buffer and frees all associated + * memory. Body data may be appended to the header data if desired. + * + * Returns CURLcode + */ +CURLcode Curl_add_buffer_send(Curl_send_buffer *in, + struct connectdata *conn, + + /* add the number of sent bytes to this + counter */ + long *bytes_written, + + /* how much of the buffer contains body data */ + size_t included_body_bytes, + int socketindex) + +{ + ssize_t amount; + CURLcode res; + char *ptr; + size_t size; + struct HTTP *http = conn->data->state.proto.http; + size_t sendsize; + curl_socket_t sockfd; + size_t headersize; + + DEBUGASSERT(socketindex <= SECONDARYSOCKET); + + sockfd = conn->sock[socketindex]; + + /* The looping below is required since we use non-blocking sockets, but due + to the circumstances we will just loop and try again and again etc */ + + ptr = in->buffer; + size = in->size_used; + + headersize = size - included_body_bytes; /* the initial part that isn't body + is header */ + + DEBUGASSERT(size > included_body_bytes); + + res = Curl_convert_to_network(conn->data, ptr, headersize); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(res) { + /* conversion failed, free memory and return to the caller */ + if(in->buffer) + free(in->buffer); + free(in); + return res; + } + + if(conn->handler->flags & PROTOPT_SSL) { + /* We never send more than CURL_MAX_WRITE_SIZE bytes in one single chunk + when we speak HTTPS, as if only a fraction of it is sent now, this data + needs to fit into the normal read-callback buffer later on and that + buffer is using this size. + */ + + sendsize= (size > CURL_MAX_WRITE_SIZE)?CURL_MAX_WRITE_SIZE:size; + + /* OpenSSL is very picky and we must send the SAME buffer pointer to the + library when we attempt to re-send this buffer. Sending the same data + is not enough, we must use the exact same address. For this reason, we + must copy the data to the uploadbuffer first, since that is the buffer + we will be using if this send is retried later. + */ + memcpy(conn->data->state.uploadbuffer, ptr, sendsize); + ptr = conn->data->state.uploadbuffer; + } + else + sendsize = size; + + res = Curl_write(conn, sockfd, ptr, sendsize, &amount); + + if(CURLE_OK == res) { + /* + * Note that we may not send the entire chunk at once, and we have a set + * number of data bytes at the end of the big buffer (out of which we may + * only send away a part). + */ + /* how much of the header that was sent */ + size_t headlen = (size_t)amount>headersize?headersize:(size_t)amount; + size_t bodylen = amount - headlen; + + if(conn->data->set.verbose) { + /* this data _may_ contain binary stuff */ + Curl_debug(conn->data, CURLINFO_HEADER_OUT, ptr, headlen, conn); + if(bodylen) { + /* there was body data sent beyond the initial header part, pass that + on to the debug callback too */ + Curl_debug(conn->data, CURLINFO_DATA_OUT, + ptr+headlen, bodylen, conn); + } + } + if(bodylen) + /* since we sent a piece of the body here, up the byte counter for it + accordingly */ + http->writebytecount += bodylen; + + /* 'amount' can never be a very large value here so typecasting it so a + signed 31 bit value should not cause problems even if ssize_t is + 64bit */ + *bytes_written += (long)amount; + + if(http) { + if((size_t)amount != size) { + /* The whole request could not be sent in one system call. We must + queue it up and send it later when we get the chance. We must not + loop here and wait until it might work again. */ + + size -= amount; + + ptr = in->buffer + amount; + + /* backup the currently set pointers */ + http->backup.fread_func = conn->fread_func; + http->backup.fread_in = conn->fread_in; + http->backup.postdata = http->postdata; + http->backup.postsize = http->postsize; + + /* set the new pointers for the request-sending */ + conn->fread_func = (curl_read_callback)readmoredata; + conn->fread_in = (void *)conn; + http->postdata = ptr; + http->postsize = (curl_off_t)size; + + http->send_buffer = in; + http->sending = HTTPSEND_REQUEST; + + return CURLE_OK; + } + http->sending = HTTPSEND_BODY; + /* the full buffer was sent, clean up and return */ + } + else { + if((size_t)amount != size) + /* We have no continue-send mechanism now, fail. This can only happen + when this function is used from the CONNECT sending function. We + currently (stupidly) assume that the whole request is always sent + away in the first single chunk. + + This needs FIXing. + */ + return CURLE_SEND_ERROR; + else + conn->writechannel_inuse = FALSE; + } + } + if(in->buffer) + free(in->buffer); + free(in); + + return res; +} + + +/* + * add_bufferf() add the formatted input to the buffer. + */ +CURLcode Curl_add_bufferf(Curl_send_buffer *in, const char *fmt, ...) +{ + char *s; + va_list ap; + va_start(ap, fmt); + s = vaprintf(fmt, ap); /* this allocs a new string to append */ + va_end(ap); + + if(s) { + CURLcode result = Curl_add_buffer(in, s, strlen(s)); + free(s); + return result; + } + /* If we failed, we cleanup the whole buffer and return error */ + if(in->buffer) + free(in->buffer); + free(in); + return CURLE_OUT_OF_MEMORY; +} + +/* + * add_buffer() appends a memory chunk to the existing buffer + */ +CURLcode Curl_add_buffer(Curl_send_buffer *in, const void *inptr, size_t size) +{ + char *new_rb; + size_t new_size; + + if(~size < in->size_used) { + /* If resulting used size of send buffer would wrap size_t, cleanup + the whole buffer and return error. Otherwise the required buffer + size will fit into a single allocatable memory chunk */ + Curl_safefree(in->buffer); + free(in); + return CURLE_OUT_OF_MEMORY; + } + + if(!in->buffer || + ((in->size_used + size) > (in->size_max - 1))) { + + /* If current buffer size isn't enough to hold the result, use a + buffer size that doubles the required size. If this new size + would wrap size_t, then just use the largest possible one */ + + if((size > (size_t)-1/2) || (in->size_used > (size_t)-1/2) || + (~(size*2) < (in->size_used*2))) + new_size = (size_t)-1; + else + new_size = (in->size_used+size)*2; + + if(in->buffer) + /* we have a buffer, enlarge the existing one */ + new_rb = realloc(in->buffer, new_size); + else + /* create a new buffer */ + new_rb = malloc(new_size); + + if(!new_rb) { + /* If we failed, we cleanup the whole buffer and return error */ + Curl_safefree(in->buffer); + free(in); + return CURLE_OUT_OF_MEMORY; + } + + in->buffer = new_rb; + in->size_max = new_size; + } + memcpy(&in->buffer[in->size_used], inptr, size); + + in->size_used += size; + + return CURLE_OK; +} + +/* end of the add_buffer functions */ +/* ------------------------------------------------------------------------- */ + + + +/* + * Curl_compareheader() + * + * Returns TRUE if 'headerline' contains the 'header' with given 'content'. + * Pass headers WITH the colon. + */ +bool +Curl_compareheader(const char *headerline, /* line to check */ + const char *header, /* header keyword _with_ colon */ + const char *content) /* content string to find */ +{ + /* RFC2616, section 4.2 says: "Each header field consists of a name followed + * by a colon (":") and the field value. Field names are case-insensitive. + * The field value MAY be preceded by any amount of LWS, though a single SP + * is preferred." */ + + size_t hlen = strlen(header); + size_t clen; + size_t len; + const char *start; + const char *end; + + if(!Curl_raw_nequal(headerline, header, hlen)) + return FALSE; /* doesn't start with header */ + + /* pass the header */ + start = &headerline[hlen]; + + /* pass all white spaces */ + while(*start && ISSPACE(*start)) + start++; + + /* find the end of the header line */ + end = strchr(start, '\r'); /* lines end with CRLF */ + if(!end) { + /* in case there's a non-standard compliant line here */ + end = strchr(start, '\n'); + + if(!end) + /* hm, there's no line ending here, use the zero byte! */ + end = strchr(start, '\0'); + } + + len = end-start; /* length of the content part of the input line */ + clen = strlen(content); /* length of the word to find */ + + /* find the content string in the rest of the line */ + for(;len>=clen;len--, start++) { + if(Curl_raw_nequal(start, content, clen)) + return TRUE; /* match! */ + } + + return FALSE; /* no match */ +} + +/* + * Curl_http_connect() performs HTTP stuff to do at connect-time, called from + * the generic Curl_connect(). + */ +CURLcode Curl_http_connect(struct connectdata *conn, bool *done) +{ + struct SessionHandle *data; + CURLcode result; + + data=conn->data; + + /* We default to persistent connections. We set this already in this connect + function to make the re-use checks properly be able to check this bit. */ + conn->bits.close = FALSE; + + if(data->state.used_interface == Curl_if_multi) { + /* when the multi interface is used, the CONNECT procedure might not have + been completed */ + result = Curl_proxy_connect(conn); + if(result) + return result; + } + + if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) + /* nothing else to do except wait right now - we're not done here. */ + return CURLE_OK; + + if(conn->given->flags & PROTOPT_SSL) { + /* perform SSL initialization */ + if(data->state.used_interface == Curl_if_multi) { + result = https_connecting(conn, done); + if(result) + return result; + } + else { + /* BLOCKING */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(result) + return result; + *done = TRUE; + } + } + else { + *done = TRUE; + } + + return CURLE_OK; +} + +/* this returns the socket to wait for in the DO and DOING state for the multi + interface and then we're always _sending_ a request and thus we wait for + the single socket to become writable only */ +static int http_getsock_do(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + /* write mode */ + (void)numsocks; /* unused, we trust it to be at least 1 */ + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_WRITESOCK(0); +} + +#ifdef USE_SSL +static CURLcode https_connecting(struct connectdata *conn, bool *done) +{ + CURLcode result; + DEBUGASSERT((conn) && (conn->handler->flags & PROTOPT_SSL)); + + /* perform SSL initialization for this socket */ + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done); + if(result) + conn->bits.close = TRUE; /* a failed connection is marked for closure + to prevent (bad) re-use or similar */ + return result; +} +#endif + +#if defined(USE_SSLEAY) || defined(USE_GNUTLS) || defined(USE_SCHANNEL) || \ + defined(USE_DARWINSSL) +/* This function is for OpenSSL, GnuTLS, darwinssl, and schannel only. + It should be made to query the generic SSL layer instead. */ +static int https_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + if(conn->handler->flags & PROTOPT_SSL) { + struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; + + if(!numsocks) + return GETSOCK_BLANK; + + if(connssl->connecting_state == ssl_connect_2_writing) { + /* write mode */ + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_WRITESOCK(0); + } + else if(connssl->connecting_state == ssl_connect_2_reading) { + /* read mode */ + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0); + } + } + return CURLE_OK; +} +#else +#ifdef USE_SSL +static int https_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + (void)conn; + (void)socks; + (void)numsocks; + return GETSOCK_BLANK; +} +#endif /* USE_SSL */ +#endif /* USE_SSLEAY || USE_GNUTLS || USE_SCHANNEL */ + +/* + * Curl_http_done() gets called from Curl_done() after a single HTTP request + * has been performed. + */ + +CURLcode Curl_http_done(struct connectdata *conn, + CURLcode status, bool premature) +{ + struct SessionHandle *data = conn->data; + struct HTTP *http =data->state.proto.http; + + Curl_unencode_cleanup(conn); + + /* set the proper values (possibly modified on POST) */ + conn->fread_func = data->set.fread_func; /* restore */ + conn->fread_in = data->set.in; /* restore */ + conn->seek_func = data->set.seek_func; /* restore */ + conn->seek_client = data->set.seek_client; /* restore */ + + if(http == NULL) + return CURLE_OK; + + if(http->send_buffer) { + Curl_send_buffer *buff = http->send_buffer; + + free(buff->buffer); + free(buff); + http->send_buffer = NULL; /* clear the pointer */ + } + + if(HTTPREQ_POST_FORM == data->set.httpreq) { + data->req.bytecount = http->readbytecount + http->writebytecount; + + Curl_formclean(&http->sendit); /* Now free that whole lot */ + if(http->form.fp) { + /* a file being uploaded was left opened, close it! */ + fclose(http->form.fp); + http->form.fp = NULL; + } + } + else if(HTTPREQ_PUT == data->set.httpreq) + data->req.bytecount = http->readbytecount + http->writebytecount; + + if(status != CURLE_OK) + return (status); + + if(!premature && /* this check is pointless when DONE is called before the + entire operation is complete */ + !conn->bits.retry && + ((http->readbytecount + + data->req.headerbytecount - + data->req.deductheadercount)) <= 0) { + /* If this connection isn't simply closed to be retried, AND nothing was + read from the HTTP server (that counts), this can't be right so we + return an error here */ + failf(data, "Empty reply from server"); + return CURLE_GOT_NOTHING; + } + + return CURLE_OK; +} + + +/* Determine if we should use HTTP 1.1 for this request. Reasons to avoid it + are if the user specifically requested HTTP 1.0, if the server we are + connected to only supports 1.0, or if any server previously contacted to + handle this request only supports 1.0. */ +static bool use_http_1_1(const struct SessionHandle *data, + const struct connectdata *conn) +{ + return ((data->set.httpversion == CURL_HTTP_VERSION_1_1) || + ((data->set.httpversion != CURL_HTTP_VERSION_1_0) && + ((conn->httpversion == 11) || + ((conn->httpversion != 10) && + (data->state.httpversion != 10))))) ? TRUE : FALSE; +} + +/* check and possibly add an Expect: header */ +static CURLcode expect100(struct SessionHandle *data, + struct connectdata *conn, + Curl_send_buffer *req_buffer) +{ + CURLcode result = CURLE_OK; + const char *ptr; + data->state.expect100header = FALSE; /* default to false unless it is set + to TRUE below */ + if(use_http_1_1(data, conn)) { + /* if not doing HTTP 1.0 or disabled explicitly, we add a Expect: + 100-continue to the headers which actually speeds up post operations + (as there is one packet coming back from the web server) */ + ptr = Curl_checkheaders(data, "Expect:"); + if(ptr) { + data->state.expect100header = + Curl_compareheader(ptr, "Expect:", "100-continue"); + } + else { + result = Curl_add_bufferf(req_buffer, + "Expect: 100-continue\r\n"); + if(result == CURLE_OK) + data->state.expect100header = TRUE; + } + } + return result; +} + +CURLcode Curl_add_custom_headers(struct connectdata *conn, + Curl_send_buffer *req_buffer) +{ + char *ptr; + struct curl_slist *headers=conn->data->set.headers; + + while(headers) { + ptr = strchr(headers->data, ':'); + if(ptr) { + /* we require a colon for this to be a true header */ + + ptr++; /* pass the colon */ + while(*ptr && ISSPACE(*ptr)) + ptr++; + + if(*ptr) { + /* only send this if the contents was non-blank */ + + if(conn->allocptr.host && + /* a Host: header was sent already, don't pass on any custom Host: + header as that will produce *two* in the same request! */ + checkprefix("Host:", headers->data)) + ; + else if(conn->data->set.httpreq == HTTPREQ_POST_FORM && + /* this header (extended by curl_formdata.c) is sent later */ + checkprefix("Content-Type:", headers->data)) + ; + else if(conn->bits.authneg && + /* while doing auth neg, don't allow the custom length since + we will force length zero then */ + checkprefix("Content-Length", headers->data)) + ; + else if(conn->allocptr.te && + /* when asking for Transfer-Encoding, don't pass on a custom + Connection: */ + checkprefix("Connection", headers->data)) + ; + else { + CURLcode result = Curl_add_bufferf(req_buffer, "%s\r\n", + headers->data); + if(result) + return result; + } + } + } + else { + ptr = strchr(headers->data, ';'); + if(ptr) { + + ptr++; /* pass the semicolon */ + while(*ptr && ISSPACE(*ptr)) + ptr++; + + if(*ptr) { + /* this may be used for something else in the future */ + } + else { + if(*(--ptr) == ';') { + CURLcode result; + + /* send no-value custom header if terminated by semicolon */ + *ptr = ':'; + result = Curl_add_bufferf(req_buffer, "%s\r\n", + headers->data); + if(result) + return result; + } + } + } + } + headers = headers->next; + } + return CURLE_OK; +} + +CURLcode Curl_add_timecondition(struct SessionHandle *data, + Curl_send_buffer *req_buffer) +{ + const struct tm *tm; + char *buf = data->state.buffer; + CURLcode result = CURLE_OK; + struct tm keeptime; + + result = Curl_gmtime(data->set.timevalue, &keeptime); + if(result) { + failf(data, "Invalid TIMEVALUE"); + return result; + } + tm = &keeptime; + + /* The If-Modified-Since header family should have their times set in + * GMT as RFC2616 defines: "All HTTP date/time stamps MUST be + * represented in Greenwich Mean Time (GMT), without exception. For the + * purposes of HTTP, GMT is exactly equal to UTC (Coordinated Universal + * Time)." (see page 20 of RFC2616). + */ + + /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ + snprintf(buf, BUFSIZE-1, + "%s, %02d %s %4d %02d:%02d:%02d GMT", + Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], + tm->tm_mday, + Curl_month[tm->tm_mon], + tm->tm_year + 1900, + tm->tm_hour, + tm->tm_min, + tm->tm_sec); + + switch(data->set.timecondition) { + case CURL_TIMECOND_IFMODSINCE: + default: + result = Curl_add_bufferf(req_buffer, + "If-Modified-Since: %s\r\n", buf); + break; + case CURL_TIMECOND_IFUNMODSINCE: + result = Curl_add_bufferf(req_buffer, + "If-Unmodified-Since: %s\r\n", buf); + break; + case CURL_TIMECOND_LASTMOD: + result = Curl_add_bufferf(req_buffer, + "Last-Modified: %s\r\n", buf); + break; + } + + return result; +} + +/* + * Curl_http() gets called from the generic Curl_do() function when a HTTP + * request is to be performed. This creates and sends a properly constructed + * HTTP request. + */ +CURLcode Curl_http(struct connectdata *conn, bool *done) +{ + struct SessionHandle *data=conn->data; + CURLcode result=CURLE_OK; + struct HTTP *http; + const char *ppath = data->state.path; + bool paste_ftp_userpwd = FALSE; + char ftp_typecode[sizeof("/;type=?")] = ""; + const char *host = conn->host.name; + const char *te = ""; /* transfer-encoding */ + const char *ptr; + const char *request; + Curl_HttpReq httpreq = data->set.httpreq; + char *addcookies = NULL; + curl_off_t included_body = 0; + const char *httpstring; + Curl_send_buffer *req_buffer; + curl_off_t postsize = 0; /* curl_off_t to handle large file sizes */ + int seekerr = CURL_SEEKFUNC_OK; + + /* Always consider the DO phase done after this function call, even if there + may be parts of the request that is not yet sent, since we can deal with + the rest of the request in the PERFORM phase. */ + *done = TRUE; + + /* If there already is a protocol-specific struct allocated for this + sessionhandle, deal with it */ + Curl_reset_reqproto(conn); + + if(!data->state.proto.http) { + /* Only allocate this struct if we don't already have it! */ + + http = calloc(1, sizeof(struct HTTP)); + if(!http) + return CURLE_OUT_OF_MEMORY; + data->state.proto.http = http; + } + else + http = data->state.proto.http; + + if(!data->state.this_is_a_follow) { + /* this is not a followed location, get the original host name */ + if(data->state.first_host) + /* Free to avoid leaking memory on multiple requests*/ + free(data->state.first_host); + + data->state.first_host = strdup(conn->host.name); + if(!data->state.first_host) + return CURLE_OUT_OF_MEMORY; + } + http->writebytecount = http->readbytecount = 0; + + if((conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_FTP)) && + data->set.upload) { + httpreq = HTTPREQ_PUT; + } + + /* Now set the 'request' pointer to the proper request string */ + if(data->set.str[STRING_CUSTOMREQUEST]) + request = data->set.str[STRING_CUSTOMREQUEST]; + else { + if(data->set.opt_no_body) + request = "HEAD"; + else { + DEBUGASSERT((httpreq > HTTPREQ_NONE) && (httpreq < HTTPREQ_LAST)); + switch(httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + request = "POST"; + break; + case HTTPREQ_PUT: + request = "PUT"; + break; + default: /* this should never happen */ + case HTTPREQ_GET: + request = "GET"; + break; + case HTTPREQ_HEAD: + request = "HEAD"; + break; + } + } + } + + /* The User-Agent string might have been allocated in curl_url.c already, + because it might have been used in the proxy connect, but if we have + got a header with the user-agent string specified, we erase the + previously made string here. */ + if(Curl_checkheaders(data, "User-Agent:") && conn->allocptr.uagent) { + free(conn->allocptr.uagent); + conn->allocptr.uagent=NULL; + } + + /* setup the authentication headers */ + result = Curl_http_output_auth(conn, request, ppath, FALSE); + if(result) + return result; + + if((data->state.authhost.multi || data->state.authproxy.multi) && + (httpreq != HTTPREQ_GET) && + (httpreq != HTTPREQ_HEAD)) { + /* Auth is required and we are not authenticated yet. Make a PUT or POST + with content-length zero as a "probe". */ + conn->bits.authneg = TRUE; + } + else + conn->bits.authneg = FALSE; + + Curl_safefree(conn->allocptr.ref); + if(data->change.referer && !Curl_checkheaders(data, "Referer:")) + conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); + else + conn->allocptr.ref = NULL; + + if(data->set.str[STRING_COOKIE] && !Curl_checkheaders(data, "Cookie:")) + addcookies = data->set.str[STRING_COOKIE]; + + if(!Curl_checkheaders(data, "Accept-Encoding:") && + data->set.str[STRING_ENCODING]) { + Curl_safefree(conn->allocptr.accept_encoding); + conn->allocptr.accept_encoding = + aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); + if(!conn->allocptr.accept_encoding) + return CURLE_OUT_OF_MEMORY; + } + +#ifdef HAVE_LIBZ + /* we only consider transfer-encoding magic if libz support is built-in */ + + if(!Curl_checkheaders(data, "TE:") && data->set.http_transfer_encoding) { + /* When we are to insert a TE: header in the request, we must also insert + TE in a Connection: header, so we need to merge the custom provided + Connection: header and prevent the original to get sent. Note that if + the user has inserted his/hers own TE: header we don't do this magic + but then assume that the user will handle it all! */ + char *cptr = Curl_checkheaders(data, "Connection:"); +#define TE_HEADER "TE: gzip\r\n" + + Curl_safefree(conn->allocptr.te); + + /* Create the (updated) Connection: header */ + conn->allocptr.te = cptr? aprintf("%s, TE\r\n" TE_HEADER, cptr): + strdup("Connection: TE\r\n" TE_HEADER); + + if(!conn->allocptr.te) + return CURLE_OUT_OF_MEMORY; + } +#endif + + ptr = Curl_checkheaders(data, "Transfer-Encoding:"); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + data->req.upload_chunky = + Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); + } + else { + if((conn->handler->protocol&CURLPROTO_HTTP) && + data->set.upload && + (data->set.infilesize == -1)) { + if(conn->bits.authneg) + /* don't enable chunked during auth neg */ + ; + else if(use_http_1_1(data, conn)) { + /* HTTP, upload, unknown file size and not HTTP 1.0 */ + data->req.upload_chunky = TRUE; + } + else { + failf(data, "Chunky upload is not supported by HTTP 1.0"); + return CURLE_UPLOAD_FAILED; + } + } + else { + /* else, no chunky upload */ + data->req.upload_chunky = FALSE; + } + + if(data->req.upload_chunky) + te = "Transfer-Encoding: chunked\r\n"; + } + + Curl_safefree(conn->allocptr.host); + + ptr = Curl_checkheaders(data, "Host:"); + if(ptr && (!data->state.this_is_a_follow || + Curl_raw_equal(data->state.first_host, conn->host.name))) { +#if !defined(CURL_DISABLE_COOKIES) + /* If we have a given custom Host: header, we extract the host name in + order to possibly use it for cookie reasons later on. We only allow the + custom Host: header if this is NOT a redirect, as setting Host: in the + redirected request is being out on thin ice. Except if the host name + is the same as the first one! */ + char *cookiehost = copy_header_value(ptr); + if(!cookiehost) + return CURLE_OUT_OF_MEMORY; + if(!*cookiehost) + /* ignore empty data */ + free(cookiehost); + else { + /* If the host begins with '[', we start searching for the port after + the bracket has been closed */ + int startsearch = 0; + if(*cookiehost == '[') { + char *closingbracket; + /* since the 'cookiehost' is an allocated memory area that will be + freed later we cannot simply increment the pointer */ + memmove(cookiehost, cookiehost + 1, strlen(cookiehost) - 1); + closingbracket = strchr(cookiehost, ']'); + if(closingbracket) + *closingbracket = 0; + } + else { + char *colon = strchr(cookiehost + startsearch, ':'); + if(colon) + *colon = 0; /* The host must not include an embedded port number */ + } + Curl_safefree(conn->allocptr.cookiehost); + conn->allocptr.cookiehost = cookiehost; + } +#endif + + conn->allocptr.host = NULL; + } + else { + /* When building Host: headers, we must put the host name within + [brackets] if the host name is a plain IPv6-address. RFC2732-style. */ + + if(((conn->given->protocol&CURLPROTO_HTTPS) && + (conn->remote_port == PORT_HTTPS)) || + ((conn->given->protocol&CURLPROTO_HTTP) && + (conn->remote_port == PORT_HTTP)) ) + /* if(HTTPS on port 443) OR (HTTP on port 80) then don't include + the port number in the host string */ + conn->allocptr.host = aprintf("Host: %s%s%s\r\n", + conn->bits.ipv6_ip?"[":"", + host, + conn->bits.ipv6_ip?"]":""); + else + conn->allocptr.host = aprintf("Host: %s%s%s:%hu\r\n", + conn->bits.ipv6_ip?"[":"", + host, + conn->bits.ipv6_ip?"]":"", + conn->remote_port); + + if(!conn->allocptr.host) + /* without Host: we can't make a nice request */ + return CURLE_OUT_OF_MEMORY; + } + +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + /* Using a proxy but does not tunnel through it */ + + /* The path sent to the proxy is in fact the entire URL. But if the remote + host is a IDN-name, we must make sure that the request we produce only + uses the encoded host name! */ + if(conn->host.dispname != conn->host.name) { + char *url = data->change.url; + ptr = strstr(url, conn->host.dispname); + if(ptr) { + /* This is where the display name starts in the URL, now replace this + part with the encoded name. TODO: This method of replacing the host + name is rather crude as I believe there's a slight risk that the + user has entered a user name or password that contain the host name + string. */ + size_t currlen = strlen(conn->host.dispname); + size_t newlen = strlen(conn->host.name); + size_t urllen = strlen(url); + + char *newurl; + + newurl = malloc(urllen + newlen - currlen + 1); + if(newurl) { + /* copy the part before the host name */ + memcpy(newurl, url, ptr - url); + /* append the new host name instead of the old */ + memcpy(newurl + (ptr - url), conn->host.name, newlen); + /* append the piece after the host name */ + memcpy(newurl + newlen + (ptr - url), + ptr + currlen, /* copy the trailing zero byte too */ + urllen - (ptr-url) - currlen + 1); + if(data->change.url_alloc) { + Curl_safefree(data->change.url); + data->change.url_alloc = FALSE; + } + data->change.url = newurl; + data->change.url_alloc = TRUE; + } + else + return CURLE_OUT_OF_MEMORY; + } + } + ppath = data->change.url; + if(checkprefix("ftp://", ppath)) { + if(data->set.proxy_transfer_mode) { + /* when doing ftp, append ;type= if not present */ + char *type = strstr(ppath, ";type="); + if(type && type[6] && type[7] == 0) { + switch (Curl_raw_toupper(type[6])) { + case 'A': + case 'D': + case 'I': + break; + default: + type = NULL; + } + } + if(!type) { + char *p = ftp_typecode; + /* avoid sending invalid URLs like ftp://example.com;type=i if the + * user specified ftp://example.com without the slash */ + if(!*data->state.path && ppath[strlen(ppath) - 1] != '/') { + *p++ = '/'; + } + snprintf(p, sizeof(ftp_typecode) - 1, ";type=%c", + data->set.prefer_ascii ? 'a' : 'i'); + } + } + if(conn->bits.user_passwd && !conn->bits.userpwd_in_url) + paste_ftp_userpwd = TRUE; + } + } +#endif /* CURL_DISABLE_PROXY */ + + if(HTTPREQ_POST_FORM == httpreq) { + /* we must build the whole post sequence first, so that we have a size of + the whole transfer before we start to send it */ + result = Curl_getformdata(data, &http->sendit, data->set.httppost, + Curl_checkheaders(data, "Content-Type:"), + &http->postsize); + if(result) + return result; + } + + http->p_accept = Curl_checkheaders(data, "Accept:")?NULL:"Accept: */*\r\n"; + + if(( (HTTPREQ_POST == httpreq) || + (HTTPREQ_POST_FORM == httpreq) || + (HTTPREQ_PUT == httpreq) ) && + data->state.resume_from) { + /********************************************************************** + * Resuming upload in HTTP means that we PUT or POST and that we have + * got a resume_from value set. The resume value has already created + * a Range: header that will be passed along. We need to "fast forward" + * the file the given number of bytes and decrease the assume upload + * file size before we continue this venture in the dark lands of HTTP. + *********************************************************************/ + + if(data->state.resume_from < 0 ) { + /* + * This is meant to get the size of the present remote-file by itself. + * We don't support this now. Bail out! + */ + data->state.resume_from = 0; + } + + if(data->state.resume_from && !data->state.this_is_a_follow) { + /* do we still game? */ + + /* Now, let's read off the proper amount of bytes from the + input. */ + if(conn->seek_func) { + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_READ_ERROR; + } + /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + else { + curl_off_t passed=0; + do { + size_t readthisamountnow = + (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? + BUFSIZE : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + data->set.fread_func(data->state.buffer, 1, readthisamountnow, + data->set.in); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Could only read %" FORMAT_OFF_T + " bytes from the input", + passed); + return CURLE_READ_ERROR; + } + } while(passed < data->state.resume_from); + } + } + + /* now, decrease the size of the read */ + if(data->set.infilesize>0) { + data->set.infilesize -= data->state.resume_from; + + if(data->set.infilesize <= 0) { + failf(data, "File already completely uploaded"); + return CURLE_PARTIAL_FILE; + } + } + /* we've passed, proceed as normal */ + } + } + if(data->state.use_range) { + /* + * A range is selected. We use different headers whether we're downloading + * or uploading and we always let customized headers override our internal + * ones if any such are specified. + */ + if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && + !Curl_checkheaders(data, "Range:")) { + /* if a line like this was already allocated, free the previous one */ + if(conn->allocptr.rangeline) + free(conn->allocptr.rangeline); + conn->allocptr.rangeline = aprintf("Range: bytes=%s\r\n", + data->state.range); + } + else if((httpreq != HTTPREQ_GET) && + !Curl_checkheaders(data, "Content-Range:")) { + + /* if a line like this was already allocated, free the previous one */ + if(conn->allocptr.rangeline) + free(conn->allocptr.rangeline); + + if(data->set.set_resume_from < 0) { + /* Upload resume was asked for, but we don't know the size of the + remote part so we tell the server (and act accordingly) that we + upload the whole file (again) */ + conn->allocptr.rangeline = + aprintf("Content-Range: bytes 0-%" FORMAT_OFF_T + "/%" FORMAT_OFF_T "\r\n", + data->set.infilesize - 1, data->set.infilesize); + + } + else if(data->state.resume_from) { + /* This is because "resume" was selected */ + curl_off_t total_expected_size= + data->state.resume_from + data->set.infilesize; + conn->allocptr.rangeline = + aprintf("Content-Range: bytes %s%" FORMAT_OFF_T + "/%" FORMAT_OFF_T "\r\n", + data->state.range, total_expected_size-1, + total_expected_size); + } + else { + /* Range was selected and then we just pass the incoming range and + append total size */ + conn->allocptr.rangeline = + aprintf("Content-Range: bytes %s/%" FORMAT_OFF_T "\r\n", + data->state.range, data->set.infilesize); + } + if(!conn->allocptr.rangeline) + return CURLE_OUT_OF_MEMORY; + } + } + + /* Use 1.1 unless the user specifically asked for 1.0 or the server only + supports 1.0 */ + httpstring= use_http_1_1(data, conn)?"1.1":"1.0"; + + /* initialize a dynamic send-buffer */ + req_buffer = Curl_add_buffer_init(); + + if(!req_buffer) + return CURLE_OUT_OF_MEMORY; + + /* add the main request stuff */ + /* GET/HEAD/POST/PUT */ + result = Curl_add_bufferf(req_buffer, "%s ", request); + if(result) + return result; + + /* url */ + if(paste_ftp_userpwd) + result = Curl_add_bufferf(req_buffer, "ftp://%s:%s@%s", + conn->user, conn->passwd, + ppath + sizeof("ftp://") - 1); + else + result = Curl_add_buffer(req_buffer, ppath, strlen(ppath)); + if(result) + return result; + + result = + Curl_add_bufferf(req_buffer, + "%s" /* ftp typecode (;type=x) */ + " HTTP/%s\r\n" /* HTTP version */ + "%s" /* proxyuserpwd */ + "%s" /* userpwd */ + "%s" /* range */ + "%s" /* user agent */ + "%s" /* host */ + "%s" /* accept */ + "%s" /* TE: */ + "%s" /* accept-encoding */ + "%s" /* referer */ + "%s" /* Proxy-Connection */ + "%s",/* transfer-encoding */ + + ftp_typecode, + httpstring, + conn->allocptr.proxyuserpwd? + conn->allocptr.proxyuserpwd:"", + conn->allocptr.userpwd?conn->allocptr.userpwd:"", + (data->state.use_range && conn->allocptr.rangeline)? + conn->allocptr.rangeline:"", + (data->set.str[STRING_USERAGENT] && + *data->set.str[STRING_USERAGENT] && + conn->allocptr.uagent)? + conn->allocptr.uagent:"", + (conn->allocptr.host?conn->allocptr.host:""), + http->p_accept?http->p_accept:"", + conn->allocptr.te?conn->allocptr.te:"", + (data->set.str[STRING_ENCODING] && + *data->set.str[STRING_ENCODING] && + conn->allocptr.accept_encoding)? + conn->allocptr.accept_encoding:"", + (data->change.referer && conn->allocptr.ref)? + conn->allocptr.ref:"" /* Referer: */, + (conn->bits.httpproxy && + !conn->bits.tunnel_proxy && + !Curl_checkheaders(data, "Proxy-Connection:"))? + "Proxy-Connection: Keep-Alive\r\n":"", + te + ); + + /* + * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM + * with basic and digest, it will be freed anyway by the next request + */ + + Curl_safefree (conn->allocptr.userpwd); + conn->allocptr.userpwd = NULL; + + if(result) + return result; + +#if !defined(CURL_DISABLE_COOKIES) + if(data->cookies || addcookies) { + struct Cookie *co=NULL; /* no cookies from start */ + int count=0; + + if(data->cookies) { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); + co = Curl_cookie_getlist(data->cookies, + conn->allocptr.cookiehost? + conn->allocptr.cookiehost:host, + data->state.path, + (conn->handler->protocol&CURLPROTO_HTTPS)? + TRUE:FALSE); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } + if(co) { + struct Cookie *store=co; + /* now loop through all cookies that matched */ + while(co) { + if(co->value) { + if(0 == count) { + result = Curl_add_bufferf(req_buffer, "Cookie: "); + if(result) + break; + } + result = Curl_add_bufferf(req_buffer, + "%s%s=%s", count?"; ":"", + co->name, co->value); + if(result) + break; + count++; + } + co = co->next; /* next cookie please */ + } + Curl_cookie_freelist(store, FALSE); /* free the cookie list */ + } + if(addcookies && (CURLE_OK == result)) { + if(!count) + result = Curl_add_bufferf(req_buffer, "Cookie: "); + if(CURLE_OK == result) { + result = Curl_add_bufferf(req_buffer, "%s%s", + count?"; ":"", + addcookies); + count++; + } + } + if(count && (CURLE_OK == result)) + result = Curl_add_buffer(req_buffer, "\r\n", 2); + + if(result) + return result; + } +#endif + + if(data->set.timecondition) { + result = Curl_add_timecondition(data, req_buffer); + if(result) + return result; + } + + result = Curl_add_custom_headers(conn, req_buffer); + if(result) + return result; + + http->postdata = NULL; /* nothing to post at this point */ + Curl_pgrsSetUploadSize(data, 0); /* upload size is 0 atm */ + + /* If 'authdone' is FALSE, we must not set the write socket index to the + Curl_transfer() call below, as we're not ready to actually upload any + data yet. */ + + switch(httpreq) { + + case HTTPREQ_POST_FORM: + if(!http->sendit || conn->bits.authneg) { + /* nothing to post! */ + result = Curl_add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); + if(result) + return result; + + result = Curl_add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, FIRSTSOCKET); + if(result) + failf(data, "Failed sending POST request"); + else + /* setup variables for the upcoming transfer */ + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, + -1, NULL); + break; + } + + if(Curl_FormInit(&http->form, http->sendit)) { + failf(data, "Internal HTTP POST error!"); + return CURLE_HTTP_POST_ERROR; + } + + /* Get the currently set callback function pointer and store that in the + form struct since we might want the actual user-provided callback later + on. The conn->fread_func pointer itself will be changed for the + multipart case to the function that returns a multipart formatted + stream. */ + http->form.fread_func = conn->fread_func; + + /* Set the read function to read from the generated form data */ + conn->fread_func = (curl_read_callback)Curl_FormReader; + conn->fread_in = &http->form; + + http->sending = HTTPSEND_BODY; + + if(!data->req.upload_chunky && + !Curl_checkheaders(data, "Content-Length:")) { + /* only add Content-Length if not uploading chunked */ + result = Curl_add_bufferf(req_buffer, + "Content-Length: %" FORMAT_OFF_T "\r\n", + http->postsize); + if(result) + return result; + } + + result = expect100(data, conn, req_buffer); + if(result) + return result; + + { + + /* Get Content-Type: line from Curl_formpostheader. + */ + char *contentType; + size_t linelength=0; + contentType = Curl_formpostheader((void *)&http->form, + &linelength); + if(!contentType) { + failf(data, "Could not get Content-Type header line!"); + return CURLE_HTTP_POST_ERROR; + } + + result = Curl_add_buffer(req_buffer, contentType, linelength); + if(result) + return result; + } + + /* make the request end in a true CRLF */ + result = Curl_add_buffer(req_buffer, "\r\n", 2); + if(result) + return result; + + /* set upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, http->postsize); + + /* fire away the whole request to the server */ + result = Curl_add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, FIRSTSOCKET); + if(result) + failf(data, "Failed sending POST request"); + else + /* setup variables for the upcoming transfer */ + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, FIRSTSOCKET, + &http->writebytecount); + + if(result) { + Curl_formclean(&http->sendit); /* free that whole lot */ + return result; + } + + /* convert the form data */ + result = Curl_convert_form(data, http->sendit); + if(result) { + Curl_formclean(&http->sendit); /* free that whole lot */ + return result; + } + + break; + + case HTTPREQ_PUT: /* Let's PUT the data to the server! */ + + if(conn->bits.authneg) + postsize = 0; + else + postsize = data->set.infilesize; + + if((postsize != -1) && !data->req.upload_chunky && + !Curl_checkheaders(data, "Content-Length:")) { + /* only add Content-Length if not uploading chunked */ + result = Curl_add_bufferf(req_buffer, + "Content-Length: %" FORMAT_OFF_T "\r\n", + postsize ); + if(result) + return result; + } + + result = expect100(data, conn, req_buffer); + if(result) + return result; + + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers */ + if(result) + return result; + + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, postsize); + + /* this sends the buffer and frees all the buffer resources */ + result = Curl_add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, FIRSTSOCKET); + if(result) + failf(data, "Failed sending PUT request"); + else + /* prepare for transfer */ + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, postsize?FIRSTSOCKET:-1, + postsize?&http->writebytecount:NULL); + if(result) + return result; + break; + + case HTTPREQ_POST: + /* this is the simple POST, using x-www-form-urlencoded style */ + + if(conn->bits.authneg) + postsize = 0; + else { + /* figure out the size of the postfields */ + postsize = (data->set.postfieldsize != -1)? + data->set.postfieldsize: + (data->set.postfields? (curl_off_t)strlen(data->set.postfields):-1); + } + if(!data->req.upload_chunky) { + /* We only set Content-Length and allow a custom Content-Length if + we don't upload data chunked, as RFC2616 forbids us to set both + kinds of headers (Transfer-Encoding: chunked and Content-Length) */ + + if(conn->bits.authneg || !Curl_checkheaders(data, "Content-Length:")) { + /* we allow replacing this header if not during auth negotiation, + although it isn't very wise to actually set your own */ + result = Curl_add_bufferf(req_buffer, + "Content-Length: %" FORMAT_OFF_T"\r\n", + postsize); + if(result) + return result; + } + } + + if(!Curl_checkheaders(data, "Content-Type:")) { + result = Curl_add_bufferf(req_buffer, + "Content-Type: application/" + "x-www-form-urlencoded\r\n"); + if(result) + return result; + } + + /* For really small posts we don't use Expect: headers at all, and for + the somewhat bigger ones we allow the app to disable it. Just make + sure that the expect100header is always set to the preferred value + here. */ + ptr = Curl_checkheaders(data, "Expect:"); + if(ptr) { + data->state.expect100header = + Curl_compareheader(ptr, "Expect:", "100-continue"); + } + else if(postsize > TINY_INITIAL_POST_SIZE || postsize < 0) { + result = expect100(data, conn, req_buffer); + if(result) + return result; + } + else + data->state.expect100header = FALSE; + + if(data->set.postfields) { + + if(!data->state.expect100header && + (postsize < MAX_INITIAL_POST_SIZE)) { + /* if we don't use expect: 100 AND + postsize is less than MAX_INITIAL_POST_SIZE + + then append the post data to the HTTP request header. This limit + is no magic limit but only set to prevent really huge POSTs to + get the data duplicated with malloc() and family. */ + + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ + if(result) + return result; + + if(!data->req.upload_chunky) { + /* We're not sending it 'chunked', append it to the request + already now to reduce the number if send() calls */ + result = Curl_add_buffer(req_buffer, data->set.postfields, + (size_t)postsize); + included_body = postsize; + } + else { + if(postsize) { + /* Append the POST data chunky-style */ + result = Curl_add_bufferf(req_buffer, "%x\r\n", (int)postsize); + if(CURLE_OK == result) { + result = Curl_add_buffer(req_buffer, data->set.postfields, + (size_t)postsize); + if(CURLE_OK == result) + result = Curl_add_buffer(req_buffer, "\r\n", 2); + included_body = postsize + 2; + } + } + if(CURLE_OK == result) + result = Curl_add_buffer(req_buffer, + "\x30\x0d\x0a\x0d\x0a", 5); + /* 0 CR LF CR LF */ + included_body += 5; + } + if(result) + return result; + /* Make sure the progress information is accurate */ + Curl_pgrsSetUploadSize(data, postsize); + } + else { + /* A huge POST coming up, do data separate from the request */ + http->postsize = postsize; + http->postdata = data->set.postfields; + + http->sending = HTTPSEND_BODY; + + conn->fread_func = (curl_read_callback)readmoredata; + conn->fread_in = (void *)conn; + + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, http->postsize); + + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ + if(result) + return result; + } + } + else { + result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ + if(result) + return result; + + if(data->req.upload_chunky && conn->bits.authneg) { + /* Chunky upload is selected and we're negotiating auth still, send + end-of-data only */ + result = Curl_add_buffer(req_buffer, + "\x30\x0d\x0a\x0d\x0a", 5); + /* 0 CR LF CR LF */ + if(result) + return result; + } + + else if(data->set.postfieldsize) { + /* set the upload size to the progress meter */ + Curl_pgrsSetUploadSize(data, postsize?postsize:-1); + + /* set the pointer to mark that we will send the post body using the + read callback, but only if we're not in authenticate + negotiation */ + if(!conn->bits.authneg) { + http->postdata = (char *)&http->postdata; + http->postsize = postsize; + } + } + } + /* issue the request */ + result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, + (size_t)included_body, FIRSTSOCKET); + + if(result) + failf(data, "Failed sending HTTP POST request"); + else + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, http->postdata?FIRSTSOCKET:-1, + http->postdata?&http->writebytecount:NULL); + break; + + default: + result = Curl_add_buffer(req_buffer, "\r\n", 2); + if(result) + return result; + + /* issue the request */ + result = Curl_add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, FIRSTSOCKET); + + if(result) + failf(data, "Failed sending HTTP request"); + else + /* HTTP GET/HEAD download: */ + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, + http->postdata?FIRSTSOCKET:-1, + http->postdata?&http->writebytecount:NULL); + } + if(result) + return result; + + if(http->writebytecount) { + /* if a request-body has been sent off, we make sure this progress is noted + properly */ + Curl_pgrsSetUploadCounter(data, http->writebytecount); + if(Curl_pgrsUpdate(conn)) + result = CURLE_ABORTED_BY_CALLBACK; + + if(http->writebytecount >= postsize) { + /* already sent the entire request body, mark the "upload" as + complete */ + infof(data, "upload completely sent off: %" FORMAT_OFF_T " out of " + "%" FORMAT_OFF_T " bytes\n", + http->writebytecount, postsize); + data->req.upload_done = TRUE; + data->req.keepon &= ~KEEP_SEND; /* we're done writing */ + data->req.exp100 = EXP100_SEND_DATA; /* already sent */ + } + } + + return result; +} + +/* + * checkhttpprefix() + * + * Returns TRUE if member of the list matches prefix of string + */ +static bool +checkhttpprefix(struct SessionHandle *data, + const char *s) +{ + struct curl_slist *head = data->set.http200aliases; + bool rc = FALSE; +#ifdef CURL_DOES_CONVERSIONS + /* convert from the network encoding using a scratch area */ + char *scratch = strdup(s); + if(NULL == scratch) { + failf (data, "Failed to allocate memory for conversion!"); + return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ + } + if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) { + /* Curl_convert_from_network calls failf if unsuccessful */ + free(scratch); + return FALSE; /* can't return CURLE_foobar so return FALSE */ + } + s = scratch; +#endif /* CURL_DOES_CONVERSIONS */ + + while(head) { + if(checkprefix(head->data, s)) { + rc = TRUE; + break; + } + head = head->next; + } + + if(!rc && (checkprefix("HTTP/", s))) + rc = TRUE; + +#ifdef CURL_DOES_CONVERSIONS + free(scratch); +#endif /* CURL_DOES_CONVERSIONS */ + return rc; +} + +#ifndef CURL_DISABLE_RTSP +static bool +checkrtspprefix(struct SessionHandle *data, + const char *s) +{ + +#ifdef CURL_DOES_CONVERSIONS + /* convert from the network encoding using a scratch area */ + char *scratch = strdup(s); + if(NULL == scratch) { + failf (data, "Failed to allocate memory for conversion!"); + return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ + } + if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) { + /* Curl_convert_from_network calls failf if unsuccessful */ + free(scratch); + return FALSE; /* can't return CURLE_foobar so return FALSE */ + } + s = scratch; +#else + (void)data; /* unused */ +#endif /* CURL_DOES_CONVERSIONS */ + if(checkprefix("RTSP/", s)) + return TRUE; + else + return FALSE; +} +#endif /* CURL_DISABLE_RTSP */ + +static bool +checkprotoprefix(struct SessionHandle *data, struct connectdata *conn, + const char *s) +{ +#ifndef CURL_DISABLE_RTSP + if(conn->handler->protocol & CURLPROTO_RTSP) + return checkrtspprefix(data, s); +#else + (void)conn; +#endif /* CURL_DISABLE_RTSP */ + + return checkhttpprefix(data, s); +} + +/* + * header_append() copies a chunk of data to the end of the already received + * header. We make sure that the full string fit in the allocated header + * buffer, or else we enlarge it. + */ +static CURLcode header_append(struct SessionHandle *data, + struct SingleRequest *k, + size_t length) +{ + if(k->hbuflen + length >= data->state.headersize) { + /* We enlarge the header buffer as it is too small */ + char *newbuff; + size_t hbufp_index; + size_t newsize; + + if(k->hbuflen + length > CURL_MAX_HTTP_HEADER) { + /* The reason to have a max limit for this is to avoid the risk of a bad + server feeding libcurl with a never-ending header that will cause + reallocs infinitely */ + failf (data, "Avoided giant realloc for header (max is %d)!", + CURL_MAX_HTTP_HEADER); + return CURLE_OUT_OF_MEMORY; + } + + newsize=CURLMAX((k->hbuflen+ length)*3/2, data->state.headersize*2); + hbufp_index = k->hbufp - data->state.headerbuff; + newbuff = realloc(data->state.headerbuff, newsize); + if(!newbuff) { + failf (data, "Failed to alloc memory for big header!"); + return CURLE_OUT_OF_MEMORY; + } + data->state.headersize=newsize; + data->state.headerbuff = newbuff; + k->hbufp = data->state.headerbuff + hbufp_index; + } + memcpy(k->hbufp, k->str_start, length); + k->hbufp += length; + k->hbuflen += length; + *k->hbufp = 0; + + return CURLE_OK; +} + +static void print_http_error(struct SessionHandle *data) +{ + struct SingleRequest *k = &data->req; + char *beg = k->p; + + /* make sure that data->req.p points to the HTTP status line */ + if(!strncmp(beg, "HTTP", 4)) { + + /* skip to HTTP status code */ + beg = strchr(beg, ' '); + if(beg && *++beg) { + + /* find trailing CR */ + char end_char = '\r'; + char *end = strchr(beg, end_char); + if(!end) { + /* try to find LF (workaround for non-compliant HTTP servers) */ + end_char = '\n'; + end = strchr(beg, end_char); + } + + if(end) { + /* temporarily replace CR or LF by NUL and print the error message */ + *end = '\0'; + failf(data, "The requested URL returned error: %s", beg); + + /* restore the previously replaced CR or LF */ + *end = end_char; + return; + } + } + } + + /* fall-back to printing the HTTP status code only */ + failf(data, "The requested URL returned error: %d", k->httpcode); +} + +/* + * Read any HTTP header lines from the server and pass them to the client app. + */ +CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, + struct connectdata *conn, + ssize_t *nread, + bool *stop_reading) +{ + CURLcode result; + struct SingleRequest *k = &data->req; + + /* header line within buffer loop */ + do { + size_t rest_length; + size_t full_length; + int writetype; + + /* str_start is start of line within buf */ + k->str_start = k->str; + + /* data is in network encoding so use 0x0a instead of '\n' */ + k->end_ptr = memchr(k->str_start, 0x0a, *nread); + + if(!k->end_ptr) { + /* Not a complete header line within buffer, append the data to + the end of the headerbuff. */ + result = header_append(data, k, *nread); + if(result) + return result; + + if(!k->headerline && (k->hbuflen>5)) { + /* make a first check that this looks like a protocol header */ + if(!checkprotoprefix(data, conn, data->state.headerbuff)) { + /* this is not the beginning of a protocol first header line */ + k->header = FALSE; + k->badheader = HEADER_ALLBAD; + break; + } + } + + break; /* read more and try again */ + } + + /* decrease the size of the remaining (supposed) header line */ + rest_length = (k->end_ptr - k->str)+1; + *nread -= (ssize_t)rest_length; + + k->str = k->end_ptr + 1; /* move past new line */ + + full_length = k->str - k->str_start; + + result = header_append(data, k, full_length); + if(result) + return result; + + k->end_ptr = k->hbufp; + k->p = data->state.headerbuff; + + /**** + * We now have a FULL header line that p points to + *****/ + + if(!k->headerline) { + /* the first read header */ + if((k->hbuflen>5) && + !checkprotoprefix(data, conn, data->state.headerbuff)) { + /* this is not the beginning of a protocol first header line */ + k->header = FALSE; + if(*nread) + /* since there's more, this is a partial bad header */ + k->badheader = HEADER_PARTHEADER; + else { + /* this was all we read so it's all a bad header */ + k->badheader = HEADER_ALLBAD; + *nread = (ssize_t)rest_length; + } + break; + } + } + + /* headers are in network encoding so + use 0x0a and 0x0d instead of '\n' and '\r' */ + if((0x0a == *k->p) || (0x0d == *k->p)) { + size_t headerlen; + /* Zero-length header line means end of headers! */ + +#ifdef CURL_DOES_CONVERSIONS + if(0x0d == *k->p) { + *k->p = '\r'; /* replace with CR in host encoding */ + k->p++; /* pass the CR byte */ + } + if(0x0a == *k->p) { + *k->p = '\n'; /* replace with LF in host encoding */ + k->p++; /* pass the LF byte */ + } +#else + if('\r' == *k->p) + k->p++; /* pass the \r byte */ + if('\n' == *k->p) + k->p++; /* pass the \n byte */ +#endif /* CURL_DOES_CONVERSIONS */ + + if(100 <= k->httpcode && 199 >= k->httpcode) { + /* + * We have made a HTTP PUT or POST and this is 1.1-lingo + * that tells us that the server is OK with this and ready + * to receive the data. + * However, we'll get more headers now so we must get + * back into the header-parsing state! + */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + + /* if we did wait for this do enable write now! */ + if(k->exp100) { + k->exp100 = EXP100_SEND_DATA; + k->keepon |= KEEP_SEND; + } + } + else { + k->header = FALSE; /* no more header to parse! */ + + if((k->size == -1) && !k->chunk && !conn->bits.close && + (conn->httpversion >= 11) && + !(conn->handler->protocol & CURLPROTO_RTSP) && + data->set.httpreq != HTTPREQ_HEAD) { + /* On HTTP 1.1, when connection is not to get closed, but no + Content-Length nor Content-Encoding chunked have been + received, according to RFC2616 section 4.4 point 5, we + assume that the server will close the connection to + signal the end of the document. */ + infof(data, "no chunk, no close, no size. Assume close to " + "signal end\n"); + conn->bits.close = TRUE; + } + } + + /* + * When all the headers have been parsed, see if we should give + * up and return an error. + */ + if(http_should_fail(conn)) { + failf (data, "The requested URL returned error: %d", + k->httpcode); + return CURLE_HTTP_RETURNED_ERROR; + } + + /* now, only output this if the header AND body are requested: + */ + writetype = CLIENTWRITE_HEADER; + if(data->set.include_header) + writetype |= CLIENTWRITE_BODY; + + headerlen = k->p - data->state.headerbuff; + + result = Curl_client_write(conn, writetype, + data->state.headerbuff, + headerlen); + if(result) + return result; + + data->info.header_size += (long)headerlen; + data->req.headerbytecount += (long)headerlen; + + data->req.deductheadercount = + (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; + + if(!*stop_reading) { + /* Curl_http_auth_act() checks what authentication methods + * that are available and decides which one (if any) to + * use. It will set 'newurl' if an auth method was picked. */ + result = Curl_http_auth_act(conn); + + if(result) + return result; + + if(k->httpcode >= 300) { + if((!conn->bits.authneg) && !conn->bits.close && + !conn->bits.rewindaftersend) { + /* + * General treatment of errors when about to send data. Including : + * "417 Expectation Failed", while waiting for 100-continue. + * + * The check for close above is done simply because of something + * else has already deemed the connection to get closed then + * something else should've considered the big picture and we + * avoid this check. + * + * rewindaftersend indicates that something has told libcurl to + * continue sending even if it gets discarded + */ + + switch(data->set.httpreq) { + case HTTPREQ_PUT: + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + /* We got an error response. If this happened before the whole + * request body has been sent we stop sending and mark the + * connection for closure after we've read the entire response. + */ + if(!k->upload_done) { + infof(data, "HTTP error before end of send, stop sending\n"); + conn->bits.close = TRUE; /* close after this */ + k->upload_done = TRUE; + k->keepon &= ~KEEP_SEND; /* don't send */ + if(data->state.expect100header) + k->exp100 = EXP100_FAILED; + } + break; + + default: /* default label present to avoid compiler warnings */ + break; + } + } + } + + if(conn->bits.rewindaftersend) { + /* We rewind after a complete send, so thus we continue + sending now */ + infof(data, "Keep sending data to get tossed away!\n"); + k->keepon |= KEEP_SEND; + } + } + + if(!k->header) { + /* + * really end-of-headers. + * + * If we requested a "no body", this is a good time to get + * out and return home. + */ + if(data->set.opt_no_body) + *stop_reading = TRUE; + else { + /* If we know the expected size of this document, we set the + maximum download size to the size of the expected + document or else, we won't know when to stop reading! + + Note that we set the download maximum even if we read a + "Connection: close" header, to make sure that + "Content-Length: 0" still prevents us from attempting to + read the (missing) response-body. + */ + /* According to RFC2616 section 4.4, we MUST ignore + Content-Length: headers if we are now receiving data + using chunked Transfer-Encoding. + */ + if(k->chunk) + k->maxdownload = k->size = -1; + } + if(-1 != k->size) { + /* We do this operation even if no_body is true, since this + data might be retrieved later with curl_easy_getinfo() + and its CURLINFO_CONTENT_LENGTH_DOWNLOAD option. */ + + Curl_pgrsSetDownloadSize(data, k->size); + k->maxdownload = k->size; + } + + /* If max download size is *zero* (nothing) we already + have nothing and can safely return ok now! */ + if(0 == k->maxdownload) + *stop_reading = TRUE; + + if(*stop_reading) { + /* we make sure that this socket isn't read more now */ + k->keepon &= ~KEEP_RECV; + } + + if(data->set.verbose) + Curl_debug(data, CURLINFO_HEADER_IN, + k->str_start, headerlen, conn); + break; /* exit header line loop */ + } + + /* We continue reading headers, so reset the line-based + header parsing variables hbufp && hbuflen */ + k->hbufp = data->state.headerbuff; + k->hbuflen = 0; + continue; + } + + /* + * Checks for special headers coming up. + */ + + if(!k->headerline++) { + /* This is the first header, it MUST be the error code line + or else we consider this to be the body right away! */ + int httpversion_major; + int rtspversion_major; + int nc = 0; +#ifdef CURL_DOES_CONVERSIONS +#define HEADER1 scratch +#define SCRATCHSIZE 21 + CURLcode res; + char scratch[SCRATCHSIZE+1]; /* "HTTP/major.minor 123" */ + /* We can't really convert this yet because we + don't know if it's the 1st header line or the body. + So we do a partial conversion into a scratch area, + leaving the data at k->p as-is. + */ + strncpy(&scratch[0], k->p, SCRATCHSIZE); + scratch[SCRATCHSIZE] = 0; /* null terminate */ + res = Curl_convert_from_network(data, + &scratch[0], + SCRATCHSIZE); + if(res) + /* Curl_convert_from_network calls failf if unsuccessful */ + return res; +#else +#define HEADER1 k->p /* no conversion needed, just use k->p */ +#endif /* CURL_DOES_CONVERSIONS */ + + if(conn->handler->protocol & CURLPROTO_HTTP) { + nc = sscanf(HEADER1, + " HTTP/%d.%d %3d", + &httpversion_major, + &conn->httpversion, + &k->httpcode); + if(nc==3) { + conn->httpversion += 10 * httpversion_major; + } + else { + /* this is the real world, not a Nirvana + NCSA 1.5.x returns this crap when asked for HTTP/1.1 + */ + nc=sscanf(HEADER1, " HTTP %3d", &k->httpcode); + conn->httpversion = 10; + + /* If user has set option HTTP200ALIASES, + compare header line against list of aliases + */ + if(!nc) { + if(checkhttpprefix(data, k->p)) { + nc = 1; + k->httpcode = 200; + conn->httpversion = 10; + } + } + } + } + else if(conn->handler->protocol & CURLPROTO_RTSP) { + nc = sscanf(HEADER1, + " RTSP/%d.%d %3d", + &rtspversion_major, + &conn->rtspversion, + &k->httpcode); + if(nc==3) { + conn->rtspversion += 10 * rtspversion_major; + conn->httpversion = 11; /* For us, RTSP acts like HTTP 1.1 */ + } + else { + /* TODO: do we care about the other cases here? */ + nc = 0; + } + } + + if(nc) { + data->info.httpcode = k->httpcode; + + data->info.httpversion = conn->httpversion; + if(!data->state.httpversion || + data->state.httpversion > conn->httpversion) + /* store the lowest server version we encounter */ + data->state.httpversion = conn->httpversion; + + /* + * This code executes as part of processing the header. As a + * result, it's not totally clear how to interpret the + * response code yet as that depends on what other headers may + * be present. 401 and 407 may be errors, but may be OK + * depending on how authentication is working. Other codes + * are definitely errors, so give up here. + */ + if(data->set.http_fail_on_error && (k->httpcode >= 400) && + ((k->httpcode != 401) || !conn->bits.user_passwd) && + ((k->httpcode != 407) || !conn->bits.proxy_user_passwd) ) { + + if(data->state.resume_from && + (data->set.httpreq==HTTPREQ_GET) && + (k->httpcode == 416)) { + /* "Requested Range Not Satisfiable", just proceed and + pretend this is no error */ + } + else { + /* serious error, go home! */ + print_http_error(data); + return CURLE_HTTP_RETURNED_ERROR; + } + } + + if(conn->httpversion == 10) { + /* Default action for HTTP/1.0 must be to close, unless + we get one of those fancy headers that tell us the + server keeps it open for us! */ + infof(data, "HTTP 1.0, assume close after body\n"); + conn->bits.close = TRUE; + } + else if(conn->httpversion >= 11 && + !conn->bits.close) { + + /* If HTTP version is >= 1.1 and connection is persistent + server supports pipelining. */ + DEBUGF(infof(data, + "HTTP 1.1 or later with persistent connection, " + "pipelining supported\n")); + conn->server_supports_pipelining = TRUE; + } + + switch(k->httpcode) { + case 204: + /* (quote from RFC2616, section 10.2.5): The server has + * fulfilled the request but does not need to return an + * entity-body ... The 204 response MUST NOT include a + * message-body, and thus is always terminated by the first + * empty line after the header fields. */ + /* FALLTHROUGH */ + case 304: + /* (quote from RFC2616, section 10.3.5): The 304 response + * MUST NOT contain a message-body, and thus is always + * terminated by the first empty line after the header + * fields. */ + if(data->set.timecondition) + data->info.timecond = TRUE; + k->size=0; + k->maxdownload=0; + k->ignorecl = TRUE; /* ignore Content-Length headers */ + break; + default: + /* nothing */ + break; + } + } + else { + k->header = FALSE; /* this is not a header line */ + break; + } + } + + result = Curl_convert_from_network(data, k->p, strlen(k->p)); + /* Curl_convert_from_network calls failf if unsuccessful */ + if(result) + return result; + + /* Check for Content-Length: header lines to get size */ + if(!k->ignorecl && !data->set.ignorecl && + checkprefix("Content-Length:", k->p)) { + curl_off_t contentlength = curlx_strtoofft(k->p+15, NULL, 10); + if(data->set.max_filesize && + contentlength > data->set.max_filesize) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + if(contentlength >= 0) { + k->size = contentlength; + k->maxdownload = k->size; + /* we set the progress download size already at this point + just to make it easier for apps/callbacks to extract this + info as soon as possible */ + Curl_pgrsSetDownloadSize(data, k->size); + } + else { + /* Negative Content-Length is really odd, and we know it + happens for example when older Apache servers send large + files */ + conn->bits.close = TRUE; + infof(data, "Negative content-length: %" FORMAT_OFF_T + ", closing after transfer\n", contentlength); + } + } + /* check for Content-Type: header lines to get the MIME-type */ + else if(checkprefix("Content-Type:", k->p)) { + char *contenttype = copy_header_value(k->p); + if(!contenttype) + return CURLE_OUT_OF_MEMORY; + if(!*contenttype) + /* ignore empty data */ + free(contenttype); + else { + Curl_safefree(data->info.contenttype); + data->info.contenttype = contenttype; + } + } + else if((conn->httpversion == 10) && + conn->bits.httpproxy && + Curl_compareheader(k->p, + "Proxy-Connection:", "keep-alive")) { + /* + * When a HTTP/1.0 reply comes when using a proxy, the + * 'Proxy-Connection: keep-alive' line tells us the + * connection will be kept alive for our pleasure. + * Default action for 1.0 is to close. + */ + conn->bits.close = FALSE; /* don't close when done */ + infof(data, "HTTP/1.0 proxy connection set to keep alive!\n"); + } + else if((conn->httpversion == 11) && + conn->bits.httpproxy && + Curl_compareheader(k->p, + "Proxy-Connection:", "close")) { + /* + * We get a HTTP/1.1 response from a proxy and it says it'll + * close down after this transfer. + */ + conn->bits.close = TRUE; /* close when done */ + infof(data, "HTTP/1.1 proxy connection set close!\n"); + } + else if((conn->httpversion == 10) && + Curl_compareheader(k->p, "Connection:", "keep-alive")) { + /* + * A HTTP/1.0 reply with the 'Connection: keep-alive' line + * tells us the connection will be kept alive for our + * pleasure. Default action for 1.0 is to close. + * + * [RFC2068, section 19.7.1] */ + conn->bits.close = FALSE; /* don't close when done */ + infof(data, "HTTP/1.0 connection set to keep alive!\n"); + } + else if(Curl_compareheader(k->p, "Connection:", "close")) { + /* + * [RFC 2616, section 8.1.2.1] + * "Connection: close" is HTTP/1.1 language and means that + * the connection will close when this request has been + * served. + */ + conn->bits.close = TRUE; /* close when done */ + } + else if(checkprefix("Transfer-Encoding:", k->p)) { + /* One or more encodings. We check for chunked and/or a compression + algorithm. */ + /* + * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding + * means that the server will send a series of "chunks". Each + * chunk starts with line with info (including size of the + * coming block) (terminated with CRLF), then a block of data + * with the previously mentioned size. There can be any amount + * of chunks, and a chunk-data set to zero signals the + * end-of-chunks. */ + + char *start; + + /* Find the first non-space letter */ + start = k->p + 18; + + for(;;) { + /* skip whitespaces and commas */ + while(*start && (ISSPACE(*start) || (*start == ','))) + start++; + + if(checkprefix("chunked", start)) { + k->chunk = TRUE; /* chunks coming our way */ + + /* init our chunky engine */ + Curl_httpchunk_init(conn); + + start += 7; + } + + if(k->auto_decoding) + /* TODO: we only support the first mentioned compression for now */ + break; + + if(checkprefix("identity", start)) { + k->auto_decoding = IDENTITY; + start += 8; + } + else if(checkprefix("deflate", start)) { + k->auto_decoding = DEFLATE; + start += 7; + } + else if(checkprefix("gzip", start)) { + k->auto_decoding = GZIP; + start += 4; + } + else if(checkprefix("x-gzip", start)) { + k->auto_decoding = GZIP; + start += 6; + } + else if(checkprefix("compress", start)) { + k->auto_decoding = COMPRESS; + start += 8; + } + else if(checkprefix("x-compress", start)) { + k->auto_decoding = COMPRESS; + start += 10; + } + else + /* unknown! */ + break; + + } + + } + else if(checkprefix("Content-Encoding:", k->p) && + data->set.str[STRING_ENCODING]) { + /* + * Process Content-Encoding. Look for the values: identity, + * gzip, deflate, compress, x-gzip and x-compress. x-gzip and + * x-compress are the same as gzip and compress. (Sec 3.5 RFC + * 2616). zlib cannot handle compress. However, errors are + * handled further down when the response body is processed + */ + char *start; + + /* Find the first non-space letter */ + start = k->p + 17; + while(*start && ISSPACE(*start)) + start++; + + /* Record the content-encoding for later use */ + if(checkprefix("identity", start)) + k->auto_decoding = IDENTITY; + else if(checkprefix("deflate", start)) + k->auto_decoding = DEFLATE; + else if(checkprefix("gzip", start) + || checkprefix("x-gzip", start)) + k->auto_decoding = GZIP; + else if(checkprefix("compress", start) + || checkprefix("x-compress", start)) + k->auto_decoding = COMPRESS; + } + else if(checkprefix("Content-Range:", k->p)) { + /* Content-Range: bytes [num]- + Content-Range: bytes: [num]- + Content-Range: [num]- + + The second format was added since Sun's webserver + JavaWebServer/1.1.1 obviously sends the header this way! + The third added since some servers use that! + */ + + char *ptr = k->p + 14; + + /* Move forward until first digit */ + while(*ptr && !ISDIGIT(*ptr)) + ptr++; + + k->offset = curlx_strtoofft(ptr, NULL, 10); + + if(data->state.resume_from == k->offset) + /* we asked for a resume and we got it */ + k->content_range = TRUE; + } +#if !defined(CURL_DISABLE_COOKIES) + else if(data->cookies && + checkprefix("Set-Cookie:", k->p)) { + Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, + CURL_LOCK_ACCESS_SINGLE); + Curl_cookie_add(data, + data->cookies, TRUE, k->p+11, + /* If there is a custom-set Host: name, use it + here, or else use real peer host name. */ + conn->allocptr.cookiehost? + conn->allocptr.cookiehost:conn->host.name, + data->state.path); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + } +#endif + else if(checkprefix("Last-Modified:", k->p) && + (data->set.timecondition || data->set.get_filetime) ) { + time_t secs=time(NULL); + k->timeofdoc = curl_getdate(k->p+strlen("Last-Modified:"), + &secs); + if(data->set.get_filetime) + data->info.filetime = (long)k->timeofdoc; + } + else if((checkprefix("WWW-Authenticate:", k->p) && + (401 == k->httpcode)) || + (checkprefix("Proxy-authenticate:", k->p) && + (407 == k->httpcode))) { + result = Curl_http_input_auth(conn, k->httpcode, k->p); + if(result) + return result; + } + else if((k->httpcode >= 300 && k->httpcode < 400) && + checkprefix("Location:", k->p) && + !data->req.location) { + /* this is the URL that the server advises us to use instead */ + char *location = copy_header_value(k->p); + if(!location) + return CURLE_OUT_OF_MEMORY; + if(!*location) + /* ignore empty data */ + free(location); + else { + data->req.location = location; + + if(data->set.http_follow_location) { + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->req.location); /* clone */ + if(!data->req.newurl) + return CURLE_OUT_OF_MEMORY; + + /* some cases of POST and PUT etc needs to rewind the data + stream at this point */ + result = http_perhapsrewind(conn); + if(result) + return result; + } + } + } + else if(conn->handler->protocol & CURLPROTO_RTSP) { + result = Curl_rtsp_parseheader(conn, k->p); + if(result) + return result; + } + + /* + * End of header-checks. Write them to the client. + */ + + writetype = CLIENTWRITE_HEADER; + if(data->set.include_header) + writetype |= CLIENTWRITE_BODY; + + if(data->set.verbose) + Curl_debug(data, CURLINFO_HEADER_IN, + k->p, (size_t)k->hbuflen, conn); + + result = Curl_client_write(conn, writetype, k->p, k->hbuflen); + if(result) + return result; + + data->info.header_size += (long)k->hbuflen; + data->req.headerbytecount += (long)k->hbuflen; + + /* reset hbufp pointer && hbuflen */ + k->hbufp = data->state.headerbuff; + k->hbuflen = 0; + } + while(!*stop_reading && *k->str); /* header line within buffer */ + + /* We might have reached the end of the header part here, but + there might be a non-header part left in the end of the read + buffer. */ + + return CURLE_OK; +} + +#endif /* CURL_DISABLE_HTTP */ diff --git a/lib/curl_http_chunks.c b/lib/curl_http_chunks.c new file mode 100644 index 000000000..2112f72ec --- /dev/null +++ b/lib/curl_http_chunks.c @@ -0,0 +1,397 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_HTTP + +#include "curl_urldata.h" /* it includes curl_http_chunks.h */ +#include "curl_sendf.h" /* for the client write stuff */ + +#include "curl_content_encoding.h" +#include "curl_http.h" +#include "curl_memory.h" +#include "curl_non_ascii.h" /* for Curl_convert_to_network prototype */ + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + * Chunk format (simplified): + * + * [ chunk extension ] CRLF + * CRLF + * + * Highlights from RFC2616 section 3.6 say: + + The chunked encoding modifies the body of a message in order to + transfer it as a series of chunks, each with its own size indicator, + followed by an OPTIONAL trailer containing entity-header fields. This + allows dynamically produced content to be transferred along with the + information necessary for the recipient to verify that it has + received the full message. + + Chunked-Body = *chunk + last-chunk + trailer + CRLF + + chunk = chunk-size [ chunk-extension ] CRLF + chunk-data CRLF + chunk-size = 1*HEX + last-chunk = 1*("0") [ chunk-extension ] CRLF + + chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) + chunk-ext-name = token + chunk-ext-val = token | quoted-string + chunk-data = chunk-size(OCTET) + trailer = *(entity-header CRLF) + + The chunk-size field is a string of hex digits indicating the size of + the chunk. The chunked encoding is ended by any chunk whose size is + zero, followed by the trailer, which is terminated by an empty line. + + */ + +/* Check for an ASCII hex digit. + We avoid the use of isxdigit to accommodate non-ASCII hosts. */ +static bool Curl_isxdigit(char digit) +{ + return ( (digit >= 0x30 && digit <= 0x39) /* 0-9 */ + || (digit >= 0x41 && digit <= 0x46) /* A-F */ + || (digit >= 0x61 && digit <= 0x66) /* a-f */ ) ? TRUE : FALSE; +} + +void Curl_httpchunk_init(struct connectdata *conn) +{ + struct Curl_chunker *chunk = &conn->chunk; + chunk->hexindex=0; /* start at 0 */ + chunk->dataleft=0; /* no data left yet! */ + chunk->state = CHUNK_HEX; /* we get hex first! */ +} + +/* + * chunk_read() returns a OK for normal operations, or a positive return code + * for errors. STOP means this sequence of chunks is complete. The 'wrote' + * argument is set to tell the caller how many bytes we actually passed to the + * client (for byte-counting and whatever). + * + * The states and the state-machine is further explained in the header file. + * + * This function always uses ASCII hex values to accommodate non-ASCII hosts. + * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. + */ +CHUNKcode Curl_httpchunk_read(struct connectdata *conn, + char *datap, + ssize_t datalen, + ssize_t *wrotep) +{ + CURLcode result=CURLE_OK; + struct SessionHandle *data = conn->data; + struct Curl_chunker *ch = &conn->chunk; + struct SingleRequest *k = &data->req; + size_t piece; + size_t length = (size_t)datalen; + size_t *wrote = (size_t *)wrotep; + + *wrote = 0; /* nothing's written yet */ + + /* the original data is written to the client, but we go on with the + chunk read process, to properly calculate the content length*/ + if(data->set.http_te_skip && !k->ignorebody) { + result = Curl_client_write(conn, CLIENTWRITE_BODY, datap, datalen); + if(result) + return CHUNKE_WRITE_ERROR; + } + + while(length) { + switch(ch->state) { + case CHUNK_HEX: + if(Curl_isxdigit(*datap)) { + if(ch->hexindex < MAXNUM_SIZE) { + ch->hexbuffer[ch->hexindex] = *datap; + datap++; + length--; + ch->hexindex++; + } + else { + return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */ + } + } + else { + if(0 == ch->hexindex) { + /* This is illegal data, we received junk where we expected + a hexadecimal digit. */ + return CHUNKE_ILLEGAL_HEX; + } + /* length and datap are unmodified */ + ch->hexbuffer[ch->hexindex]=0; + + /* convert to host encoding before calling strtoul */ + result = Curl_convert_from_network(conn->data, ch->hexbuffer, + ch->hexindex); + if(result) { + /* Curl_convert_from_network calls failf if unsuccessful */ + /* Treat it as a bad hex character */ + return(CHUNKE_ILLEGAL_HEX); + } + + ch->datasize=strtoul(ch->hexbuffer, NULL, 16); + ch->state = CHUNK_POSTHEX; + } + break; + + case CHUNK_POSTHEX: + /* In this state, we're waiting for CRLF to arrive. We support + this to allow so called chunk-extensions to show up here + before the CRLF comes. */ + if(*datap == 0x0d) + ch->state = CHUNK_CR; + length--; + datap++; + break; + + case CHUNK_CR: + /* waiting for the LF */ + if(*datap == 0x0a) { + /* we're now expecting data to come, unless size was zero! */ + if(0 == ch->datasize) { + ch->state = CHUNK_TRAILER; /* now check for trailers */ + conn->trlPos=0; + } + else { + ch->state = CHUNK_DATA; + } + } + else + /* previously we got a fake CR, go back to CR waiting! */ + ch->state = CHUNK_CR; + datap++; + length--; + break; + + case CHUNK_DATA: + /* we get pure and fine data + + We expect another 'datasize' of data. We have 'length' right now, + it can be more or less than 'datasize'. Get the smallest piece. + */ + piece = (ch->datasize >= length)?length:ch->datasize; + + /* Write the data portion available */ +#ifdef HAVE_LIBZ + switch (conn->data->set.http_ce_skip? + IDENTITY : data->req.auto_decoding) { + case IDENTITY: +#endif + if(!k->ignorebody) { + if(!data->set.http_te_skip) + result = Curl_client_write(conn, CLIENTWRITE_BODY, datap, + piece); + else + result = CURLE_OK; + } +#ifdef HAVE_LIBZ + break; + + case DEFLATE: + /* update data->req.keep.str to point to the chunk data. */ + data->req.str = datap; + result = Curl_unencode_deflate_write(conn, &data->req, + (ssize_t)piece); + break; + + case GZIP: + /* update data->req.keep.str to point to the chunk data. */ + data->req.str = datap; + result = Curl_unencode_gzip_write(conn, &data->req, + (ssize_t)piece); + break; + + case COMPRESS: + default: + failf (conn->data, + "Unrecognized content encoding type. " + "libcurl understands `identity', `deflate' and `gzip' " + "content encodings."); + return CHUNKE_BAD_ENCODING; + } +#endif + + if(result) + return CHUNKE_WRITE_ERROR; + + *wrote += piece; + + ch->datasize -= piece; /* decrease amount left to expect */ + datap += piece; /* move read pointer forward */ + length -= piece; /* decrease space left in this round */ + + if(0 == ch->datasize) + /* end of data this round, we now expect a trailing CRLF */ + ch->state = CHUNK_POSTCR; + break; + + case CHUNK_POSTCR: + if(*datap == 0x0d) { + ch->state = CHUNK_POSTLF; + datap++; + length--; + } + else + return CHUNKE_BAD_CHUNK; + + break; + + case CHUNK_POSTLF: + if(*datap == 0x0a) { + /* + * The last one before we go back to hex state and start all + * over. + */ + Curl_httpchunk_init(conn); + datap++; + length--; + } + else + return CHUNKE_BAD_CHUNK; + + break; + + case CHUNK_TRAILER: + if(*datap == 0x0d) { + /* this is the end of a trailer, but if the trailer was zero bytes + there was no trailer and we move on */ + + if(conn->trlPos) { + /* we allocate trailer with 3 bytes extra room to fit this */ + conn->trailer[conn->trlPos++]=0x0d; + conn->trailer[conn->trlPos++]=0x0a; + conn->trailer[conn->trlPos]=0; + + /* Convert to host encoding before calling Curl_client_write */ + result = Curl_convert_from_network(conn->data, conn->trailer, + conn->trlPos); + if(result) + /* Curl_convert_from_network calls failf if unsuccessful */ + /* Treat it as a bad chunk */ + return CHUNKE_BAD_CHUNK; + + if(!data->set.http_te_skip) { + result = Curl_client_write(conn, CLIENTWRITE_HEADER, + conn->trailer, conn->trlPos); + if(result) + return CHUNKE_WRITE_ERROR; + } + conn->trlPos=0; + ch->state = CHUNK_TRAILER_CR; + } + else { + /* no trailer, we're on the final CRLF pair */ + ch->state = CHUNK_TRAILER_POSTCR; + break; /* don't advance the pointer */ + } + } + else { + /* conn->trailer is assumed to be freed in curl_url.c on a + connection basis */ + if(conn->trlPos >= conn->trlMax) { + /* we always allocate three extra bytes, just because when the full + header has been received we append CRLF\0 */ + char *ptr; + if(conn->trlMax) { + conn->trlMax *= 2; + ptr = realloc(conn->trailer, conn->trlMax + 3); + } + else { + conn->trlMax=128; + ptr = malloc(conn->trlMax + 3); + } + if(!ptr) + return CHUNKE_OUT_OF_MEMORY; + conn->trailer = ptr; + } + conn->trailer[conn->trlPos++]=*datap; + } + datap++; + length--; + break; + + case CHUNK_TRAILER_CR: + if(*datap == 0x0a) { + ch->state = CHUNK_TRAILER_POSTCR; + datap++; + length--; + } + else + return CHUNKE_BAD_CHUNK; + break; + + case CHUNK_TRAILER_POSTCR: + /* We enter this state when a CR should arrive so we expect to + have to first pass a CR before we wait for LF */ + if(*datap != 0x0d) { + /* not a CR then it must be another header in the trailer */ + ch->state = CHUNK_TRAILER; + break; + } + datap++; + length--; + /* now wait for the final LF */ + ch->state = CHUNK_STOP; + break; + + case CHUNK_STOPCR: + /* Read the final CRLF that ends all chunk bodies */ + + if(*datap == 0x0d) { + ch->state = CHUNK_STOP; + datap++; + length--; + } + else + return CHUNKE_BAD_CHUNK; + break; + + case CHUNK_STOP: + if(*datap == 0x0a) { + length--; + + /* Record the length of any data left in the end of the buffer + even if there's no more chunks to read */ + + ch->dataleft = length; + return CHUNKE_STOP; /* return stop */ + } + else + return CHUNKE_BAD_CHUNK; + + default: + return CHUNKE_STATE_ERROR; + } + } + return CHUNKE_OK; +} +#endif /* CURL_DISABLE_HTTP */ diff --git a/lib/curl_http_digest.c b/lib/curl_http_digest.c new file mode 100644 index 000000000..dae679903 --- /dev/null +++ b/lib/curl_http_digest.c @@ -0,0 +1,583 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_rawstr.h" +#include "curl_base64.h" +#include "curl_md5.h" +#include "curl_http_digest.h" +#include "curl_strtok.h" +#include "curl_url.h" +#include "curl_memory.h" +#include "curl_non_ascii.h" /* included for Curl_convert_... prototypes */ +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#define MAX_VALUE_LENGTH 256 +#define MAX_CONTENT_LENGTH 1024 + +static void digest_cleanup_one(struct digestdata *dig); + +/* + * Return 0 on success and then the buffers are filled in fine. + * + * Non-zero means failure to parse. + */ +static int get_pair(const char *str, char *value, char *content, + const char **endptr) +{ + int c; + bool starts_with_quote = FALSE; + bool escape = FALSE; + + for(c=MAX_VALUE_LENGTH-1; (*str && (*str != '=') && c--); ) + *value++ = *str++; + *value=0; + + if('=' != *str++) + /* eek, no match */ + return 1; + + if('\"' == *str) { + /* this starts with a quote so it must end with one as well! */ + str++; + starts_with_quote = TRUE; + } + + for(c=MAX_CONTENT_LENGTH-1; *str && c--; str++) { + switch(*str) { + case '\\': + if(!escape) { + /* possibly the start of an escaped quote */ + escape = TRUE; + *content++ = '\\'; /* even though this is an escape character, we still + store it as-is in the target buffer */ + continue; + } + break; + case ',': + if(!starts_with_quote) { + /* this signals the end of the content if we didn't get a starting + quote and then we do "sloppy" parsing */ + c=0; /* the end */ + continue; + } + break; + case '\r': + case '\n': + /* end of string */ + c=0; + continue; + case '\"': + if(!escape && starts_with_quote) { + /* end of string */ + c=0; + continue; + } + break; + } + escape = FALSE; + *content++ = *str; + } + *content=0; + + *endptr = str; + + return 0; /* all is fine! */ +} + +/* Test example headers: + +WWW-Authenticate: Digest realm="testrealm", nonce="1053604598" +Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" + +*/ + +CURLdigest Curl_input_digest(struct connectdata *conn, + bool proxy, + const char *header) /* rest of the *-authenticate: + header */ +{ + char *token = NULL; + char *tmp = NULL; + bool foundAuth = FALSE; + bool foundAuthInt = FALSE; + struct SessionHandle *data=conn->data; + bool before = FALSE; /* got a nonce before */ + struct digestdata *d; + + if(proxy) { + d = &data->state.proxydigest; + } + else { + d = &data->state.digest; + } + + /* skip initial whitespaces */ + while(*header && ISSPACE(*header)) + header++; + + if(checkprefix("Digest", header)) { + header += strlen("Digest"); + + /* If we already have received a nonce, keep that in mind */ + if(d->nonce) + before = TRUE; + + /* clear off any former leftovers and init to defaults */ + digest_cleanup_one(d); + + for(;;) { + char value[MAX_VALUE_LENGTH]; + char content[MAX_CONTENT_LENGTH]; + + while(*header && ISSPACE(*header)) + header++; + + /* extract a value=content pair */ + if(!get_pair(header, value, content, &header)) { + if(Curl_raw_equal(value, "nonce")) { + d->nonce = strdup(content); + if(!d->nonce) + return CURLDIGEST_NOMEM; + } + else if(Curl_raw_equal(value, "stale")) { + if(Curl_raw_equal(content, "true")) { + d->stale = TRUE; + d->nc = 1; /* we make a new nonce now */ + } + } + else if(Curl_raw_equal(value, "realm")) { + d->realm = strdup(content); + if(!d->realm) + return CURLDIGEST_NOMEM; + } + else if(Curl_raw_equal(value, "opaque")) { + d->opaque = strdup(content); + if(!d->opaque) + return CURLDIGEST_NOMEM; + } + else if(Curl_raw_equal(value, "qop")) { + char *tok_buf; + /* tokenize the list and choose auth if possible, use a temporary + clone of the buffer since strtok_r() ruins it */ + tmp = strdup(content); + if(!tmp) + return CURLDIGEST_NOMEM; + token = strtok_r(tmp, ",", &tok_buf); + while(token != NULL) { + if(Curl_raw_equal(token, "auth")) { + foundAuth = TRUE; + } + else if(Curl_raw_equal(token, "auth-int")) { + foundAuthInt = TRUE; + } + token = strtok_r(NULL, ",", &tok_buf); + } + free(tmp); + /*select only auth o auth-int. Otherwise, ignore*/ + if(foundAuth) { + d->qop = strdup("auth"); + if(!d->qop) + return CURLDIGEST_NOMEM; + } + else if(foundAuthInt) { + d->qop = strdup("auth-int"); + if(!d->qop) + return CURLDIGEST_NOMEM; + } + } + else if(Curl_raw_equal(value, "algorithm")) { + d->algorithm = strdup(content); + if(!d->algorithm) + return CURLDIGEST_NOMEM; + if(Curl_raw_equal(content, "MD5-sess")) + d->algo = CURLDIGESTALGO_MD5SESS; + else if(Curl_raw_equal(content, "MD5")) + d->algo = CURLDIGESTALGO_MD5; + else + return CURLDIGEST_BADALGO; + } + else { + /* unknown specifier, ignore it! */ + } + } + else + break; /* we're done here */ + + /* pass all additional spaces here */ + while(*header && ISSPACE(*header)) + header++; + if(',' == *header) + /* allow the list to be comma-separated */ + header++; + } + /* We had a nonce since before, and we got another one now without + 'stale=true'. This means we provided bad credentials in the previous + request */ + if(before && !d->stale) + return CURLDIGEST_BAD; + + /* We got this header without a nonce, that's a bad Digest line! */ + if(!d->nonce) + return CURLDIGEST_BAD; + } + else + /* else not a digest, get out */ + return CURLDIGEST_NONE; + + return CURLDIGEST_FINE; +} + +/* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/ +static void md5_to_ascii(unsigned char *source, /* 16 bytes */ + unsigned char *dest) /* 33 bytes */ +{ + int i; + for(i=0; i<16; i++) + snprintf((char *)&dest[i*2], 3, "%02x", source[i]); +} + +CURLcode Curl_output_digest(struct connectdata *conn, + bool proxy, + const unsigned char *request, + const unsigned char *uripath) +{ + /* We have a Digest setup for this, use it! Now, to get all the details for + this sorted out, I must urge you dear friend to read up on the RFC2617 + section 3.2.2, */ + unsigned char md5buf[16]; /* 16 bytes/128 bits */ + unsigned char request_digest[33]; + unsigned char *md5this; + unsigned char *ha1; + unsigned char ha2[33];/* 32 digits and 1 zero byte */ + char cnoncebuf[33]; + char *cnonce = NULL; + size_t cnonce_sz = 0; + char *tmp = NULL; + struct timeval now; + + char **allocuserpwd; + const char *userp; + const char *passwdp; + struct auth *authp; + + struct SessionHandle *data = conn->data; + struct digestdata *d; + CURLcode rc; +/* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines. + It converts digest text to ASCII so the MD5 will be correct for + what ultimately goes over the network. +*/ +#define CURL_OUTPUT_DIGEST_CONV(a, b) \ + rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \ + if(rc != CURLE_OK) { \ + free(b); \ + return rc; \ + } + + if(proxy) { + d = &data->state.proxydigest; + allocuserpwd = &conn->allocptr.proxyuserpwd; + userp = conn->proxyuser; + passwdp = conn->proxypasswd; + authp = &data->state.authproxy; + } + else { + d = &data->state.digest; + allocuserpwd = &conn->allocptr.userpwd; + userp = conn->user; + passwdp = conn->passwd; + authp = &data->state.authhost; + } + + if(*allocuserpwd) { + Curl_safefree(*allocuserpwd); + *allocuserpwd = NULL; + } + + /* not set means empty */ + if(!userp) + userp=""; + + if(!passwdp) + passwdp=""; + + if(!d->nonce) { + authp->done = FALSE; + return CURLE_OK; + } + authp->done = TRUE; + + if(!d->nc) + d->nc = 1; + + if(!d->cnonce) { + /* Generate a cnonce */ + now = Curl_tvnow(); + snprintf(cnoncebuf, sizeof(cnoncebuf), "%32ld", + (long)now.tv_sec + now.tv_usec); + + rc = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), + &cnonce, &cnonce_sz); + if(rc) + return rc; + d->cnonce = cnonce; + } + + /* + if the algorithm is "MD5" or unspecified (which then defaults to MD5): + + A1 = unq(username-value) ":" unq(realm-value) ":" passwd + + if the algorithm is "MD5-sess" then: + + A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) + ":" unq(nonce-value) ":" unq(cnonce-value) + */ + + md5this = (unsigned char *) + aprintf("%s:%s:%s", userp, d->realm, passwdp); + if(!md5this) + return CURLE_OUT_OF_MEMORY; + + CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ + Curl_md5it(md5buf, md5this); + free(md5this); /* free this again */ + + ha1 = malloc(33); /* 32 digits and 1 zero byte */ + if(!ha1) + return CURLE_OUT_OF_MEMORY; + + md5_to_ascii(md5buf, ha1); + + if(d->algo == CURLDIGESTALGO_MD5SESS) { + /* nonce and cnonce are OUTSIDE the hash */ + tmp = aprintf("%s:%s:%s", ha1, d->nonce, d->cnonce); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */ + Curl_md5it(md5buf, (unsigned char *)tmp); + free(tmp); /* free this again */ + md5_to_ascii(md5buf, ha1); + } + + /* + If the "qop" directive's value is "auth" or is unspecified, then A2 is: + + A2 = Method ":" digest-uri-value + + If the "qop" value is "auth-int", then A2 is: + + A2 = Method ":" digest-uri-value ":" H(entity-body) + + (The "Method" value is the HTTP request method as specified in section + 5.1.1 of RFC 2616) + */ + + /* So IE browsers < v7 cut off the URI part at the query part when they + evaluate the MD5 and some (IIS?) servers work with them so we may need to + do the Digest IE-style. Note that the different ways cause different MD5 + sums to get sent. + + Apache servers can be set to do the Digest IE-style automatically using + the BrowserMatch feature: + http://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie + + Further details on Digest implementation differences: + http://www.fngtps.com/2006/09/http-authentication + */ + if(authp->iestyle && ((tmp = strchr((char *)uripath, '?')) != NULL)) { + md5this = (unsigned char *)aprintf("%s:%.*s", request, + curlx_sztosi(tmp - (char *)uripath), + uripath); + } + else + md5this = (unsigned char *)aprintf("%s:%s", request, uripath); + + if(!md5this) { + free(ha1); + return CURLE_OUT_OF_MEMORY; + } + + if(d->qop && Curl_raw_equal(d->qop, "auth-int")) { + /* We don't support auth-int at the moment. I can't see a easy way to get + entity-body here */ + /* TODO: Append H(entity-body)*/ + } + CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ + Curl_md5it(md5buf, md5this); + free(md5this); /* free this again */ + md5_to_ascii(md5buf, ha2); + + if(d->qop) { + md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s", + ha1, + d->nonce, + d->nc, + d->cnonce, + d->qop, + ha2); + } + else { + md5this = (unsigned char *)aprintf("%s:%s:%s", + ha1, + d->nonce, + ha2); + } + free(ha1); + if(!md5this) + return CURLE_OUT_OF_MEMORY; + + CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ + Curl_md5it(md5buf, md5this); + free(md5this); /* free this again */ + md5_to_ascii(md5buf, request_digest); + + /* for test case 64 (snooped from a Mozilla 1.3a request) + + Authorization: Digest username="testuser", realm="testrealm", \ + nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" + */ + + if(d->qop) { + *allocuserpwd = + aprintf( "%sAuthorization: Digest " + "username=\"%s\", " + "realm=\"%s\", " + "nonce=\"%s\", " + "uri=\"%s\", " + "cnonce=\"%s\", " + "nc=%08x, " + "qop=%s, " + "response=\"%s\"", + proxy?"Proxy-":"", + userp, + d->realm, + d->nonce, + uripath, /* this is the PATH part of the URL */ + d->cnonce, + d->nc, + d->qop, + request_digest); + + if(Curl_raw_equal(d->qop, "auth")) + d->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded + which tells to the server how many times you are using the + same nonce in the qop=auth mode. */ + } + else { + *allocuserpwd = + aprintf( "%sAuthorization: Digest " + "username=\"%s\", " + "realm=\"%s\", " + "nonce=\"%s\", " + "uri=\"%s\", " + "response=\"%s\"", + proxy?"Proxy-":"", + userp, + d->realm, + d->nonce, + uripath, /* this is the PATH part of the URL */ + request_digest); + } + if(!*allocuserpwd) + return CURLE_OUT_OF_MEMORY; + + /* Add optional fields */ + if(d->opaque) { + /* append opaque */ + tmp = aprintf("%s, opaque=\"%s\"", *allocuserpwd, d->opaque); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + free(*allocuserpwd); + *allocuserpwd = tmp; + } + + if(d->algorithm) { + /* append algorithm */ + tmp = aprintf("%s, algorithm=\"%s\"", *allocuserpwd, d->algorithm); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + free(*allocuserpwd); + *allocuserpwd = tmp; + } + + /* append CRLF + zero (3 bytes) to the userpwd header */ + tmp = realloc(*allocuserpwd, strlen(*allocuserpwd) + 3); + if(!tmp) + return CURLE_OUT_OF_MEMORY; + strcat(tmp, "\r\n"); + *allocuserpwd = tmp; + + return CURLE_OK; +} + +static void digest_cleanup_one(struct digestdata *d) +{ + if(d->nonce) + free(d->nonce); + d->nonce = NULL; + + if(d->cnonce) + free(d->cnonce); + d->cnonce = NULL; + + if(d->realm) + free(d->realm); + d->realm = NULL; + + if(d->opaque) + free(d->opaque); + d->opaque = NULL; + + if(d->qop) + free(d->qop); + d->qop = NULL; + + if(d->algorithm) + free(d->algorithm); + d->algorithm = NULL; + + d->nc = 0; + d->algo = CURLDIGESTALGO_MD5; /* default algorithm */ + d->stale = FALSE; /* default means normal, not stale */ +} + + +void Curl_digest_cleanup(struct SessionHandle *data) +{ + digest_cleanup_one(&data->state.digest); + digest_cleanup_one(&data->state.proxydigest); +} + +#endif diff --git a/lib/curl_http_negotiate.c b/lib/curl_http_negotiate.c new file mode 100644 index 000000000..446c49bb9 --- /dev/null +++ b/lib/curl_http_negotiate.c @@ -0,0 +1,373 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_GSSAPI +#ifdef HAVE_OLD_GSSMIT +#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +#define NCOMPAT 1 +#endif + +#ifndef CURL_DISABLE_HTTP + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_gssapi.h" +#include "curl_rawstr.h" +#include "curl_base64.h" +#include "curl_http_negotiate.h" +#include "curl_memory.h" +#include "curl_url.h" + +#ifdef HAVE_SPNEGO +# include +# ifdef USE_SSLEAY +# ifdef USE_OPENSSL +# include +# else +# include +# endif +# else +# error "Can't compile SPNEGO support without OpenSSL." +# endif +#endif + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +static int +get_gss_name(struct connectdata *conn, bool proxy, gss_name_t *server) +{ + struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: + &conn->data->state.negotiate; + OM_uint32 major_status, minor_status; + gss_buffer_desc token = GSS_C_EMPTY_BUFFER; + char name[2048]; + const char* service; + + /* GSSAPI implementation by Globus (known as GSI) requires the name to be + of form "/" instead of @ (ie. slash instead + of at-sign). Also GSI servers are often identified as 'host' not 'khttp'. + Change following lines if you want to use GSI */ + + /* IIS uses the @ form but uses 'http' as the service name */ + + if(neg_ctx->gss) + service = "KHTTP"; + else + service = "HTTP"; + + token.length = strlen(service) + 1 + strlen(proxy ? conn->proxy.name : + conn->host.name) + 1; + if(token.length + 1 > sizeof(name)) + return EMSGSIZE; + + snprintf(name, sizeof(name), "%s@%s", service, proxy ? conn->proxy.name : + conn->host.name); + + token.value = (void *) name; + major_status = gss_import_name(&minor_status, + &token, + GSS_C_NT_HOSTBASED_SERVICE, + server); + + return GSS_ERROR(major_status) ? -1 : 0; +} + +static void +log_gss_error(struct connectdata *conn, OM_uint32 error_status, + const char *prefix) +{ + OM_uint32 maj_stat, min_stat; + OM_uint32 msg_ctx = 0; + gss_buffer_desc status_string; + char buf[1024]; + size_t len; + + snprintf(buf, sizeof(buf), "%s", prefix); + len = strlen(buf); + do { + maj_stat = gss_display_status(&min_stat, + error_status, + GSS_C_MECH_CODE, + GSS_C_NO_OID, + &msg_ctx, + &status_string); + if(sizeof(buf) > len + status_string.length + 1) { + snprintf(buf + len, sizeof(buf) - len, + ": %s", (char*) status_string.value); + len += status_string.length; + } + gss_release_buffer(&min_stat, &status_string); + } while(!GSS_ERROR(maj_stat) && msg_ctx != 0); + + infof(conn->data, "%s\n", buf); +} + +/* returning zero (0) means success, everything else is treated as "failure" + with no care exactly what the failure was */ +int Curl_input_negotiate(struct connectdata *conn, bool proxy, + const char *header) +{ + struct SessionHandle *data = conn->data; + struct negotiatedata *neg_ctx = proxy?&data->state.proxyneg: + &data->state.negotiate; + OM_uint32 major_status, minor_status, minor_status2; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + int ret; + size_t len; + size_t rawlen = 0; + bool gss; + const char* protocol; + CURLcode error; + + while(*header && ISSPACE(*header)) + header++; + if(checkprefix("GSS-Negotiate", header)) { + protocol = "GSS-Negotiate"; + gss = TRUE; + } + else if(checkprefix("Negotiate", header)) { + protocol = "Negotiate"; + gss = FALSE; + } + else + return -1; + + if(neg_ctx->context) { + if(neg_ctx->gss != gss) { + return -1; + } + } + else { + neg_ctx->protocol = protocol; + neg_ctx->gss = gss; + } + + if(neg_ctx->context && neg_ctx->status == GSS_S_COMPLETE) { + /* We finished successfully our part of authentication, but server + * rejected it (since we're again here). Exit with an error since we + * can't invent anything better */ + Curl_cleanup_negotiate(data); + return -1; + } + + if(neg_ctx->server_name == NULL && + (ret = get_gss_name(conn, proxy, &neg_ctx->server_name))) + return ret; + + header += strlen(neg_ctx->protocol); + while(*header && ISSPACE(*header)) + header++; + + len = strlen(header); + if(len > 0) { + error = Curl_base64_decode(header, + (unsigned char **)&input_token.value, &rawlen); + if(error || rawlen == 0) + return -1; + input_token.length = rawlen; + +#ifdef HAVE_SPNEGO /* Handle SPNEGO */ + if(checkprefix("Negotiate", header)) { + ASN1_OBJECT * object = NULL; + unsigned char * spnegoToken = NULL; + size_t spnegoTokenLength = 0; + unsigned char * mechToken = NULL; + size_t mechTokenLength = 0; + + if(input_token.value == NULL) + return CURLE_OUT_OF_MEMORY; + + spnegoToken = malloc(input_token.length); + if(spnegoToken == NULL) + return CURLE_OUT_OF_MEMORY; + + spnegoTokenLength = input_token.length; + + object = OBJ_txt2obj ("1.2.840.113554.1.2.2", 1); + if(!parseSpnegoTargetToken(spnegoToken, + spnegoTokenLength, + NULL, + NULL, + &mechToken, + &mechTokenLength, + NULL, + NULL)) { + free(spnegoToken); + spnegoToken = NULL; + infof(data, "Parse SPNEGO Target Token failed\n"); + } + else { + free(input_token.value); + input_token.value = malloc(mechTokenLength); + if(input_token.value == NULL) + return CURLE_OUT_OF_MEMORY; + + memcpy(input_token.value, mechToken,mechTokenLength); + input_token.length = mechTokenLength; + free(mechToken); + mechToken = NULL; + infof(data, "Parse SPNEGO Target Token succeeded\n"); + } + } +#endif + } + + major_status = Curl_gss_init_sec_context(data, + &minor_status, + &neg_ctx->context, + neg_ctx->server_name, + GSS_C_NO_CHANNEL_BINDINGS, + &input_token, + &output_token, + NULL); + if(input_token.length > 0) + gss_release_buffer(&minor_status2, &input_token); + neg_ctx->status = major_status; + if(GSS_ERROR(major_status)) { + /* Curl_cleanup_negotiate(data) ??? */ + log_gss_error(conn, minor_status, + "gss_init_sec_context() failed: "); + return -1; + } + + if(output_token.length == 0) { + return -1; + } + + neg_ctx->output_token = output_token; + /* conn->bits.close = FALSE; */ + + return 0; +} + + +CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy) +{ + struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: + &conn->data->state.negotiate; + char *encoded = NULL; + size_t len = 0; + char *userp; + CURLcode error; + +#ifdef HAVE_SPNEGO /* Handle SPNEGO */ + if(checkprefix("Negotiate", neg_ctx->protocol)) { + ASN1_OBJECT * object = NULL; + unsigned char * spnegoToken = NULL; + size_t spnegoTokenLength = 0; + unsigned char * responseToken = NULL; + size_t responseTokenLength = 0; + + responseToken = malloc(neg_ctx->output_token.length); + if(responseToken == NULL) + return CURLE_OUT_OF_MEMORY; + memcpy(responseToken, neg_ctx->output_token.value, + neg_ctx->output_token.length); + responseTokenLength = neg_ctx->output_token.length; + + object=OBJ_txt2obj ("1.2.840.113554.1.2.2", 1); + if(!makeSpnegoInitialToken (object, + responseToken, + responseTokenLength, + &spnegoToken, + &spnegoTokenLength)) { + free(responseToken); + responseToken = NULL; + infof(conn->data, "Make SPNEGO Initial Token failed\n"); + } + else { + free(responseToken); + responseToken = NULL; + free(neg_ctx->output_token.value); + neg_ctx->output_token.value = malloc(spnegoTokenLength); + if(neg_ctx->output_token.value == NULL) { + free(spnegoToken); + spnegoToken = NULL; + return CURLE_OUT_OF_MEMORY; + } + memcpy(neg_ctx->output_token.value, spnegoToken,spnegoTokenLength); + neg_ctx->output_token.length = spnegoTokenLength; + free(spnegoToken); + spnegoToken = NULL; + infof(conn->data, "Make SPNEGO Initial Token succeeded\n"); + } + } +#endif + error = Curl_base64_encode(conn->data, + neg_ctx->output_token.value, + neg_ctx->output_token.length, + &encoded, &len); + if(error) { + Curl_safefree(neg_ctx->output_token.value); + neg_ctx->output_token.value = NULL; + return error; + } + + if(len == 0) { + Curl_safefree(neg_ctx->output_token.value); + neg_ctx->output_token.value = NULL; + return CURLE_REMOTE_ACCESS_DENIED; + } + + userp = aprintf("%sAuthorization: %s %s\r\n", proxy ? "Proxy-" : "", + neg_ctx->protocol, encoded); + + if(proxy) + conn->allocptr.proxyuserpwd = userp; + else + conn->allocptr.userpwd = userp; + free(encoded); + Curl_cleanup_negotiate (conn->data); + return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK; +} + +static void cleanup(struct negotiatedata *neg_ctx) +{ + OM_uint32 minor_status; + if(neg_ctx->context != GSS_C_NO_CONTEXT) + gss_delete_sec_context(&minor_status, &neg_ctx->context, GSS_C_NO_BUFFER); + + if(neg_ctx->output_token.length != 0) + gss_release_buffer(&minor_status, &neg_ctx->output_token); + + if(neg_ctx->server_name != GSS_C_NO_NAME) + gss_release_name(&minor_status, &neg_ctx->server_name); + + memset(neg_ctx, 0, sizeof(*neg_ctx)); +} + +void Curl_cleanup_negotiate(struct SessionHandle *data) +{ + cleanup(&data->state.negotiate); + cleanup(&data->state.proxyneg); +} + + +#endif +#endif diff --git a/lib/curl_http_negotiate_sspi.c b/lib/curl_http_negotiate_sspi.c new file mode 100644 index 000000000..d82b27110 --- /dev/null +++ b/lib/curl_http_negotiate_sspi.c @@ -0,0 +1,308 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_WINDOWS_SSPI + +#ifndef CURL_DISABLE_HTTP + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_rawstr.h" +#include "curl_warnless.h" +#include "curl_base64.h" +#include "curl_http_negotiate.h" +#include "curl_memory.h" +#include "curl_multibyte.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +static int +get_gss_name(struct connectdata *conn, bool proxy, + struct negotiatedata *neg_ctx) +{ + const char* service; + size_t length; + + if(proxy && !conn->proxy.name) + /* proxy auth requested but no given proxy name, error out! */ + return -1; + + /* GSSAPI implementation by Globus (known as GSI) requires the name to be + of form "/" instead of @ (ie. slash instead + of at-sign). Also GSI servers are often identified as 'host' not 'khttp'. + Change following lines if you want to use GSI */ + + /* IIS uses the @ form but uses 'http' as the service name, + and SSPI then generates an NTLM token. When using / a + Kerberos token is generated. */ + + if(neg_ctx->gss) + service = "KHTTP"; + else + service = "HTTP"; + + length = strlen(service) + 1 + strlen(proxy ? conn->proxy.name : + conn->host.name) + 1; + if(length + 1 > sizeof(neg_ctx->server_name)) + return EMSGSIZE; + + snprintf(neg_ctx->server_name, sizeof(neg_ctx->server_name), "%s/%s", + service, proxy ? conn->proxy.name : conn->host.name); + + return 0; +} + +/* returning zero (0) means success, everything else is treated as "failure" + with no care exactly what the failure was */ +int Curl_input_negotiate(struct connectdata *conn, bool proxy, + const char *header) +{ + struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: + &conn->data->state.negotiate; + BYTE *input_token = 0; + SecBufferDesc out_buff_desc; + SecBuffer out_sec_buff; + SecBufferDesc in_buff_desc; + SecBuffer in_sec_buff; + unsigned long context_attributes; + TimeStamp lifetime; + TCHAR *sname; + int ret; + size_t len = 0, input_token_len = 0; + bool gss = FALSE; + const char* protocol; + CURLcode error; + + while(*header && ISSPACE(*header)) + header++; + + if(checkprefix("GSS-Negotiate", header)) { + protocol = "GSS-Negotiate"; + gss = TRUE; + } + else if(checkprefix("Negotiate", header)) { + protocol = "Negotiate"; + gss = FALSE; + } + else + return -1; + + if(neg_ctx->context) { + if(neg_ctx->gss != gss) { + return -1; + } + } + else { + neg_ctx->protocol = protocol; + neg_ctx->gss = gss; + } + + if(neg_ctx->context && neg_ctx->status == SEC_E_OK) { + /* We finished successfully our part of authentication, but server + * rejected it (since we're again here). Exit with an error since we + * can't invent anything better */ + Curl_cleanup_negotiate(conn->data); + return -1; + } + + if(0 == strlen(neg_ctx->server_name)) { + ret = get_gss_name(conn, proxy, neg_ctx); + if(ret) + return ret; + } + + if(!neg_ctx->output_token) { + PSecPkgInfo SecurityPackage; + ret = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT("Negotiate"), + &SecurityPackage); + if(ret != SEC_E_OK) + return -1; + + /* Allocate input and output buffers according to the max token size + as indicated by the security package */ + neg_ctx->max_token_length = SecurityPackage->cbMaxToken; + neg_ctx->output_token = malloc(neg_ctx->max_token_length); + s_pSecFn->FreeContextBuffer(SecurityPackage); + } + + /* Obtain the input token, if any */ + header += strlen(neg_ctx->protocol); + while(*header && ISSPACE(*header)) + header++; + + len = strlen(header); + if(!len) { + /* first call in a new negotation, we have to acquire credentials, + and allocate memory for the context */ + + neg_ctx->credentials = malloc(sizeof(CredHandle)); + neg_ctx->context = malloc(sizeof(CtxtHandle)); + + if(!neg_ctx->credentials || !neg_ctx->context) + return -1; + + neg_ctx->status = + s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *) TEXT("Negotiate"), + SECPKG_CRED_OUTBOUND, NULL, NULL, + NULL, NULL, neg_ctx->credentials, + &lifetime); + if(neg_ctx->status != SEC_E_OK) + return -1; + } + else { + input_token = malloc(neg_ctx->max_token_length); + if(!input_token) + return -1; + + error = Curl_base64_decode(header, + (unsigned char **)&input_token, + &input_token_len); + if(error || input_token_len == 0) + return -1; + } + + /* prepare the output buffers, and input buffers if present */ + out_buff_desc.ulVersion = 0; + out_buff_desc.cBuffers = 1; + out_buff_desc.pBuffers = &out_sec_buff; + + out_sec_buff.cbBuffer = curlx_uztoul(neg_ctx->max_token_length); + out_sec_buff.BufferType = SECBUFFER_TOKEN; + out_sec_buff.pvBuffer = neg_ctx->output_token; + + + if(input_token) { + in_buff_desc.ulVersion = 0; + in_buff_desc.cBuffers = 1; + in_buff_desc.pBuffers = &in_sec_buff; + + in_sec_buff.cbBuffer = curlx_uztoul(input_token_len); + in_sec_buff.BufferType = SECBUFFER_TOKEN; + in_sec_buff.pvBuffer = input_token; + } + + sname = Curl_convert_UTF8_to_tchar(neg_ctx->server_name); + if(!sname) + return CURLE_OUT_OF_MEMORY; + + neg_ctx->status = s_pSecFn->InitializeSecurityContext( + neg_ctx->credentials, + input_token ? neg_ctx->context : 0, + sname, + ISC_REQ_CONFIDENTIALITY, + 0, + SECURITY_NATIVE_DREP, + input_token ? &in_buff_desc : 0, + 0, + neg_ctx->context, + &out_buff_desc, + &context_attributes, + &lifetime); + + Curl_unicodefree(sname); + + if(GSS_ERROR(neg_ctx->status)) + return -1; + + if(neg_ctx->status == SEC_I_COMPLETE_NEEDED || + neg_ctx->status == SEC_I_COMPLETE_AND_CONTINUE) { + neg_ctx->status = s_pSecFn->CompleteAuthToken(neg_ctx->context, + &out_buff_desc); + if(GSS_ERROR(neg_ctx->status)) + return -1; + } + + neg_ctx->output_token_length = out_sec_buff.cbBuffer; + + return 0; +} + + +CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy) +{ + struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: + &conn->data->state.negotiate; + char *encoded = NULL; + size_t len = 0; + char *userp; + CURLcode error; + + error = Curl_base64_encode(conn->data, + (const char*)neg_ctx->output_token, + neg_ctx->output_token_length, + &encoded, &len); + if(error) + return error; + + if(len == 0) + return CURLE_REMOTE_ACCESS_DENIED; + + userp = aprintf("%sAuthorization: %s %s\r\n", proxy ? "Proxy-" : "", + neg_ctx->protocol, encoded); + + if(proxy) + conn->allocptr.proxyuserpwd = userp; + else + conn->allocptr.userpwd = userp; + free(encoded); + Curl_cleanup_negotiate (conn->data); + return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK; +} + +static void cleanup(struct negotiatedata *neg_ctx) +{ + if(neg_ctx->context) { + s_pSecFn->DeleteSecurityContext(neg_ctx->context); + free(neg_ctx->context); + neg_ctx->context = 0; + } + + if(neg_ctx->credentials) { + s_pSecFn->FreeCredentialsHandle(neg_ctx->credentials); + free(neg_ctx->credentials); + neg_ctx->credentials = 0; + } + + if(neg_ctx->output_token) { + free(neg_ctx->output_token); + neg_ctx->output_token = 0; + } + + neg_ctx->max_token_length = 0; +} + +void Curl_cleanup_negotiate(struct SessionHandle *data) +{ + cleanup(&data->state.negotiate); + cleanup(&data->state.proxyneg); +} + + +#endif +#endif diff --git a/lib/curl_http_proxy.c b/lib/curl_http_proxy.c new file mode 100644 index 000000000..14ef9dc1e --- /dev/null +++ b/lib/curl_http_proxy.c @@ -0,0 +1,595 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) + +#include "curl_urldata.h" +#include +#include "curl_http_proxy.h" +#include "curl_sendf.h" +#include "curl_http.h" +#include "curl_url.h" +#include "curl_select.h" +#include "curl_rawstr.h" +#include "curl_progress.h" +#include "curl_non_ascii.h" +#include "curl_connect.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curlx.h" + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +CURLcode Curl_proxy_connect(struct connectdata *conn) +{ + if(conn->bits.tunnel_proxy && conn->bits.httpproxy) { +#ifndef CURL_DISABLE_PROXY + /* for [protocol] tunneled through HTTP proxy */ + struct HTTP http_proxy; + void *prot_save; + CURLcode result; + + /* BLOCKING */ + /* We want "seamless" operations through HTTP proxy tunnel */ + + /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the + * member conn->proto.http; we want [protocol] through HTTP and we have + * to change the member temporarily for connecting to the HTTP + * proxy. After Curl_proxyCONNECT we have to set back the member to the + * original pointer + * + * This function might be called several times in the multi interface case + * if the proxy's CONNTECT response is not instant. + */ + prot_save = conn->data->state.proto.generic; + memset(&http_proxy, 0, sizeof(http_proxy)); + conn->data->state.proto.http = &http_proxy; + conn->bits.close = FALSE; + result = Curl_proxyCONNECT(conn, FIRSTSOCKET, + conn->host.name, conn->remote_port); + conn->data->state.proto.generic = prot_save; + if(CURLE_OK != result) + return result; +#else + return CURLE_NOT_BUILT_IN; +#endif + } + /* no HTTP tunnel proxy, just return */ + return CURLE_OK; +} + +/* + * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This + * function will issue the necessary commands to get a seamless tunnel through + * this proxy. After that, the socket can be used just as a normal socket. + * + * This badly needs to be rewritten. CONNECT should be sent and dealt with + * like any ordinary HTTP request, and not specially crafted like this. This + * function only remains here like this for now since the rewrite is a bit too + * much work to do at the moment. + * + * This function is BLOCKING which is nasty for all multi interface using apps. + */ + +CURLcode Curl_proxyCONNECT(struct connectdata *conn, + int sockindex, + const char *hostname, + unsigned short remote_port) +{ + int subversion=0; + struct SessionHandle *data=conn->data; + struct SingleRequest *k = &data->req; + CURLcode result; + long timeout = + data->set.timeout?data->set.timeout:PROXY_TIMEOUT; /* in milliseconds */ + curl_socket_t tunnelsocket = conn->sock[sockindex]; + curl_off_t cl=0; + bool closeConnection = FALSE; + bool chunked_encoding = FALSE; + long check; + +#define SELECT_OK 0 +#define SELECT_ERROR 1 +#define SELECT_TIMEOUT 2 + int error = SELECT_OK; + + if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE) + return CURLE_OK; /* CONNECT is already completed */ + + conn->bits.proxy_connect_closed = FALSE; + + do { + if(TUNNEL_INIT == conn->tunnel_state[sockindex]) { + /* BEGIN CONNECT PHASE */ + char *host_port; + Curl_send_buffer *req_buffer; + + infof(data, "Establish HTTP proxy tunnel to %s:%hu\n", + hostname, remote_port); + + if(data->req.newurl) { + /* This only happens if we've looped here due to authentication + reasons, and we don't really use the newly cloned URL here + then. Just free() it. */ + free(data->req.newurl); + data->req.newurl = NULL; + } + + /* initialize a dynamic send-buffer */ + req_buffer = Curl_add_buffer_init(); + + if(!req_buffer) + return CURLE_OUT_OF_MEMORY; + + host_port = aprintf("%s:%hu", hostname, remote_port); + if(!host_port) { + free(req_buffer); + return CURLE_OUT_OF_MEMORY; + } + + /* Setup the proxy-authorization header, if any */ + result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE); + + free(host_port); + + if(CURLE_OK == result) { + char *host=(char *)""; + const char *proxyconn=""; + const char *useragent=""; + const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ? + "1.0" : "1.1"; + char *hostheader= /* host:port with IPv6 support */ + aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"", + hostname, conn->bits.ipv6_ip?"]":"", + remote_port); + if(!hostheader) { + free(req_buffer); + return CURLE_OUT_OF_MEMORY; + } + + if(!Curl_checkheaders(data, "Host:")) { + host = aprintf("Host: %s\r\n", hostheader); + if(!host) { + free(hostheader); + free(req_buffer); + return CURLE_OUT_OF_MEMORY; + } + } + if(!Curl_checkheaders(data, "Proxy-Connection:")) + proxyconn = "Proxy-Connection: Keep-Alive\r\n"; + + if(!Curl_checkheaders(data, "User-Agent:") && + data->set.str[STRING_USERAGENT]) + useragent = conn->allocptr.uagent; + + result = + Curl_add_bufferf(req_buffer, + "CONNECT %s HTTP/%s\r\n" + "%s" /* Host: */ + "%s" /* Proxy-Authorization */ + "%s" /* User-Agent */ + "%s", /* Proxy-Connection */ + hostheader, + http, + host, + conn->allocptr.proxyuserpwd? + conn->allocptr.proxyuserpwd:"", + useragent, + proxyconn); + + if(host && *host) + free(host); + free(hostheader); + + if(CURLE_OK == result) + result = Curl_add_custom_headers(conn, req_buffer); + + if(CURLE_OK == result) + /* CRLF terminate the request */ + result = Curl_add_bufferf(req_buffer, "\r\n"); + + if(CURLE_OK == result) { + /* Send the connect request to the proxy */ + /* BLOCKING */ + result = + Curl_add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, sockindex); + } + req_buffer = NULL; + if(result) + failf(data, "Failed sending CONNECT to proxy"); + } + + Curl_safefree(req_buffer); + if(result) + return result; + + conn->tunnel_state[sockindex] = TUNNEL_CONNECT; + } /* END CONNECT PHASE */ + + /* now we've issued the CONNECT and we're waiting to hear back - + we try not to block here in multi-mode because that might be a LONG + wait if the proxy cannot connect-through to the remote host. */ + + /* if timeout is requested, find out how much remaining time we have */ + check = timeout - /* timeout time */ + Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ + if(check <= 0) { + failf(data, "Proxy CONNECT aborted due to timeout"); + return CURLE_RECV_ERROR; + } + + /* if we're in multi-mode and we would block, return instead for a retry */ + if(Curl_if_multi == data->state.used_interface) { + if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0)) + /* return so we'll be called again polling-style */ + return CURLE_OK; + else { + DEBUGF(infof(data, + "Multi mode finished polling for response from " + "proxy CONNECT\n")); + } + } + else { + DEBUGF(infof(data, "Easy mode waiting response from proxy CONNECT\n")); + } + + /* at this point, either: + 1) we're in easy-mode and so it's okay to block waiting for a CONNECT + response + 2) we're in multi-mode and we didn't block - it's either an error or we + now have some data waiting. + In any case, the tunnel_connecting phase is over. */ + + { /* BEGIN NEGOTIATION PHASE */ + size_t nread; /* total size read */ + int perline; /* count bytes per line */ + int keepon=TRUE; + ssize_t gotbytes; + char *ptr; + char *line_start; + + ptr=data->state.buffer; + line_start = ptr; + + nread=0; + perline=0; + keepon=TRUE; + + while((nreadnow); /* spent time */ + if(check <= 0) { + failf(data, "Proxy CONNECT aborted due to timeout"); + error = SELECT_TIMEOUT; /* already too little time */ + break; + } + + /* loop every second at least, less if the timeout is near */ + switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, + check<1000L?check:1000)) { + case -1: /* select() error, stop reading */ + error = SELECT_ERROR; + failf(data, "Proxy CONNECT aborted due to select/poll error"); + break; + case 0: /* timeout */ + break; + default: + DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1); + result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread, + &gotbytes); + if(result==CURLE_AGAIN) + continue; /* go loop yourself */ + else if(result) + keepon = FALSE; + else if(gotbytes <= 0) { + keepon = FALSE; + if(data->set.proxyauth && data->state.authproxy.avail) { + /* proxy auth was requested and there was proxy auth available, + then deem this as "mere" proxy disconnect */ + conn->bits.proxy_connect_closed = TRUE; + } + else { + error = SELECT_ERROR; + failf(data, "Proxy CONNECT aborted"); + } + } + else { + /* + * We got a whole chunk of data, which can be anything from one + * byte to a set of lines and possibly just a piece of the last + * line. + */ + int i; + + nread += gotbytes; + + if(keepon > TRUE) { + /* This means we are currently ignoring a response-body */ + + nread = 0; /* make next read start over in the read buffer */ + ptr=data->state.buffer; + if(cl) { + /* A Content-Length based body: simply count down the counter + and make sure to break out of the loop when we're done! */ + cl -= gotbytes; + if(cl<=0) { + keepon = FALSE; + break; + } + } + else { + /* chunked-encoded body, so we need to do the chunked dance + properly to know when the end of the body is reached */ + CHUNKcode r; + ssize_t tookcareof=0; + + /* now parse the chunked piece of data so that we can + properly tell when the stream ends */ + r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof); + if(r == CHUNKE_STOP) { + /* we're done reading chunks! */ + infof(data, "chunk reading DONE\n"); + keepon = FALSE; + /* we did the full CONNECT treatment, go COMPLETE */ + conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; + } + else + infof(data, "Read %zd bytes of chunk, continue\n", + tookcareof); + } + } + else + for(i = 0; i < gotbytes; ptr++, i++) { + perline++; /* amount of bytes in this line so far */ + if(*ptr == 0x0a) { + char letter; + int writetype; + + /* convert from the network encoding */ + result = Curl_convert_from_network(data, line_start, + perline); + /* Curl_convert_from_network calls failf if unsuccessful */ + if(result) + return result; + + /* output debug if that is requested */ + if(data->set.verbose) + Curl_debug(data, CURLINFO_HEADER_IN, + line_start, (size_t)perline, conn); + + /* send the header to the callback */ + writetype = CLIENTWRITE_HEADER; + if(data->set.include_header) + writetype |= CLIENTWRITE_BODY; + + result = Curl_client_write(conn, writetype, line_start, + perline); + if(result) + return result; + + /* Newlines are CRLF, so the CR is ignored as the line isn't + really terminated until the LF comes. Treat a following CR + as end-of-headers as well.*/ + + if(('\r' == line_start[0]) || + ('\n' == line_start[0])) { + /* end of response-headers from the proxy */ + nread = 0; /* make next read start over in the read + buffer */ + ptr=data->state.buffer; + if((407 == k->httpcode) && !data->state.authproblem) { + /* If we get a 407 response code with content length + when we have no auth problem, we must ignore the + whole response-body */ + keepon = 2; + + if(cl) { + + infof(data, "Ignore %" FORMAT_OFF_T + " bytes of response-body\n", cl); + /* remove the remaining chunk of what we already + read */ + cl -= (gotbytes - i); + + if(cl<=0) + /* if the whole thing was already read, we are done! + */ + keepon=FALSE; + } + else if(chunked_encoding) { + CHUNKcode r; + /* We set ignorebody true here since the chunked + decoder function will acknowledge that. Pay + attention so that this is cleared again when this + function returns! */ + k->ignorebody = TRUE; + infof(data, "%zd bytes of chunk left\n", gotbytes-i); + + if(line_start[1] == '\n') { + /* this can only be a LF if the letter at index 0 + was a CR */ + line_start++; + i++; + } + + /* now parse the chunked piece of data so that we can + properly tell when the stream ends */ + r = Curl_httpchunk_read(conn, line_start+1, + gotbytes -i, &gotbytes); + if(r == CHUNKE_STOP) { + /* we're done reading chunks! */ + infof(data, "chunk reading DONE\n"); + keepon = FALSE; + /* we did the full CONNECT treatment, go to + COMPLETE */ + conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; + } + else + infof(data, "Read %zd bytes of chunk, continue\n", + gotbytes); + } + else { + /* without content-length or chunked encoding, we + can't keep the connection alive since the close is + the end signal so we bail out at once instead */ + keepon=FALSE; + } + } + else { + keepon = FALSE; + if(200 == data->info.httpproxycode) { + if(gotbytes - (i+1)) + failf(data, "Proxy CONNECT followed by %zd bytes " + "of opaque data. Data ignored (known bug #39)", + gotbytes - (i+1)); + } + } + /* we did the full CONNECT treatment, go to COMPLETE */ + conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; + break; /* breaks out of for-loop, not switch() */ + } + + /* keep a backup of the position we are about to blank */ + letter = line_start[perline]; + line_start[perline]=0; /* zero terminate the buffer */ + if((checkprefix("WWW-Authenticate:", line_start) && + (401 == k->httpcode)) || + (checkprefix("Proxy-authenticate:", line_start) && + (407 == k->httpcode))) { + result = Curl_http_input_auth(conn, k->httpcode, + line_start); + if(result) + return result; + } + else if(checkprefix("Content-Length:", line_start)) { + cl = curlx_strtoofft(line_start + + strlen("Content-Length:"), NULL, 10); + } + else if(Curl_compareheader(line_start, + "Connection:", "close")) + closeConnection = TRUE; + else if(Curl_compareheader(line_start, + "Transfer-Encoding:", + "chunked")) { + infof(data, "CONNECT responded chunked\n"); + chunked_encoding = TRUE; + /* init our chunky engine */ + Curl_httpchunk_init(conn); + } + else if(Curl_compareheader(line_start, + "Proxy-Connection:", "close")) + closeConnection = TRUE; + else if(2 == sscanf(line_start, "HTTP/1.%d %d", + &subversion, + &k->httpcode)) { + /* store the HTTP code from the proxy */ + data->info.httpproxycode = k->httpcode; + } + /* put back the letter we blanked out before */ + line_start[perline]= letter; + + perline=0; /* line starts over here */ + line_start = ptr+1; /* this skips the zero byte we wrote */ + } + } + } + break; + } /* switch */ + if(Curl_pgrsUpdate(conn)) + return CURLE_ABORTED_BY_CALLBACK; + } /* while there's buffer left and loop is requested */ + + if(error) + return CURLE_RECV_ERROR; + + if(data->info.httpproxycode != 200) { + /* Deal with the possibly already received authenticate + headers. 'newurl' is set to a new URL if we must loop. */ + result = Curl_http_auth_act(conn); + if(result) + return result; + + if(conn->bits.close) + /* the connection has been marked for closure, most likely in the + Curl_http_auth_act() function and thus we can kill it at once + below + */ + closeConnection = TRUE; + } + + if(closeConnection && data->req.newurl) { + /* Connection closed by server. Don't use it anymore */ + Curl_closesocket(conn, conn->sock[sockindex]); + conn->sock[sockindex] = CURL_SOCKET_BAD; + break; + } + } /* END NEGOTIATION PHASE */ + + /* If we are supposed to continue and request a new URL, which basically + * means the HTTP authentication is still going on so if the tunnel + * is complete we start over in INIT state */ + if(data->req.newurl && + (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) { + conn->tunnel_state[sockindex] = TUNNEL_INIT; + infof(data, "TUNNEL_STATE switched to: %d\n", + conn->tunnel_state[sockindex]); + } + + } while(data->req.newurl); + + if(200 != data->req.httpcode) { + failf(data, "Received HTTP code %d from proxy after CONNECT", + data->req.httpcode); + + if(closeConnection && data->req.newurl) + conn->bits.proxy_connect_closed = TRUE; + + /* to back to init state */ + conn->tunnel_state[sockindex] = TUNNEL_INIT; + + return CURLE_RECV_ERROR; + } + + conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; + + /* If a proxy-authorization header was used for the proxy, then we should + make sure that it isn't accidentally used for the document request + after we've connected. So let's free and clear it here. */ + Curl_safefree(conn->allocptr.proxyuserpwd); + conn->allocptr.proxyuserpwd = NULL; + + data->state.authproxy.done = TRUE; + + infof (data, "Proxy replied OK to CONNECT request\n"); + data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */ + return CURLE_OK; +} +#endif /* CURL_DISABLE_PROXY */ diff --git a/lib/curl_idn_win32.c b/lib/curl_idn_win32.c new file mode 100644 index 000000000..eca225483 --- /dev/null +++ b/lib/curl_idn_win32.c @@ -0,0 +1,85 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + + /* + * IDN conversions using Windows kernel32 and normaliz libraries. + */ + +#include "curl_setup.h" + +#ifdef USE_WIN32_IDN + +#include "curl_multibyte.h" + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifdef WANT_IDN_PROTOTYPES +WINBASEAPI int WINAPI IdnToAscii(DWORD, const WCHAR *, int, WCHAR *, int); +WINBASEAPI int WINAPI IdnToUnicode(DWORD, const WCHAR *, int, WCHAR *, int); +#endif + +#define IDN_MAX_LENGTH 255 + +int curl_win32_idn_to_ascii(const char *in, char **out); +int curl_win32_ascii_to_idn(const char *in, size_t in_len, char **out_utf8); + +int curl_win32_idn_to_ascii(const char *in, char **out) +{ + wchar_t *in_w = Curl_convert_UTF8_to_wchar(in); + if(in_w) { + wchar_t punycode[IDN_MAX_LENGTH]; + if(IdnToAscii(0, in_w, -1, punycode, IDN_MAX_LENGTH) == 0) { + wprintf(L"ERROR %d converting to Punycode\n", GetLastError()); + free(in_w); + return 0; + } + free(in_w); + + *out = Curl_convert_wchar_to_UTF8(punycode); + if(!*out) + return 0; + } + return 1; +} + +int curl_win32_ascii_to_idn(const char *in, size_t in_len, char **out_utf8) +{ + (void)in_len; /* unused */ + if(in) { + WCHAR unicode[IDN_MAX_LENGTH]; + + if(IdnToUnicode(0, (wchar_t *)in, -1, unicode, IDN_MAX_LENGTH) == 0) { + wprintf(L"ERROR %d converting to Punycode\n", GetLastError()); + return 0; + } + else { + *out_utf8 = Curl_convert_wchar_to_UTF8(unicode); + if(!*out_utf8) + return 0; + } + } + return 1; +} + +#endif /* USE_WIN32_IDN */ diff --git a/lib/curl_if2ip.c b/lib/curl_if2ip.c new file mode 100644 index 000000000..db9c78446 --- /dev/null +++ b/lib/curl_if2ip.c @@ -0,0 +1,189 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif +#ifdef HAVE_NET_IF_H +# include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif +#ifdef HAVE_NETDB_H +# include +#endif +#ifdef HAVE_SYS_SOCKIO_H +# include +#endif +#ifdef HAVE_IFADDRS_H +# include +#endif +#ifdef HAVE_STROPTS_H +# include +#endif +#ifdef __VMS +# include +#endif + +#include "curl_inet_ntop.h" +#include "curl_strequal.h" +#include "curl_if2ip.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* ------------------------------------------------------------------ */ + +#if defined(HAVE_GETIFADDRS) + +bool Curl_if_is_interface_name(const char *interf) +{ + bool result = FALSE; + + struct ifaddrs *iface, *head; + + if(getifaddrs(&head) >= 0) { + for(iface=head; iface != NULL; iface=iface->ifa_next) { + if(curl_strequal(iface->ifa_name, interf)) { + result = TRUE; + break; + } + } + freeifaddrs(head); + } + return result; +} + +char *Curl_if2ip(int af, const char *interf, char *buf, int buf_size) +{ + struct ifaddrs *iface, *head; + char *ip = NULL; + + if(getifaddrs(&head) >= 0) { + for(iface=head; iface != NULL; iface=iface->ifa_next) { + if((iface->ifa_addr != NULL) && + (iface->ifa_addr->sa_family == af) && + curl_strequal(iface->ifa_name, interf)) { + void *addr; + char scope[12]=""; +#ifdef ENABLE_IPV6 + if(af == AF_INET6) { + unsigned int scopeid = 0; + addr = &((struct sockaddr_in6 *)iface->ifa_addr)->sin6_addr; +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID + /* Include the scope of this interface as part of the address */ + scopeid = ((struct sockaddr_in6 *)iface->ifa_addr)->sin6_scope_id; +#endif + if(scopeid) + snprintf(scope, sizeof(scope), "%%%u", scopeid); + } + else +#endif + addr = &((struct sockaddr_in *)iface->ifa_addr)->sin_addr; + ip = (char *) Curl_inet_ntop(af, addr, buf, buf_size); + strlcat(buf, scope, buf_size); + break; + } + } + freeifaddrs(head); + } + return ip; +} + +#elif defined(HAVE_IOCTL_SIOCGIFADDR) + +bool Curl_if_is_interface_name(const char *interf) +{ + /* This is here just to support the old interfaces */ + char buf[256]; + + char *ip = Curl_if2ip(AF_INET, interf, buf, sizeof(buf)); + + return (ip != NULL) ? TRUE : FALSE; +} + +char *Curl_if2ip(int af, const char *interf, char *buf, int buf_size) +{ + struct ifreq req; + struct in_addr in; + struct sockaddr_in *s; + curl_socket_t dummy; + size_t len; + char *ip; + + if(!interf || (af != AF_INET)) + return NULL; + + len = strlen(interf); + if(len >= sizeof(req.ifr_name)) + return NULL; + + dummy = socket(AF_INET, SOCK_STREAM, 0); + if(CURL_SOCKET_BAD == dummy) + return NULL; + + memset(&req, 0, sizeof(req)); + memcpy(req.ifr_name, interf, len+1); + req.ifr_addr.sa_family = AF_INET; + + if(ioctl(dummy, SIOCGIFADDR, &req) < 0) { + sclose(dummy); + return NULL; + } + + s = (struct sockaddr_in *)&req.ifr_addr; + memcpy(&in, &s->sin_addr, sizeof(in)); + ip = (char *) Curl_inet_ntop(s->sin_family, &in, buf, buf_size); + + sclose(dummy); + return ip; +} + +#else + +bool Curl_if_is_interface_name(const char *interf) +{ + (void) interf; + + return FALSE; +} + +char *Curl_if2ip(int af, const char *interf, char *buf, int buf_size) +{ + (void) af; + (void) interf; + (void) buf; + (void) buf_size; + return NULL; +} + +#endif diff --git a/lib/curl_imap.c b/lib/curl_imap.c new file mode 100644 index 000000000..8175daa1a --- /dev/null +++ b/lib/curl_imap.c @@ -0,0 +1,1139 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + * RFC3501 IMAPv4 protocol + * RFC5092 IMAP URL Scheme + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_IMAP + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_if2ip.h" +#include "curl_hostip.h" +#include "curl_progress.h" +#include "curl_transfer.h" +#include "curl_escape.h" +#include "curl_http.h" /* for HTTP proxy tunnel stuff */ +#include "curl_socks.h" +#include "curl_imap.h" + +#include "curl_strtoofft.h" +#include "curl_strequal.h" +#include "curl_sslgen.h" +#include "curl_connect.h" +#include "curl_strerror.h" +#include "curl_select.h" +#include "curl_multiif.h" +#include "curl_url.h" +#include "curl_rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* Local API functions */ +static CURLcode imap_parse_url_path(struct connectdata *conn); +static CURLcode imap_regular_transfer(struct connectdata *conn, bool *done); +static CURLcode imap_do(struct connectdata *conn, bool *done); +static CURLcode imap_done(struct connectdata *conn, CURLcode status, + bool premature); +static CURLcode imap_connect(struct connectdata *conn, bool *done); +static CURLcode imap_disconnect(struct connectdata *conn, bool dead); +static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done); +static int imap_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); +static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done); +static CURLcode imap_setup_connection(struct connectdata *conn); +static CURLcode imap_state_upgrade_tls(struct connectdata *conn); + +/* + * IMAP protocol handler. + */ + +const struct Curl_handler Curl_handler_imap = { + "IMAP", /* scheme */ + imap_setup_connection, /* setup_connection */ + imap_do, /* do_it */ + imap_done, /* done */ + ZERO_NULL, /* do_more */ + imap_connect, /* connect_it */ + imap_multi_statemach, /* connecting */ + imap_doing, /* doing */ + imap_getsock, /* proto_getsock */ + imap_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + imap_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_IMAP, /* defport */ + CURLPROTO_IMAP, /* protocol */ + PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD + | PROTOPT_NOURLQUERY /* flags */ +}; + + +#ifdef USE_SSL +/* + * IMAPS protocol handler. + */ + +const struct Curl_handler Curl_handler_imaps = { + "IMAPS", /* scheme */ + imap_setup_connection, /* setup_connection */ + imap_do, /* do_it */ + imap_done, /* done */ + ZERO_NULL, /* do_more */ + imap_connect, /* connect_it */ + imap_multi_statemach, /* connecting */ + imap_doing, /* doing */ + imap_getsock, /* proto_getsock */ + imap_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + imap_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_IMAPS, /* defport */ + CURLPROTO_IMAP | CURLPROTO_IMAPS, /* protocol */ + PROTOPT_CLOSEACTION | PROTOPT_SSL | PROTOPT_NEEDSPWD + | PROTOPT_NOURLQUERY /* flags */ +}; +#endif + +#ifndef CURL_DISABLE_HTTP +/* + * HTTP-proxyed IMAP protocol handler. + */ + +static const struct Curl_handler Curl_handler_imap_proxy = { + "IMAP", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_IMAP, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; + + +#ifdef USE_SSL +/* + * HTTP-proxyed IMAPS protocol handler. + */ + +static const struct Curl_handler Curl_handler_imaps_proxy = { + "IMAPS", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_IMAPS, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; +#endif +#endif + +/*********************************************************************** + * + * imap_sendf() + * + * Sends the formated string as an IMAP command to the server. + * + * Designed to never block. + */ +static CURLcode imap_sendf(struct connectdata *conn, + const char *idstr, /* command id to wait for */ + const char *fmt, ...) +{ + CURLcode res; + struct imap_conn *imapc = &conn->proto.imapc; + va_list ap; + va_start(ap, fmt); + + imapc->idstr = idstr; + + res = Curl_pp_vsendf(&imapc->pp, fmt, ap); + + va_end(ap); + + return res; +} + +static const char *getcmdid(struct connectdata *conn) +{ + static const char * const ids[]= { + "A", + "B", + "C", + "D" + }; + + struct imap_conn *imapc = &conn->proto.imapc; + + /* Get the next id, but wrap at end of table */ + imapc->cmdid = (int)((imapc->cmdid + 1) % (sizeof(ids) / sizeof(ids[0]))); + + return ids[imapc->cmdid]; +} + +/*********************************************************************** + * + * imap_atom() + * + * Checks the input string for characters that need escaping and returns an + * atom ready for sending to the server. + * + * The returned string needs to be freed. + * + */ +static char* imap_atom(const char* str) +{ + const char *p1; + char *p2; + size_t backsp_count = 0; + size_t quote_count = 0; + bool space_exists = FALSE; + size_t newlen = 0; + char *newstr = NULL; + + if(!str) + return NULL; + + /* Count any unescapped characters */ + p1 = str; + while(*p1) { + if(*p1 == '\\') + backsp_count++; + else if(*p1 == '"') + quote_count++; + else if(*p1 == ' ') + space_exists = TRUE; + + p1++; + } + + /* Does the input contain any unescapped characters? */ + if(!backsp_count && !quote_count && !space_exists) + return strdup(str); + + /* Calculate the new string length */ + newlen = strlen(str) + backsp_count + quote_count + (space_exists ? 2 : 0); + + /* Allocate the new string */ + newstr = (char *) malloc((newlen + 1) * sizeof(char)); + if(!newstr) + return NULL; + + /* Surround the string in quotes if necessary */ + p2 = newstr; + if(space_exists) { + newstr[0] = '"'; + newstr[newlen - 1] = '"'; + p2++; + } + + /* Copy the string, escaping backslash and quote characters along the way */ + p1 = str; + while(*p1) { + if(*p1 == '\\' || *p1 == '"') { + *p2 = '\\'; + p2++; + } + + *p2 = *p1; + + p1++; + p2++; + } + + /* Terminate the string */ + newstr[newlen] = '\0'; + + return newstr; +} + +/* Function that checks for an ending imap status code at the start of the + given string. */ +static int imap_endofresp(struct pingpong *pp, int *resp) +{ + char *line = pp->linestart_resp; + size_t len = pp->nread_resp; + struct imap_conn *imapc = &pp->conn->proto.imapc; + const char *id = imapc->idstr; + size_t id_len = strlen(id); + + /* Do we have a generic command response? */ + if(len >= id_len + 3) { + if(!memcmp(id, line, id_len) && line[id_len] == ' ') { + *resp = line[id_len + 1]; /* O, N or B */ + return TRUE; + } + } + + /* Are we processing FETCH command responses? */ + if(imapc->state == IMAP_FETCH) { + /* Do we have a valid response? */ + if(len >= 2 && !memcmp("* ", line, 2)) { + *resp = '*'; + return TRUE; + } + } + + return FALSE; /* nothing for us */ +} + +/* This is the ONLY way to change IMAP state! */ +static void state(struct connectdata *conn, + imapstate newstate) +{ + struct imap_conn *imapc = &conn->proto.imapc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[]={ + "STOP", + "SERVERGREET", + "STARTTLS", + "UPGRADETLS", + "LOGIN", + "SELECT", + "FETCH", + "LOGOUT", + /* LAST */ + }; + + if(imapc->state != newstate) + infof(conn->data, "IMAP %p state change from %s to %s\n", + imapc, names[imapc->state], names[newstate]); +#endif + + imapc->state = newstate; +} + +static CURLcode imap_state_login(struct connectdata *conn) +{ + CURLcode result; + struct FTP *imap = conn->data->state.proto.imap; + const char *str = getcmdid(conn); + char *user = imap_atom(imap->user); + char *passwd = imap_atom(imap->passwd); + + /* send USER and password */ + result = imap_sendf(conn, str, "%s LOGIN %s %s", str, + user ? user : "", passwd ? passwd : ""); + + Curl_safefree(user); + Curl_safefree(passwd); + + if(result) + return result; + + state(conn, IMAP_LOGIN); + + return CURLE_OK; +} + +/* For the IMAP "protocol connect" and "doing" phases only */ +static int imap_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + return Curl_pp_getsock(&conn->proto.imapc.pp, socks, numsocks); +} + +#ifdef USE_SSL +static void imap_to_imaps(struct connectdata *conn) +{ + conn->handler = &Curl_handler_imaps; +} +#else +#define imap_to_imaps(x) Curl_nop_stmt +#endif + +/* For the initial server greeting */ +static CURLcode imap_state_servergreet_resp(struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(imapcode != 'O') { + failf(data, "Got unexpected imap-server response"); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { + /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch + to TLS connection now */ + const char *str = getcmdid(conn); + result = imap_sendf(conn, str, "%s STARTTLS", str); + state(conn, IMAP_STARTTLS); + } + else + result = imap_state_login(conn); + + return result; +} + +/* For STARTTLS responses */ +static CURLcode imap_state_starttls_resp(struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(imapcode != 'O') { + if(data->set.use_ssl != CURLUSESSL_TRY) { + failf(data, "STARTTLS denied. %c", imapcode); + result = CURLE_USE_SSL_FAILED; + } + else + result = imap_state_login(conn); + } + else { + if(data->state.used_interface == Curl_if_multi) { + state(conn, IMAP_UPGRADETLS); + result = imap_state_upgrade_tls(conn); + } + else { + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(CURLE_OK == result) { + imap_to_imaps(conn); + result = imap_state_login(conn); + } + } + } + + return result; +} + +static CURLcode imap_state_upgrade_tls(struct connectdata *conn) +{ + struct imap_conn *imapc = &conn->proto.imapc; + CURLcode result; + + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); + + if(imapc->ssldone) { + imap_to_imaps(conn); + result = imap_state_login(conn); + } + + return result; +} + +/* For LOGIN responses */ +static CURLcode imap_state_login_resp(struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(imapcode != 'O') { + failf(data, "Access denied. %c", imapcode); + result = CURLE_LOGIN_DENIED; + } + else + /* End of connect phase */ + state(conn, IMAP_STOP); + + return result; +} + +/* Start the DO phase */ +static CURLcode imap_select(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + const char *str = getcmdid(conn); + + result = imap_sendf(conn, str, "%s SELECT %s", str, + imapc->mailbox?imapc->mailbox:""); + if(result) + return result; + + state(conn, IMAP_SELECT); + + return result; +} + +static CURLcode imap_fetch(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + const char *str = getcmdid(conn); + + /* TODO: make this select the correct mail + * Use "1 body[text]" to get the full mail body of mail 1 + */ + result = imap_sendf(conn, str, "%s FETCH 1 BODY[TEXT]", str); + if(result) + return result; + + /* + * When issued, the server will respond with a single line similar to + * '* 1 FETCH (BODY[TEXT] {2021}' + * + * Identifying the fetch and how many bytes of contents we can expect. We + * must extract that number before continuing to "download as usual". + */ + + state(conn, IMAP_FETCH); + + return result; +} + +/* For SELECT responses */ +static CURLcode imap_state_select_resp(struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(imapcode != 'O') { + failf(data, "Select failed"); + result = CURLE_LOGIN_DENIED; + } + else + result = imap_fetch(conn); + + return result; +} + +/* For the (first line of) FETCH BODY[TEXT] response */ +static CURLcode imap_state_fetch_resp(struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct imap_conn *imapc = &conn->proto.imapc; + struct FTP *imap = data->state.proto.imap; + struct pingpong *pp = &imapc->pp; + const char *ptr = data->state.buffer; + + (void)instate; /* no use for this yet */ + + if('*' != imapcode) { + Curl_pgrsSetDownloadSize(data, 0); + state(conn, IMAP_STOP); + return CURLE_OK; + } + + /* Something like this comes "* 1 FETCH (BODY[TEXT] {2021}\r" */ + while(*ptr && (*ptr != '{')) + ptr++; + + if(*ptr == '{') { + curl_off_t filesize = curlx_strtoofft(ptr + 1, NULL, 10); + if(filesize) + Curl_pgrsSetDownloadSize(data, filesize); + + infof(data, "Found %" FORMAT_OFF_TU " bytes to download\n", filesize); + + if(pp->cache) { + /* At this point there is a bunch of data in the header "cache" that is + actually body content, send it as body and then skip it. Do note + that there may even be additional "headers" after the body. */ + size_t chunk = pp->cache_size; + + if(chunk > (size_t)filesize) + /* the conversion from curl_off_t to size_t is always fine here */ + chunk = (size_t)filesize; + + result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk); + if(result) + return result; + + filesize -= chunk; + + /* we've now used parts of or the entire cache */ + if(pp->cache_size > chunk) { + /* part of, move the trailing data to the start and reduce the size */ + memmove(pp->cache, pp->cache+chunk, + pp->cache_size - chunk); + pp->cache_size -= chunk; + } + else { + /* cache is drained */ + Curl_safefree(pp->cache); + pp->cache = NULL; + pp->cache_size = 0; + } + } + + infof(data, "Filesize left: %" FORMAT_OFF_T "\n", filesize); + + if(!filesize) + /* the entire data is already transferred! */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + else + /* IMAP download */ + Curl_setup_transfer(conn, FIRSTSOCKET, filesize, FALSE, + imap->bytecountp, -1, NULL); /* no upload here */ + + data->req.maxdownload = filesize; + } + else + /* We don't know how to parse this line */ + result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */ + + /* End of do phase */ + state(conn, IMAP_STOP); + + return result; +} + +static CURLcode imap_statemach_act(struct connectdata *conn) +{ + CURLcode result; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int imapcode; + struct imap_conn *imapc = &conn->proto.imapc; + struct pingpong *pp = &imapc->pp; + size_t nread = 0; + + /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */ + if(imapc->state == IMAP_UPGRADETLS) + return imap_state_upgrade_tls(conn); + + /* Flush any data that needs to be sent */ + if(pp->sendleft) + return Curl_pp_flushsend(pp); + + /* Read the response from the server */ + result = Curl_pp_readresp(sock, pp, &imapcode, &nread); + if(result) + return result; + + if(imapcode) { + /* We have now received a full IMAP server response */ + switch(imapc->state) { + case IMAP_SERVERGREET: + result = imap_state_servergreet_resp(conn, imapcode, imapc->state); + break; + + case IMAP_STARTTLS: + result = imap_state_starttls_resp(conn, imapcode, imapc->state); + break; + + case IMAP_LOGIN: + result = imap_state_login_resp(conn, imapcode, imapc->state); + break; + + case IMAP_FETCH: + result = imap_state_fetch_resp(conn, imapcode, imapc->state); + break; + + case IMAP_SELECT: + result = imap_state_select_resp(conn, imapcode, imapc->state); + break; + + case IMAP_LOGOUT: + /* fallthrough, just stop! */ + default: + /* internal error */ + state(conn, IMAP_STOP); + break; + } + } + + return result; +} + +/* Called repeatedly until done from curl_multi.c */ +static CURLcode imap_multi_statemach(struct connectdata *conn, + bool *done) +{ + struct imap_conn *imapc = &conn->proto.imapc; + CURLcode result; + + if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); + else + result = Curl_pp_multi_statemach(&imapc->pp); + + *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE; + + return result; +} + +static CURLcode imap_easy_statemach(struct connectdata *conn) +{ + struct imap_conn *imapc = &conn->proto.imapc; + struct pingpong *pp = &imapc->pp; + CURLcode result = CURLE_OK; + + while(imapc->state != IMAP_STOP) { + result = Curl_pp_easy_statemach(pp); + if(result) + break; + } + + return result; +} + +/* Allocate and initialize the struct IMAP for the current SessionHandle if + required */ +static CURLcode imap_init(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct FTP *imap = data->state.proto.imap; + + if(!imap) { + imap = data->state.proto.imap = calloc(sizeof(struct FTP), 1); + if(!imap) + return CURLE_OUT_OF_MEMORY; + } + + /* Get some initial data into the imap struct */ + imap->bytecountp = &data->req.bytecount; + + /* No need to duplicate user+password, the connectdata struct won't change + during a session, but we re-init them here since on subsequent inits + since the conn struct may have changed or been replaced. + */ + imap->user = conn->user; + imap->passwd = conn->passwd; + + return CURLE_OK; +} + +/*********************************************************************** + * + * imap_connect() should do everything that is to be considered a part of + * the connection phase. + * + * The variable 'done' points to will be TRUE if the protocol-layer connect + * phase is done when this function returns, or FALSE is not. When called as + * a part of the easy interface, it will always be TRUE. + */ +static CURLcode imap_connect(struct connectdata *conn, + bool *done) /* see description above */ +{ + CURLcode result; + struct imap_conn *imapc = &conn->proto.imapc; + struct SessionHandle *data=conn->data; + struct pingpong *pp = &imapc->pp; + + *done = FALSE; /* default to not done yet */ + + /* If there already is a protocol-specific struct allocated for this + sessionhandle, deal with it */ + Curl_reset_reqproto(conn); + + result = imap_init(conn); + if(CURLE_OK != result) + return result; + + /* We always support persistent connections on imap */ + conn->bits.close = FALSE; + + pp->response_time = RESP_TIMEOUT; /* set default response time-out */ + pp->statemach_act = imap_statemach_act; + pp->endofresp = imap_endofresp; + pp->conn = conn; + + if((conn->handler->flags & PROTOPT_SSL) && + data->state.used_interface != Curl_if_multi) { + /* IMAPS is simply imap with SSL for the control channel */ + /* so perform the SSL initialization for this socket */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(result) + return result; + } + + /* Initialise the response reader stuff */ + Curl_pp_init(pp); + + /* Start off waiting for the server greeting response */ + state(conn, IMAP_SERVERGREET); + + /* Start off with an id of '*' */ + imapc->idstr = "*"; + + if(data->state.used_interface == Curl_if_multi) + result = imap_multi_statemach(conn, done); + else { + result = imap_easy_statemach(conn); + if(!result) + *done = TRUE; + } + + return result; +} + +/*********************************************************************** + * + * imap_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode imap_done(struct connectdata *conn, CURLcode status, + bool premature) +{ + struct SessionHandle *data = conn->data; + struct FTP *imap = data->state.proto.imap; + CURLcode result=CURLE_OK; + + (void)premature; + + if(!imap) + /* When the easy handle is removed from the multi while libcurl is still + * trying to resolve the host name, it seems that the imap struct is not + * yet initialized, but the removal action calls Curl_done() which calls + * this function. So we simply return success if no imap pointer is set. + */ + return CURLE_OK; + + if(status) { + conn->bits.close = TRUE; /* marked for closure */ + result = status; /* use the already set error code */ + } + + /* Clear the transfer mode for the next connection */ + imap->transfer = FTPTRANSFER_BODY; + + return result; +} + +/*********************************************************************** + * + * imap_perform() + * + * This is the actual DO function for IMAP. Get a file/directory according to + * the options previously setup. + */ +static CURLcode imap_perform(struct connectdata *conn, bool *connected, + bool *dophase_done) +{ + /* This is IMAP and no proxy */ + CURLcode result = CURLE_OK; + + DEBUGF(infof(conn->data, "DO phase starts\n")); + + if(conn->data->set.opt_no_body) { + /* Requested no body means no transfer */ + struct FTP *imap = conn->data->state.proto.imap; + imap->transfer = FTPTRANSFER_INFO; + } + + *dophase_done = FALSE; /* not done yet */ + + /* Start the first command in the DO phase */ + result = imap_select(conn); + if(result) + return result; + + /* Run the state-machine */ + if(conn->data->state.used_interface == Curl_if_multi) + result = imap_multi_statemach(conn, dophase_done); + else { + result = imap_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect[FIRSTSOCKET]; + + if(*dophase_done) + DEBUGF(infof(conn->data, "DO phase is complete\n")); + + return result; +} + +/*********************************************************************** + * + * imap_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (imap_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode imap_do(struct connectdata *conn, bool *done) +{ + CURLcode retcode = CURLE_OK; + + *done = FALSE; /* default to false */ + + /* + Since connections can be re-used between SessionHandles, this might be a + connection already existing but on a fresh SessionHandle struct so we must + make sure we have a good 'struct IMAP' to play with. For new connections, + the struct IMAP is allocated and setup in the imap_connect() function. + */ + Curl_reset_reqproto(conn); + retcode = imap_init(conn); + if(retcode) + return retcode; + + /* Parse the URL path */ + retcode = imap_parse_url_path(conn); + if(retcode) + return retcode; + + retcode = imap_regular_transfer(conn, done); + + return retcode; +} + +/*********************************************************************** + * + * imap_logout() + * + * This should be called before calling sclose(). We should then wait for the + * response from the server before returning. The calling code should then try + * to close the connection. + * + */ +static CURLcode imap_logout(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + const char *str = getcmdid(conn); + + result = imap_sendf(conn, str, "%s LOGOUT", str, NULL); + if(result) + return result; + + state(conn, IMAP_LOGOUT); + + result = imap_easy_statemach(conn); + + return result; +} + +/*********************************************************************** + * + * imap_disconnect() + * + * Disconnect from an IMAP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection) +{ + struct imap_conn *imapc= &conn->proto.imapc; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to */ + + /* The IMAP session may or may not have been allocated/setup at this + point! */ + if(!dead_connection && imapc->pp.conn) + (void)imap_logout(conn); /* ignore errors on the LOGOUT */ + + /* Disconnect from the server */ + Curl_pp_disconnect(&imapc->pp); + + /* Cleanup our connection based variables */ + Curl_safefree(imapc->mailbox); + + return CURLE_OK; +} + +/*********************************************************************** + * + * imap_parse_url_path() + * + * Parse the URL path into separate path components. + * + */ +static CURLcode imap_parse_url_path(struct connectdata *conn) +{ + /* The imap struct is already inited in imap_connect() */ + struct imap_conn *imapc = &conn->proto.imapc; + struct SessionHandle *data = conn->data; + const char *path = data->state.path; + + if(!*path) + path = "INBOX"; + + /* URL decode the path and use this mailbox */ + return Curl_urldecode(data, path, 0, &imapc->mailbox, NULL, TRUE); +} + +/* Call this when the DO phase has completed */ +static CURLcode imap_dophase_done(struct connectdata *conn, bool connected) +{ + struct FTP *imap = conn->data->state.proto.imap; + + (void)connected; + + if(imap->transfer != FTPTRANSFER_BODY) + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + return CURLE_OK; +} + +/* Called from curl_multi.c while DOing */ +static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done) +{ + CURLcode result = imap_multi_statemach(conn, dophase_done); + + if(result) + DEBUGF(infof(conn->data, "DO phase failed\n")); + else { + if(*dophase_done) { + result = imap_dophase_done(conn, FALSE /* not connected */); + + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + } + + return result; +} + +/*********************************************************************** + * + * imap_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + */ +static CURLcode imap_regular_transfer(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + bool connected = FALSE; + struct SessionHandle *data = conn->data; + + /* Make sure size is unknown at this point */ + data->req.size = -1; + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, 0); + Curl_pgrsSetDownloadSize(data, 0); + + result = imap_perform(conn, &connected, dophase_done); + + if(CURLE_OK == result) { + if(!*dophase_done) + /* The DO phase has not completed yet */ + return CURLE_OK; + + result = imap_dophase_done(conn, connected); + if(result) + return result; + } + + return result; +} + +static CURLcode imap_setup_connection(struct connectdata * conn) +{ + struct SessionHandle *data = conn->data; + + if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { + /* Unless we have asked to tunnel imap operations through the proxy, we + switch and use HTTP operations only */ +#ifndef CURL_DISABLE_HTTP + if(conn->handler == &Curl_handler_imap) + conn->handler = &Curl_handler_imap_proxy; + else { +#ifdef USE_SSL + conn->handler = &Curl_handler_imaps_proxy; +#else + failf(data, "IMAPS not supported!"); + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + + /* We explicitly mark this connection as persistent here as we're doing + IMAP over HTTP and thus we accidentally avoid setting this value + otherwise */ + conn->bits.close = FALSE; +#else + failf(data, "IMAP over http proxy requires HTTP support built-in!"); + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + + data->state.path++; /* don't include the initial slash */ + + return CURLE_OK; +} + +#endif /* CURL_DISABLE_IMAP */ diff --git a/lib/curl_inet_ntop.c b/lib/curl_inet_ntop.c new file mode 100644 index 000000000..b184029f6 --- /dev/null +++ b/lib/curl_inet_ntop.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 1996-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* + * Original code by Paul Vixie. "curlified" by Gisle Vanem. + */ + +#include "curl_setup.h" + +#ifndef HAVE_INET_NTOP + +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_inet_ntop.h" + +#define IN6ADDRSZ 16 +#define INADDRSZ 4 +#define INT16SZ 2 + +/* + * Format an IPv4 address, more or less like inet_ntoa(). + * + * Returns `dst' (as a const) + * Note: + * - uses no statics + * - takes a unsigned char* not an in_addr as input + */ +static char *inet_ntop4 (const unsigned char *src, char *dst, size_t size) +{ + char tmp[sizeof "255.255.255.255"]; + size_t len; + + DEBUGASSERT(size >= 16); + + tmp[0] = '\0'; + (void)snprintf(tmp, sizeof(tmp), "%d.%d.%d.%d", + ((int)((unsigned char)src[0])) & 0xff, + ((int)((unsigned char)src[1])) & 0xff, + ((int)((unsigned char)src[2])) & 0xff, + ((int)((unsigned char)src[3])) & 0xff); + + len = strlen(tmp); + if(len == 0 || len >= size) { + SET_ERRNO(ENOSPC); + return (NULL); + } + strcpy(dst, tmp); + return dst; +} + +#ifdef ENABLE_IPV6 +/* + * Convert IPv6 binary address into presentation (printable) format. + */ +static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) +{ + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + char *tp; + struct { + long base; + long len; + } best, cur; + unsigned long words[IN6ADDRSZ / INT16SZ]; + int i; + + /* Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof(words)); + for(i = 0; i < IN6ADDRSZ; i++) + words[i/2] |= (src[i] << ((1 - (i % 2)) << 3)); + + best.base = -1; + cur.base = -1; + best.len = 0; + cur.len = 0; + + for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { + if(words[i] == 0) { + if(cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } + else if(cur.base != -1) { + if(best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + if((cur.base != -1) && (best.base == -1 || cur.len > best.len)) + best = cur; + if(best.base != -1 && best.len < 2) + best.base = -1; + /* Format the result. */ + tp = tmp; + for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { + /* Are we inside the best run of 0x00's? */ + if(best.base != -1 && i >= best.base && i < (best.base + best.len)) { + if(i == best.base) + *tp++ = ':'; + continue; + } + + /* Are we following an initial run of 0x00s or any real hex? + */ + if(i != 0) + *tp++ = ':'; + + /* Is this address an encapsulated IPv4? + */ + if(i == 6 && best.base == 0 && + (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { + if(!inet_ntop4(src+12, tp, sizeof(tmp) - (tp - tmp))) { + SET_ERRNO(ENOSPC); + return (NULL); + } + tp += strlen(tp); + break; + } + tp += snprintf(tp, 5, "%lx", words[i]); + } + + /* Was it a trailing run of 0x00's? + */ + if(best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ)) + *tp++ = ':'; + *tp++ = '\0'; + + /* Check for overflow, copy, and we're done. + */ + if((size_t)(tp - tmp) > size) { + SET_ERRNO(ENOSPC); + return (NULL); + } + strcpy(dst, tmp); + return dst; +} +#endif /* ENABLE_IPV6 */ + +/* + * Convert a network format address to presentation format. + * + * Returns pointer to presentation format address (`buf'). + * Returns NULL on error and errno set with the specific + * error, EAFNOSUPPORT or ENOSPC. + * + * On Windows we store the error in the thread errno, not + * in the winsock error code. This is to avoid losing the + * actual last winsock error. So use macro ERRNO to fetch the + * errno this function sets when returning NULL, not SOCKERRNO. + */ +char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size) +{ + switch (af) { + case AF_INET: + return inet_ntop4((const unsigned char*)src, buf, size); +#ifdef ENABLE_IPV6 + case AF_INET6: + return inet_ntop6((const unsigned char*)src, buf, size); +#endif + default: + SET_ERRNO(EAFNOSUPPORT); + return NULL; + } +} +#endif /* HAVE_INET_NTOP */ diff --git a/lib/curl_inet_pton.c b/lib/curl_inet_pton.c new file mode 100644 index 000000000..175f938cd --- /dev/null +++ b/lib/curl_inet_pton.c @@ -0,0 +1,234 @@ +/* This is from the BIND 4.9.4 release, modified to compile by itself */ + +/* Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +#include "curl_setup.h" + +#ifndef HAVE_INET_PTON + +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#include "curl_inet_pton.h" + +#define IN6ADDRSZ 16 +#define INADDRSZ 4 +#define INT16SZ 2 + +/* + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + +static int inet_pton4(const char *src, unsigned char *dst); +#ifdef ENABLE_IPV6 +static int inet_pton6(const char *src, unsigned char *dst); +#endif + +/* int + * inet_pton(af, src, dst) + * convert from presentation format (which usually means ASCII printable) + * to network format (which is usually some kind of binary format). + * return: + * 1 if the address was valid for the specified address family + * 0 if the address wasn't valid (`dst' is untouched in this case) + * -1 if some other error occurred (`dst' is untouched in this case, too) + * notice: + * On Windows we store the error in the thread errno, not + * in the winsock error code. This is to avoid losing the + * actual last winsock error. So use macro ERRNO to fetch the + * errno this function sets when returning (-1), not SOCKERRNO. + * author: + * Paul Vixie, 1996. + */ +int +Curl_inet_pton(int af, const char *src, void *dst) +{ + switch (af) { + case AF_INET: + return (inet_pton4(src, (unsigned char *)dst)); +#ifdef ENABLE_IPV6 + case AF_INET6: + return (inet_pton6(src, (unsigned char *)dst)); +#endif + default: + SET_ERRNO(EAFNOSUPPORT); + return (-1); + } + /* NOTREACHED */ +} + +/* int + * inet_pton4(src, dst) + * like inet_aton() but without all the hexadecimal and shorthand. + * return: + * 1 if `src' is a valid dotted quad, else 0. + * notice: + * does not touch `dst' unless it's returning 1. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton4(const char *src, unsigned char *dst) +{ + static const char digits[] = "0123456789"; + int saw_digit, octets, ch; + unsigned char tmp[INADDRSZ], *tp; + + saw_digit = 0; + octets = 0; + tp = tmp; + *tp = 0; + while((ch = *src++) != '\0') { + const char *pch; + + if((pch = strchr(digits, ch)) != NULL) { + unsigned int val = *tp * 10 + (unsigned int)(pch - digits); + + if(saw_digit && *tp == 0) + return (0); + if(val > 255) + return (0); + *tp = (unsigned char)val; + if(! saw_digit) { + if(++octets > 4) + return (0); + saw_digit = 1; + } + } + else if(ch == '.' && saw_digit) { + if(octets == 4) + return (0); + *++tp = 0; + saw_digit = 0; + } + else + return (0); + } + if(octets < 4) + return (0); + memcpy(dst, tmp, INADDRSZ); + return (1); +} + +#ifdef ENABLE_IPV6 +/* int + * inet_pton6(src, dst) + * convert presentation level address to network order binary form. + * return: + * 1 if `src' is a valid [RFC1884 2.2] address, else 0. + * notice: + * (1) does not touch `dst' unless it's returning 1. + * (2) :: in a full address is silently ignored. + * credit: + * inspired by Mark Andrews. + * author: + * Paul Vixie, 1996. + */ +static int +inet_pton6(const char *src, unsigned char *dst) +{ + static const char xdigits_l[] = "0123456789abcdef", + xdigits_u[] = "0123456789ABCDEF"; + unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; + const char *xdigits, *curtok; + int ch, saw_xdigit; + size_t val; + + memset((tp = tmp), 0, IN6ADDRSZ); + endp = tp + IN6ADDRSZ; + colonp = NULL; + /* Leading :: requires some special handling. */ + if(*src == ':') + if(*++src != ':') + return (0); + curtok = src; + saw_xdigit = 0; + val = 0; + while((ch = *src++) != '\0') { + const char *pch; + + if((pch = strchr((xdigits = xdigits_l), ch)) == NULL) + pch = strchr((xdigits = xdigits_u), ch); + if(pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if(++saw_xdigit > 4) + return (0); + continue; + } + if(ch == ':') { + curtok = src; + if(!saw_xdigit) { + if(colonp) + return (0); + colonp = tp; + continue; + } + if(tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if(ch == '.' && ((tp + INADDRSZ) <= endp) && + inet_pton4(curtok, tp) > 0) { + tp += INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return (0); + } + if(saw_xdigit) { + if(tp + INT16SZ > endp) + return (0); + *tp++ = (unsigned char) (val >> 8) & 0xff; + *tp++ = (unsigned char) val & 0xff; + } + if(colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const ssize_t n = tp - colonp; + ssize_t i; + + if(tp == endp) + return (0); + for(i = 1; i <= n; i++) { + *(endp - i) = *(colonp + n - i); + *(colonp + n - i) = 0; + } + tp = endp; + } + if(tp != endp) + return (0); + memcpy(dst, tmp, IN6ADDRSZ); + return (1); +} +#endif /* ENABLE_IPV6 */ + +#endif /* HAVE_INET_PTON */ diff --git a/lib/curl_krb4.c b/lib/curl_krb4.c new file mode 100644 index 000000000..8ba326ed7 --- /dev/null +++ b/lib/curl_krb4.c @@ -0,0 +1,440 @@ +/* This source code was modified by Martin Hedenfalk for + * use in Curl. Martin's 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. + * + * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * Copyright (c) 2004 - 2011 Daniel Stenberg + * 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 "curl_setup.h" + +#ifndef CURL_DISABLE_FTP +#ifdef HAVE_KRB4 + +#ifdef HAVE_NETDB_H +#include +#endif +#include +#include + +#include "curl_urldata.h" +#include "curl_base64.h" +#include "curl_ftp.h" +#include "curl_sendf.h" +#include "curl_krb4.h" +#include "curl_inet_ntop.h" +#include "curl_memory.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#define LOCAL_ADDR (&conn->local_addr) +#define REMOTE_ADDR conn->ip_addr->ai_addr +#define myctladdr LOCAL_ADDR +#define hisctladdr REMOTE_ADDR + +struct krb4_data { + des_cblock key; + des_key_schedule schedule; + char name[ANAME_SZ]; + char instance[INST_SZ]; + char realm[REALM_SZ]; +}; + +#ifndef HAVE_STRLCPY +/* if it ever goes non-static, make it Curl_ prefixed! */ +static size_t +strlcpy (char *dst, const char *src, size_t dst_sz) +{ + size_t n; + char *p; + + for(p = dst, n = 0; + n + 1 < dst_sz && *src != '\0'; + ++p, ++src, ++n) + *p = *src; + *p = '\0'; + if(*src == '\0') + return n; + else + return n + strlen (src); +} +#else +size_t strlcpy (char *dst, const char *src, size_t dst_sz); +#endif + +static int +krb4_check_prot(void *app_data, int level) +{ + app_data = NULL; /* prevent compiler warning */ + if(level == PROT_CONFIDENTIAL) + return -1; + return 0; +} + +static int +krb4_decode(void *app_data, void *buf, int len, int level, + struct connectdata *conn) +{ + MSG_DAT m; + int e; + struct krb4_data *d = app_data; + + if(level == PROT_SAFE) + e = krb_rd_safe(buf, len, &d->key, + (struct sockaddr_in *)REMOTE_ADDR, + (struct sockaddr_in *)LOCAL_ADDR, &m); + else + e = krb_rd_priv(buf, len, d->schedule, &d->key, + (struct sockaddr_in *)REMOTE_ADDR, + (struct sockaddr_in *)LOCAL_ADDR, &m); + if(e) { + struct SessionHandle *data = conn->data; + infof(data, "krb4_decode: %s\n", krb_get_err_text(e)); + return -1; + } + memmove(buf, m.app_data, m.app_length); + return m.app_length; +} + +static int +krb4_overhead(void *app_data, int level, int len) +{ + /* no arguments are used, just init them to prevent compiler warnings */ + app_data = NULL; + level = 0; + len = 0; + return 31; +} + +static int +krb4_encode(void *app_data, const void *from, int length, int level, void **to, + struct connectdata *conn) +{ + struct krb4_data *d = app_data; + *to = malloc(length + 31); + if(!*to) + return -1; + if(level == PROT_SAFE) + /* NOTE that the void* cast is safe, krb_mk_safe/priv don't modify the + * input buffer + */ + return krb_mk_safe((void*)from, *to, length, &d->key, + (struct sockaddr_in *)LOCAL_ADDR, + (struct sockaddr_in *)REMOTE_ADDR); + else if(level == PROT_PRIVATE) + return krb_mk_priv((void*)from, *to, length, d->schedule, &d->key, + (struct sockaddr_in *)LOCAL_ADDR, + (struct sockaddr_in *)REMOTE_ADDR); + else + return -1; +} + +static int +mk_auth(struct krb4_data *d, KTEXT adat, + const char *service, char *host, int checksum) +{ + int ret; + CREDENTIALS cred; + char sname[SNAME_SZ], inst[INST_SZ], realm[REALM_SZ]; + + strlcpy(sname, service, sizeof(sname)); + strlcpy(inst, krb_get_phost(host), sizeof(inst)); + strlcpy(realm, krb_realmofhost(host), sizeof(realm)); + ret = krb_mk_req(adat, sname, inst, realm, checksum); + if(ret) + return ret; + strlcpy(sname, service, sizeof(sname)); + strlcpy(inst, krb_get_phost(host), sizeof(inst)); + strlcpy(realm, krb_realmofhost(host), sizeof(realm)); + ret = krb_get_cred(sname, inst, realm, &cred); + memmove(&d->key, &cred.session, sizeof(des_cblock)); + des_key_sched(&d->key, d->schedule); + memset(&cred, 0, sizeof(cred)); + return ret; +} + +#ifdef HAVE_KRB_GET_OUR_IP_FOR_REALM +int krb_get_our_ip_for_realm(char *, struct in_addr *); +#endif + +static int +krb4_auth(void *app_data, struct connectdata *conn) +{ + int ret; + char *p; + unsigned char *ptr; + size_t len = 0; + KTEXT_ST adat; + MSG_DAT msg_data; + int checksum; + u_int32_t cs; + struct krb4_data *d = app_data; + char *host = conn->host.name; + ssize_t nread; + int l = sizeof(conn->local_addr); + struct SessionHandle *data = conn->data; + CURLcode result; + size_t base64_sz = 0; + + if(getsockname(conn->sock[FIRSTSOCKET], + (struct sockaddr *)LOCAL_ADDR, &l) < 0) + perror("getsockname()"); + + checksum = getpid(); + ret = mk_auth(d, &adat, "ftp", host, checksum); + if(ret == KDC_PR_UNKNOWN) + ret = mk_auth(d, &adat, "rcmd", host, checksum); + if(ret) { + infof(data, "%s\n", krb_get_err_text(ret)); + return AUTH_CONTINUE; + } + +#ifdef HAVE_KRB_GET_OUR_IP_FOR_REALM + if(krb_get_config_bool("nat_in_use")) { + struct sockaddr_in *localaddr = (struct sockaddr_in *)LOCAL_ADDR; + struct in_addr natAddr; + + if(krb_get_our_ip_for_realm(krb_realmofhost(host), + &natAddr) != KSUCCESS + && krb_get_our_ip_for_realm(NULL, &natAddr) != KSUCCESS) + infof(data, "Can't get address for realm %s\n", + krb_realmofhost(host)); + else { + if(natAddr.s_addr != localaddr->sin_addr.s_addr) { + char addr_buf[128]; + if(Curl_inet_ntop(AF_INET, natAddr, addr_buf, sizeof(addr_buf))) + infof(data, "Using NAT IP address (%s) for kerberos 4\n", addr_buf); + localaddr->sin_addr = natAddr; + } + } + } +#endif + + result = Curl_base64_encode(conn->data, (char *)adat.dat, adat.length, + &p, &base64_sz); + if(result) { + Curl_failf(data, "base64-encoding: %s", curl_easy_strerror(result)); + return AUTH_CONTINUE; + } + + result = Curl_ftpsendf(conn, "ADAT %s", p); + + free(p); + + if(result) + return -2; + + if(Curl_GetFTPResponse(&nread, conn, NULL)) + return -1; + + if(data->state.buffer[0] != '2') { + Curl_failf(data, "Server didn't accept auth data"); + return AUTH_ERROR; + } + + p = strstr(data->state.buffer, "ADAT="); + if(!p) { + Curl_failf(data, "Remote host didn't send adat reply"); + return AUTH_ERROR; + } + p += 5; + result = Curl_base64_decode(p, &ptr, &len); + if(result) { + Curl_failf(data, "base64-decoding: %s", curl_easy_strerror(result)); + return AUTH_ERROR; + } + if(len > sizeof(adat.dat)-1) { + free(ptr); + ptr = NULL; + len = 0; + } + if(!len || !ptr) { + Curl_failf(data, "Failed to decode base64 from server"); + return AUTH_ERROR; + } + memcpy((char *)adat.dat, ptr, len); + free(ptr); + adat.length = len; + ret = krb_rd_safe(adat.dat, adat.length, &d->key, + (struct sockaddr_in *)hisctladdr, + (struct sockaddr_in *)myctladdr, &msg_data); + if(ret) { + Curl_failf(data, "Error reading reply from server: %s", + krb_get_err_text(ret)); + return AUTH_ERROR; + } + krb_get_int(msg_data.app_data, &cs, 4, 0); + if(cs - checksum != 1) { + Curl_failf(data, "Bad checksum returned from server"); + return AUTH_ERROR; + } + return AUTH_OK; +} + +struct Curl_sec_client_mech Curl_krb4_client_mech = { + "KERBEROS_V4", + sizeof(struct krb4_data), + NULL, /* init */ + krb4_auth, + NULL, /* end */ + krb4_check_prot, + krb4_overhead, + krb4_encode, + krb4_decode +}; + +static enum protection_level +krb4_set_command_prot(struct connectdata *conn, enum protection_level level) +{ + enum protection_level old = conn->command_prot; + DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); + conn->command_prot = level; + return old; +} + +CURLcode Curl_krb_kauth(struct connectdata *conn) +{ + des_cblock key; + des_key_schedule schedule; + KTEXT_ST tkt, tktcopy; + char *name; + char *p; + char passwd[100]; + size_t tmp = 0; + ssize_t nread; + enum protection_level save; + CURLcode result; + unsigned char *ptr; + size_t base64_sz = 0; + + save = krb4_set_command_prot(conn, PROT_PRIVATE); + + result = Curl_ftpsendf(conn, "SITE KAUTH %s", conn->user); + + if(result) + return result; + + result = Curl_GetFTPResponse(&nread, conn, NULL); + if(result) + return result; + + if(conn->data->state.buffer[0] != '3') { + krb4_set_command_prot(conn, save); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + p = strstr(conn->data->state.buffer, "T="); + if(!p) { + Curl_failf(conn->data, "Bad reply from server"); + krb4_set_command_prot(conn, save); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + p += 2; + result = Curl_base64_decode(p, &ptr, &tmp); + if(result) { + Curl_failf(conn->data, "base64-decoding: %s", curl_easy_strerror(result)); + return result; + } + if(tmp >= sizeof(tkt.dat)) { + free(ptr); + ptr = NULL; + tmp = 0; + } + if(!tmp || !ptr) { + Curl_failf(conn->data, "Failed to decode base64 in reply"); + krb4_set_command_prot(conn, save); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + memcpy((char *)tkt.dat, ptr, tmp); + free(ptr); + tkt.length = tmp; + tktcopy.length = tkt.length; + + p = strstr(conn->data->state.buffer, "P="); + if(!p) { + Curl_failf(conn->data, "Bad reply from server"); + krb4_set_command_prot(conn, save); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + name = p + 2; + for(; *p && *p != ' ' && *p != '\r' && *p != '\n'; p++); + *p = 0; + + des_string_to_key (conn->passwd, &key); + des_key_sched(&key, schedule); + + des_pcbc_encrypt((void *)tkt.dat, (void *)tktcopy.dat, + tkt.length, + schedule, &key, DES_DECRYPT); + if(strcmp ((char*)tktcopy.dat + 8, + KRB_TICKET_GRANTING_TICKET) != 0) { + afs_string_to_key(passwd, + krb_realmofhost(conn->host.name), + &key); + des_key_sched(&key, schedule); + des_pcbc_encrypt((void *)tkt.dat, (void *)tktcopy.dat, + tkt.length, + schedule, &key, DES_DECRYPT); + } + memset(key, 0, sizeof(key)); + memset(schedule, 0, sizeof(schedule)); + memset(passwd, 0, sizeof(passwd)); + result = Curl_base64_encode(conn->data, (char *)tktcopy.dat, tktcopy.length, + &p, &base64_sz); + if(result) { + Curl_failf(conn->data, "base64-encoding: %s", curl_easy_strerror(result)); + krb4_set_command_prot(conn, save); + return result; + } + memset (tktcopy.dat, 0, tktcopy.length); + + result = Curl_ftpsendf(conn, "SITE KAUTH %s %s", name, p); + free(p); + if(result) + return result; + + result = Curl_GetFTPResponse(&nread, conn, NULL); + if(result) + return result; + krb4_set_command_prot(conn, save); + + return CURLE_OK; +} + +#endif /* HAVE_KRB4 */ +#endif /* CURL_DISABLE_FTP */ diff --git a/lib/curl_krb5.c b/lib/curl_krb5.c new file mode 100644 index 000000000..d793fef02 --- /dev/null +++ b/lib/curl_krb5.c @@ -0,0 +1,341 @@ +/* GSSAPI/krb5 support for FTP - loosely based on old curl_krb4.c + * + * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * Copyright (c) 2004 - 2013 Daniel Stenberg + * 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 "curl_setup.h" + +#ifndef CURL_DISABLE_FTP +#ifdef HAVE_GSSAPI + +#ifdef HAVE_OLD_GSSMIT +#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +#define NCOMPAT 1 +#endif + +#ifdef HAVE_NETDB_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_base64.h" +#include "curl_ftp.h" +#include "curl_gssapi.h" +#include "curl_sendf.h" +#include "curl_krb4.h" +#include "curl_memory.h" +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#define LOCAL_ADDR (&conn->local_addr) +#define REMOTE_ADDR conn->ip_addr->ai_addr + +static int +krb5_init(void *app_data) +{ + gss_ctx_id_t *context = app_data; + /* Make sure our context is initialized for krb5_end. */ + *context = GSS_C_NO_CONTEXT; + return 0; +} + +static int +krb5_check_prot(void *app_data, int level) +{ + (void)app_data; /* unused */ + if(level == PROT_CONFIDENTIAL) + return -1; + return 0; +} + +static int +krb5_decode(void *app_data, void *buf, int len, + int level UNUSED_PARAM, + struct connectdata *conn UNUSED_PARAM) +{ + gss_ctx_id_t *context = app_data; + OM_uint32 maj, min; + gss_buffer_desc enc, dec; + + (void)level; + (void)conn; + + 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 = curlx_uztosi(dec.length); + gss_release_buffer(&min, &dec); + + return len; +} + +static int +krb5_overhead(void *app_data, int level, int len) +{ + /* no arguments are used */ + (void)app_data; + (void)level; + (void)len; + return 0; +} + +static int +krb5_encode(void *app_data, const void *from, int length, int level, void **to, + struct connectdata *conn UNUSED_PARAM) +{ + gss_ctx_id_t *context = app_data; + gss_buffer_desc dec, enc; + OM_uint32 maj, min; + int state; + int len; + + /* shut gcc up */ + conn = NULL; + + /* NOTE that the cast is safe, neither of the krb5, gnu gss and heimdal + * libraries modify the input buffer in gss_seal() + */ + dec.value = (void*)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 = curlx_uztosi(enc.length); + gss_release_buffer(&min, &enc); + return len; +} + +static int +krb5_auth(void *app_data, struct connectdata *conn) +{ + int ret = AUTH_OK; + char *p; + const char *host = conn->host.name; + ssize_t nread; + curl_socklen_t l = sizeof(conn->local_addr); + struct SessionHandle *data = conn->data; + CURLcode result; + const char *service = "ftp", *srv_host = "host"; + gss_buffer_desc input_buffer, output_buffer, _gssresp, *gssresp; + OM_uint32 maj, min; + gss_name_t gssname; + gss_ctx_id_t *context = app_data; + struct gss_channel_bindings_struct chan; + size_t base64_sz = 0; + + 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) */ + for(;;) { + /* 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; + } + + input_buffer.value = data->state.buffer; + input_buffer.length = snprintf(input_buffer.value, BUFSIZE, "%s@%s", + service, host); + maj = gss_import_name(&min, &input_buffer, GSS_C_NT_HOSTBASED_SERVICE, + &gssname); + if(maj != GSS_S_COMPLETE) { + gss_release_name(&min, &gssname); + if(service == srv_host) { + Curl_failf(data, "Error importing service name %s", + input_buffer.value); + return AUTH_ERROR; + } + service = srv_host; + continue; + } + /* We pass NULL as |output_name_type| to avoid a leak. */ + gss_display_name(&min, gssname, &output_buffer, NULL); + Curl_infof(data, "Trying against %s\n", output_buffer.value); + gssresp = GSS_C_NO_BUFFER; + *context = GSS_C_NO_CONTEXT; + + do { + /* Release the buffer at each iteration to avoid leaking: the first time + we are releasing the memory from gss_display_name. The last item is + taken care by a final gss_release_buffer. */ + gss_release_buffer(&min, &output_buffer); + ret = AUTH_OK; + maj = Curl_gss_init_sec_context(data, + &min, + context, + gssname, + &chan, + gssresp, + &output_buffer, + NULL); + + if(gssresp) { + free(_gssresp.value); + gssresp = NULL; + } + + if(GSS_ERROR(maj)) { + Curl_infof(data, "Error creating security context\n"); + ret = AUTH_ERROR; + break; + } + + if(output_buffer.length != 0) { + result = Curl_base64_encode(data, (char *)output_buffer.value, + output_buffer.length, &p, &base64_sz); + if(result) { + Curl_infof(data,"base64-encoding: %s\n", curl_easy_strerror(result)); + 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) { + result = Curl_base64_decode(p + 5, + (unsigned char **)&_gssresp.value, + &_gssresp.length); + if(result) { + Curl_failf(data,"base64-decoding: %s", curl_easy_strerror(result)); + ret = AUTH_CONTINUE; + break; + } + } + + gssresp = &_gssresp; + } + } while(maj == GSS_S_CONTINUE_NEEDED); + + gss_release_name(&min, &gssname); + gss_release_buffer(&min, &output_buffer); + + if(gssresp) + free(_gssresp.value); + + if(ret == AUTH_OK || service == srv_host) + return ret; + + service = srv_host; + } + return ret; +} + +static void krb5_end(void *app_data) +{ + OM_uint32 min; + gss_ctx_id_t *context = app_data; + if(*context != GSS_C_NO_CONTEXT) { +#ifdef DEBUGBUILD + OM_uint32 maj = +#endif + gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER); + DEBUGASSERT(maj == GSS_S_COMPLETE); + } +} + +struct Curl_sec_client_mech Curl_krb5_client_mech = { + "GSSAPI", + sizeof(gss_ctx_id_t), + krb5_init, + krb5_auth, + krb5_end, + krb5_check_prot, + krb5_overhead, + krb5_encode, + krb5_decode +}; + +#endif /* HAVE_GSSAPI */ +#endif /* CURL_DISABLE_FTP */ diff --git a/lib/curl_ldap.c b/lib/curl_ldap.c new file mode 100644 index 000000000..59f3b832e --- /dev/null +++ b/lib/curl_ldap.c @@ -0,0 +1,725 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_LDAP) && !defined(USE_OPENLDAP) + +/* + * Notice that USE_OPENLDAP is only a source code selection switch. When + * libcurl is built with USE_OPENLDAP defined the libcurl source code that + * gets compiled is the code from curl_openldap.c, otherwise the code that + * gets compiled is the code from curl_ldap.c. + * + * When USE_OPENLDAP is defined a recent version of the OpenLDAP library + * might be required for compilation and runtime. In order to use ancient + * OpenLDAP library versions, USE_OPENLDAP shall not be defined. + */ + +#ifdef CURL_LDAP_WIN /* Use Windows LDAP implementation. */ +# include +# ifndef LDAP_VENDOR_NAME +# error Your Platform SDK is NOT sufficient for LDAP support! \ + Update your Platform SDK, or disable LDAP support! +# else +# include +# endif +#else +# define LDAP_DEPRECATED 1 /* Be sure ldap_init() is defined. */ +# ifdef HAVE_LBER_H +# include +# endif +# include +# if (defined(HAVE_LDAP_SSL) && defined(HAVE_LDAP_SSL_H)) +# include +# endif /* HAVE_LDAP_SSL && HAVE_LDAP_SSL_H */ +#endif + +#include "curl_urldata.h" +#include +#include "curl_sendf.h" +#include "curl_escape.h" +#include "curl_progress.h" +#include "curl_transfer.h" +#include "curl_strequal.h" +#include "curl_strtok.h" +#include "curl_ldap.h" +#include "curl_memory.h" +#include "curl_base64.h" +#include "curl_rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memdebug.h" + +#ifndef HAVE_LDAP_URL_PARSE + +/* Use our own implementation. */ + +typedef struct { + char *lud_host; + int lud_port; + char *lud_dn; + char **lud_attrs; + int lud_scope; + char *lud_filter; + char **lud_exts; +} CURL_LDAPURLDesc; + +#undef LDAPURLDesc +#define LDAPURLDesc CURL_LDAPURLDesc + +static int _ldap_url_parse (const struct connectdata *conn, + LDAPURLDesc **ludp); +static void _ldap_free_urldesc (LDAPURLDesc *ludp); + +#undef ldap_free_urldesc +#define ldap_free_urldesc _ldap_free_urldesc +#endif + +#ifdef DEBUG_LDAP + #define LDAP_TRACE(x) do { \ + _ldap_trace ("%u: ", __LINE__); \ + _ldap_trace x; \ + } WHILE_FALSE + + static void _ldap_trace (const char *fmt, ...); +#else + #define LDAP_TRACE(x) Curl_nop_stmt +#endif + + +static CURLcode Curl_ldap(struct connectdata *conn, bool *done); + +/* + * LDAP protocol handler. + */ + +const struct Curl_handler Curl_handler_ldap = { + "LDAP", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_ldap, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_LDAP, /* defport */ + CURLPROTO_LDAP, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +#ifdef HAVE_LDAP_SSL +/* + * LDAPS protocol handler. + */ + +const struct Curl_handler Curl_handler_ldaps = { + "LDAPS", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_ldap, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_LDAPS, /* defport */ + CURLPROTO_LDAP | CURLPROTO_LDAPS, /* protocol */ + PROTOPT_SSL /* flags */ +}; +#endif + + +static CURLcode Curl_ldap(struct connectdata *conn, bool *done) +{ + CURLcode status = CURLE_OK; + int rc = 0; + LDAP *server = NULL; + LDAPURLDesc *ludp = NULL; + LDAPMessage *result = NULL; + LDAPMessage *entryIterator; + int num = 0; + struct SessionHandle *data=conn->data; + int ldap_proto = LDAP_VERSION3; + int ldap_ssl = 0; + char *val_b64 = NULL; + size_t val_b64_sz = 0; + curl_off_t dlsize = 0; +#ifdef LDAP_OPT_NETWORK_TIMEOUT + struct timeval ldap_timeout = {10,0}; /* 10 sec connection/search timeout */ +#endif + + *done = TRUE; /* unconditionally */ + infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d\n", + LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); + infof(data, "LDAP local: %s\n", data->change.url); + +#ifdef HAVE_LDAP_URL_PARSE + rc = ldap_url_parse(data->change.url, &ludp); +#else + rc = _ldap_url_parse(conn, &ludp); +#endif + if(rc != 0) { + failf(data, "LDAP local: %s", ldap_err2string(rc)); + status = CURLE_LDAP_INVALID_URL; + goto quit; + } + + /* Get the URL scheme ( either ldap or ldaps ) */ + if(conn->given->flags & PROTOPT_SSL) + ldap_ssl = 1; + infof(data, "LDAP local: trying to establish %s connection\n", + ldap_ssl ? "encrypted" : "cleartext"); + +#ifdef LDAP_OPT_NETWORK_TIMEOUT + ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &ldap_timeout); +#endif + ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); + + if(ldap_ssl) { +#ifdef HAVE_LDAP_SSL +#ifdef CURL_LDAP_WIN + /* Win32 LDAP SDK doesn't support insecure mode without CA! */ + server = ldap_sslinit(conn->host.name, (int)conn->port, 1); + ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON); +#else + int ldap_option; + char* ldap_ca = data->set.str[STRING_SSL_CAFILE]; +#if defined(CURL_HAS_NOVELL_LDAPSDK) + rc = ldapssl_client_init(NULL, NULL); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ldapssl_client_init %s", ldap_err2string(rc)); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } + if(data->set.ssl.verifypeer) { + /* Novell SDK supports DER or BASE64 files. */ + int cert_type = LDAPSSL_CERT_FILETYPE_B64; + if((data->set.str[STRING_CERT_TYPE]) && + (Curl_raw_equal(data->set.str[STRING_CERT_TYPE], "DER"))) + cert_type = LDAPSSL_CERT_FILETYPE_DER; + if(!ldap_ca) { + failf(data, "LDAP local: ERROR %s CA cert not set!", + (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM")); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } + infof(data, "LDAP local: using %s CA cert '%s'\n", + (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), + ldap_ca); + rc = ldapssl_add_trusted_cert(ldap_ca, cert_type); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting %s CA cert: %s", + (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), + ldap_err2string(rc)); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } + ldap_option = LDAPSSL_VERIFY_SERVER; + } + else + ldap_option = LDAPSSL_VERIFY_NONE; + rc = ldapssl_set_verify_mode(ldap_option); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting cert verify mode: %s", + ldap_err2string(rc)); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } + server = ldapssl_init(conn->host.name, (int)conn->port, 1); + if(server == NULL) { + failf(data, "LDAP local: Cannot connect to %s:%hu", + conn->host.name, conn->port); + status = CURLE_COULDNT_CONNECT; + goto quit; + } +#elif defined(LDAP_OPT_X_TLS) + if(data->set.ssl.verifypeer) { + /* OpenLDAP SDK supports BASE64 files. */ + if((data->set.str[STRING_CERT_TYPE]) && + (!Curl_raw_equal(data->set.str[STRING_CERT_TYPE], "PEM"))) { + failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type!"); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } + if(!ldap_ca) { + failf(data, "LDAP local: ERROR PEM CA cert not set!"); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } + infof(data, "LDAP local: using PEM CA cert: %s\n", ldap_ca); + rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ldap_ca); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting PEM CA cert: %s", + ldap_err2string(rc)); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } + ldap_option = LDAP_OPT_X_TLS_DEMAND; + } + else + ldap_option = LDAP_OPT_X_TLS_NEVER; + + rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &ldap_option); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting cert verify mode: %s", + ldap_err2string(rc)); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } + server = ldap_init(conn->host.name, (int)conn->port); + if(server == NULL) { + failf(data, "LDAP local: Cannot connect to %s:%hu", + conn->host.name, conn->port); + status = CURLE_COULDNT_CONNECT; + goto quit; + } + ldap_option = LDAP_OPT_X_TLS_HARD; + rc = ldap_set_option(server, LDAP_OPT_X_TLS, &ldap_option); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR setting SSL/TLS mode: %s", + ldap_err2string(rc)); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } +/* + rc = ldap_start_tls_s(server, NULL, NULL); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ERROR starting SSL/TLS mode: %s", + ldap_err2string(rc)); + status = CURLE_SSL_CERTPROBLEM; + goto quit; + } +*/ +#else + /* we should probably never come up to here since configure + should check in first place if we can support LDAP SSL/TLS */ + failf(data, "LDAP local: SSL/TLS not supported with this version " + "of the OpenLDAP toolkit\n"); + status = CURLE_SSL_CERTPROBLEM; + goto quit; +#endif +#endif +#endif /* CURL_LDAP_USE_SSL */ + } + else { + server = ldap_init(conn->host.name, (int)conn->port); + if(server == NULL) { + failf(data, "LDAP local: Cannot connect to %s:%hu", + conn->host.name, conn->port); + status = CURLE_COULDNT_CONNECT; + goto quit; + } + } +#ifdef CURL_LDAP_WIN + ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); +#endif + + rc = ldap_simple_bind_s(server, + conn->bits.user_passwd ? conn->user : NULL, + conn->bits.user_passwd ? conn->passwd : NULL); + if(!ldap_ssl && rc != 0) { + ldap_proto = LDAP_VERSION2; + ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); + rc = ldap_simple_bind_s(server, + conn->bits.user_passwd ? conn->user : NULL, + conn->bits.user_passwd ? conn->passwd : NULL); + } + if(rc != 0) { + failf(data, "LDAP local: ldap_simple_bind_s %s", ldap_err2string(rc)); + status = CURLE_LDAP_CANNOT_BIND; + goto quit; + } + + rc = ldap_search_s(server, ludp->lud_dn, ludp->lud_scope, + ludp->lud_filter, ludp->lud_attrs, 0, &result); + + if(rc != 0 && rc != LDAP_SIZELIMIT_EXCEEDED) { + failf(data, "LDAP remote: %s", ldap_err2string(rc)); + status = CURLE_LDAP_SEARCH_FAILED; + goto quit; + } + + for(num = 0, entryIterator = ldap_first_entry(server, result); + entryIterator; + entryIterator = ldap_next_entry(server, entryIterator), num++) { + BerElement *ber = NULL; + char *attribute; /*! suspicious that this isn't 'const' */ + char *dn = ldap_get_dn(server, entryIterator); + int i; + + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)dn, 0); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); + + dlsize += strlen(dn)+5; + + for(attribute = ldap_first_attribute(server, entryIterator, &ber); + attribute; + attribute = ldap_next_attribute(server, entryIterator, ber)) { + BerValue **vals = ldap_get_values_len(server, entryIterator, attribute); + + if(vals != NULL) { + for(i = 0; (vals[i] != NULL); i++) { + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *) attribute, 0); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)": ", 2); + dlsize += strlen(attribute)+3; + + if((strlen(attribute) > 7) && + (strcmp(";binary", + (char *)attribute + + (strlen((char *)attribute) - 7)) == 0)) { + /* Binary attribute, encode to base64. */ + CURLcode error = Curl_base64_encode(data, + vals[i]->bv_val, + vals[i]->bv_len, + &val_b64, + &val_b64_sz); + if(error) { + ldap_value_free_len(vals); + ldap_memfree(attribute); + ldap_memfree(dn); + if(ber) + ber_free(ber, 0); + status = error; + goto quit; + } + if(val_b64_sz > 0) { + Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, val_b64_sz); + free(val_b64); + dlsize += val_b64_sz; + } + } + else { + Curl_client_write(conn, CLIENTWRITE_BODY, vals[i]->bv_val, + vals[i]->bv_len); + dlsize += vals[i]->bv_len; + } + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); + dlsize++; + } + + /* Free memory used to store values */ + ldap_value_free_len(vals); + } + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); + dlsize++; + Curl_pgrsSetDownloadCounter(data, dlsize); + ldap_memfree(attribute); + } + ldap_memfree(dn); + if(ber) + ber_free(ber, 0); + } + +quit: + if(result) { + ldap_msgfree(result); + LDAP_TRACE (("Received %d entries\n", num)); + } + if(rc == LDAP_SIZELIMIT_EXCEEDED) + infof(data, "There are more than %d entries\n", num); + if(ludp) + ldap_free_urldesc(ludp); + if(server) + ldap_unbind_s(server); +#if defined(HAVE_LDAP_SSL) && defined(CURL_HAS_NOVELL_LDAPSDK) + if(ldap_ssl) + ldapssl_client_deinit(); +#endif /* HAVE_LDAP_SSL && CURL_HAS_NOVELL_LDAPSDK */ + + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + conn->bits.close = TRUE; + + return status; +} + +#ifdef DEBUG_LDAP +static void _ldap_trace (const char *fmt, ...) +{ + static int do_trace = -1; + va_list args; + + if(do_trace == -1) { + const char *env = getenv("CURL_TRACE"); + do_trace = (env && strtol(env, NULL, 10) > 0); + } + if(!do_trace) + return; + + va_start (args, fmt); + vfprintf (stderr, fmt, args); + va_end (args); +} +#endif + +#ifndef HAVE_LDAP_URL_PARSE + +/* + * Return scope-value for a scope-string. + */ +static int str2scope (const char *p) +{ + if(strequal(p, "one")) + return LDAP_SCOPE_ONELEVEL; + if(strequal(p, "onetree")) + return LDAP_SCOPE_ONELEVEL; + if(strequal(p, "base")) + return LDAP_SCOPE_BASE; + if(strequal(p, "sub")) + return LDAP_SCOPE_SUBTREE; + if(strequal( p, "subtree")) + return LDAP_SCOPE_SUBTREE; + return (-1); +} + +/* + * Split 'str' into strings separated by commas. + * Note: res[] points into 'str'. + */ +static char **split_str (char *str) +{ + char **res, *lasts, *s; + int i; + + for(i = 2, s = strchr(str,','); s; i++) + s = strchr(++s,','); + + res = calloc(i, sizeof(char*)); + if(!res) + return NULL; + + for(i = 0, s = strtok_r(str, ",", &lasts); s; + s = strtok_r(NULL, ",", &lasts), i++) + res[i] = s; + return res; +} + +/* + * Unescape the LDAP-URL components + */ +static bool unescape_elements (void *data, LDAPURLDesc *ludp) +{ + int i; + + if(ludp->lud_filter) { + ludp->lud_filter = curl_easy_unescape(data, ludp->lud_filter, 0, NULL); + if(!ludp->lud_filter) + return (FALSE); + } + + for(i = 0; ludp->lud_attrs && ludp->lud_attrs[i]; i++) { + ludp->lud_attrs[i] = curl_easy_unescape(data, ludp->lud_attrs[i], 0, NULL); + if(!ludp->lud_attrs[i]) + return (FALSE); + } + + for(i = 0; ludp->lud_exts && ludp->lud_exts[i]; i++) { + ludp->lud_exts[i] = curl_easy_unescape(data, ludp->lud_exts[i], 0, NULL); + if(!ludp->lud_exts[i]) + return (FALSE); + } + + if(ludp->lud_dn) { + char *dn = ludp->lud_dn; + char *new_dn = curl_easy_unescape(data, dn, 0, NULL); + + free(dn); + ludp->lud_dn = new_dn; + if(!new_dn) + return (FALSE); + } + return (TRUE); +} + +/* + * Break apart the pieces of an LDAP URL. + * Syntax: + * ldap://:/???? + * + * already known from 'conn->host.name'. + * already known from 'conn->remote_port'. + * extract the rest from 'conn->data->state.path+1'. All fields are optional. + * e.g. + * ldap://:/??? + * yields ludp->lud_dn = "". + * + * Defined in RFC4516 section 2. + */ +static int _ldap_url_parse2 (const struct connectdata *conn, LDAPURLDesc *ludp) +{ + char *p, *q; + int i; + + if(!conn->data || + !conn->data->state.path || + conn->data->state.path[0] != '/' || + !checkprefix("LDAP", conn->data->change.url)) + return LDAP_INVALID_SYNTAX; + + ludp->lud_scope = LDAP_SCOPE_BASE; + ludp->lud_port = conn->remote_port; + ludp->lud_host = conn->host.name; + + /* parse DN (Distinguished Name). + */ + ludp->lud_dn = strdup(conn->data->state.path+1); + if(!ludp->lud_dn) + return LDAP_NO_MEMORY; + + p = strchr(ludp->lud_dn, '?'); + LDAP_TRACE (("DN '%.*s'\n", p ? (size_t)(p-ludp->lud_dn) : + strlen(ludp->lud_dn), ludp->lud_dn)); + + if(!p) + goto success; + + *p++ = '\0'; + + /* parse attributes. skip "??". + */ + q = strchr(p, '?'); + if(q) + *q++ = '\0'; + + if(*p && *p != '?') { + ludp->lud_attrs = split_str(p); + if(!ludp->lud_attrs) + return LDAP_NO_MEMORY; + + for(i = 0; ludp->lud_attrs[i]; i++) + LDAP_TRACE (("attr[%d] '%s'\n", i, ludp->lud_attrs[i])); + } + + p = q; + if(!p) + goto success; + + /* parse scope. skip "??" + */ + q = strchr(p, '?'); + if(q) + *q++ = '\0'; + + if(*p && *p != '?') { + ludp->lud_scope = str2scope(p); + if(ludp->lud_scope == -1) + return LDAP_INVALID_SYNTAX; + LDAP_TRACE (("scope %d\n", ludp->lud_scope)); + } + + p = q; + if(!p) + goto success; + + /* parse filter + */ + q = strchr(p, '?'); + if(q) + *q++ = '\0'; + if(!*p) + return LDAP_INVALID_SYNTAX; + + ludp->lud_filter = p; + LDAP_TRACE (("filter '%s'\n", ludp->lud_filter)); + + p = q; + if(!p) + goto success; + + /* parse extensions + */ + ludp->lud_exts = split_str(p); + if(!ludp->lud_exts) + return LDAP_NO_MEMORY; + + for(i = 0; ludp->lud_exts[i]; i++) + LDAP_TRACE (("exts[%d] '%s'\n", i, ludp->lud_exts[i])); + + success: + if(!unescape_elements(conn->data, ludp)) + return LDAP_NO_MEMORY; + return LDAP_SUCCESS; +} + +static int _ldap_url_parse (const struct connectdata *conn, + LDAPURLDesc **ludpp) +{ + LDAPURLDesc *ludp = calloc(1, sizeof(*ludp)); + int rc; + + *ludpp = NULL; + if(!ludp) + return LDAP_NO_MEMORY; + + rc = _ldap_url_parse2 (conn, ludp); + if(rc != LDAP_SUCCESS) { + _ldap_free_urldesc(ludp); + ludp = NULL; + } + *ludpp = ludp; + return (rc); +} + +static void _ldap_free_urldesc (LDAPURLDesc *ludp) +{ + int i; + + if(!ludp) + return; + + if(ludp->lud_dn) + free(ludp->lud_dn); + + if(ludp->lud_filter) + free(ludp->lud_filter); + + if(ludp->lud_attrs) { + for(i = 0; ludp->lud_attrs[i]; i++) + free(ludp->lud_attrs[i]); + free(ludp->lud_attrs); + } + + if(ludp->lud_exts) { + for(i = 0; ludp->lud_exts[i]; i++) + free(ludp->lud_exts[i]); + free(ludp->lud_exts); + } + free (ludp); +} +#endif /* !HAVE_LDAP_URL_PARSE */ +#endif /* !CURL_DISABLE_LDAP && !USE_OPENLDAP */ diff --git a/lib/curl_llist.c b/lib/curl_llist.c new file mode 100644 index 000000000..46a8d9960 --- /dev/null +++ b/lib/curl_llist.c @@ -0,0 +1,212 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_llist.h" +#include "curl_memory.h" + +/* this must be the last include file */ +#include "curl_memdebug.h" + +/* + * @unittest: 1300 + */ +static void +llist_init(struct curl_llist *l, curl_llist_dtor dtor) +{ + l->size = 0; + l->dtor = dtor; + l->head = NULL; + l->tail = NULL; +} + +struct curl_llist * +Curl_llist_alloc(curl_llist_dtor dtor) +{ + struct curl_llist *list; + + list = malloc(sizeof(struct curl_llist)); + if(!list) + return NULL; + + llist_init(list, dtor); + + return list; +} + +/* + * Curl_llist_insert_next() + * + * Inserts a new list element after the given one 'e'. If the given existing + * entry is NULL and the list already has elements, the new one will be + * inserted first in the list. + * + * Returns: 1 on success and 0 on failure. + * + * @unittest: 1300 + */ +int +Curl_llist_insert_next(struct curl_llist *list, struct curl_llist_element *e, + const void *p) +{ + struct curl_llist_element *ne = malloc(sizeof(struct curl_llist_element)); + if(!ne) + return 0; + + ne->ptr = (void *) p; + if(list->size == 0) { + list->head = ne; + list->head->prev = NULL; + list->head->next = NULL; + list->tail = ne; + } + else { + /* if 'e' is NULL here, we insert the new element first in the list */ + ne->next = e?e->next:list->head; + ne->prev = e; + if(!e) { + list->head->prev = ne; + list->head = ne; + } + else if(e->next) { + e->next->prev = ne; + } + else { + list->tail = ne; + } + if(e) + e->next = ne; + } + + ++list->size; + + return 1; +} + +/* + * @unittest: 1300 + */ +int +Curl_llist_remove(struct curl_llist *list, struct curl_llist_element *e, + void *user) +{ + if(e == NULL || list->size == 0) + return 1; + + if(e == list->head) { + list->head = e->next; + + if(list->head == NULL) + list->tail = NULL; + else + e->next->prev = NULL; + } + else { + e->prev->next = e->next; + if(!e->next) + list->tail = e->prev; + else + e->next->prev = e->prev; + } + + list->dtor(user, e->ptr); + + e->ptr = NULL; + e->prev = NULL; + e->next = NULL; + + free(e); + --list->size; + + return 1; +} + +void +Curl_llist_destroy(struct curl_llist *list, void *user) +{ + if(list) { + while(list->size > 0) + Curl_llist_remove(list, list->tail, user); + + free(list); + } +} + +size_t +Curl_llist_count(struct curl_llist *list) +{ + return list->size; +} + +/* + * @unittest: 1300 + */ +int Curl_llist_move(struct curl_llist *list, struct curl_llist_element *e, + struct curl_llist *to_list, + struct curl_llist_element *to_e) +{ + /* Remove element from list */ + if(e == NULL || list->size == 0) + return 0; + + if(e == list->head) { + list->head = e->next; + + if(list->head == NULL) + list->tail = NULL; + else + e->next->prev = NULL; + } + else { + e->prev->next = e->next; + if(!e->next) + list->tail = e->prev; + else + e->next->prev = e->prev; + } + + --list->size; + + /* Add element to to_list after to_e */ + if(to_list->size == 0) { + to_list->head = e; + to_list->head->prev = NULL; + to_list->head->next = NULL; + to_list->tail = e; + } + else { + e->next = to_e->next; + e->prev = to_e; + if(to_e->next) { + to_e->next->prev = e; + } + else { + to_list->tail = e; + } + to_e->next = e; + } + + ++to_list->size; + + return 1; +} diff --git a/lib/curl_md4.c b/lib/curl_md4.c new file mode 100644 index 000000000..d64b472ea --- /dev/null +++ b/lib/curl_md4.c @@ -0,0 +1,282 @@ +/*- + Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD4 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD4 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +#include "curl_setup.h" + +/* NSS crypto library does not provide the MD4 hash algorithm, so that we have + * a local implementation of it */ +#ifdef USE_NSS + +#include "curl_md4.h" +#include "curl_warnless.h" + +typedef unsigned int UINT4; + +typedef struct MD4Context { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +/* Constants for MD4Transform routine. + */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static void MD4Transform(UINT4 [4], const unsigned char [64]); +static void Encode(unsigned char *, UINT4 *, unsigned int); +static void Decode(UINT4 *, const unsigned char *, unsigned int); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) { \ + (a) += F ((b), (c), (d)) + (x); \ + (a) = ROTATE_LEFT ((a), (s)); \ + } +#define GG(a, b, c, d, x, s) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; \ + (a) = ROTATE_LEFT ((a), (s)); \ + } +#define HH(a, b, c, d, x, s) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; \ + (a) = ROTATE_LEFT ((a), (s)); \ + } + +/* MD4 initialization. Begins an MD4 operation, writing a new context. + */ +static void MD4Init(MD4_CTX *context) +{ + context->count[0] = context->count[1] = 0; + + /* Load magic initialization constants. + */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest + operation, processing another message block, and updating the + context. + */ +static void MD4Update(MD4_CTX *context, const unsigned char *input, + unsigned int inputLen) +{ + unsigned int i, bufindex, partLen; + + /* Compute number of bytes mod 64 */ + bufindex = (unsigned int)((context->count[0] >> 3) & 0x3F); + /* Update number of bits */ + if((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - bufindex; + /* Transform as many times as possible. + */ + if(inputLen >= partLen) { + memcpy(&context->buffer[bufindex], input, partLen); + MD4Transform (context->state, context->buffer); + + for(i = partLen; i + 63 < inputLen; i += 64) + MD4Transform (context->state, &input[i]); + + bufindex = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy(&context->buffer[bufindex], &input[i], inputLen-i); +} + +/* MD4 padding. */ +static void MD4Pad(MD4_CTX *context) +{ + unsigned char bits[8]; + unsigned int bufindex, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. + */ + bufindex = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (bufindex < 56) ? (56 - bufindex) : (120 - bufindex); + MD4Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4Update (context, bits, 8); +} + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the + the message digest and zeroizing the context. + */ +static void MD4Final (unsigned char digest[16], MD4_CTX *context) +{ + /* Do padding */ + MD4Pad (context); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. + */ + memset(context, 0, sizeof(*context)); +} + +/* MD4 basic transformation. Transforms state based on block. + */ +static void MD4Transform (UINT4 state[4], const unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11); /* 1 */ + FF (d, a, b, c, x[ 1], S12); /* 2 */ + FF (c, d, a, b, x[ 2], S13); /* 3 */ + FF (b, c, d, a, x[ 3], S14); /* 4 */ + FF (a, b, c, d, x[ 4], S11); /* 5 */ + FF (d, a, b, c, x[ 5], S12); /* 6 */ + FF (c, d, a, b, x[ 6], S13); /* 7 */ + FF (b, c, d, a, x[ 7], S14); /* 8 */ + FF (a, b, c, d, x[ 8], S11); /* 9 */ + FF (d, a, b, c, x[ 9], S12); /* 10 */ + FF (c, d, a, b, x[10], S13); /* 11 */ + FF (b, c, d, a, x[11], S14); /* 12 */ + FF (a, b, c, d, x[12], S11); /* 13 */ + FF (d, a, b, c, x[13], S12); /* 14 */ + FF (c, d, a, b, x[14], S13); /* 15 */ + FF (b, c, d, a, x[15], S14); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 0], S21); /* 17 */ + GG (d, a, b, c, x[ 4], S22); /* 18 */ + GG (c, d, a, b, x[ 8], S23); /* 19 */ + GG (b, c, d, a, x[12], S24); /* 20 */ + GG (a, b, c, d, x[ 1], S21); /* 21 */ + GG (d, a, b, c, x[ 5], S22); /* 22 */ + GG (c, d, a, b, x[ 9], S23); /* 23 */ + GG (b, c, d, a, x[13], S24); /* 24 */ + GG (a, b, c, d, x[ 2], S21); /* 25 */ + GG (d, a, b, c, x[ 6], S22); /* 26 */ + GG (c, d, a, b, x[10], S23); /* 27 */ + GG (b, c, d, a, x[14], S24); /* 28 */ + GG (a, b, c, d, x[ 3], S21); /* 29 */ + GG (d, a, b, c, x[ 7], S22); /* 30 */ + GG (c, d, a, b, x[11], S23); /* 31 */ + GG (b, c, d, a, x[15], S24); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 0], S31); /* 33 */ + HH (d, a, b, c, x[ 8], S32); /* 34 */ + HH (c, d, a, b, x[ 4], S33); /* 35 */ + HH (b, c, d, a, x[12], S34); /* 36 */ + HH (a, b, c, d, x[ 2], S31); /* 37 */ + HH (d, a, b, c, x[10], S32); /* 38 */ + HH (c, d, a, b, x[ 6], S33); /* 39 */ + HH (b, c, d, a, x[14], S34); /* 40 */ + HH (a, b, c, d, x[ 1], S31); /* 41 */ + HH (d, a, b, c, x[ 9], S32); /* 42 */ + HH (c, d, a, b, x[ 5], S33); /* 43 */ + HH (b, c, d, a, x[13], S34); /* 44 */ + HH (a, b, c, d, x[ 3], S31); /* 45 */ + HH (d, a, b, c, x[11], S32); /* 46 */ + HH (c, d, a, b, x[ 7], S33); /* 47 */ + HH (b, c, d, a, x[15], S34); /* 48 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. + */ + memset(x, 0, sizeof(x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode(unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for(i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode (UINT4 *output, const unsigned char *input, + unsigned int len) +{ + unsigned int i, j; + + for(i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +void Curl_md4it(unsigned char *output, const unsigned char *input, size_t len) +{ + MD4_CTX ctx; + MD4Init(&ctx); + MD4Update(&ctx, input, curlx_uztoui(len)); + MD4Final(output, &ctx); +} +#endif /* USE_NSS */ diff --git a/lib/curl_md5.c b/lib/curl_md5.c new file mode 100644 index 000000000..74f53f61b --- /dev/null +++ b/lib/curl_md5.c @@ -0,0 +1,521 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_CRYPTO_AUTH + +#include "curl_md5.h" +#include "curl_hmac.h" +#include "curl_warnless.h" + +#include "curl_memory.h" + +#if defined(USE_GNUTLS_NETTLE) + +#include +/* The last #include file should be: */ +#include "curl_memdebug.h" + +typedef struct md5_ctx MD5_CTX; + +static void MD5_Init(MD5_CTX * ctx) +{ + md5_init(ctx); +} + +static void MD5_Update(MD5_CTX * ctx, + const unsigned char * input, + unsigned int inputLen) +{ + md5_update(ctx, inputLen, input); +} + +static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx) +{ + md5_digest(ctx, 16, digest); +} + +#elif defined(USE_GNUTLS) + +#include +/* The last #include file should be: */ +#include "curl_memdebug.h" + +typedef gcry_md_hd_t MD5_CTX; + +static void MD5_Init(MD5_CTX * ctx) +{ + gcry_md_open(ctx, GCRY_MD_MD5, 0); +} + +static void MD5_Update(MD5_CTX * ctx, + const unsigned char * input, + unsigned int inputLen) +{ + gcry_md_write(*ctx, input, inputLen); +} + +static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx) +{ + memcpy(digest, gcry_md_read(*ctx, 0), 16); + gcry_md_close(*ctx); +} + +#elif defined(USE_SSLEAY) +/* When OpenSSL is available we use the MD5-function from OpenSSL */ + +# ifdef USE_OPENSSL +# include +# else +# include +# endif + +#elif defined(__MAC_10_4) || defined(__IPHONE_5_0) + +/* For Apple operating systems: CommonCrypto has the functions we need. + The library's headers are even backward-compatible with OpenSSL's + headers as long as we define COMMON_DIGEST_FOR_OPENSSL first. + + These functions are available on Tiger and later, as well as iOS 5.0 + and later. If you're building for an older cat, well, sorry. */ +# define COMMON_DIGEST_FOR_OPENSSL +# include + +#elif defined(_WIN32) + +#include + +typedef struct { + HCRYPTPROV hCryptProv; + HCRYPTHASH hHash; +} MD5_CTX; + +static void MD5_Init(MD5_CTX *ctx) +{ + if(CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + CryptCreateHash(ctx->hCryptProv, CALG_MD5, 0, 0, &ctx->hHash); + } +} + +static void MD5_Update(MD5_CTX *ctx, + const unsigned char *input, + unsigned int inputLen) +{ + CryptHashData(ctx->hHash, (unsigned char *)input, inputLen, 0); +} + +static void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) +{ + unsigned long length; + CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); + if(length == 16) + CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &length, 0); + if(ctx->hHash) + CryptDestroyHash(ctx->hHash); + if(ctx->hCryptProv) + CryptReleaseContext(ctx->hCryptProv, 0); +} + +#else +/* When no other crypto library is available we use this code segment */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +/* UINT4 defines a four byte word */ +typedef unsigned int UINT4; + +/* MD5 context. */ +struct md5_ctx { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +}; + +typedef struct md5_ctx MD5_CTX; + +static void MD5_Init(struct md5_ctx *); +static void MD5_Update(struct md5_ctx *, const unsigned char *, unsigned int); +static void MD5_Final(unsigned char [16], struct md5_ctx *); + +/* Constants for MD5Transform routine. + */ + +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform(UINT4 [4], const unsigned char [64]); +static void Encode(unsigned char *, UINT4 *, unsigned int); +static void Decode(UINT4 *, const unsigned char *, unsigned int); + +static const unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +static void MD5_Init(struct md5_ctx *context) +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants. */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +static void MD5_Update (struct md5_ctx *context, /* context */ + const unsigned char *input, /* input block */ + unsigned int inputLen) /* length of input block */ +{ + unsigned int i, bufindex, partLen; + + /* Compute number of bytes mod 64 */ + bufindex = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if((context->count[0] += ((UINT4)inputLen << 3)) + < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - bufindex; + + /* Transform as many times as possible. */ + if(inputLen >= partLen) { + memcpy(&context->buffer[bufindex], input, partLen); + MD5Transform(context->state, context->buffer); + + for(i = partLen; i + 63 < inputLen; i += 64) + MD5Transform(context->state, &input[i]); + + bufindex = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy(&context->buffer[bufindex], &input[i], inputLen-i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. +*/ +static void MD5_Final(unsigned char digest[16], /* message digest */ + struct md5_ctx *context) /* context */ +{ + unsigned char bits[8]; + unsigned int count, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. */ + count = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (count < 56) ? (56 - count) : (120 - count); + MD5_Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5_Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. */ + memset ((void *)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. */ +static void MD5Transform(UINT4 state[4], + const unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. */ + memset((void *)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode (unsigned char *output, + UINT4 *input, + unsigned int len) +{ + unsigned int i, j; + + for(i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. +*/ +static void Decode (UINT4 *output, + const unsigned char *input, + unsigned int len) +{ + unsigned int i, j; + + for(i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +#endif /* CRYPTO LIBS */ + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +const HMAC_params Curl_HMAC_MD5[] = { + { + (HMAC_hinit_func) MD5_Init, /* Hash initialization function. */ + (HMAC_hupdate_func) MD5_Update, /* Hash update function. */ + (HMAC_hfinal_func) MD5_Final, /* Hash computation end function. */ + sizeof(MD5_CTX), /* Size of hash context structure. */ + 64, /* Maximum key length. */ + 16 /* Result size. */ + } +}; + +const MD5_params Curl_DIGEST_MD5[] = { + { + (Curl_MD5_init_func) MD5_Init, /* Digest initialization function */ + (Curl_MD5_update_func) MD5_Update, /* Digest update function */ + (Curl_MD5_final_func) MD5_Final, /* Digest computation end function */ + sizeof(MD5_CTX), /* Size of digest context struct */ + 16 /* Result size */ + } +}; + +void Curl_md5it(unsigned char *outbuffer, /* 16 bytes */ + const unsigned char *input) +{ + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, input, curlx_uztoui(strlen((char *)input))); + MD5_Final(outbuffer, &ctx); +} + +MD5_context *Curl_MD5_init(const MD5_params *md5params) +{ + MD5_context *ctxt; + + /* Create MD5 context */ + ctxt = malloc(sizeof *ctxt); + + if(!ctxt) + return ctxt; + + ctxt->md5_hashctx = malloc(md5params->md5_ctxtsize); + + if(!ctxt->md5_hashctx) { + free(ctxt); + return NULL; + } + + ctxt->md5_hash = md5params; + + (*md5params->md5_init_func)(ctxt->md5_hashctx); + + return ctxt; +} + +int Curl_MD5_update(MD5_context *context, + const unsigned char *data, + unsigned int len) +{ + (*context->md5_hash->md5_update_func)(context->md5_hashctx, data, len); + + return 0; +} + +int Curl_MD5_final(MD5_context *context, unsigned char *result) +{ + (*context->md5_hash->md5_final_func)(result, context->md5_hashctx); + + free(context->md5_hashctx); + free(context); + + return 0; +} + +#endif /* CURL_DISABLE_CRYPTO_AUTH */ diff --git a/lib/curl_memdebug.c b/lib/curl_memdebug.c new file mode 100644 index 000000000..e756126b2 --- /dev/null +++ b/lib/curl_memdebug.c @@ -0,0 +1,445 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef CURLDEBUG + +#include + +#define _MPRINTF_REPLACE +#include +#include "curl_urldata.h" + +#define MEMDEBUG_NODEFINES /* don't redefine the standard functions */ +#include "curl_memory.h" +#include "curl_memdebug.h" + +#ifndef HAVE_ASSERT_H +# define assert(x) Curl_nop_stmt +#endif + +/* + * Until 2011-08-17 libcurl's Memory Tracking feature also performed + * automatic malloc and free filling operations using 0xA5 and 0x13 + * values. Our own preinitialization of dynamically allocated memory + * might be useful when not using third party memory debuggers, but + * on the other hand this would fool memory debuggers into thinking + * that all dynamically allocated memory is properly initialized. + * + * As a default setting, libcurl's Memory Tracking feature no longer + * performs preinitialization of dynamically allocated memory on its + * own. If you know what you are doing, and really want to retain old + * behavior, you can achieve this compiling with preprocessor symbols + * CURL_MT_MALLOC_FILL and CURL_MT_FREE_FILL defined with appropriate + * values. + */ + +#ifdef CURL_MT_MALLOC_FILL +# if (CURL_MT_MALLOC_FILL < 0) || (CURL_MT_MALLOC_FILL > 0xff) +# error "invalid CURL_MT_MALLOC_FILL or out of range" +# endif +#endif + +#ifdef CURL_MT_FREE_FILL +# if (CURL_MT_FREE_FILL < 0) || (CURL_MT_FREE_FILL > 0xff) +# error "invalid CURL_MT_FREE_FILL or out of range" +# endif +#endif + +#if defined(CURL_MT_MALLOC_FILL) && defined(CURL_MT_FREE_FILL) +# if (CURL_MT_MALLOC_FILL == CURL_MT_FREE_FILL) +# error "CURL_MT_MALLOC_FILL same as CURL_MT_FREE_FILL" +# endif +#endif + +#ifdef CURL_MT_MALLOC_FILL +# define mt_malloc_fill(buf,len) memset((buf), CURL_MT_MALLOC_FILL, (len)) +#else +# define mt_malloc_fill(buf,len) Curl_nop_stmt +#endif + +#ifdef CURL_MT_FREE_FILL +# define mt_free_fill(buf,len) memset((buf), CURL_MT_FREE_FILL, (len)) +#else +# define mt_free_fill(buf,len) Curl_nop_stmt +#endif + +struct memdebug { + size_t size; + union { + curl_off_t o; + double d; + void * p; + } mem[1]; + /* I'm hoping this is the thing with the strictest alignment + * requirements. That also means we waste some space :-( */ +}; + +/* + * Note that these debug functions are very simple and they are meant to + * remain so. For advanced analysis, record a log file and write perl scripts + * to analyze them! + * + * Don't use these with multithreaded test programs! + */ + +#define logfile curl_debuglogfile +FILE *curl_debuglogfile = NULL; +static bool memlimit = FALSE; /* enable memory limit */ +static long memsize = 0; /* set number of mallocs allowed */ + +/* this sets the log file name */ +void curl_memdebug(const char *logname) +{ + if(!logfile) { + if(logname && *logname) + logfile = fopen(logname, "w"); + else + logfile = stderr; +#ifdef MEMDEBUG_LOG_SYNC + /* Flush the log file after every line so the log isn't lost in a crash */ + setvbuf(logfile, (char *)NULL, _IOLBF, 0); +#endif + } +} + +/* This function sets the number of malloc() calls that should return + successfully! */ +void curl_memlimit(long limit) +{ + if(!memlimit) { + memlimit = TRUE; + memsize = limit; + } +} + +/* returns TRUE if this isn't allowed! */ +static bool countcheck(const char *func, int line, const char *source) +{ + /* if source is NULL, then the call is made internally and this check + should not be made */ + if(memlimit && source) { + if(!memsize) { + if(source) { + /* log to file */ + curl_memlog("LIMIT %s:%d %s reached memlimit\n", + source, line, func); + /* log to stderr also */ + fprintf(stderr, "LIMIT %s:%d %s reached memlimit\n", + source, line, func); + } + SET_ERRNO(ENOMEM); + return TRUE; /* RETURN ERROR! */ + } + else + memsize--; /* countdown */ + + /* log the countdown */ + if(source) + curl_memlog("LIMIT %s:%d %ld ALLOCS left\n", + source, line, memsize); + + } + + return FALSE; /* allow this */ +} + +void *curl_domalloc(size_t wantedsize, int line, const char *source) +{ + struct memdebug *mem; + size_t size; + + assert(wantedsize != 0); + + if(countcheck("malloc", line, source)) + return NULL; + + /* alloc at least 64 bytes */ + size = sizeof(struct memdebug)+wantedsize; + + mem = (Curl_cmalloc)(size); + if(mem) { + /* fill memory with junk */ + mt_malloc_fill(mem->mem, wantedsize); + mem->size = wantedsize; + } + + if(source) + curl_memlog("MEM %s:%d malloc(%zd) = %p\n", + source, line, wantedsize, mem ? mem->mem : 0); + return (mem ? mem->mem : NULL); +} + +void *curl_docalloc(size_t wanted_elements, size_t wanted_size, + int line, const char *source) +{ + struct memdebug *mem; + size_t size, user_size; + + assert(wanted_elements != 0); + assert(wanted_size != 0); + + if(countcheck("calloc", line, source)) + return NULL; + + /* alloc at least 64 bytes */ + user_size = wanted_size * wanted_elements; + size = sizeof(struct memdebug) + user_size; + + mem = (Curl_ccalloc)(1, size); + if(mem) + mem->size = user_size; + + if(source) + curl_memlog("MEM %s:%d calloc(%zu,%zu) = %p\n", + source, line, wanted_elements, wanted_size, mem?mem->mem:0); + return (mem ? mem->mem : NULL); +} + +char *curl_dostrdup(const char *str, int line, const char *source) +{ + char *mem; + size_t len; + + assert(str != NULL); + + if(countcheck("strdup", line, source)) + return NULL; + + len=strlen(str)+1; + + mem=curl_domalloc(len, 0, NULL); /* NULL prevents logging */ + if(mem) + memcpy(mem, str, len); + + if(source) + curl_memlog("MEM %s:%d strdup(%p) (%zu) = %p\n", + source, line, str, len, mem); + + return mem; +} + +/* We provide a realloc() that accepts a NULL as pointer, which then + performs a malloc(). In order to work with ares. */ +void *curl_dorealloc(void *ptr, size_t wantedsize, + int line, const char *source) +{ + struct memdebug *mem=NULL; + + size_t size = sizeof(struct memdebug)+wantedsize; + + assert(wantedsize != 0); + + if(countcheck("realloc", line, source)) + return NULL; + +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:1684) + /* 1684: conversion from pointer to same-sized integral type */ +#endif + + if(ptr) + mem = (void *)((char *)ptr - offsetof(struct memdebug, mem)); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif + + mem = (Curl_crealloc)(mem, size); + if(source) + curl_memlog("MEM %s:%d realloc(%p, %zu) = %p\n", + source, line, ptr, wantedsize, mem?mem->mem:NULL); + + if(mem) { + mem->size = wantedsize; + return mem->mem; + } + + return NULL; +} + +void curl_dofree(void *ptr, int line, const char *source) +{ + struct memdebug *mem; + + assert(ptr != NULL); + +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:1684) + /* 1684: conversion from pointer to same-sized integral type */ +#endif + + mem = (void *)((char *)ptr - offsetof(struct memdebug, mem)); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif + + /* destroy */ + mt_free_fill(mem->mem, mem->size); + + /* free for real */ + (Curl_cfree)(mem); + + if(source) + curl_memlog("MEM %s:%d free(%p)\n", source, line, ptr); +} + +curl_socket_t curl_socket(int domain, int type, int protocol, + int line, const char *source) +{ + const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? + "FD %s:%d socket() = %d\n" : + (sizeof(curl_socket_t) == sizeof(long)) ? + "FD %s:%d socket() = %ld\n" : + "FD %s:%d socket() = %zd\n" ; + + curl_socket_t sockfd = socket(domain, type, protocol); + if(source && (sockfd != CURL_SOCKET_BAD)) + curl_memlog(fmt, source, line, sockfd); + return sockfd; +} + +#ifdef HAVE_SOCKETPAIR +int curl_socketpair(int domain, int type, int protocol, + curl_socket_t socket_vector[2], + int line, const char *source) +{ + const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? + "FD %s:%d socketpair() = %d %d\n" : + (sizeof(curl_socket_t) == sizeof(long)) ? + "FD %s:%d socketpair() = %ld %ld\n" : + "FD %s:%d socketpair() = %zd %zd\n" ; + + int res = socketpair(domain, type, protocol, socket_vector); + if(source && (0 == res)) + curl_memlog(fmt, source, line, socket_vector[0], socket_vector[1]); + return res; +} +#endif + +curl_socket_t curl_accept(curl_socket_t s, void *saddr, void *saddrlen, + int line, const char *source) +{ + const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? + "FD %s:%d accept() = %d\n" : + (sizeof(curl_socket_t) == sizeof(long)) ? + "FD %s:%d accept() = %ld\n" : + "FD %s:%d accept() = %zd\n" ; + + struct sockaddr *addr = (struct sockaddr *)saddr; + curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen; + curl_socket_t sockfd = accept(s, addr, addrlen); + if(source && (sockfd != CURL_SOCKET_BAD)) + curl_memlog(fmt, source, line, sockfd); + return sockfd; +} + +/* separate function to allow libcurl to mark a "faked" close */ +void curl_mark_sclose(curl_socket_t sockfd, int line, const char *source) +{ + const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? + "FD %s:%d sclose(%d)\n" : + (sizeof(curl_socket_t) == sizeof(long)) ? + "FD %s:%d sclose(%ld)\n" : + "FD %s:%d sclose(%zd)\n" ; + + if(source) + curl_memlog(fmt, source, line, sockfd); +} + +/* this is our own defined way to close sockets on *ALL* platforms */ +int curl_sclose(curl_socket_t sockfd, int line, const char *source) +{ + int res=sclose(sockfd); + curl_mark_sclose(sockfd, line, source); + return res; +} + +FILE *curl_fopen(const char *file, const char *mode, + int line, const char *source) +{ + FILE *res=fopen(file, mode); + if(source) + curl_memlog("FILE %s:%d fopen(\"%s\",\"%s\") = %p\n", + source, line, file, mode, res); + return res; +} + +#ifdef HAVE_FDOPEN +FILE *curl_fdopen(int filedes, const char *mode, + int line, const char *source) +{ + FILE *res=fdopen(filedes, mode); + if(source) + curl_memlog("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n", + source, line, filedes, mode, res); + return res; +} +#endif + +int curl_fclose(FILE *file, int line, const char *source) +{ + int res; + + assert(file != NULL); + + res=fclose(file); + if(source) + curl_memlog("FILE %s:%d fclose(%p)\n", + source, line, file); + return res; +} + +#define LOGLINE_BUFSIZE 1024 + +/* this does the writting to the memory tracking log file */ +void curl_memlog(const char *format, ...) +{ + char *buf; + int nchars; + va_list ap; + + if(!logfile) + return; + + buf = (Curl_cmalloc)(LOGLINE_BUFSIZE); + if(!buf) + return; + + va_start(ap, format); + nchars = vsnprintf(buf, LOGLINE_BUFSIZE, format, ap); + va_end(ap); + + if(nchars > LOGLINE_BUFSIZE - 1) + nchars = LOGLINE_BUFSIZE - 1; + + if(nchars > 0) + fwrite(buf, 1, nchars, logfile); + + (Curl_cfree)(buf); +} + +#endif /* CURLDEBUG */ diff --git a/lib/curl_mprintf.c b/lib/curl_mprintf.c new file mode 100644 index 000000000..35b9f644f --- /dev/null +++ b/lib/curl_mprintf.c @@ -0,0 +1,1197 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1999 - 2013, 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 + * 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. + * + * + * Purpose: + * A merge of Bjorn Reese's format() function and Daniel's dsprintf() + * 1.0. A full blooded printf() clone with full support for $ + * everywhere (parameters, widths and precisions) including variabled + * sized parameters (like doubles, long longs, long doubles and even + * void * in 64-bit architectures). + * + * Current restrictions: + * - Max 128 parameters + * - No 'long double' support. + * + * If you ever want truly portable and good *printf() clones, the project that + * took on from here is named 'Trio' and you find more details on the trio web + * page at http://daniel.haxx.se/trio/ + */ + +#include "curl_setup.h" + +#if defined(DJGPP) && (DJGPP_MINOR < 4) +#undef _MPRINTF_REPLACE /* don't use x_was_used() here */ +#endif + +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifndef SIZEOF_LONG_DOUBLE +#define SIZEOF_LONG_DOUBLE 0 +#endif + +/* + * If SIZEOF_SIZE_T has not been defined, default to the size of long. + */ + +#ifndef SIZEOF_SIZE_T +# define SIZEOF_SIZE_T CURL_SIZEOF_LONG +#endif + +#ifdef HAVE_LONGLONG +# define LONG_LONG_TYPE long long +# define HAVE_LONG_LONG_TYPE +#else +# if defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) +# define LONG_LONG_TYPE __int64 +# define HAVE_LONG_LONG_TYPE +# else +# undef LONG_LONG_TYPE +# undef HAVE_LONG_LONG_TYPE +# endif +#endif + +/* + * Max integer data types that curl_mprintf.c is capable + */ + +#ifdef HAVE_LONG_LONG_TYPE +# define mp_intmax_t LONG_LONG_TYPE +# define mp_uintmax_t unsigned LONG_LONG_TYPE +#else +# define mp_intmax_t long +# define mp_uintmax_t unsigned long +#endif + +#define BUFFSIZE 256 /* buffer for long-to-str and float-to-str calcs */ +#define MAX_PARAMETERS 128 /* lame static limit */ + +#ifdef __AMIGA__ +# undef FORMAT_INT +#endif + +/* Lower-case digits. */ +static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +/* Upper-case digits. */ +static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +#define OUTCHAR(x) \ + do{ \ + if(stream((unsigned char)(x), (FILE *)data) != -1) \ + done++; \ + else \ + return done; /* return immediately on failure */ \ + } WHILE_FALSE + +/* Data type to read from the arglist */ +typedef enum { + FORMAT_UNKNOWN = 0, + FORMAT_STRING, + FORMAT_PTR, + FORMAT_INT, + FORMAT_INTPTR, + FORMAT_LONG, + FORMAT_LONGLONG, + FORMAT_DOUBLE, + FORMAT_LONGDOUBLE, + FORMAT_WIDTH /* For internal use */ +} FormatType; + +/* conversion and display flags */ +enum { + FLAGS_NEW = 0, + FLAGS_SPACE = 1<<0, + FLAGS_SHOWSIGN = 1<<1, + FLAGS_LEFT = 1<<2, + FLAGS_ALT = 1<<3, + FLAGS_SHORT = 1<<4, + FLAGS_LONG = 1<<5, + FLAGS_LONGLONG = 1<<6, + FLAGS_LONGDOUBLE = 1<<7, + FLAGS_PAD_NIL = 1<<8, + FLAGS_UNSIGNED = 1<<9, + FLAGS_OCTAL = 1<<10, + FLAGS_HEX = 1<<11, + FLAGS_UPPER = 1<<12, + FLAGS_WIDTH = 1<<13, /* '*' or '*$' used */ + FLAGS_WIDTHPARAM = 1<<14, /* width PARAMETER was specified */ + FLAGS_PREC = 1<<15, /* precision was specified */ + FLAGS_PRECPARAM = 1<<16, /* precision PARAMETER was specified */ + FLAGS_CHAR = 1<<17, /* %c story */ + FLAGS_FLOATE = 1<<18, /* %e or %E */ + FLAGS_FLOATG = 1<<19 /* %g or %G */ +}; + +typedef struct { + FormatType type; + int flags; + long width; /* width OR width parameter number */ + long precision; /* precision OR precision parameter number */ + union { + char *str; + void *ptr; + union { + mp_intmax_t as_signed; + mp_uintmax_t as_unsigned; + } num; + double dnum; + } data; +} va_stack_t; + +struct nsprintf { + char *buffer; + size_t length; + size_t max; +}; + +struct asprintf { + char *buffer; /* allocated buffer */ + size_t len; /* length of string */ + size_t alloc; /* length of alloc */ + int fail; /* (!= 0) if an alloc has failed and thus + the output is not the complete data */ +}; + +static long dprintf_DollarString(char *input, char **end) +{ + int number=0; + while(ISDIGIT(*input)) { + number *= 10; + number += *input-'0'; + input++; + } + if(number && ('$'==*input++)) { + *end = input; + return number; + } + return 0; +} + +static int dprintf_IsQualifierNoDollar(char c) +{ + switch (c) { + case '-': case '+': case ' ': case '#': case '.': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'h': case 'l': case 'L': case 'z': case 'q': + case '*': case 'O': + return 1; /* true */ + default: + return 0; /* false */ + } +} + +#ifdef DPRINTF_DEBUG2 +static void dprintf_Pass1Report(va_stack_t *vto, int max) +{ + int i; + char buffer[256]; + int bit; + int flags; + + for(i=0; i max_param) + max_param = this_param; + + /* + * The parameter with number 'i' should be used. Next, we need + * to get SIZE and TYPE of the parameter. Add the information + * to our array. + */ + + width = 0; + precision = 0; + + /* Handle the flags */ + + while(dprintf_IsQualifierNoDollar(*fmt)) { + switch (*fmt++) { + case ' ': + flags |= FLAGS_SPACE; + break; + case '+': + flags |= FLAGS_SHOWSIGN; + break; + case '-': + flags |= FLAGS_LEFT; + flags &= ~FLAGS_PAD_NIL; + break; + case '#': + flags |= FLAGS_ALT; + break; + case '.': + flags |= FLAGS_PREC; + if('*' == *fmt) { + /* The precision is picked from a specified parameter */ + + flags |= FLAGS_PRECPARAM; + fmt++; + param_num++; + + i = dprintf_DollarString(fmt, &fmt); + if(i) + precision = i; + else + precision = param_num; + + if(precision > max_param) + max_param = precision; + } + else { + flags |= FLAGS_PREC; + precision = strtol(fmt, &fmt, 10); + } + break; + case 'h': + flags |= FLAGS_SHORT; + break; + case 'l': + if(flags & FLAGS_LONG) + flags |= FLAGS_LONGLONG; + else + flags |= FLAGS_LONG; + break; + case 'L': + flags |= FLAGS_LONGDOUBLE; + break; + case 'q': + flags |= FLAGS_LONGLONG; + break; + case 'z': + /* the code below generates a warning if -Wunreachable-code is + used */ +#if (SIZEOF_SIZE_T > CURL_SIZEOF_LONG) + flags |= FLAGS_LONGLONG; +#else + flags |= FLAGS_LONG; +#endif + break; + case 'O': +#if (CURL_SIZEOF_CURL_OFF_T > CURL_SIZEOF_LONG) + flags |= FLAGS_LONGLONG; +#else + flags |= FLAGS_LONG; +#endif + break; + case '0': + if(!(flags & FLAGS_LEFT)) + flags |= FLAGS_PAD_NIL; + /* FALLTHROUGH */ + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + flags |= FLAGS_WIDTH; + width = strtol(fmt-1, &fmt, 10); + break; + case '*': /* Special case */ + flags |= FLAGS_WIDTHPARAM; + param_num++; + + i = dprintf_DollarString(fmt, &fmt); + if(i) + width = i; + else + width = param_num; + if(width > max_param) + max_param=width; + break; + default: + break; + } + } /* switch */ + + /* Handle the specifier */ + + i = this_param - 1; + + switch (*fmt) { + case 'S': + flags |= FLAGS_ALT; + /* FALLTHROUGH */ + case 's': + vto[i].type = FORMAT_STRING; + break; + case 'n': + vto[i].type = FORMAT_INTPTR; + break; + case 'p': + vto[i].type = FORMAT_PTR; + break; + case 'd': case 'i': + vto[i].type = FORMAT_INT; + break; + case 'u': + vto[i].type = FORMAT_INT; + flags |= FLAGS_UNSIGNED; + break; + case 'o': + vto[i].type = FORMAT_INT; + flags |= FLAGS_OCTAL; + break; + case 'x': + vto[i].type = FORMAT_INT; + flags |= FLAGS_HEX; + break; + case 'X': + vto[i].type = FORMAT_INT; + flags |= FLAGS_HEX|FLAGS_UPPER; + break; + case 'c': + vto[i].type = FORMAT_INT; + flags |= FLAGS_CHAR; + break; + case 'f': + vto[i].type = FORMAT_DOUBLE; + break; + case 'e': + vto[i].type = FORMAT_DOUBLE; + flags |= FLAGS_FLOATE; + break; + case 'E': + vto[i].type = FORMAT_DOUBLE; + flags |= FLAGS_FLOATE|FLAGS_UPPER; + break; + case 'g': + vto[i].type = FORMAT_DOUBLE; + flags |= FLAGS_FLOATG; + break; + case 'G': + vto[i].type = FORMAT_DOUBLE; + flags |= FLAGS_FLOATG|FLAGS_UPPER; + break; + default: + vto[i].type = FORMAT_UNKNOWN; + break; + } /* switch */ + + vto[i].flags = flags; + vto[i].width = width; + vto[i].precision = precision; + + if(flags & FLAGS_WIDTHPARAM) { + /* we have the width specified from a parameter, so we make that + parameter's info setup properly */ + vto[i].width = width - 1; + i = width - 1; + vto[i].type = FORMAT_WIDTH; + vto[i].flags = FLAGS_NEW; + vto[i].precision = vto[i].width = 0; /* can't use width or precision + of width! */ + } + if(flags & FLAGS_PRECPARAM) { + /* we have the precision specified from a parameter, so we make that + parameter's info setup properly */ + vto[i].precision = precision - 1; + i = precision - 1; + vto[i].type = FORMAT_WIDTH; + vto[i].flags = FLAGS_NEW; + vto[i].precision = vto[i].width = 0; /* can't use width or precision + of width! */ + } + *endpos++ = fmt + 1; /* end of this sequence */ + } + } + +#ifdef DPRINTF_DEBUG2 + dprintf_Pass1Report(vto, max_param); +#endif + + /* Read the arg list parameters into our data list */ + for(i=0; i$ sequence */ + param=dprintf_DollarString(f, &f); + + if(!param) + param = param_num; + else + --param; + + param_num++; /* increase this always to allow "%2$s %1$s %s" and then the + third %s will pick the 3rd argument */ + + p = &vto[param]; + + /* pick up the specified width */ + if(p->flags & FLAGS_WIDTHPARAM) + width = (long)vto[p->width].data.num.as_signed; + else + width = p->width; + + /* pick up the specified precision */ + if(p->flags & FLAGS_PRECPARAM) { + prec = (long)vto[p->precision].data.num.as_signed; + param_num++; /* since the precision is extraced from a parameter, we + must skip that to get to the next one properly */ + } + else if(p->flags & FLAGS_PREC) + prec = p->precision; + else + prec = -1; + + is_alt = (p->flags & FLAGS_ALT) ? 1 : 0; + + switch (p->type) { + case FORMAT_INT: + num = p->data.num.as_unsigned; + if(p->flags & FLAGS_CHAR) { + /* Character. */ + if(!(p->flags & FLAGS_LEFT)) + while(--width > 0) + OUTCHAR(' '); + OUTCHAR((char) num); + if(p->flags & FLAGS_LEFT) + while(--width > 0) + OUTCHAR(' '); + break; + } + if(p->flags & FLAGS_UNSIGNED) { + /* Decimal unsigned integer. */ + base = 10; + goto unsigned_number; + } + if(p->flags & FLAGS_OCTAL) { + /* Octal unsigned integer. */ + base = 8; + goto unsigned_number; + } + if(p->flags & FLAGS_HEX) { + /* Hexadecimal unsigned integer. */ + + digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; + base = 16; + goto unsigned_number; + } + + /* Decimal integer. */ + base = 10; + + is_neg = (p->data.num.as_signed < (mp_intmax_t)0) ? 1 : 0; + if(is_neg) { + /* signed_num might fail to hold absolute negative minimum by 1 */ + signed_num = p->data.num.as_signed + (mp_intmax_t)1; + signed_num = -signed_num; + num = (mp_uintmax_t)signed_num; + num += (mp_uintmax_t)1; + } + + goto number; + + unsigned_number: + /* Unsigned number of base BASE. */ + is_neg = 0; + + number: + /* Number of base BASE. */ + { + char *workend = &work[sizeof(work) - 1]; + char *w; + + /* Supply a default precision if none was given. */ + if(prec == -1) + prec = 1; + + /* Put the number in WORK. */ + w = workend; + while(num > 0) { + *w-- = digits[num % base]; + num /= base; + } + width -= (long)(workend - w); + prec -= (long)(workend - w); + + if(is_alt && base == 8 && prec <= 0) { + *w-- = '0'; + --width; + } + + if(prec > 0) { + width -= prec; + while(prec-- > 0) + *w-- = '0'; + } + + if(is_alt && base == 16) + width -= 2; + + if(is_neg || (p->flags & FLAGS_SHOWSIGN) || (p->flags & FLAGS_SPACE)) + --width; + + if(!(p->flags & FLAGS_LEFT) && !(p->flags & FLAGS_PAD_NIL)) + while(width-- > 0) + OUTCHAR(' '); + + if(is_neg) + OUTCHAR('-'); + else if(p->flags & FLAGS_SHOWSIGN) + OUTCHAR('+'); + else if(p->flags & FLAGS_SPACE) + OUTCHAR(' '); + + if(is_alt && base == 16) { + OUTCHAR('0'); + if(p->flags & FLAGS_UPPER) + OUTCHAR('X'); + else + OUTCHAR('x'); + } + + if(!(p->flags & FLAGS_LEFT) && (p->flags & FLAGS_PAD_NIL)) + while(width-- > 0) + OUTCHAR('0'); + + /* Write the number. */ + while(++w <= workend) { + OUTCHAR(*w); + } + + if(p->flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + } + break; + + case FORMAT_STRING: + /* String. */ + { + static const char null[] = "(nil)"; + const char *str; + size_t len; + + str = (char *) p->data.str; + if(str == NULL) { + /* Write null[] if there's space. */ + if(prec == -1 || prec >= (long) sizeof(null) - 1) { + str = null; + len = sizeof(null) - 1; + /* Disable quotes around (nil) */ + p->flags &= (~FLAGS_ALT); + } + else { + str = ""; + len = 0; + } + } + else + len = strlen(str); + + if(prec != -1 && (size_t) prec < len) + len = (size_t)prec; + width -= (long)len; + + if(p->flags & FLAGS_ALT) + OUTCHAR('"'); + + if(!(p->flags&FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + + while(len-- > 0) + OUTCHAR(*str++); + if(p->flags&FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + + if(p->flags & FLAGS_ALT) + OUTCHAR('"'); + } + break; + + case FORMAT_PTR: + /* Generic pointer. */ + { + void *ptr; + ptr = (void *) p->data.ptr; + if(ptr != NULL) { + /* If the pointer is not NULL, write it as a %#x spec. */ + base = 16; + digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; + is_alt = 1; + num = (size_t) ptr; + is_neg = 0; + goto number; + } + else { + /* Write "(nil)" for a nil pointer. */ + static const char strnil[] = "(nil)"; + const char *point; + + width -= (long)(sizeof(strnil) - 1); + if(p->flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + for(point = strnil; *point != '\0'; ++point) + OUTCHAR(*point); + if(! (p->flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + } + } + break; + + case FORMAT_DOUBLE: + { + char formatbuf[32]="%"; + char *fptr; + size_t left = sizeof(formatbuf)-strlen(formatbuf); + int len; + + width = -1; + if(p->flags & FLAGS_WIDTH) + width = p->width; + else if(p->flags & FLAGS_WIDTHPARAM) + width = (long)vto[p->width].data.num.as_signed; + + prec = -1; + if(p->flags & FLAGS_PREC) + prec = p->precision; + else if(p->flags & FLAGS_PRECPARAM) + prec = (long)vto[p->precision].data.num.as_signed; + + if(p->flags & FLAGS_LEFT) + strcat(formatbuf, "-"); + if(p->flags & FLAGS_SHOWSIGN) + strcat(formatbuf, "+"); + if(p->flags & FLAGS_SPACE) + strcat(formatbuf, " "); + if(p->flags & FLAGS_ALT) + strcat(formatbuf, "#"); + + fptr=&formatbuf[strlen(formatbuf)]; + + if(width >= 0) { + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, "%ld", width); + fptr += len; + left -= len; + } + if(prec >= 0) { + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, ".%ld", prec); + fptr += len; + } + if(p->flags & FLAGS_LONG) + *fptr++ = 'l'; + + if(p->flags & FLAGS_FLOATE) + *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'E':'e'); + else if(p->flags & FLAGS_FLOATG) + *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'G' : 'g'); + else + *fptr++ = 'f'; + + *fptr = 0; /* and a final zero termination */ + + /* NOTE NOTE NOTE!! Not all sprintf() implementations returns number + of output characters */ + (sprintf)(work, formatbuf, p->data.dnum); + + for(fptr=work; *fptr; fptr++) + OUTCHAR(*fptr); + } + break; + + case FORMAT_INTPTR: + /* Answer the count of characters written. */ +#ifdef HAVE_LONG_LONG_TYPE + if(p->flags & FLAGS_LONGLONG) + *(LONG_LONG_TYPE *) p->data.ptr = (LONG_LONG_TYPE)done; + else +#endif + if(p->flags & FLAGS_LONG) + *(long *) p->data.ptr = (long)done; + else if(!(p->flags & FLAGS_SHORT)) + *(int *) p->data.ptr = (int)done; + else + *(short *) p->data.ptr = (short)done; + break; + + default: + break; + } + f = *end++; /* goto end of %-code */ + + } + return done; +} + +/* fputc() look-alike */ +static int addbyter(int output, FILE *data) +{ + struct nsprintf *infop=(struct nsprintf *)data; + unsigned char outc = (unsigned char)output; + + if(infop->length < infop->max) { + /* only do this if we haven't reached max length yet */ + infop->buffer[0] = outc; /* store */ + infop->buffer++; /* increase pointer */ + infop->length++; /* we are now one byte larger */ + return outc; /* fputc() returns like this on success */ + } + return -1; +} + +int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, + va_list ap_save) +{ + int retcode; + struct nsprintf info; + + info.buffer = buffer; + info.length = 0; + info.max = maxlength; + + retcode = dprintf_formatf(&info, addbyter, format, ap_save); + if(info.max) { + /* we terminate this with a zero byte */ + if(info.max == info.length) + /* we're at maximum, scrap the last letter */ + info.buffer[-1] = 0; + else + info.buffer[0] = 0; + } + return retcode; +} + +int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) +{ + int retcode; + va_list ap_save; /* argument pointer */ + va_start(ap_save, format); + retcode = curl_mvsnprintf(buffer, maxlength, format, ap_save); + va_end(ap_save); + return retcode; +} + +/* fputc() look-alike */ +static int alloc_addbyter(int output, FILE *data) +{ + struct asprintf *infop=(struct asprintf *)data; + unsigned char outc = (unsigned char)output; + + if(!infop->buffer) { + infop->buffer = malloc(32); + if(!infop->buffer) { + infop->fail = 1; + return -1; /* fail */ + } + infop->alloc = 32; + infop->len =0; + } + else if(infop->len+1 >= infop->alloc) { + char *newptr; + + newptr = realloc(infop->buffer, infop->alloc*2); + + if(!newptr) { + infop->fail = 1; + return -1; /* fail */ + } + infop->buffer = newptr; + infop->alloc *= 2; + } + + infop->buffer[ infop->len ] = outc; + + infop->len++; + + return outc; /* fputc() returns like this on success */ +} + +char *curl_maprintf(const char *format, ...) +{ + va_list ap_save; /* argument pointer */ + int retcode; + struct asprintf info; + + info.buffer = NULL; + info.len = 0; + info.alloc = 0; + info.fail = 0; + + va_start(ap_save, format); + retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save); + va_end(ap_save); + if((-1 == retcode) || info.fail) { + if(info.alloc) + free(info.buffer); + return NULL; + } + if(info.alloc) { + info.buffer[info.len] = 0; /* we terminate this with a zero byte */ + return info.buffer; + } + else + return strdup(""); +} + +char *curl_mvaprintf(const char *format, va_list ap_save) +{ + int retcode; + struct asprintf info; + + info.buffer = NULL; + info.len = 0; + info.alloc = 0; + info.fail = 0; + + retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save); + if((-1 == retcode) || info.fail) { + if(info.alloc) + free(info.buffer); + return NULL; + } + + if(info.alloc) { + info.buffer[info.len] = 0; /* we terminate this with a zero byte */ + return info.buffer; + } + else + return strdup(""); +} + +static int storebuffer(int output, FILE *data) +{ + char **buffer = (char **)data; + unsigned char outc = (unsigned char)output; + **buffer = outc; + (*buffer)++; + return outc; /* act like fputc() ! */ +} + +int curl_msprintf(char *buffer, const char *format, ...) +{ + va_list ap_save; /* argument pointer */ + int retcode; + va_start(ap_save, format); + retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + va_end(ap_save); + *buffer=0; /* we terminate this with a zero byte */ + return retcode; +} + +int curl_mprintf(const char *format, ...) +{ + int retcode; + va_list ap_save; /* argument pointer */ + va_start(ap_save, format); + + retcode = dprintf_formatf(stdout, fputc, format, ap_save); + va_end(ap_save); + return retcode; +} + +int curl_mfprintf(FILE *whereto, const char *format, ...) +{ + int retcode; + va_list ap_save; /* argument pointer */ + va_start(ap_save, format); + retcode = dprintf_formatf(whereto, fputc, format, ap_save); + va_end(ap_save); + return retcode; +} + +int curl_mvsprintf(char *buffer, const char *format, va_list ap_save) +{ + int retcode; + retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); + *buffer=0; /* we terminate this with a zero byte */ + return retcode; +} + +int curl_mvprintf(const char *format, va_list ap_save) +{ + return dprintf_formatf(stdout, fputc, format, ap_save); +} + +int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save) +{ + return dprintf_formatf(whereto, fputc, format, ap_save); +} diff --git a/lib/curl_multi.c b/lib/curl_multi.c new file mode 100644 index 000000000..9553883cb --- /dev/null +++ b/lib/curl_multi.c @@ -0,0 +1,2814 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#include "curl_urldata.h" +#include "curl_transfer.h" +#include "curl_url.h" +#include "curl_connect.h" +#include "curl_progress.h" +#include "curl_easyif.h" +#include "curl_multiif.h" +#include "curl_sendf.h" +#include "curl_timeval.h" +#include "curl_http.h" +#include "curl_select.h" +#include "curl_warnless.h" +#include "curl_speedcheck.h" +#include "curl_conncache.h" +#include "curl_bundles.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + CURL_SOCKET_HASH_TABLE_SIZE should be a prime number. Increasing it from 97 + to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every + CURL handle takes 45-50 K memory, therefore this 3K are not significant. +*/ +#ifndef CURL_SOCKET_HASH_TABLE_SIZE +#define CURL_SOCKET_HASH_TABLE_SIZE 911 +#endif + +struct Curl_message { + /* the 'CURLMsg' is the part that is visible to the external user */ + struct CURLMsg extmsg; +}; + +/* NOTE: if you add a state here, add the name to the statename[] array as + well! +*/ +typedef enum { + CURLM_STATE_INIT, /* 0 - start in this state */ + CURLM_STATE_CONNECT, /* 1 - resolve/connect has been sent off */ + CURLM_STATE_WAITRESOLVE, /* 2 - awaiting the resolve to finalize */ + CURLM_STATE_WAITCONNECT, /* 3 - awaiting the connect to finalize */ + CURLM_STATE_WAITPROXYCONNECT, /* 4 - awaiting proxy CONNECT to finalize */ + CURLM_STATE_PROTOCONNECT, /* 5 - completing the protocol-specific connect + phase */ + CURLM_STATE_WAITDO, /* 6 - wait for our turn to send the request */ + CURLM_STATE_DO, /* 7 - start send off the request (part 1) */ + CURLM_STATE_DOING, /* 8 - sending off the request (part 1) */ + CURLM_STATE_DO_MORE, /* 9 - send off the request (part 2) */ + CURLM_STATE_DO_DONE, /* 10 - done sending off request */ + CURLM_STATE_WAITPERFORM, /* 11 - wait for our turn to read the response */ + CURLM_STATE_PERFORM, /* 12 - transfer data */ + CURLM_STATE_TOOFAST, /* 13 - wait because limit-rate exceeded */ + CURLM_STATE_DONE, /* 14 - post data transfer operation */ + CURLM_STATE_COMPLETED, /* 15 - operation complete */ + CURLM_STATE_MSGSENT, /* 16 - the operation complete message is sent */ + CURLM_STATE_LAST /* 17 - not a true state, never use this */ +} CURLMstate; + +/* we support N sockets per easy handle. Set the corresponding bit to what + action we should wait for */ +#define MAX_SOCKSPEREASYHANDLE 5 +#define GETSOCK_READABLE (0x00ff) +#define GETSOCK_WRITABLE (0xff00) + +struct Curl_one_easy { + /* first, two fields for the linked list of these */ + struct Curl_one_easy *next; + struct Curl_one_easy *prev; + + struct SessionHandle *easy_handle; /* the easy handle for this unit */ + struct connectdata *easy_conn; /* the "unit's" connection */ + + CURLMstate state; /* the handle's state */ + CURLcode result; /* previous result */ + + struct Curl_message msg; /* A single posted message. */ + + /* Array with the plain socket numbers this handle takes care of, in no + particular order. Note that all sockets are added to the sockhash, where + the state etc are also kept. This array is mostly used to detect when a + socket is to be removed from the hash. See singlesocket(). */ + curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; + int numsocks; +}; + +#define CURL_MULTI_HANDLE 0x000bab1e + +#define GOOD_MULTI_HANDLE(x) \ + ((x) && (((struct Curl_multi *)(x))->type == CURL_MULTI_HANDLE)) +#define GOOD_EASY_HANDLE(x) \ + ((x) && (((struct SessionHandle *)(x))->magic == CURLEASY_MAGIC_NUMBER)) + +/* This is the struct known as CURLM on the outside */ +struct Curl_multi { + /* First a simple identifier to easier detect if a user mix up + this multi handle with an easy handle. Set this to CURL_MULTI_HANDLE. */ + long type; + + /* We have a doubly-linked circular list with easy handles */ + struct Curl_one_easy easy; + + int num_easy; /* amount of entries in the linked list above. */ + int num_alive; /* amount of easy handles that are added but have not yet + reached COMPLETE state */ + + struct curl_llist *msglist; /* a list of messages from completed transfers */ + + /* callback function and user data pointer for the *socket() API */ + curl_socket_callback socket_cb; + void *socket_userp; + + /* Hostname cache */ + struct curl_hash *hostcache; + + /* timetree points to the splay-tree of time nodes to figure out expire + times of all currently set timers */ + struct Curl_tree *timetree; + + /* 'sockhash' is the lookup hash for socket descriptor => easy handles (note + the pluralis form, there can be more than one easy handle waiting on the + same actual socket) */ + struct curl_hash *sockhash; + + /* Whether pipelining is enabled for this multi handle */ + bool pipelining_enabled; + + /* Shared connection cache (bundles)*/ + struct conncache *conn_cache; + + /* This handle will be used for closing the cached connections in + curl_multi_cleanup() */ + struct SessionHandle *closure_handle; + + long maxconnects; /* if >0, a fixed limit of the maximum number of entries + we're allowed to grow the connection cache to */ + + /* timer callback and user data pointer for the *socket() API */ + curl_multi_timer_callback timer_cb; + void *timer_userp; + struct timeval timer_lastcall; /* the fixed time for the timeout for the + previous callback */ +}; + +static void singlesocket(struct Curl_multi *multi, + struct Curl_one_easy *easy); +static int update_timer(struct Curl_multi *multi); + +static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, + struct connectdata *conn); +static int checkPendPipeline(struct connectdata *conn); +static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle, + struct connectdata *conn); +static void moveHandleFromRecvToDonePipeline(struct SessionHandle *handle, + struct connectdata *conn); +static bool isHandleAtHead(struct SessionHandle *handle, + struct curl_llist *pipeline); +static CURLMcode add_next_timeout(struct timeval now, + struct Curl_multi *multi, + struct SessionHandle *d); + +#ifdef DEBUGBUILD +static const char * const statename[]={ + "INIT", + "CONNECT", + "WAITRESOLVE", + "WAITCONNECT", + "WAITPROXYCONNECT", + "PROTOCONNECT", + "WAITDO", + "DO", + "DOING", + "DO_MORE", + "DO_DONE", + "WAITPERFORM", + "PERFORM", + "TOOFAST", + "DONE", + "COMPLETED", + "MSGSENT", +}; +#endif + +static void multi_freetimeout(void *a, void *b); + +/* always use this function to change state, to make debugging easier */ +static void multistate(struct Curl_one_easy *easy, CURLMstate state) +{ +#ifdef DEBUGBUILD + long connection_id = -5000; +#endif + CURLMstate oldstate = easy->state; + + if(oldstate == state) + /* don't bother when the new state is the same as the old state */ + return; + + easy->state = state; + +#ifdef DEBUGBUILD + if(easy->easy_conn) { + if(easy->state > CURLM_STATE_CONNECT && + easy->state < CURLM_STATE_COMPLETED) + connection_id = easy->easy_conn->connection_id; + + infof(easy->easy_handle, + "STATE: %s => %s handle %p; (connection #%ld) \n", + statename[oldstate], statename[easy->state], + (char *)easy, connection_id); + } +#endif + if(state == CURLM_STATE_COMPLETED) + /* changing to COMPLETED means there's one less easy handle 'alive' */ + easy->easy_handle->multi->num_alive--; +} + +/* + * We add one of these structs to the sockhash for a particular socket + */ + +struct Curl_sh_entry { + struct SessionHandle *easy; + time_t timestamp; + int action; /* what action READ/WRITE this socket waits for */ + curl_socket_t socket; /* mainly to ease debugging */ + void *socketp; /* settable by users with curl_multi_assign() */ +}; +/* bits for 'action' having no bits means this socket is not expecting any + action */ +#define SH_READ 1 +#define SH_WRITE 2 + +/* make sure this socket is present in the hash for this handle */ +static struct Curl_sh_entry *sh_addentry(struct curl_hash *sh, + curl_socket_t s, + struct SessionHandle *data) +{ + struct Curl_sh_entry *there = + Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); + struct Curl_sh_entry *check; + + if(there) + /* it is present, return fine */ + return there; + + /* not present, add it */ + check = calloc(1, sizeof(struct Curl_sh_entry)); + if(!check) + return NULL; /* major failure */ + check->easy = data; + check->socket = s; + + /* make/add new hash entry */ + if(NULL == Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { + free(check); + return NULL; /* major failure */ + } + + return check; /* things are good in sockhash land */ +} + + +/* delete the given socket + handle from the hash */ +static void sh_delentry(struct curl_hash *sh, curl_socket_t s) +{ + struct Curl_sh_entry *there = + Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); + + if(there) { + /* this socket is in the hash */ + /* We remove the hash entry. (This'll end up in a call to + sh_freeentry().) */ + Curl_hash_delete(sh, (char *)&s, sizeof(curl_socket_t)); + } +} + +/* + * free a sockhash entry + */ +static void sh_freeentry(void *freethis) +{ + struct Curl_sh_entry *p = (struct Curl_sh_entry *) freethis; + + if(p) + free(p); +} + +static size_t fd_key_compare(void*k1, size_t k1_len, void*k2, size_t k2_len) +{ + (void) k1_len; (void) k2_len; + + return (*((int* ) k1)) == (*((int* ) k2)); +} + +static size_t hash_fd(void* key, size_t key_length, size_t slots_num) +{ + int fd = * ((int* ) key); + (void) key_length; + + return (fd % (int)slots_num); +} + +/* + * sh_init() creates a new socket hash and returns the handle for it. + * + * Quote from README.multi_socket: + * + * "Some tests at 7000 and 9000 connections showed that the socket hash lookup + * is somewhat of a bottle neck. Its current implementation may be a bit too + * limiting. It simply has a fixed-size array, and on each entry in the array + * it has a linked list with entries. So the hash only checks which list to + * scan through. The code I had used so for used a list with merely 7 slots + * (as that is what the DNS hash uses) but with 7000 connections that would + * make an average of 1000 nodes in each list to run through. I upped that to + * 97 slots (I believe a prime is suitable) and noticed a significant speed + * increase. I need to reconsider the hash implementation or use a rather + * large default value like this. At 9000 connections I was still below 10us + * per call." + * + */ +static struct curl_hash *sh_init(void) +{ + return Curl_hash_alloc(CURL_SOCKET_HASH_TABLE_SIZE, hash_fd, fd_key_compare, + sh_freeentry); +} + +/* + * multi_addmsg() + * + * Called when a transfer is completed. Adds the given msg pointer to + * the list kept in the multi handle. + */ +static CURLMcode multi_addmsg(struct Curl_multi *multi, + struct Curl_message *msg) +{ + if(!Curl_llist_insert_next(multi->msglist, multi->msglist->tail, msg)) + return CURLM_OUT_OF_MEMORY; + + return CURLM_OK; +} + +/* + * multi_freeamsg() + * + * Callback used by the llist system when a single list entry is destroyed. + */ +static void multi_freeamsg(void *a, void *b) +{ + (void)a; + (void)b; +} + +CURLM *curl_multi_init(void) +{ + struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); + + if(!multi) + return NULL; + + multi->type = CURL_MULTI_HANDLE; + + multi->hostcache = Curl_mk_dnscache(); + if(!multi->hostcache) + goto error; + + multi->sockhash = sh_init(); + if(!multi->sockhash) + goto error; + + multi->conn_cache = Curl_conncache_init(CONNCACHE_MULTI); + if(!multi->conn_cache) + goto error; + + multi->msglist = Curl_llist_alloc(multi_freeamsg); + if(!multi->msglist) + goto error; + + /* Let's make the doubly-linked list a circular list. This makes + the linked list code simpler and allows inserting at the end + with less work (we didn't keep a tail pointer before). */ + multi->easy.next = &multi->easy; + multi->easy.prev = &multi->easy; + + return (CURLM *) multi; + + error: + + Curl_hash_destroy(multi->sockhash); + multi->sockhash = NULL; + Curl_hash_destroy(multi->hostcache); + multi->hostcache = NULL; + Curl_conncache_destroy(multi->conn_cache); + multi->conn_cache = NULL; + + free(multi); + return NULL; +} + +CURLMcode curl_multi_add_handle(CURLM *multi_handle, + CURL *easy_handle) +{ + struct curl_llist *timeoutlist; + struct Curl_one_easy *easy; + struct Curl_multi *multi = (struct Curl_multi *)multi_handle; + struct SessionHandle *data = (struct SessionHandle *)easy_handle; + struct SessionHandle *new_closure = NULL; + struct curl_hash *hostcache = NULL; + + /* First, make some basic checks that the CURLM handle is a good handle */ + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + /* Verify that we got a somewhat good easy handle too */ + if(!GOOD_EASY_HANDLE(easy_handle)) + return CURLM_BAD_EASY_HANDLE; + + /* Prevent users from adding same easy handle more than + once and prevent adding to more than one multi stack */ + if(data->multi) + /* possibly we should create a new unique error code for this condition */ + return CURLM_BAD_EASY_HANDLE; + + /* Allocate and initialize timeout list for easy handle */ + timeoutlist = Curl_llist_alloc(multi_freetimeout); + if(!timeoutlist) + return CURLM_OUT_OF_MEMORY; + + /* Allocate new node for the doubly-linked circular list of + Curl_one_easy structs that holds pointers to easy handles */ + easy = calloc(1, sizeof(struct Curl_one_easy)); + if(!easy) { + Curl_llist_destroy(timeoutlist, NULL); + return CURLM_OUT_OF_MEMORY; + } + + /* In case multi handle has no hostcache yet, allocate one */ + if(!multi->hostcache) { + hostcache = Curl_mk_dnscache(); + if(!hostcache) { + free(easy); + Curl_llist_destroy(timeoutlist, NULL); + return CURLM_OUT_OF_MEMORY; + } + } + + /* In case multi handle has no closure_handle yet, allocate + a new easy handle to use when closing cached connections */ + if(!multi->closure_handle) { + new_closure = (struct SessionHandle *)curl_easy_init(); + if(!new_closure) { + Curl_hash_destroy(hostcache); + free(easy); + Curl_llist_destroy(timeoutlist, NULL); + return CURLM_OUT_OF_MEMORY; + } + } + + /* + ** No failure allowed in this function beyond this point. And + ** no modification of easy nor multi handle allowed before this + ** except for potential multi's connection cache growing which + ** won't be undone in this function no matter what. + */ + + /* In case a new closure handle has been initialized above, it + is associated now with the multi handle which lacked one. */ + if(new_closure) { + multi->closure_handle = new_closure; + Curl_easy_addmulti(multi->closure_handle, multi_handle); + multi->closure_handle->state.conn_cache = multi->conn_cache; + } + + /* In case hostcache has been allocated above, + it is associated now with the multi handle. */ + if(hostcache) + multi->hostcache = hostcache; + + /* Make easy handle use timeout list initialized above */ + data->state.timeoutlist = timeoutlist; + timeoutlist = NULL; + + /* set the easy handle */ + easy->easy_handle = data; + multistate(easy, CURLM_STATE_INIT); + + /* set the back pointer to one_easy to assist in removal */ + easy->easy_handle->multi_pos = easy; + + /* for multi interface connections, we share DNS cache automatically if the + easy handle's one is currently private. */ + if(easy->easy_handle->dns.hostcache && + (easy->easy_handle->dns.hostcachetype == HCACHE_PRIVATE)) { + Curl_hash_destroy(easy->easy_handle->dns.hostcache); + easy->easy_handle->dns.hostcache = NULL; + easy->easy_handle->dns.hostcachetype = HCACHE_NONE; + } + + if(!easy->easy_handle->dns.hostcache || + (easy->easy_handle->dns.hostcachetype == HCACHE_NONE)) { + easy->easy_handle->dns.hostcache = multi->hostcache; + easy->easy_handle->dns.hostcachetype = HCACHE_MULTI; + } + + /* On a multi stack the connection cache, owned by the multi handle, + is shared between all easy handles within the multi handle. + Therefore we free the private connection cache if there is one */ + if(easy->easy_handle->state.conn_cache && + easy->easy_handle->state.conn_cache->type == CONNCACHE_PRIVATE) { + Curl_conncache_destroy(easy->easy_handle->state.conn_cache); + } + + /* Point now to this multi's connection cache */ + easy->easy_handle->state.conn_cache = multi->conn_cache; + + /* This adds the new entry at the 'end' of the doubly-linked circular + list of Curl_one_easy structs to try and maintain a FIFO queue so + the pipelined requests are in order. */ + + /* We add this new entry last in the list. We make our 'next' point to the + 'first' struct and our 'prev' point to the previous 'prev' */ + easy->next = &multi->easy; + easy->prev = multi->easy.prev; + + /* make 'easy' the last node in the chain */ + multi->easy.prev = easy; + + /* if there was a prev node, make sure its 'next' pointer links to + the new node */ + easy->prev->next = easy; + + /* make the SessionHandle refer back to this multi handle */ + Curl_easy_addmulti(easy_handle, multi_handle); + + /* make the SessionHandle struct refer back to this struct */ + easy->easy_handle->set.one_easy = easy; + + /* Set the timeout for this handle to expire really soon so that it will + be taken care of even when this handle is added in the midst of operation + when only the curl_multi_socket() API is used. During that flow, only + sockets that time-out or have actions will be dealt with. Since this + handle has no action yet, we make sure it times out to get things to + happen. */ + Curl_expire(easy->easy_handle, 1); + + /* increase the node-counter */ + multi->num_easy++; + + /* increase the alive-counter */ + multi->num_alive++; + + /* A somewhat crude work-around for a little glitch in update_timer() that + happens if the lastcall time is set to the same time when the handle is + removed as when the next handle is added, as then the check in + update_timer() that prevents calling the application multiple times with + the same timer infor will not trigger and then the new handle's timeout + will not be notified to the app. + + The work-around is thus simply to clear the 'lastcall' variable to force + update_timer() to always trigger a callback to the app when a new easy + handle is added */ + memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); + + update_timer(multi); + return CURLM_OK; +} + +#if 0 +/* Debug-function, used like this: + * + * Curl_hash_print(multi->sockhash, debug_print_sock_hash); + * + * Enable the hash print function first by editing curl_hash.c + */ +static void debug_print_sock_hash(void *p) +{ + struct Curl_sh_entry *sh = (struct Curl_sh_entry *)p; + + fprintf(stderr, " [easy %p/magic %x/socket %d]", + (void *)sh->easy, sh->easy->magic, (int)sh->socket); +} +#endif + +CURLMcode curl_multi_remove_handle(CURLM *multi_handle, + CURL *curl_handle) +{ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + struct Curl_one_easy *easy; + struct SessionHandle *data = curl_handle; + + /* First, make some basic checks that the CURLM handle is a good handle */ + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + /* Verify that we got a somewhat good easy handle too */ + if(!GOOD_EASY_HANDLE(curl_handle)) + return CURLM_BAD_EASY_HANDLE; + + /* pick-up from the 'curl_handle' the kept position in the list */ + easy = data->multi_pos; + + if(easy) { + bool premature = (easy->state < CURLM_STATE_COMPLETED) ? TRUE : FALSE; + bool easy_owns_conn = (easy->easy_conn && + (easy->easy_conn->data == easy->easy_handle)) ? + TRUE : FALSE; + + /* If the 'state' is not INIT or COMPLETED, we might need to do something + nice to put the easy_handle in a good known state when this returns. */ + if(premature) + /* this handle is "alive" so we need to count down the total number of + alive connections when this is removed */ + multi->num_alive--; + + if(easy->easy_conn && + (easy->easy_conn->send_pipe->size + + easy->easy_conn->recv_pipe->size > 1) && + easy->state > CURLM_STATE_WAITDO && + easy->state < CURLM_STATE_COMPLETED) { + /* If the handle is in a pipeline and has started sending off its + request but not received its response yet, we need to close + connection. */ + easy->easy_conn->bits.close = TRUE; + /* Set connection owner so that Curl_done() closes it. + We can sefely do this here since connection is killed. */ + easy->easy_conn->data = easy->easy_handle; + } + + /* The timer must be shut down before easy->multi is set to NULL, + else the timenode will remain in the splay tree after + curl_easy_cleanup is called. */ + Curl_expire(easy->easy_handle, 0); + + /* destroy the timeout list that is held in the easy handle */ + if(data->state.timeoutlist) { + Curl_llist_destroy(data->state.timeoutlist, NULL); + data->state.timeoutlist = NULL; + } + + if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) { + if(multi->num_easy == 1) { + if(easy_owns_conn) { + Curl_resolver_cancel(easy->easy_conn); + if(easy->easy_conn->dns_entry) { + Curl_resolv_unlock(easy->easy_handle, easy->easy_conn->dns_entry); + easy->easy_conn->dns_entry = NULL; + } + } + Curl_hostcache_destroy(easy->easy_handle); + multi->hostcache = NULL; + } + /* clear out the usage of the shared DNS cache */ + easy->easy_handle->dns.hostcache = NULL; + easy->easy_handle->dns.hostcachetype = HCACHE_NONE; + } + + if(easy->easy_conn) { + + /* we must call Curl_done() here (if we still "own it") so that we don't + leave a half-baked one around */ + if(easy_owns_conn) { + + /* Curl_done() clears the conn->data field to lose the association + between the easy handle and the connection + + Note that this ignores the return code simply because there's + nothing really useful to do with it anyway! */ + (void)Curl_done(&easy->easy_conn, easy->result, premature); + } + else + /* Clear connection pipelines, if Curl_done above was not called */ + Curl_getoff_all_pipelines(easy->easy_handle, easy->easy_conn); + } + + if(easy->easy_handle->state.conn_cache->type == CONNCACHE_MULTI) { + /* if this was using the shared connection cache we clear the pointer + to that since we're not part of that handle anymore */ + easy->easy_handle->state.conn_cache = NULL; + easy->easy_handle->state.lastconnect = NULL; + } + + /* change state without using multistate(), only to make singlesocket() do + what we want */ + easy->state = CURLM_STATE_COMPLETED; + singlesocket(multi, easy); /* to let the application know what sockets + that vanish with this handle */ + + /* Remove the association between the connection and the handle */ + if(easy->easy_conn) { + easy->easy_conn->data = NULL; + easy->easy_conn = NULL; + } + + Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association + to this multi handle */ + + { + /* make sure there's no pending message in the queue sent from this easy + handle */ + struct curl_llist_element *e; + + for(e = multi->msglist->head; e; e = e->next) { + struct Curl_message *msg = e->ptr; + + if(msg->extmsg.easy_handle == easy->easy_handle) { + Curl_llist_remove(multi->msglist, e, NULL); + /* there can only be one from this specific handle */ + break; + } + } + } + + /* make the previous node point to our next */ + if(easy->prev) + easy->prev->next = easy->next; + /* make our next point to our previous node */ + if(easy->next) + easy->next->prev = easy->prev; + + easy->easy_handle->set.one_easy = NULL; /* detached */ + + /* Null the position in the controlling structure */ + easy->easy_handle->multi_pos = NULL; + + /* NOTE NOTE NOTE + We do not touch the easy handle here! */ + free(easy); + + multi->num_easy--; /* one less to care about now */ + + update_timer(multi); + return CURLM_OK; + } + else + return CURLM_BAD_EASY_HANDLE; /* twasn't found */ +} + +bool Curl_multi_canPipeline(const struct Curl_multi* multi) +{ + return multi->pipelining_enabled; +} + +void Curl_multi_handlePipeBreak(struct SessionHandle *data) +{ + struct Curl_one_easy *one_easy = data->set.one_easy; + + if(one_easy) + one_easy->easy_conn = NULL; +} + +static int waitconnect_getsock(struct connectdata *conn, + curl_socket_t *sock, + int numsocks) +{ + if(!numsocks) + return GETSOCK_BLANK; + + sock[0] = conn->sock[FIRSTSOCKET]; + + /* when we've sent a CONNECT to a proxy, we should rather wait for the + socket to become readable to be able to get the response headers */ + if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) + return GETSOCK_READSOCK(0); + + return GETSOCK_WRITESOCK(0); +} + +static int domore_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + if(conn && conn->handler->domore_getsock) + return conn->handler->domore_getsock(conn, socks, numsocks); + return GETSOCK_BLANK; +} + +/* returns bitmapped flags for this handle and its sockets */ +static int multi_getsock(struct Curl_one_easy *easy, + curl_socket_t *socks, /* points to numsocks number + of sockets */ + int numsocks) +{ + /* If the pipe broke, or if there's no connection left for this easy handle, + then we MUST bail out now with no bitmask set. The no connection case can + happen when this is called from curl_multi_remove_handle() => + singlesocket() => multi_getsock(). + */ + if(easy->easy_handle->state.pipe_broke || !easy->easy_conn) + return 0; + + if(easy->state > CURLM_STATE_CONNECT && + easy->state < CURLM_STATE_COMPLETED) { + /* Set up ownership correctly */ + easy->easy_conn->data = easy->easy_handle; + } + + switch(easy->state) { + default: +#if 0 /* switch back on these cases to get the compiler to check for all enums + to be present */ + case CURLM_STATE_TOOFAST: /* returns 0, so will not select. */ + case CURLM_STATE_COMPLETED: + case CURLM_STATE_MSGSENT: + case CURLM_STATE_INIT: + case CURLM_STATE_CONNECT: + case CURLM_STATE_WAITDO: + case CURLM_STATE_DONE: + case CURLM_STATE_LAST: + /* this will get called with CURLM_STATE_COMPLETED when a handle is + removed */ +#endif + return 0; + + case CURLM_STATE_WAITRESOLVE: + return Curl_resolver_getsock(easy->easy_conn, socks, numsocks); + + case CURLM_STATE_PROTOCONNECT: + return Curl_protocol_getsock(easy->easy_conn, socks, numsocks); + + case CURLM_STATE_DO: + case CURLM_STATE_DOING: + return Curl_doing_getsock(easy->easy_conn, socks, numsocks); + + case CURLM_STATE_WAITPROXYCONNECT: + case CURLM_STATE_WAITCONNECT: + return waitconnect_getsock(easy->easy_conn, socks, numsocks); + + case CURLM_STATE_DO_MORE: + return domore_getsock(easy->easy_conn, socks, numsocks); + + case CURLM_STATE_DO_DONE: /* since is set after DO is completed, we switch + to waiting for the same as the *PERFORM + states */ + case CURLM_STATE_PERFORM: + case CURLM_STATE_WAITPERFORM: + return Curl_single_getsock(easy->easy_conn, socks, numsocks); + } + +} + +CURLMcode curl_multi_fdset(CURLM *multi_handle, + fd_set *read_fd_set, fd_set *write_fd_set, + fd_set *exc_fd_set, int *max_fd) +{ + /* Scan through all the easy handles to get the file descriptors set. + Some easy handles may not have connected to the remote host yet, + and then we must make sure that is done. */ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + struct Curl_one_easy *easy; + int this_max_fd=-1; + curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; + int bitmap; + int i; + (void)exc_fd_set; /* not used */ + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + easy=multi->easy.next; + while(easy != &multi->easy) { + bitmap = multi_getsock(easy, sockbunch, MAX_SOCKSPEREASYHANDLE); + + for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { + curl_socket_t s = CURL_SOCKET_BAD; + + if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { + FD_SET(sockbunch[i], read_fd_set); + s = sockbunch[i]; + } + if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { + FD_SET(sockbunch[i], write_fd_set); + s = sockbunch[i]; + } + if(s == CURL_SOCKET_BAD) + /* this socket is unused, break out of loop */ + break; + else { + if((int)s > this_max_fd) + this_max_fd = (int)s; + } + } + + easy = easy->next; /* check next handle */ + } + + *max_fd = this_max_fd; + + return CURLM_OK; +} + +CURLMcode curl_multi_wait(CURLM *multi_handle, + struct curl_waitfd extra_fds[], + unsigned int extra_nfds, + int timeout_ms, + int *ret) +{ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + struct Curl_one_easy *easy; + curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; + int bitmap; + unsigned int i; + unsigned int nfds = extra_nfds; + struct pollfd *ufds = NULL; + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + /* Count up how many fds we have from the multi handle */ + easy=multi->easy.next; + while(easy != &multi->easy) { + bitmap = multi_getsock(easy, sockbunch, MAX_SOCKSPEREASYHANDLE); + + for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { + curl_socket_t s = CURL_SOCKET_BAD; + + if(bitmap & GETSOCK_READSOCK(i)) { + ++nfds; + s = sockbunch[i]; + } + if(bitmap & GETSOCK_WRITESOCK(i)) { + ++nfds; + s = sockbunch[i]; + } + if(s == CURL_SOCKET_BAD) { + break; + } + } + + easy = easy->next; /* check next handle */ + } + + if(nfds) { + ufds = malloc(nfds * sizeof(struct pollfd)); + if(!ufds) + return CURLM_OUT_OF_MEMORY; + } + nfds = 0; + + /* Add the curl handles to our pollfds first */ + easy=multi->easy.next; + while(easy != &multi->easy) { + bitmap = multi_getsock(easy, sockbunch, MAX_SOCKSPEREASYHANDLE); + + for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { + curl_socket_t s = CURL_SOCKET_BAD; + + if(bitmap & GETSOCK_READSOCK(i)) { + ufds[nfds].fd = sockbunch[i]; + ufds[nfds].events = POLLIN; + ++nfds; + s = sockbunch[i]; + } + if(bitmap & GETSOCK_WRITESOCK(i)) { + ufds[nfds].fd = sockbunch[i]; + ufds[nfds].events = POLLOUT; + ++nfds; + s = sockbunch[i]; + } + if(s == CURL_SOCKET_BAD) { + break; + } + } + + easy = easy->next; /* check next handle */ + } + + /* Add external file descriptions from poll-like struct curl_waitfd */ + for(i = 0; i < extra_nfds; i++) { + ufds[nfds].fd = extra_fds[i].fd; + ufds[nfds].events = 0; + if(extra_fds[i].events & CURL_WAIT_POLLIN) + ufds[nfds].events |= POLLIN; + if(extra_fds[i].events & CURL_WAIT_POLLPRI) + ufds[nfds].events |= POLLPRI; + if(extra_fds[i].events & CURL_WAIT_POLLOUT) + ufds[nfds].events |= POLLOUT; + ++nfds; + } + + if(nfds) + /* wait... */ + i = Curl_poll(ufds, nfds, timeout_ms); + else + i = 0; + + Curl_safefree(ufds); + if(ret) + *ret = i; + return CURLM_OK; +} + +static CURLMcode multi_runsingle(struct Curl_multi *multi, + struct timeval now, + struct Curl_one_easy *easy) +{ + struct Curl_message *msg = NULL; + bool connected; + bool async; + bool protocol_connect = FALSE; + bool dophase_done; + bool done = FALSE; + CURLMcode result = CURLM_OK; + struct SingleRequest *k; + struct SessionHandle *data; + long timeout_ms; + + if(!GOOD_EASY_HANDLE(easy->easy_handle)) + return CURLM_BAD_EASY_HANDLE; + + data = easy->easy_handle; + + do { + /* this is a single-iteration do-while loop just to allow a + break to skip to the end of it */ + bool disconnect_conn = FALSE; + + /* Handle the case when the pipe breaks, i.e., the connection + we're using gets cleaned up and we're left with nothing. */ + if(data->state.pipe_broke) { + infof(data, "Pipe broke: handle 0x%p, url = %s\n", + easy, data->state.path); + + if(easy->state < CURLM_STATE_COMPLETED) { + /* Head back to the CONNECT state */ + multistate(easy, CURLM_STATE_CONNECT); + result = CURLM_CALL_MULTI_PERFORM; + easy->result = CURLE_OK; + } + + data->state.pipe_broke = FALSE; + easy->easy_conn = NULL; + break; + } + + if(!easy->easy_conn && + easy->state > CURLM_STATE_CONNECT && + easy->state < CURLM_STATE_DONE) { + /* In all these states, the code will blindly access 'easy->easy_conn' + so this is precaution that it isn't NULL. And it silences static + analyzers. */ + failf(data, "In state %d with no easy_conn, bail out!\n", easy->state); + return CURLM_INTERNAL_ERROR; + } + + if(easy->easy_conn && easy->state > CURLM_STATE_CONNECT && + easy->state < CURLM_STATE_COMPLETED) + /* Make sure we set the connection's current owner */ + easy->easy_conn->data = data; + + if(easy->easy_conn && + (easy->state >= CURLM_STATE_CONNECT) && + (easy->state < CURLM_STATE_COMPLETED)) { + /* we need to wait for the connect state as only then is the start time + stored, but we must not check already completed handles */ + + timeout_ms = Curl_timeleft(data, &now, + (easy->state <= CURLM_STATE_WAITDO)? + TRUE:FALSE); + + if(timeout_ms < 0) { + /* Handle timed out */ + if(easy->state == CURLM_STATE_WAITRESOLVE) + failf(data, "Resolving timed out after %ld milliseconds", + Curl_tvdiff(now, data->progress.t_startsingle)); + else if(easy->state == CURLM_STATE_WAITCONNECT) + failf(data, "Connection timed out after %ld milliseconds", + Curl_tvdiff(now, data->progress.t_startsingle)); + else { + k = &data->req; + failf(data, "Operation timed out after %ld milliseconds with %" + FORMAT_OFF_T " out of %" FORMAT_OFF_T " bytes received", + Curl_tvdiff(now, data->progress.t_startsingle), k->bytecount, + k->size); + } + + /* Force the connection closed because the server could continue to + send us stuff at any time. (The disconnect_conn logic used below + doesn't work at this point). */ + easy->easy_conn->bits.close = TRUE; + easy->result = CURLE_OPERATION_TIMEDOUT; + multistate(easy, CURLM_STATE_COMPLETED); + break; + } + } + + switch(easy->state) { + case CURLM_STATE_INIT: + /* init this transfer. */ + easy->result=Curl_pretransfer(data); + + if(CURLE_OK == easy->result) { + /* after init, go CONNECT */ + multistate(easy, CURLM_STATE_CONNECT); + result = CURLM_CALL_MULTI_PERFORM; + + data->state.used_interface = Curl_if_multi; + } + break; + + case CURLM_STATE_CONNECT: + /* Connect. We get a connection identifier filled in. */ + Curl_pgrsTime(data, TIMER_STARTSINGLE); + easy->result = Curl_connect(data, &easy->easy_conn, + &async, &protocol_connect); + + if(CURLE_OK == easy->result) { + /* Add this handle to the send or pend pipeline */ + easy->result = addHandleToSendOrPendPipeline(data, + easy->easy_conn); + if(CURLE_OK != easy->result) + disconnect_conn = TRUE; + else { + if(async) + /* We're now waiting for an asynchronous name lookup */ + multistate(easy, CURLM_STATE_WAITRESOLVE); + else { + /* after the connect has been sent off, go WAITCONNECT unless the + protocol connect is already done and we can go directly to + WAITDO or DO! */ + result = CURLM_CALL_MULTI_PERFORM; + + if(protocol_connect) + multistate(easy, multi->pipelining_enabled? + CURLM_STATE_WAITDO:CURLM_STATE_DO); + else { +#ifndef CURL_DISABLE_HTTP + if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) + multistate(easy, CURLM_STATE_WAITPROXYCONNECT); + else +#endif + multistate(easy, CURLM_STATE_WAITCONNECT); + } + } + } + } + break; + + case CURLM_STATE_WAITRESOLVE: + /* awaiting an asynch name resolve to complete */ + { + struct Curl_dns_entry *dns = NULL; + + /* check if we have the name resolved by now */ + easy->result = Curl_resolver_is_resolved(easy->easy_conn, &dns); + + /* Update sockets here, because the socket(s) may have been + closed and the application thus needs to be told, even if it + is likely that the same socket(s) will again be used further + down. If the name has not yet been resolved, it is likely + that new sockets have been opened in an attempt to contact + another resolver. */ + singlesocket(multi, easy); + + if(dns) { + /* Perform the next step in the connection phase, and then move on + to the WAITCONNECT state */ + easy->result = Curl_async_resolved(easy->easy_conn, + &protocol_connect); + + if(CURLE_OK != easy->result) + /* if Curl_async_resolved() returns failure, the connection struct + is already freed and gone */ + easy->easy_conn = NULL; /* no more connection */ + else { + /* call again please so that we get the next socket setup */ + result = CURLM_CALL_MULTI_PERFORM; + if(protocol_connect) + multistate(easy, multi->pipelining_enabled? + CURLM_STATE_WAITDO:CURLM_STATE_DO); + else { +#ifndef CURL_DISABLE_HTTP + if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) + multistate(easy, CURLM_STATE_WAITPROXYCONNECT); + else +#endif + multistate(easy, CURLM_STATE_WAITCONNECT); + } + } + } + + if(CURLE_OK != easy->result) { + /* failure detected */ + disconnect_conn = TRUE; + break; + } + } + break; + +#ifndef CURL_DISABLE_HTTP + case CURLM_STATE_WAITPROXYCONNECT: + /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ + easy->result = Curl_http_connect(easy->easy_conn, &protocol_connect); + + if(easy->easy_conn->bits.proxy_connect_closed) { + /* reset the error buffer */ + if(data->set.errorbuffer) + data->set.errorbuffer[0] = '\0'; + data->state.errorbuf = FALSE; + + easy->result = CURLE_OK; + result = CURLM_CALL_MULTI_PERFORM; + multistate(easy, CURLM_STATE_CONNECT); + } + else if(CURLE_OK == easy->result) { + if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_COMPLETE) + multistate(easy, CURLM_STATE_WAITCONNECT); + } + break; +#endif + + case CURLM_STATE_WAITCONNECT: + /* awaiting a completion of an asynch connect */ + easy->result = Curl_is_connected(easy->easy_conn, + FIRSTSOCKET, + &connected); + if(connected) { + + if(!easy->result) + /* if everything is still fine we do the protocol-specific connect + setup */ + easy->result = Curl_protocol_connect(easy->easy_conn, + &protocol_connect); + } + + if(CURLE_OK != easy->result) { + /* failure detected */ + /* Just break, the cleaning up is handled all in one place */ + disconnect_conn = TRUE; + break; + } + + if(connected) { + if(!protocol_connect) { + /* We have a TCP connection, but 'protocol_connect' may be false + and then we continue to 'STATE_PROTOCONNECT'. If protocol + connect is TRUE, we move on to STATE_DO. + BUT if we are using a proxy we must change to WAITPROXYCONNECT + */ +#ifndef CURL_DISABLE_HTTP + if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) + multistate(easy, CURLM_STATE_WAITPROXYCONNECT); + else +#endif + multistate(easy, CURLM_STATE_PROTOCONNECT); + + } + else + /* after the connect has completed, go WAITDO or DO */ + multistate(easy, multi->pipelining_enabled? + CURLM_STATE_WAITDO:CURLM_STATE_DO); + + result = CURLM_CALL_MULTI_PERFORM; + } + break; + + case CURLM_STATE_PROTOCONNECT: + /* protocol-specific connect phase */ + easy->result = Curl_protocol_connecting(easy->easy_conn, + &protocol_connect); + if((easy->result == CURLE_OK) && protocol_connect) { + /* after the connect has completed, go WAITDO or DO */ + multistate(easy, multi->pipelining_enabled? + CURLM_STATE_WAITDO:CURLM_STATE_DO); + result = CURLM_CALL_MULTI_PERFORM; + } + else if(easy->result) { + /* failure detected */ + Curl_posttransfer(data); + Curl_done(&easy->easy_conn, easy->result, TRUE); + disconnect_conn = TRUE; + } + break; + + case CURLM_STATE_WAITDO: + /* Wait for our turn to DO when we're pipelining requests */ +#ifdef DEBUGBUILD + infof(data, "WAITDO: Conn %ld send pipe %zu inuse %d athead %d\n", + easy->easy_conn->connection_id, + easy->easy_conn->send_pipe->size, + easy->easy_conn->writechannel_inuse?1:0, + isHandleAtHead(data, + easy->easy_conn->send_pipe)?1:0); +#endif + if(!easy->easy_conn->writechannel_inuse && + isHandleAtHead(data, + easy->easy_conn->send_pipe)) { + /* Grab the channel */ + easy->easy_conn->writechannel_inuse = TRUE; + multistate(easy, CURLM_STATE_DO); + result = CURLM_CALL_MULTI_PERFORM; + } + break; + + case CURLM_STATE_DO: + if(data->set.connect_only) { + /* keep connection open for application to use the socket */ + easy->easy_conn->bits.close = FALSE; + multistate(easy, CURLM_STATE_DONE); + easy->result = CURLE_OK; + result = CURLM_CALL_MULTI_PERFORM; + } + else { + /* Perform the protocol's DO action */ + easy->result = Curl_do(&easy->easy_conn, + &dophase_done); + + if(CURLE_OK == easy->result) { + if(!dophase_done) { + /* some steps needed for wildcard matching */ + if(data->set.wildcardmatch) { + struct WildcardData *wc = &data->wildcard; + if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { + /* skip some states if it is important */ + Curl_done(&easy->easy_conn, CURLE_OK, FALSE); + multistate(easy, CURLM_STATE_DONE); + result = CURLM_CALL_MULTI_PERFORM; + break; + } + } + /* DO was not completed in one function call, we must continue + DOING... */ + multistate(easy, CURLM_STATE_DOING); + result = CURLM_OK; + } + + /* after DO, go DO_DONE... or DO_MORE */ + else if(easy->easy_conn->bits.do_more) { + /* we're supposed to do more, but we need to sit down, relax + and wait a little while first */ + multistate(easy, CURLM_STATE_DO_MORE); + result = CURLM_OK; + } + else { + /* we're done with the DO, now DO_DONE */ + multistate(easy, CURLM_STATE_DO_DONE); + result = CURLM_CALL_MULTI_PERFORM; + } + } + else if((CURLE_SEND_ERROR == easy->result) && + easy->easy_conn->bits.reuse) { + /* + * In this situation, a connection that we were trying to use + * may have unexpectedly died. If possible, send the connection + * back to the CONNECT phase so we can try again. + */ + char *newurl = NULL; + followtype follow=FOLLOW_NONE; + CURLcode drc; + bool retry = FALSE; + + drc = Curl_retry_request(easy->easy_conn, &newurl); + if(drc) { + /* a failure here pretty much implies an out of memory */ + easy->result = drc; + disconnect_conn = TRUE; + } + else + retry = (newurl)?TRUE:FALSE; + + Curl_posttransfer(data); + drc = Curl_done(&easy->easy_conn, easy->result, FALSE); + + /* When set to retry the connection, we must to go back to + * the CONNECT state */ + if(retry) { + if((drc == CURLE_OK) || (drc == CURLE_SEND_ERROR)) { + follow = FOLLOW_RETRY; + drc = Curl_follow(data, newurl, follow); + if(drc == CURLE_OK) { + multistate(easy, CURLM_STATE_CONNECT); + result = CURLM_CALL_MULTI_PERFORM; + easy->result = CURLE_OK; + } + else { + /* Follow failed */ + easy->result = drc; + free(newurl); + } + } + else { + /* done didn't return OK or SEND_ERROR */ + easy->result = drc; + free(newurl); + } + } + else { + /* Have error handler disconnect conn if we can't retry */ + disconnect_conn = TRUE; + } + } + else { + /* failure detected */ + Curl_posttransfer(data); + Curl_done(&easy->easy_conn, easy->result, FALSE); + disconnect_conn = TRUE; + } + } + break; + + case CURLM_STATE_DOING: + /* we continue DOING until the DO phase is complete */ + easy->result = Curl_protocol_doing(easy->easy_conn, + &dophase_done); + if(CURLE_OK == easy->result) { + if(dophase_done) { + /* after DO, go DO_DONE or DO_MORE */ + multistate(easy, easy->easy_conn->bits.do_more? + CURLM_STATE_DO_MORE: + CURLM_STATE_DO_DONE); + result = CURLM_CALL_MULTI_PERFORM; + } /* dophase_done */ + } + else { + /* failure detected */ + Curl_posttransfer(data); + Curl_done(&easy->easy_conn, easy->result, FALSE); + disconnect_conn = TRUE; + } + break; + + case CURLM_STATE_DO_MORE: + /* + * When we are connected, DO MORE and then go DO_DONE + */ + easy->result = Curl_do_more(easy->easy_conn, &dophase_done); + + /* No need to remove this handle from the send pipeline here since that + is done in Curl_done() */ + if(CURLE_OK == easy->result) { + if(dophase_done) { + multistate(easy, CURLM_STATE_DO_DONE); + result = CURLM_CALL_MULTI_PERFORM; + } + else + /* stay in DO_MORE */ + result = CURLM_OK; + } + else { + /* failure detected */ + Curl_posttransfer(data); + Curl_done(&easy->easy_conn, easy->result, FALSE); + disconnect_conn = TRUE; + } + break; + + case CURLM_STATE_DO_DONE: + /* Move ourselves from the send to recv pipeline */ + moveHandleFromSendToRecvPipeline(data, easy->easy_conn); + /* Check if we can move pending requests to send pipe */ + checkPendPipeline(easy->easy_conn); + multistate(easy, CURLM_STATE_WAITPERFORM); + result = CURLM_CALL_MULTI_PERFORM; + break; + + case CURLM_STATE_WAITPERFORM: + /* Wait for our turn to PERFORM */ + if(!easy->easy_conn->readchannel_inuse && + isHandleAtHead(data, + easy->easy_conn->recv_pipe)) { + /* Grab the channel */ + easy->easy_conn->readchannel_inuse = TRUE; + multistate(easy, CURLM_STATE_PERFORM); + result = CURLM_CALL_MULTI_PERFORM; + } +#ifdef DEBUGBUILD + else { + infof(data, "WAITPERFORM: Conn %ld recv pipe %zu inuse %d athead %d\n", + easy->easy_conn->connection_id, + easy->easy_conn->recv_pipe->size, + easy->easy_conn->readchannel_inuse?1:0, + isHandleAtHead(data, + easy->easy_conn->recv_pipe)?1:0); + } +#endif + break; + + case CURLM_STATE_TOOFAST: /* limit-rate exceeded in either direction */ + /* if both rates are within spec, resume transfer */ + if(Curl_pgrsUpdate(easy->easy_conn)) + easy->result = CURLE_ABORTED_BY_CALLBACK; + else + easy->result = Curl_speedcheck(data, now); + + if(( (data->set.max_send_speed == 0) || + (data->progress.ulspeed < data->set.max_send_speed )) && + ( (data->set.max_recv_speed == 0) || + (data->progress.dlspeed < data->set.max_recv_speed))) + multistate(easy, CURLM_STATE_PERFORM); + break; + + case CURLM_STATE_PERFORM: + { + char *newurl = NULL; + bool retry = FALSE; + + /* check if over send speed */ + if((data->set.max_send_speed > 0) && + (data->progress.ulspeed > data->set.max_send_speed)) { + int buffersize; + + multistate(easy, CURLM_STATE_TOOFAST); + + /* calculate upload rate-limitation timeout. */ + buffersize = (int)(data->set.buffer_size ? + data->set.buffer_size : BUFSIZE); + timeout_ms = Curl_sleep_time(data->set.max_send_speed, + data->progress.ulspeed, buffersize); + Curl_expire(data, timeout_ms); + break; + } + + /* check if over recv speed */ + if((data->set.max_recv_speed > 0) && + (data->progress.dlspeed > data->set.max_recv_speed)) { + int buffersize; + + multistate(easy, CURLM_STATE_TOOFAST); + + /* Calculate download rate-limitation timeout. */ + buffersize = (int)(data->set.buffer_size ? + data->set.buffer_size : BUFSIZE); + timeout_ms = Curl_sleep_time(data->set.max_recv_speed, + data->progress.dlspeed, buffersize); + Curl_expire(data, timeout_ms); + break; + } + + /* read/write data if it is ready to do so */ + easy->result = Curl_readwrite(easy->easy_conn, &done); + + k = &data->req; + + if(!(k->keepon & KEEP_RECV)) { + /* We're done receiving */ + easy->easy_conn->readchannel_inuse = FALSE; + } + + if(!(k->keepon & KEEP_SEND)) { + /* We're done sending */ + easy->easy_conn->writechannel_inuse = FALSE; + } + + if(done || (easy->result == CURLE_RECV_ERROR)) { + /* If CURLE_RECV_ERROR happens early enough, we assume it was a race + * condition and the server closed the re-used connection exactly when + * we wanted to use it, so figure out if that is indeed the case. + */ + CURLcode ret = Curl_retry_request(easy->easy_conn, &newurl); + if(!ret) + retry = (newurl)?TRUE:FALSE; + + if(retry) + /* if we are to retry, set the result to OK */ + easy->result = CURLE_OK; + } + + if(easy->result) { + /* + * The transfer phase returned error, we mark the connection to get + * closed to prevent being re-used. This is because we can't possibly + * know if the connection is in a good shape or not now. Unless it is + * a protocol which uses two "channels" like FTP, as then the error + * happened in the data connection. + */ + + if(!(easy->easy_conn->handler->flags & PROTOPT_DUAL)) + easy->easy_conn->bits.close = TRUE; + + Curl_posttransfer(data); + Curl_done(&easy->easy_conn, easy->result, FALSE); + } + else if(done) { + followtype follow=FOLLOW_NONE; + + /* call this even if the readwrite function returned error */ + Curl_posttransfer(data); + + /* we're no longer receiving */ + moveHandleFromRecvToDonePipeline(data, + easy->easy_conn); + + /* expire the new receiving pipeline head */ + if(easy->easy_conn->recv_pipe->head) + Curl_expire(easy->easy_conn->recv_pipe->head->ptr, 1); + + /* Check if we can move pending requests to send pipe */ + checkPendPipeline(easy->easy_conn); + + /* When we follow redirects or is set to retry the connection, we must + to go back to the CONNECT state */ + if(data->req.newurl || retry) { + if(!retry) { + /* if the URL is a follow-location and not just a retried request + then figure out the URL here */ + newurl = data->req.newurl; + data->req.newurl = NULL; + follow = FOLLOW_REDIR; + } + else + follow = FOLLOW_RETRY; + easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE); + if(easy->result == CURLE_OK) + easy->result = Curl_follow(data, newurl, follow); + if(CURLE_OK == easy->result) { + multistate(easy, CURLM_STATE_CONNECT); + result = CURLM_CALL_MULTI_PERFORM; + newurl = NULL; /* handed over the memory ownership to + Curl_follow(), make sure we don't free() it + here */ + } + } + else { + /* after the transfer is done, go DONE */ + + /* but first check to see if we got a location info even though we're + not following redirects */ + if(data->req.location) { + if(newurl) + free(newurl); + newurl = data->req.location; + data->req.location = NULL; + easy->result = Curl_follow(data, newurl, FOLLOW_FAKE); + if(CURLE_OK == easy->result) + newurl = NULL; /* allocation was handed over Curl_follow() */ + else + disconnect_conn = TRUE; + } + + multistate(easy, CURLM_STATE_DONE); + result = CURLM_CALL_MULTI_PERFORM; + } + } + + if(newurl) + free(newurl); + break; + } + + case CURLM_STATE_DONE: + + if(easy->easy_conn) { + /* Remove ourselves from the receive and done pipelines. Handle + should be on one of these lists, depending upon how we got here. */ + Curl_removeHandleFromPipeline(data, + easy->easy_conn->recv_pipe); + Curl_removeHandleFromPipeline(data, + easy->easy_conn->done_pipe); + /* Check if we can move pending requests to send pipe */ + checkPendPipeline(easy->easy_conn); + + if(easy->easy_conn->bits.stream_was_rewound) { + /* This request read past its response boundary so we quickly let + the other requests consume those bytes since there is no + guarantee that the socket will become active again */ + result = CURLM_CALL_MULTI_PERFORM; + } + + /* post-transfer command */ + easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE); + /* + * If there are other handles on the pipeline, Curl_done won't set + * easy_conn to NULL. In such a case, curl_multi_remove_handle() can + * access free'd data, if the connection is free'd and the handle + * removed before we perform the processing in CURLM_STATE_COMPLETED + */ + if(easy->easy_conn) + easy->easy_conn = NULL; + } + + if(data->set.wildcardmatch) { + if(data->wildcard.state != CURLWC_DONE) { + /* if a wildcard is set and we are not ending -> lets start again + with CURLM_STATE_INIT */ + result = CURLM_CALL_MULTI_PERFORM; + multistate(easy, CURLM_STATE_INIT); + break; + } + } + + /* after we have DONE what we're supposed to do, go COMPLETED, and + it doesn't matter what the Curl_done() returned! */ + multistate(easy, CURLM_STATE_COMPLETED); + + break; + + case CURLM_STATE_COMPLETED: + /* this is a completed transfer, it is likely to still be connected */ + + /* This node should be delinked from the list now and we should post + an information message that we are complete. */ + + /* Important: reset the conn pointer so that we don't point to memory + that could be freed anytime */ + easy->easy_conn = NULL; + + Curl_expire(data, 0); /* stop all timers */ + break; + + case CURLM_STATE_MSGSENT: + return CURLM_OK; /* do nothing */ + + default: + return CURLM_INTERNAL_ERROR; + } + + if(easy->state < CURLM_STATE_COMPLETED) { + if(CURLE_OK != easy->result) { + /* + * If an error was returned, and we aren't in completed state now, + * then we go to completed and consider this transfer aborted. + */ + + /* NOTE: no attempt to disconnect connections must be made + in the case blocks above - cleanup happens only here */ + + data->state.pipe_broke = FALSE; + + if(easy->easy_conn) { + /* if this has a connection, unsubscribe from the pipelines */ + easy->easy_conn->writechannel_inuse = FALSE; + easy->easy_conn->readchannel_inuse = FALSE; + Curl_removeHandleFromPipeline(data, + easy->easy_conn->send_pipe); + Curl_removeHandleFromPipeline(data, + easy->easy_conn->recv_pipe); + Curl_removeHandleFromPipeline(data, + easy->easy_conn->done_pipe); + /* Check if we can move pending requests to send pipe */ + checkPendPipeline(easy->easy_conn); + + if(disconnect_conn) { + /* disconnect properly */ + Curl_disconnect(easy->easy_conn, /* dead_connection */ FALSE); + + /* This is where we make sure that the easy_conn pointer is reset. + We don't have to do this in every case block above where a + failure is detected */ + easy->easy_conn = NULL; + } + } + else if(easy->state == CURLM_STATE_CONNECT) { + /* Curl_connect() failed */ + (void)Curl_posttransfer(data); + } + + multistate(easy, CURLM_STATE_COMPLETED); + } + /* if there's still a connection to use, call the progress function */ + else if(easy->easy_conn && Curl_pgrsUpdate(easy->easy_conn)) { + /* aborted due to progress callback return code must close the + connection */ + easy->easy_conn->bits.close = TRUE; + + /* if not yet in DONE state, go there, otherwise COMPLETED */ + multistate(easy, (easy->state < CURLM_STATE_DONE)? + CURLM_STATE_DONE: CURLM_STATE_COMPLETED); + result = CURLM_CALL_MULTI_PERFORM; + } + } + } WHILE_FALSE; /* just to break out from! */ + + if(CURLM_STATE_COMPLETED == easy->state) { + /* now fill in the Curl_message with this info */ + msg = &easy->msg; + + msg->extmsg.msg = CURLMSG_DONE; + msg->extmsg.easy_handle = data; + msg->extmsg.data.result = easy->result; + + result = multi_addmsg(multi, msg); + + multistate(easy, CURLM_STATE_MSGSENT); + } + + return result; +} + + +CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) +{ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + struct Curl_one_easy *easy; + CURLMcode returncode=CURLM_OK; + struct Curl_tree *t; + struct timeval now = Curl_tvnow(); + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + easy=multi->easy.next; + while(easy != &multi->easy) { + CURLMcode result; + struct WildcardData *wc = &easy->easy_handle->wildcard; + + if(easy->easy_handle->set.wildcardmatch) { + if(!wc->filelist) { + CURLcode ret = Curl_wildcard_init(wc); /* init wildcard structures */ + if(ret) + return CURLM_OUT_OF_MEMORY; + } + } + + do + result = multi_runsingle(multi, now, easy); + while(CURLM_CALL_MULTI_PERFORM == result); + + if(easy->easy_handle->set.wildcardmatch) { + /* destruct wildcard structures if it is needed */ + if(wc->state == CURLWC_DONE || result) + Curl_wildcard_dtor(wc); + } + + if(result) + returncode = result; + + easy = easy->next; /* operate on next handle */ + } + + /* + * Simply remove all expired timers from the splay since handles are dealt + * with unconditionally by this function and curl_multi_timeout() requires + * that already passed/handled expire times are removed from the splay. + * + * It is important that the 'now' value is set at the entry of this function + * and not for the current time as it may have ticked a little while since + * then and then we risk this loop to remove timers that actually have not + * been handled! + */ + do { + multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); + if(t) + /* the removed may have another timeout in queue */ + (void)add_next_timeout(now, multi, t->payload); + + } while(t); + + *running_handles = multi->num_alive; + + if(CURLM_OK >= returncode) + update_timer(multi); + + return returncode; +} + +static void close_all_connections(struct Curl_multi *multi) +{ + struct connectdata *conn; + + conn = Curl_conncache_find_first_connection(multi->conn_cache); + while(conn) { + conn->data = multi->closure_handle; + + /* This will remove the connection from the cache */ + (void)Curl_disconnect(conn, FALSE); + + conn = Curl_conncache_find_first_connection(multi->conn_cache); + } +} + +CURLMcode curl_multi_cleanup(CURLM *multi_handle) +{ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + struct Curl_one_easy *easy; + struct Curl_one_easy *nexteasy; + + if(GOOD_MULTI_HANDLE(multi)) { + multi->type = 0; /* not good anymore */ + + /* Close all the connections in the connection cache */ + close_all_connections(multi); + + Curl_close(multi->closure_handle); + multi->closure_handle = NULL; + + Curl_hash_destroy(multi->sockhash); + multi->sockhash = NULL; + + Curl_conncache_destroy(multi->conn_cache); + multi->conn_cache = NULL; + + /* remove the pending list of messages */ + Curl_llist_destroy(multi->msglist, NULL); + multi->msglist = NULL; + + /* remove all easy handles */ + easy = multi->easy.next; + while(easy != &multi->easy) { + nexteasy=easy->next; + if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) { + /* clear out the usage of the shared DNS cache */ + Curl_hostcache_clean(easy->easy_handle); + easy->easy_handle->dns.hostcache = NULL; + easy->easy_handle->dns.hostcachetype = HCACHE_NONE; + } + + /* Clear the pointer to the connection cache */ + easy->easy_handle->state.conn_cache = NULL; + + Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association */ + + free(easy); + easy = nexteasy; + } + + Curl_hash_destroy(multi->hostcache); + multi->hostcache = NULL; + + free(multi); + + return CURLM_OK; + } + else + return CURLM_BAD_HANDLE; +} + +/* + * curl_multi_info_read() + * + * This function is the primary way for a multi/multi_socket application to + * figure out if a transfer has ended. We MUST make this function as fast as + * possible as it will be polled frequently and we MUST NOT scan any lists in + * here to figure out things. We must scale fine to thousands of handles and + * beyond. The current design is fully O(1). + */ + +CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue) +{ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + struct Curl_message *msg; + + *msgs_in_queue = 0; /* default to none */ + + if(GOOD_MULTI_HANDLE(multi) && Curl_llist_count(multi->msglist)) { + /* there is one or more messages in the list */ + struct curl_llist_element *e; + + /* extract the head of the list to return */ + e = multi->msglist->head; + + msg = e->ptr; + + /* remove the extracted entry */ + Curl_llist_remove(multi->msglist, e, NULL); + + *msgs_in_queue = curlx_uztosi(Curl_llist_count(multi->msglist)); + + return &msg->extmsg; + } + else + return NULL; +} + +/* + * singlesocket() checks what sockets we deal with and their "action state" + * and if we have a different state in any of those sockets from last time we + * call the callback accordingly. + */ +static void singlesocket(struct Curl_multi *multi, + struct Curl_one_easy *easy) +{ + curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; + int i; + struct Curl_sh_entry *entry; + curl_socket_t s; + int num; + unsigned int curraction; + struct Curl_one_easy *easy_by_hash; + bool remove_sock_from_hash; + + for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) + socks[i] = CURL_SOCKET_BAD; + + /* Fill in the 'current' struct with the state as it is now: what sockets to + supervise and for what actions */ + curraction = multi_getsock(easy, socks, MAX_SOCKSPEREASYHANDLE); + + /* We have 0 .. N sockets already and we get to know about the 0 .. M + sockets we should have from now on. Detect the differences, remove no + longer supervised ones and add new ones */ + + /* walk over the sockets we got right now */ + for(i=0; (i< MAX_SOCKSPEREASYHANDLE) && + (curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))); + i++) { + int action = CURL_POLL_NONE; + + s = socks[i]; + + /* get it from the hash */ + entry = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s)); + + if(curraction & GETSOCK_READSOCK(i)) + action |= CURL_POLL_IN; + if(curraction & GETSOCK_WRITESOCK(i)) + action |= CURL_POLL_OUT; + + if(entry) { + /* yeps, already present so check if it has the same action set */ + if(entry->action == action) + /* same, continue */ + continue; + } + else { + /* this is a socket we didn't have before, add it! */ + entry = sh_addentry(multi->sockhash, s, easy->easy_handle); + if(!entry) + /* fatal */ + return; + } + + /* we know (entry != NULL) at this point, see the logic above */ + if(multi->socket_cb) + multi->socket_cb(easy->easy_handle, + s, + action, + multi->socket_userp, + entry->socketp); + + entry->action = action; /* store the current action state */ + } + + num = i; /* number of sockets */ + + /* when we've walked over all the sockets we should have right now, we must + make sure to detect sockets that are removed */ + for(i=0; i< easy->numsocks; i++) { + int j; + s = easy->sockets[i]; + for(j=0; jsockhash, (char *)&s, sizeof(s)); + if(entry) { + /* check if the socket to be removed serves a connection which has + other easy-s in a pipeline. In this case the socket should not be + removed. */ + struct connectdata *easy_conn; + + easy_by_hash = entry->easy->multi_pos; + easy_conn = easy_by_hash->easy_conn; + if(easy_conn) { + if(easy_conn->recv_pipe && easy_conn->recv_pipe->size > 1) { + /* the handle should not be removed from the pipe yet */ + remove_sock_from_hash = FALSE; + + /* Update the sockhash entry to instead point to the next in line + for the recv_pipe, or the first (in case this particular easy + isn't already) */ + if(entry->easy == easy->easy_handle) { + if(isHandleAtHead(easy->easy_handle, easy_conn->recv_pipe)) + entry->easy = easy_conn->recv_pipe->head->next->ptr; + else + entry->easy = easy_conn->recv_pipe->head->ptr; + } + } + if(easy_conn->send_pipe && easy_conn->send_pipe->size > 1) { + /* the handle should not be removed from the pipe yet */ + remove_sock_from_hash = FALSE; + + /* Update the sockhash entry to instead point to the next in line + for the send_pipe, or the first (in case this particular easy + isn't already) */ + if(entry->easy == easy->easy_handle) { + if(isHandleAtHead(easy->easy_handle, easy_conn->send_pipe)) + entry->easy = easy_conn->send_pipe->head->next->ptr; + else + entry->easy = easy_conn->send_pipe->head->ptr; + } + } + /* Don't worry about overwriting recv_pipe head with send_pipe_head, + when action will be asked on the socket (see multi_socket()), the + head of the correct pipe will be taken according to the + action. */ + } + } + else + /* just a precaution, this socket really SHOULD be in the hash already + but in case it isn't, we don't have to tell the app to remove it + either since it never got to know about it */ + remove_sock_from_hash = FALSE; + + if(remove_sock_from_hash) { + /* in this case 'entry' is always non-NULL */ + if(multi->socket_cb) + multi->socket_cb(easy->easy_handle, + s, + CURL_POLL_REMOVE, + multi->socket_userp, + entry->socketp); + sh_delentry(multi->sockhash, s); + } + + } + } + + memcpy(easy->sockets, socks, num*sizeof(curl_socket_t)); + easy->numsocks = num; +} + +/* + * add_next_timeout() + * + * Each SessionHandle has a list of timeouts. The add_next_timeout() is called + * when it has just been removed from the splay tree because the timeout has + * expired. This function is then to advance in the list to pick the next + * timeout to use (skip the already expired ones) and add this node back to + * the splay tree again. + * + * The splay tree only has each sessionhandle as a single node and the nearest + * timeout is used to sort it on. + */ +static CURLMcode add_next_timeout(struct timeval now, + struct Curl_multi *multi, + struct SessionHandle *d) +{ + struct timeval *tv = &d->state.expiretime; + struct curl_llist *list = d->state.timeoutlist; + struct curl_llist_element *e; + + /* move over the timeout list for this specific handle and remove all + timeouts that are now passed tense and store the next pending + timeout in *tv */ + for(e = list->head; e; ) { + struct curl_llist_element *n = e->next; + long diff = curlx_tvdiff(*(struct timeval *)e->ptr, now); + if(diff <= 0) + /* remove outdated entry */ + Curl_llist_remove(list, e, NULL); + else + /* the list is sorted so get out on the first mismatch */ + break; + e = n; + } + e = list->head; + if(!e) { + /* clear the expire times within the handles that we remove from the + splay tree */ + tv->tv_sec = 0; + tv->tv_usec = 0; + } + else { + /* copy the first entry to 'tv' */ + memcpy(tv, e->ptr, sizeof(*tv)); + + /* remove first entry from list */ + Curl_llist_remove(list, e, NULL); + + /* insert this node again into the splay */ + multi->timetree = Curl_splayinsert(*tv, multi->timetree, + &d->state.timenode); + } + return CURLM_OK; +} + + +static CURLMcode multi_socket(struct Curl_multi *multi, + bool checkall, + curl_socket_t s, + int ev_bitmask, + int *running_handles) +{ + CURLMcode result = CURLM_OK; + struct SessionHandle *data = NULL; + struct Curl_tree *t; + struct timeval now = Curl_tvnow(); + + if(checkall) { + struct Curl_one_easy *easyp; + /* *perform() deals with running_handles on its own */ + result = curl_multi_perform(multi, running_handles); + + /* walk through each easy handle and do the socket state change magic + and callbacks */ + easyp=multi->easy.next; + while(easyp != &multi->easy) { + singlesocket(multi, easyp); + easyp = easyp->next; + } + + /* or should we fall-through and do the timer-based stuff? */ + return result; + } + else if(s != CURL_SOCKET_TIMEOUT) { + + struct Curl_sh_entry *entry = + Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s)); + + if(!entry) + /* Unmatched socket, we can't act on it but we ignore this fact. In + real-world tests it has been proved that libevent can in fact give + the application actions even though the socket was just previously + asked to get removed, so thus we better survive stray socket actions + and just move on. */ + ; + else { + data = entry->easy; + + if(data->magic != CURLEASY_MAGIC_NUMBER) + /* bad bad bad bad bad bad bad */ + return CURLM_INTERNAL_ERROR; + + /* If the pipeline is enabled, take the handle which is in the head of + the pipeline. If we should write into the socket, take the send_pipe + head. If we should read from the socket, take the recv_pipe head. */ + if(data->set.one_easy->easy_conn) { + if((ev_bitmask & CURL_POLL_OUT) && + data->set.one_easy->easy_conn->send_pipe && + data->set.one_easy->easy_conn->send_pipe->head) + data = data->set.one_easy->easy_conn->send_pipe->head->ptr; + else if((ev_bitmask & CURL_POLL_IN) && + data->set.one_easy->easy_conn->recv_pipe && + data->set.one_easy->easy_conn->recv_pipe->head) + data = data->set.one_easy->easy_conn->recv_pipe->head->ptr; + } + + if(data->set.one_easy->easy_conn && + !(data->set.one_easy->easy_conn->handler->flags & PROTOPT_DIRLOCK)) + /* set socket event bitmask if they're not locked */ + data->set.one_easy->easy_conn->cselect_bits = ev_bitmask; + + do + result = multi_runsingle(multi, now, data->set.one_easy); + while(CURLM_CALL_MULTI_PERFORM == result); + + if(data->set.one_easy->easy_conn && + !(data->set.one_easy->easy_conn->handler->flags & PROTOPT_DIRLOCK)) + /* clear the bitmask only if not locked */ + data->set.one_easy->easy_conn->cselect_bits = 0; + + if(CURLM_OK >= result) + /* get the socket(s) and check if the state has been changed since + last */ + singlesocket(multi, data->set.one_easy); + + /* Now we fall-through and do the timer-based stuff, since we don't want + to force the user to have to deal with timeouts as long as at least + one connection in fact has traffic. */ + + data = NULL; /* set data to NULL again to avoid calling + multi_runsingle() in case there's no need to */ + } + } + + now.tv_usec += 40000; /* compensate for bad precision timers that might've + triggered too early */ + if(now.tv_usec >= 1000000) { + now.tv_sec++; + now.tv_usec -= 1000000; + } + + /* + * The loop following here will go on as long as there are expire-times left + * to process in the splay and 'data' will be re-assigned for every expired + * handle we deal with. + */ + do { + /* the first loop lap 'data' can be NULL */ + if(data) { + do + result = multi_runsingle(multi, now, data->set.one_easy); + while(CURLM_CALL_MULTI_PERFORM == result); + + if(CURLM_OK >= result) + /* get the socket(s) and check if the state has been changed since + last */ + singlesocket(multi, data->set.one_easy); + } + + /* Check if there's one (more) expired timer to deal with! This function + extracts a matching node if there is one */ + + multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); + if(t) { + data = t->payload; /* assign this for next loop */ + (void)add_next_timeout(now, multi, t->payload); + } + + } while(t); + + *running_handles = multi->num_alive; + return result; +} + +#undef curl_multi_setopt +CURLMcode curl_multi_setopt(CURLM *multi_handle, + CURLMoption option, ...) +{ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + CURLMcode res = CURLM_OK; + va_list param; + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + va_start(param, option); + + switch(option) { + case CURLMOPT_SOCKETFUNCTION: + multi->socket_cb = va_arg(param, curl_socket_callback); + break; + case CURLMOPT_SOCKETDATA: + multi->socket_userp = va_arg(param, void *); + break; + case CURLMOPT_PIPELINING: + multi->pipelining_enabled = (0 != va_arg(param, long)) ? TRUE : FALSE; + break; + case CURLMOPT_TIMERFUNCTION: + multi->timer_cb = va_arg(param, curl_multi_timer_callback); + break; + case CURLMOPT_TIMERDATA: + multi->timer_userp = va_arg(param, void *); + break; + case CURLMOPT_MAXCONNECTS: + multi->maxconnects = va_arg(param, long); + break; + default: + res = CURLM_UNKNOWN_OPTION; + break; + } + va_end(param); + return res; +} + +/* we define curl_multi_socket() in the public multi.h header */ +#undef curl_multi_socket + +CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, + int *running_handles) +{ + CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s, + 0, running_handles); + if(CURLM_OK >= result) + update_timer((struct Curl_multi *)multi_handle); + return result; +} + +CURLMcode curl_multi_socket_action(CURLM *multi_handle, curl_socket_t s, + int ev_bitmask, int *running_handles) +{ + CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s, + ev_bitmask, running_handles); + if(CURLM_OK >= result) + update_timer((struct Curl_multi *)multi_handle); + return result; +} + +CURLMcode curl_multi_socket_all(CURLM *multi_handle, int *running_handles) + +{ + CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, + TRUE, CURL_SOCKET_BAD, 0, running_handles); + if(CURLM_OK >= result) + update_timer((struct Curl_multi *)multi_handle); + return result; +} + +static CURLMcode multi_timeout(struct Curl_multi *multi, + long *timeout_ms) +{ + static struct timeval tv_zero = {0,0}; + + if(multi->timetree) { + /* we have a tree of expire times */ + struct timeval now = Curl_tvnow(); + + /* splay the lowest to the bottom */ + multi->timetree = Curl_splay(tv_zero, multi->timetree); + + if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) { + /* some time left before expiration */ + *timeout_ms = curlx_tvdiff(multi->timetree->key, now); + if(!*timeout_ms) + /* + * Since we only provide millisecond resolution on the returned value + * and the diff might be less than one millisecond here, we don't + * return zero as that may cause short bursts of busyloops on fast + * processors while the diff is still present but less than one + * millisecond! instead we return 1 until the time is ripe. + */ + *timeout_ms=1; + } + else + /* 0 means immediately */ + *timeout_ms = 0; + } + else + *timeout_ms = -1; + + return CURLM_OK; +} + +CURLMcode curl_multi_timeout(CURLM *multi_handle, + long *timeout_ms) +{ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + + /* First, make some basic checks that the CURLM handle is a good handle */ + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + return multi_timeout(multi, timeout_ms); +} + +/* + * Tell the application it should update its timers, if it subscribes to the + * update timer callback. + */ +static int update_timer(struct Curl_multi *multi) +{ + long timeout_ms; + + if(!multi->timer_cb) + return 0; + if(multi_timeout(multi, &timeout_ms)) { + return -1; + } + if(timeout_ms < 0) { + static const struct timeval none={0,0}; + if(Curl_splaycomparekeys(none, multi->timer_lastcall)) { + multi->timer_lastcall = none; + /* there's no timeout now but there was one previously, tell the app to + disable it */ + return multi->timer_cb((CURLM*)multi, -1, multi->timer_userp); + } + return 0; + } + + /* When multi_timeout() is done, multi->timetree points to the node with the + * timeout we got the (relative) time-out time for. We can thus easily check + * if this is the same (fixed) time as we got in a previous call and then + * avoid calling the callback again. */ + if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0) + return 0; + + multi->timer_lastcall = multi->timetree->key; + + return multi->timer_cb((CURLM*)multi, timeout_ms, multi->timer_userp); +} + +static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, + struct connectdata *conn) +{ + size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size; + struct curl_llist_element *sendhead = conn->send_pipe->head; + struct curl_llist *pipeline; + CURLcode rc; + + if(!Curl_isPipeliningEnabled(handle) || + pipeLen == 0) + pipeline = conn->send_pipe; + else { + if(conn->server_supports_pipelining && + pipeLen < MAX_PIPELINE_LENGTH) + pipeline = conn->send_pipe; + else + pipeline = conn->pend_pipe; + } + + rc = Curl_addHandleToPipeline(handle, pipeline); + + if(pipeline == conn->send_pipe && sendhead != conn->send_pipe->head) { + /* this is a new one as head, expire it */ + conn->writechannel_inuse = FALSE; /* not in use yet */ +#ifdef DEBUGBUILD + infof(conn->data, "%p is at send pipe head!\n", + conn->send_pipe->head->ptr); +#endif + Curl_expire(conn->send_pipe->head->ptr, 1); + } + + return rc; +} + +static int checkPendPipeline(struct connectdata *conn) +{ + int result = 0; + struct curl_llist_element *sendhead = conn->send_pipe->head; + + size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size; + if(conn->server_supports_pipelining || pipeLen == 0) { + struct curl_llist_element *curr = conn->pend_pipe->head; + const size_t maxPipeLen = + conn->server_supports_pipelining ? MAX_PIPELINE_LENGTH : 1; + + while(pipeLen < maxPipeLen && curr) { + Curl_llist_move(conn->pend_pipe, curr, + conn->send_pipe, conn->send_pipe->tail); + Curl_pgrsTime(curr->ptr, TIMER_PRETRANSFER); + ++result; /* count how many handles we moved */ + curr = conn->pend_pipe->head; + ++pipeLen; + } + } + + if(result) { + conn->now = Curl_tvnow(); + /* something moved, check for a new send pipeline leader */ + if(sendhead != conn->send_pipe->head) { + /* this is a new one as head, expire it */ + conn->writechannel_inuse = FALSE; /* not in use yet */ +#ifdef DEBUGBUILD + infof(conn->data, "%p is at send pipe head!\n", + conn->send_pipe->head->ptr); +#endif + Curl_expire(conn->send_pipe->head->ptr, 1); + } + } + + return result; +} + +/* Move this transfer from the sending list to the receiving list. + + Pay special attention to the new sending list "leader" as it needs to get + checked to update what sockets it acts on. + +*/ +static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle, + struct connectdata *conn) +{ + struct curl_llist_element *curr; + + curr = conn->send_pipe->head; + while(curr) { + if(curr->ptr == handle) { + Curl_llist_move(conn->send_pipe, curr, + conn->recv_pipe, conn->recv_pipe->tail); + + if(conn->send_pipe->head) { + /* Since there's a new easy handle at the start of the send pipeline, + set its timeout value to 1ms to make it trigger instantly */ + conn->writechannel_inuse = FALSE; /* not used now */ +#ifdef DEBUGBUILD + infof(conn->data, "%p is at send pipe head B!\n", + conn->send_pipe->head->ptr); +#endif + Curl_expire(conn->send_pipe->head->ptr, 1); + } + + /* The receiver's list is not really interesting here since either this + handle is now first in the list and we'll deal with it soon, or + another handle is already first and thus is already taken care of */ + + break; /* we're done! */ + } + curr = curr->next; + } +} + +static void moveHandleFromRecvToDonePipeline(struct SessionHandle *handle, + struct connectdata *conn) +{ + struct curl_llist_element *curr; + + curr = conn->recv_pipe->head; + while(curr) { + if(curr->ptr == handle) { + Curl_llist_move(conn->recv_pipe, curr, + conn->done_pipe, conn->done_pipe->tail); + break; + } + curr = curr->next; + } +} +static bool isHandleAtHead(struct SessionHandle *handle, + struct curl_llist *pipeline) +{ + struct curl_llist_element *curr = pipeline->head; + if(curr) + return (curr->ptr == handle) ? TRUE : FALSE; + + return FALSE; +} + +/* + * multi_freetimeout() + * + * Callback used by the llist system when a single timeout list entry is + * destroyed. + */ +static void multi_freetimeout(void *user, void *entryptr) +{ + (void)user; + + /* the entry was plain malloc()'ed */ + free(entryptr); +} + +/* + * multi_addtimeout() + * + * Add a timestamp to the list of timeouts. Keep the list sorted so that head + * of list is always the timeout nearest in time. + * + */ +static CURLMcode +multi_addtimeout(struct curl_llist *timeoutlist, + struct timeval *stamp) +{ + struct curl_llist_element *e; + struct timeval *timedup; + struct curl_llist_element *prev = NULL; + + timedup = malloc(sizeof(*timedup)); + if(!timedup) + return CURLM_OUT_OF_MEMORY; + + /* copy the timestamp */ + memcpy(timedup, stamp, sizeof(*timedup)); + + if(Curl_llist_count(timeoutlist)) { + /* find the correct spot in the list */ + for(e = timeoutlist->head; e; e = e->next) { + struct timeval *checktime = e->ptr; + long diff = curlx_tvdiff(*checktime, *timedup); + if(diff > 0) + break; + prev = e; + } + + } + /* else + this is the first timeout on the list */ + + if(!Curl_llist_insert_next(timeoutlist, prev, timedup)) { + free(timedup); + return CURLM_OUT_OF_MEMORY; + } + + return CURLM_OK; +} + +/* + * Curl_expire() + * + * given a number of milliseconds from now to use to set the 'act before + * this'-time for the transfer, to be extracted by curl_multi_timeout() + * + * Note that the timeout will be added to a queue of timeouts if it defines a + * moment in time that is later than the current head of queue. + * + * Pass zero to clear all timeout values for this handle. +*/ +void Curl_expire(struct SessionHandle *data, long milli) +{ + struct Curl_multi *multi = data->multi; + struct timeval *nowp = &data->state.expiretime; + int rc; + + /* this is only interesting for multi-interface using libcurl, and only + while there is still a multi interface struct remaining! */ + if(!multi) + return; + + if(!milli) { + /* No timeout, clear the time data. */ + if(nowp->tv_sec || nowp->tv_usec) { + /* Since this is an cleared time, we must remove the previous entry from + the splay tree */ + struct curl_llist *list = data->state.timeoutlist; + + rc = Curl_splayremovebyaddr(multi->timetree, + &data->state.timenode, + &multi->timetree); + if(rc) + infof(data, "Internal error clearing splay node = %d\n", rc); + + /* flush the timeout list too */ + while(list->size > 0) + Curl_llist_remove(list, list->tail, NULL); + +#ifdef DEBUGBUILD + infof(data, "Expire cleared\n"); +#endif + nowp->tv_sec = 0; + nowp->tv_usec = 0; + } + } + else { + struct timeval set; + + set = Curl_tvnow(); + set.tv_sec += milli/1000; + set.tv_usec += (milli%1000)*1000; + + if(set.tv_usec >= 1000000) { + set.tv_sec++; + set.tv_usec -= 1000000; + } + + if(nowp->tv_sec || nowp->tv_usec) { + /* This means that the struct is added as a node in the splay tree. + Compare if the new time is earlier, and only remove-old/add-new if it + is. */ + long diff = curlx_tvdiff(set, *nowp); + if(diff > 0) { + /* the new expire time was later so just add it to the queue + and get out */ + multi_addtimeout(data->state.timeoutlist, &set); + return; + } + + /* the new time is newer than the presently set one, so add the current + to the queue and update the head */ + multi_addtimeout(data->state.timeoutlist, nowp); + + /* Since this is an updated time, we must remove the previous entry from + the splay tree first and then re-add the new value */ + rc = Curl_splayremovebyaddr(multi->timetree, + &data->state.timenode, + &multi->timetree); + if(rc) + infof(data, "Internal error removing splay node = %d\n", rc); + } + + *nowp = set; + data->state.timenode.payload = data; + multi->timetree = Curl_splayinsert(*nowp, + multi->timetree, + &data->state.timenode); + } +#if 0 + Curl_splayprint(multi->timetree, 0, TRUE); +#endif +} + +CURLMcode curl_multi_assign(CURLM *multi_handle, + curl_socket_t s, void *hashp) +{ + struct Curl_sh_entry *there = NULL; + struct Curl_multi *multi = (struct Curl_multi *)multi_handle; + + if(s != CURL_SOCKET_BAD) + there = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(curl_socket_t)); + + if(!there) + return CURLM_BAD_SOCKET; + + there->socketp = hashp; + + return CURLM_OK; +} + +#ifdef DEBUGBUILD +void Curl_multi_dump(const struct Curl_multi *multi_handle) +{ + struct Curl_multi *multi=(struct Curl_multi *)multi_handle; + struct Curl_one_easy *easy; + int i; + fprintf(stderr, "* Multi status: %d handles, %d alive\n", + multi->num_easy, multi->num_alive); + for(easy=multi->easy.next; easy != &multi->easy; easy = easy->next) { + if(easy->state < CURLM_STATE_COMPLETED) { + /* only display handles that are not completed */ + fprintf(stderr, "handle %p, state %s, %d sockets\n", + (void *)easy->easy_handle, + statename[easy->state], easy->numsocks); + for(i=0; i < easy->numsocks; i++) { + curl_socket_t s = easy->sockets[i]; + struct Curl_sh_entry *entry = + Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s)); + + fprintf(stderr, "%d ", (int)s); + if(!entry) { + fprintf(stderr, "INTERNAL CONFUSION\n"); + continue; + } + fprintf(stderr, "[%s %s] ", + entry->action&CURL_POLL_IN?"RECVING":"", + entry->action&CURL_POLL_OUT?"SENDING":""); + } + if(easy->numsocks) + fprintf(stderr, "\n"); + } + } +} +#endif diff --git a/lib/curl_netrc.c b/lib/curl_netrc.c new file mode 100644 index 000000000..10853d395 --- /dev/null +++ b/lib/curl_netrc.c @@ -0,0 +1,186 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_PWD_H +#include +#endif +#ifdef __VMS +#include +#endif + +#include +#include "curl_netrc.h" + +#include "curl_strequal.h" +#include "curl_strtok.h" +#include "curl_memory.h" +#include "curl_rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* Get user and password from .netrc when given a machine name */ + +enum host_lookup_state { + NOTHING, + HOSTFOUND, /* the 'machine' keyword was found */ + HOSTVALID /* this is "our" machine! */ +}; + +/* + * @unittest: 1304 + */ +int Curl_parsenetrc(const char *host, + char *login, + char *password, + char *netrcfile) +{ + FILE *file; + int retcode=1; + int specific_login = (login[0] != 0); + char *home = NULL; + bool home_alloc = FALSE; + bool netrc_alloc = FALSE; + enum host_lookup_state state=NOTHING; + + char state_login=0; /* Found a login keyword */ + char state_password=0; /* Found a password keyword */ + int state_our_login=FALSE; /* With specific_login, found *our* login name */ + +#define NETRC DOT_CHAR "netrc" + + if(!netrcfile) { + home = curl_getenv("HOME"); /* portable environment reader */ + if(home) { + home_alloc = TRUE; +#if defined(HAVE_GETPWUID) && defined(HAVE_GETEUID) + } + else { + struct passwd *pw; + pw= getpwuid(geteuid()); + if(pw) { +#ifdef __VMS + home = decc_translate_vms(pw->pw_dir); +#else + home = pw->pw_dir; +#endif + } +#endif + } + + if(!home) + return -1; + + netrcfile = curl_maprintf("%s%s%s", home, DIR_CHAR, NETRC); + if(!netrcfile) { + if(home_alloc) + free(home); + return -1; + } + netrc_alloc = TRUE; + } + + file = fopen(netrcfile, "r"); + if(file) { + char *tok; + char *tok_buf; + bool done=FALSE; + char netrcbuffer[256]; + int netrcbuffsize = (int)sizeof(netrcbuffer); + + while(!done && fgets(netrcbuffer, netrcbuffsize, file)) { + tok=strtok_r(netrcbuffer, " \t\n", &tok_buf); + while(!done && tok) { + + if(login[0] && password[0]) { + done=TRUE; + break; + } + + switch(state) { + case NOTHING: + if(Curl_raw_equal("machine", tok)) { + /* the next tok is the machine name, this is in itself the + delimiter that starts the stuff entered for this machine, + after this we need to search for 'login' and + 'password'. */ + state=HOSTFOUND; + } + break; + case HOSTFOUND: + if(Curl_raw_equal(host, tok)) { + /* and yes, this is our host! */ + state=HOSTVALID; + retcode=0; /* we did find our host */ + } + else + /* not our host */ + state=NOTHING; + break; + case HOSTVALID: + /* we are now parsing sub-keywords concerning "our" host */ + if(state_login) { + if(specific_login) { + state_our_login = Curl_raw_equal(login, tok); + } + else { + strncpy(login, tok, LOGINSIZE-1); + } + state_login=0; + } + else if(state_password) { + if(state_our_login || !specific_login) { + strncpy(password, tok, PASSWORDSIZE-1); + } + state_password=0; + } + else if(Curl_raw_equal("login", tok)) + state_login=1; + else if(Curl_raw_equal("password", tok)) + state_password=1; + else if(Curl_raw_equal("machine", tok)) { + /* ok, there's machine here go => */ + state = HOSTFOUND; + state_our_login = FALSE; + } + break; + } /* switch (state) */ + + tok = strtok_r(NULL, " \t\n", &tok_buf); + } /* while(tok) */ + } /* while fgets() */ + + fclose(file); + } + + if(home_alloc) + free(home); + if(netrc_alloc) + free(netrcfile); + + return retcode; +} diff --git a/lib/curl_non-ascii.h b/lib/curl_non-ascii.h deleted file mode 100644 index 552a51311..000000000 --- a/lib/curl_non-ascii.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef HEADER_CURL_NON_ASCII_H -#define HEADER_CURL_NON_ASCII_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef CURL_DOES_CONVERSIONS - -#include "curl_urldata.h" - -/* - * Curl_convert_clone() returns a malloced copy of the source string (if - * returning CURLE_OK), with the data converted to network format. - * - * If no conversion was needed *outbuf may be NULL. - */ -CURLcode Curl_convert_clone(struct SessionHandle *data, - const char *indata, - size_t insize, - char **outbuf); - -void Curl_convert_init(struct SessionHandle *data); -void Curl_convert_setup(struct SessionHandle *data); -void Curl_convert_close(struct SessionHandle *data); - -CURLcode Curl_convert_to_network(struct SessionHandle *data, - char *buffer, size_t length); -CURLcode Curl_convert_from_network(struct SessionHandle *data, - char *buffer, size_t length); -CURLcode Curl_convert_from_utf8(struct SessionHandle *data, - char *buffer, size_t length); -CURLcode Curl_convert_form(struct SessionHandle *data, struct FormData *form); -#else -#define Curl_convert_clone(a,b,c,d) ((void)a, CURLE_OK) -#define Curl_convert_init(x) Curl_nop_stmt -#define Curl_convert_setup(x) Curl_nop_stmt -#define Curl_convert_close(x) Curl_nop_stmt -#define Curl_convert_to_network(a,b,c) ((void)a, CURLE_OK) -#define Curl_convert_from_network(a,b,c) ((void)a, CURLE_OK) -#define Curl_convert_from_utf8(a,b,c) ((void)a, CURLE_OK) -#define Curl_convert_form(a,b) CURLE_OK -#endif - -#endif /* HEADER_CURL_NON_ASCII_H */ diff --git a/lib/curl_non_ascii.c b/lib/curl_non_ascii.c new file mode 100644 index 000000000..68b33a92a --- /dev/null +++ b/lib/curl_non_ascii.c @@ -0,0 +1,343 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef CURL_DOES_CONVERSIONS + +#include + +#include "curl_non_ascii.h" +#include "curl_formdata.h" +#include "curl_sendf.h" +#include "curl_urldata.h" + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifdef HAVE_ICONV +#include +/* set default codesets for iconv */ +#ifndef CURL_ICONV_CODESET_OF_NETWORK +#define CURL_ICONV_CODESET_OF_NETWORK "ISO8859-1" +#endif +#ifndef CURL_ICONV_CODESET_FOR_UTF8 +#define CURL_ICONV_CODESET_FOR_UTF8 "UTF-8" +#endif +#define ICONV_ERROR (size_t)-1 +#endif /* HAVE_ICONV */ + +/* + * Curl_convert_clone() returns a malloced copy of the source string (if + * returning CURLE_OK), with the data converted to network format. + */ +CURLcode Curl_convert_clone(struct SessionHandle *data, + const char *indata, + size_t insize, + char **outbuf) +{ + char *convbuf; + CURLcode result; + + convbuf = malloc(insize); + if(!convbuf) + return CURLE_OUT_OF_MEMORY; + + memcpy(convbuf, indata, insize); + result = Curl_convert_to_network(data, convbuf, insize); + if(result) { + free(convbuf); + return result; + } + + *outbuf = convbuf; /* return the converted buffer */ + + return CURLE_OK; +} + +/* + * Curl_convert_to_network() is an internal function for performing ASCII + * conversions on non-ASCII platforms. It convers the buffer _in place_. + */ +CURLcode Curl_convert_to_network(struct SessionHandle *data, + char *buffer, size_t length) +{ + CURLcode rc; + + if(data->set.convtonetwork) { + /* use translation callback */ + rc = data->set.convtonetwork(buffer, length); + if(rc != CURLE_OK) { + failf(data, + "CURLOPT_CONV_TO_NETWORK_FUNCTION callback returned %d: %s", + (int)rc, curl_easy_strerror(rc)); + } + return rc; + } + else { +#ifdef HAVE_ICONV + /* do the translation ourselves */ + char *input_ptr, *output_ptr; + size_t in_bytes, out_bytes, rc; + int error; + + /* open an iconv conversion descriptor if necessary */ + if(data->outbound_cd == (iconv_t)-1) { + data->outbound_cd = iconv_open(CURL_ICONV_CODESET_OF_NETWORK, + CURL_ICONV_CODESET_OF_HOST); + if(data->outbound_cd == (iconv_t)-1) { + error = ERRNO; + failf(data, + "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", + CURL_ICONV_CODESET_OF_NETWORK, + CURL_ICONV_CODESET_OF_HOST, + error, strerror(error)); + return CURLE_CONV_FAILED; + } + } + /* call iconv */ + input_ptr = output_ptr = buffer; + in_bytes = out_bytes = length; + rc = iconv(data->outbound_cd, (const char**)&input_ptr, &in_bytes, + &output_ptr, &out_bytes); + if((rc == ICONV_ERROR) || (in_bytes != 0)) { + error = ERRNO; + failf(data, + "The Curl_convert_to_network iconv call failed with errno %i: %s", + error, strerror(error)); + return CURLE_CONV_FAILED; + } +#else + failf(data, "CURLOPT_CONV_TO_NETWORK_FUNCTION callback required"); + return CURLE_CONV_REQD; +#endif /* HAVE_ICONV */ + } + + return CURLE_OK; +} + +/* + * Curl_convert_from_network() is an internal function for performing ASCII + * conversions on non-ASCII platforms. It convers the buffer _in place_. + */ +CURLcode Curl_convert_from_network(struct SessionHandle *data, + char *buffer, size_t length) +{ + CURLcode rc; + + if(data->set.convfromnetwork) { + /* use translation callback */ + rc = data->set.convfromnetwork(buffer, length); + if(rc != CURLE_OK) { + failf(data, + "CURLOPT_CONV_FROM_NETWORK_FUNCTION callback returned %d: %s", + (int)rc, curl_easy_strerror(rc)); + } + return rc; + } + else { +#ifdef HAVE_ICONV + /* do the translation ourselves */ + char *input_ptr, *output_ptr; + size_t in_bytes, out_bytes, rc; + int error; + + /* open an iconv conversion descriptor if necessary */ + if(data->inbound_cd == (iconv_t)-1) { + data->inbound_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, + CURL_ICONV_CODESET_OF_NETWORK); + if(data->inbound_cd == (iconv_t)-1) { + error = ERRNO; + failf(data, + "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", + CURL_ICONV_CODESET_OF_HOST, + CURL_ICONV_CODESET_OF_NETWORK, + error, strerror(error)); + return CURLE_CONV_FAILED; + } + } + /* call iconv */ + input_ptr = output_ptr = buffer; + in_bytes = out_bytes = length; + rc = iconv(data->inbound_cd, (const char **)&input_ptr, &in_bytes, + &output_ptr, &out_bytes); + if((rc == ICONV_ERROR) || (in_bytes != 0)) { + error = ERRNO; + failf(data, + "Curl_convert_from_network iconv call failed with errno %i: %s", + error, strerror(error)); + return CURLE_CONV_FAILED; + } +#else + failf(data, "CURLOPT_CONV_FROM_NETWORK_FUNCTION callback required"); + return CURLE_CONV_REQD; +#endif /* HAVE_ICONV */ + } + + return CURLE_OK; +} + +/* + * Curl_convert_from_utf8() is an internal function for performing UTF-8 + * conversions on non-ASCII platforms. + */ +CURLcode Curl_convert_from_utf8(struct SessionHandle *data, + char *buffer, size_t length) +{ + CURLcode rc; + + if(data->set.convfromutf8) { + /* use translation callback */ + rc = data->set.convfromutf8(buffer, length); + if(rc != CURLE_OK) { + failf(data, + "CURLOPT_CONV_FROM_UTF8_FUNCTION callback returned %d: %s", + (int)rc, curl_easy_strerror(rc)); + } + return rc; + } + else { +#ifdef HAVE_ICONV + /* do the translation ourselves */ + const char *input_ptr; + char *output_ptr; + size_t in_bytes, out_bytes, rc; + int error; + + /* open an iconv conversion descriptor if necessary */ + if(data->utf8_cd == (iconv_t)-1) { + data->utf8_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, + CURL_ICONV_CODESET_FOR_UTF8); + if(data->utf8_cd == (iconv_t)-1) { + error = ERRNO; + failf(data, + "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", + CURL_ICONV_CODESET_OF_HOST, + CURL_ICONV_CODESET_FOR_UTF8, + error, strerror(error)); + return CURLE_CONV_FAILED; + } + } + /* call iconv */ + input_ptr = output_ptr = buffer; + in_bytes = out_bytes = length; + rc = iconv(data->utf8_cd, &input_ptr, &in_bytes, + &output_ptr, &out_bytes); + if((rc == ICONV_ERROR) || (in_bytes != 0)) { + error = ERRNO; + failf(data, + "The Curl_convert_from_utf8 iconv call failed with errno %i: %s", + error, strerror(error)); + return CURLE_CONV_FAILED; + } + if(output_ptr < input_ptr) { + /* null terminate the now shorter output string */ + *output_ptr = 0x00; + } +#else + failf(data, "CURLOPT_CONV_FROM_UTF8_FUNCTION callback required"); + return CURLE_CONV_REQD; +#endif /* HAVE_ICONV */ + } + + return CURLE_OK; +} + +/* + * Init conversion stuff for a SessionHandle + */ +void Curl_convert_init(struct SessionHandle *data) +{ +#if defined(CURL_DOES_CONVERSIONS) && defined(HAVE_ICONV) + /* conversion descriptors for iconv calls */ + data->outbound_cd = (iconv_t)-1; + data->inbound_cd = (iconv_t)-1; + data->utf8_cd = (iconv_t)-1; +#else + (void)data; +#endif /* CURL_DOES_CONVERSIONS && HAVE_ICONV */ +} + +/* + * Setup conversion stuff for a SessionHandle + */ +void Curl_convert_setup(struct SessionHandle *data) +{ + data->inbound_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, + CURL_ICONV_CODESET_OF_NETWORK); + data->outbound_cd = iconv_open(CURL_ICONV_CODESET_OF_NETWORK, + CURL_ICONV_CODESET_OF_HOST); + data->utf8_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, + CURL_ICONV_CODESET_FOR_UTF8); +} + +/* + * Close conversion stuff for a SessionHandle + */ + +void Curl_convert_close(struct SessionHandle *data) +{ +#ifdef HAVE_ICONV + /* close iconv conversion descriptors */ + if(data->inbound_cd != (iconv_t)-1) { + iconv_close(data->inbound_cd); + } + if(data->outbound_cd != (iconv_t)-1) { + iconv_close(data->outbound_cd); + } + if(data->utf8_cd != (iconv_t)-1) { + iconv_close(data->utf8_cd); + } +#else + (void)data; +#endif /* HAVE_ICONV */ +} + +/* + * Curl_convert_form() is used from curl_http.c, this converts any form items + * that need to be sent in the network encoding. Returns CURLE_OK on success. + */ +CURLcode Curl_convert_form(struct SessionHandle *data, struct FormData *form) +{ + struct FormData *next; + CURLcode rc; + + if(!form) + return CURLE_OK; + + if(!data) + return CURLE_BAD_FUNCTION_ARGUMENT; + + do { + next=form->next; /* the following form line */ + if(form->type == FORM_DATA) { + rc = Curl_convert_to_network(data, form->line, form->length); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(rc != CURLE_OK) + return rc; + } + } while((form = next) != NULL); /* continue */ + return CURLE_OK; +} + +#endif /* CURL_DOES_CONVERSIONS */ diff --git a/lib/curl_non_ascii.h b/lib/curl_non_ascii.h new file mode 100644 index 000000000..552a51311 --- /dev/null +++ b/lib/curl_non_ascii.h @@ -0,0 +1,63 @@ +#ifndef HEADER_CURL_NON_ASCII_H +#define HEADER_CURL_NON_ASCII_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef CURL_DOES_CONVERSIONS + +#include "curl_urldata.h" + +/* + * Curl_convert_clone() returns a malloced copy of the source string (if + * returning CURLE_OK), with the data converted to network format. + * + * If no conversion was needed *outbuf may be NULL. + */ +CURLcode Curl_convert_clone(struct SessionHandle *data, + const char *indata, + size_t insize, + char **outbuf); + +void Curl_convert_init(struct SessionHandle *data); +void Curl_convert_setup(struct SessionHandle *data); +void Curl_convert_close(struct SessionHandle *data); + +CURLcode Curl_convert_to_network(struct SessionHandle *data, + char *buffer, size_t length); +CURLcode Curl_convert_from_network(struct SessionHandle *data, + char *buffer, size_t length); +CURLcode Curl_convert_from_utf8(struct SessionHandle *data, + char *buffer, size_t length); +CURLcode Curl_convert_form(struct SessionHandle *data, struct FormData *form); +#else +#define Curl_convert_clone(a,b,c,d) ((void)a, CURLE_OK) +#define Curl_convert_init(x) Curl_nop_stmt +#define Curl_convert_setup(x) Curl_nop_stmt +#define Curl_convert_close(x) Curl_nop_stmt +#define Curl_convert_to_network(a,b,c) ((void)a, CURLE_OK) +#define Curl_convert_from_network(a,b,c) ((void)a, CURLE_OK) +#define Curl_convert_from_utf8(a,b,c) ((void)a, CURLE_OK) +#define Curl_convert_form(a,b) CURLE_OK +#endif + +#endif /* HEADER_CURL_NON_ASCII_H */ diff --git a/lib/curl_nonblock.c b/lib/curl_nonblock.c new file mode 100644 index 000000000..6e6d1287f --- /dev/null +++ b/lib/curl_nonblock.c @@ -0,0 +1,91 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif + +#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE)) +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#include "curl_nonblock.h" + +/* + * curlx_nonblock() set the given socket to either blocking or non-blocking + * mode based on the 'nonblock' boolean argument. This function is highly + * portable. + */ +int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ + int nonblock /* TRUE or FALSE */) +{ +#if defined(USE_BLOCKING_SOCKETS) + + return 0; /* returns success */ + +#elif defined(HAVE_FCNTL_O_NONBLOCK) + + /* most recent unix versions */ + int flags; + flags = sfcntl(sockfd, F_GETFL, 0); + if(nonblock) + return sfcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + else + return sfcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); + +#elif defined(HAVE_IOCTL_FIONBIO) + + /* older unix versions */ + int flags = nonblock ? 1 : 0; + return ioctl(sockfd, FIONBIO, &flags); + +#elif defined(HAVE_IOCTLSOCKET_FIONBIO) + + /* Windows */ + unsigned long flags = nonblock ? 1UL : 0UL; + return ioctlsocket(sockfd, FIONBIO, &flags); + +#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) + + /* Amiga */ + long flags = nonblock ? 1L : 0L; + return IoctlSocket(sockfd, FIONBIO, flags); + +#elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK) + + /* BeOS */ + long b = nonblock ? 1L : 0L; + return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b)); + +#else +# error "no non-blocking method was found/used/set" +#endif +} diff --git a/lib/curl_nss.c b/lib/curl_nss.c new file mode 100644 index 000000000..15e92a722 --- /dev/null +++ b/lib/curl_nss.c @@ -0,0 +1,1572 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +/* + * Source file for all NSS-specific code for the TLS/SSL layer. No code + * but curl_sslgen.c should ever call or use these functions. + */ + +#include "curl_setup.h" + +#ifdef USE_NSS + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_formdata.h" /* for the boundary function */ +#include "curl_url.h" /* for the ssl config check function */ +#include "curl_connect.h" +#include "curl_strequal.h" +#include "curl_select.h" +#include "curl_sslgen.h" +#include "curl_llist.h" + +#define _MPRINTF_REPLACE /* use the internal *printf() functions */ +#include + +#include "curl_nssg.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "curl_memory.h" +#include "curl_rawstr.h" +#include "curl_warnless.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#define SSL_DIR "/etc/pki/nssdb" + +/* enough to fit the string "PEM Token #[0|1]" */ +#define SLOTSIZE 13 + +PRFileDesc *PR_ImportTCPSocket(PRInt32 osfd); + +PRLock * nss_initlock = NULL; +PRLock * nss_crllock = NULL; +#ifdef HAVE_NSS_INITCONTEXT +NSSInitContext * nss_context = NULL; +#endif + +volatile int initialized = 0; + +typedef struct { + const char *name; + int num; +} cipher_s; + +#define PK11_SETATTRS(_attr, _idx, _type, _val, _len) do { \ + CK_ATTRIBUTE *ptr = (_attr) + ((_idx)++); \ + ptr->type = (_type); \ + ptr->pValue = (_val); \ + ptr->ulValueLen = (_len); \ +} WHILE_FALSE + +#define CERT_NewTempCertificate __CERT_NewTempCertificate + +#define NUM_OF_CIPHERS sizeof(cipherlist)/sizeof(cipherlist[0]) +static const cipher_s cipherlist[] = { + /* SSL2 cipher suites */ + {"rc4", SSL_EN_RC4_128_WITH_MD5}, + {"rc4-md5", SSL_EN_RC4_128_WITH_MD5}, + {"rc4export", SSL_EN_RC4_128_EXPORT40_WITH_MD5}, + {"rc2", SSL_EN_RC2_128_CBC_WITH_MD5}, + {"rc2export", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5}, + {"des", SSL_EN_DES_64_CBC_WITH_MD5}, + {"desede3", SSL_EN_DES_192_EDE3_CBC_WITH_MD5}, + /* SSL3/TLS cipher suites */ + {"rsa_rc4_128_md5", SSL_RSA_WITH_RC4_128_MD5}, + {"rsa_rc4_128_sha", SSL_RSA_WITH_RC4_128_SHA}, + {"rsa_3des_sha", SSL_RSA_WITH_3DES_EDE_CBC_SHA}, + {"rsa_des_sha", SSL_RSA_WITH_DES_CBC_SHA}, + {"rsa_rc4_40_md5", SSL_RSA_EXPORT_WITH_RC4_40_MD5}, + {"rsa_rc2_40_md5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5}, + {"rsa_null_md5", SSL_RSA_WITH_NULL_MD5}, + {"rsa_null_sha", SSL_RSA_WITH_NULL_SHA}, + {"fips_3des_sha", SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA}, + {"fips_des_sha", SSL_RSA_FIPS_WITH_DES_CBC_SHA}, + {"fortezza", SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA}, + {"fortezza_rc4_128_sha", SSL_FORTEZZA_DMS_WITH_RC4_128_SHA}, + {"fortezza_null", SSL_FORTEZZA_DMS_WITH_NULL_SHA}, + /* TLS 1.0: Exportable 56-bit Cipher Suites. */ + {"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA}, + {"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA}, + /* AES ciphers. */ + {"rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA}, + {"rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA}, +#ifdef NSS_ENABLE_ECC + /* ECC ciphers. */ + {"ecdh_ecdsa_null_sha", TLS_ECDH_ECDSA_WITH_NULL_SHA}, + {"ecdh_ecdsa_rc4_128_sha", TLS_ECDH_ECDSA_WITH_RC4_128_SHA}, + {"ecdh_ecdsa_3des_sha", TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA}, + {"ecdh_ecdsa_aes_128_sha", TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA}, + {"ecdh_ecdsa_aes_256_sha", TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA}, + {"ecdhe_ecdsa_null_sha", TLS_ECDHE_ECDSA_WITH_NULL_SHA}, + {"ecdhe_ecdsa_rc4_128_sha", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}, + {"ecdhe_ecdsa_3des_sha", TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA}, + {"ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, + {"ecdhe_ecdsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + {"ecdh_rsa_null_sha", TLS_ECDH_RSA_WITH_NULL_SHA}, + {"ecdh_rsa_128_sha", TLS_ECDH_RSA_WITH_RC4_128_SHA}, + {"ecdh_rsa_3des_sha", TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA}, + {"ecdh_rsa_aes_128_sha", TLS_ECDH_RSA_WITH_AES_128_CBC_SHA}, + {"ecdh_rsa_aes_256_sha", TLS_ECDH_RSA_WITH_AES_256_CBC_SHA}, + {"echde_rsa_null", TLS_ECDHE_RSA_WITH_NULL_SHA}, + {"ecdhe_rsa_rc4_128_sha", TLS_ECDHE_RSA_WITH_RC4_128_SHA}, + {"ecdhe_rsa_3des_sha", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA}, + {"ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, + {"ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, + {"ecdh_anon_null_sha", TLS_ECDH_anon_WITH_NULL_SHA}, + {"ecdh_anon_rc4_128sha", TLS_ECDH_anon_WITH_RC4_128_SHA}, + {"ecdh_anon_3des_sha", TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA}, + {"ecdh_anon_aes_128_sha", TLS_ECDH_anon_WITH_AES_128_CBC_SHA}, + {"ecdh_anon_aes_256_sha", TLS_ECDH_anon_WITH_AES_256_CBC_SHA}, +#endif +}; + +/* following ciphers are new in NSS 3.4 and not enabled by default, therefore + they are enabled explicitly */ +static const int enable_ciphers_by_default[] = { + TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA, + SSL_NULL_WITH_NULL_NULL +}; + +static const char* pem_library = "libnsspem.so"; +SECMODModule* mod = NULL; + +static const char* nss_error_to_name(PRErrorCode code) +{ + const char *name = PR_ErrorToName(code); + if(name) + return name; + + return "unknown error"; +} + +static void nss_print_error_message(struct SessionHandle *data, PRUint32 err) +{ + failf(data, "%s", PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT)); +} + +static SECStatus set_ciphers(struct SessionHandle *data, PRFileDesc * model, + char *cipher_list) +{ + unsigned int i; + PRBool cipher_state[NUM_OF_CIPHERS]; + PRBool found; + char *cipher; + SECStatus rv; + + /* First disable all ciphers. This uses a different max value in case + * NSS adds more ciphers later we don't want them available by + * accident + */ + for(i=0; iset.str[cert_kind]; + const char *n; + + if(!is_file(str)) + /* no such file exists, use the string as nickname */ + return strdup(str); + + /* search the last slash; we require at least one slash in a file name */ + n = strrchr(str, '/'); + if(!n) { + infof(data, "warning: certificate file name \"%s\" handled as nickname; " + "please use \"./%s\" to force file name\n", str, str); + return strdup(str); + } + + /* we'll use the PEM reader to read the certificate from file */ + return NULL; +} + +/* Call PK11_CreateGenericObject() with the given obj_class and filename. If + * the call succeeds, append the object handle to the list of objects so that + * the object can be destroyed in Curl_nss_close(). */ +static CURLcode nss_create_object(struct ssl_connect_data *ssl, + CK_OBJECT_CLASS obj_class, + const char *filename, bool cacert) +{ + PK11SlotInfo *slot; + PK11GenericObject *obj; + CK_BBOOL cktrue = CK_TRUE; + CK_BBOOL ckfalse = CK_FALSE; + CK_ATTRIBUTE attrs[/* max count of attributes */ 4]; + int attr_cnt = 0; + CURLcode err = (cacert) + ? CURLE_SSL_CACERT_BADFILE + : CURLE_SSL_CERTPROBLEM; + + const int slot_id = (cacert) ? 0 : 1; + char *slot_name = aprintf("PEM Token #%d", slot_id); + if(!slot_name) + return CURLE_OUT_OF_MEMORY; + + slot = PK11_FindSlotByName(slot_name); + free(slot_name); + if(!slot) + return err; + + PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class)); + PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL)); + PK11_SETATTRS(attrs, attr_cnt, CKA_LABEL, (unsigned char *)filename, + strlen(filename) + 1); + + if(CKO_CERTIFICATE == obj_class) { + CK_BBOOL *pval = (cacert) ? (&cktrue) : (&ckfalse); + PK11_SETATTRS(attrs, attr_cnt, CKA_TRUST, pval, sizeof(*pval)); + } + + obj = PK11_CreateGenericObject(slot, attrs, attr_cnt, PR_FALSE); + PK11_FreeSlot(slot); + if(!obj) + return err; + + if(!Curl_llist_insert_next(ssl->obj_list, ssl->obj_list->tail, obj)) { + PK11_DestroyGenericObject(obj); + return CURLE_OUT_OF_MEMORY; + } + + if(!cacert && CKO_CERTIFICATE == obj_class) + /* store reference to a client certificate */ + ssl->obj_clicert = obj; + + return CURLE_OK; +} + +/* Destroy the NSS object whose handle is given by ptr. This function is + * a callback of Curl_llist_alloc() used by Curl_llist_destroy() to destroy + * NSS objects in Curl_nss_close() */ +static void nss_destroy_object(void *user, void *ptr) +{ + PK11GenericObject *obj = (PK11GenericObject *)ptr; + (void) user; + PK11_DestroyGenericObject(obj); +} + +static CURLcode nss_load_cert(struct ssl_connect_data *ssl, + const char *filename, PRBool cacert) +{ + CURLcode err = (cacert) + ? CURLE_SSL_CACERT_BADFILE + : CURLE_SSL_CERTPROBLEM; + + /* libnsspem.so leaks memory if the requested file does not exist. For more + * details, go to . */ + if(is_file(filename)) + err = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert); + + if(CURLE_OK == err && !cacert) { + /* we have successfully loaded a client certificate */ + CERTCertificate *cert; + char *nickname = NULL; + char *n = strrchr(filename, '/'); + if(n) + n++; + + /* The following undocumented magic helps to avoid a SIGSEGV on call + * of PK11_ReadRawAttribute() from SelectClientCert() when using an + * immature version of libnsspem.so. For more details, go to + * . */ + nickname = aprintf("PEM Token #1:%s", n); + if(nickname) { + cert = PK11_FindCertFromNickname(nickname, NULL); + if(cert) + CERT_DestroyCertificate(cert); + + free(nickname); + } + } + + return err; +} + +/* add given CRL to cache if it is not already there */ +static SECStatus nss_cache_crl(SECItem *crlDER) +{ + CERTCertDBHandle *db = CERT_GetDefaultCertDB(); + CERTSignedCrl *crl = SEC_FindCrlByDERCert(db, crlDER, 0); + if(crl) { + /* CRL already cached */ + SEC_DestroyCrl(crl); + SECITEM_FreeItem(crlDER, PR_FALSE); + return SECSuccess; + } + + /* acquire lock before call of CERT_CacheCRL() */ + PR_Lock(nss_crllock); + if(SECSuccess != CERT_CacheCRL(db, crlDER)) { + /* unable to cache CRL */ + PR_Unlock(nss_crllock); + SECITEM_FreeItem(crlDER, PR_FALSE); + return SECFailure; + } + + /* we need to clear session cache, so that the CRL could take effect */ + SSL_ClearSessionCache(); + PR_Unlock(nss_crllock); + return SECSuccess; +} + +static SECStatus nss_load_crl(const char* crlfilename) +{ + PRFileDesc *infile; + PRFileInfo info; + SECItem filedata = { 0, NULL, 0 }; + SECItem crlDER = { 0, NULL, 0 }; + char *body; + + infile = PR_Open(crlfilename, PR_RDONLY, 0); + if(!infile) + return SECFailure; + + if(PR_SUCCESS != PR_GetOpenFileInfo(infile, &info)) + goto fail; + + if(!SECITEM_AllocItem(NULL, &filedata, info.size + /* zero ended */ 1)) + goto fail; + + if(info.size != PR_Read(infile, filedata.data, info.size)) + goto fail; + + /* place a trailing zero right after the visible data */ + body = (char*)filedata.data; + body[--filedata.len] = '\0'; + + body = strstr(body, "-----BEGIN"); + if(body) { + /* assume ASCII */ + char *trailer; + char *begin = PORT_Strchr(body, '\n'); + if(!begin) + begin = PORT_Strchr(body, '\r'); + if(!begin) + goto fail; + + trailer = strstr(++begin, "-----END"); + if(!trailer) + goto fail; + + /* retrieve DER from ASCII */ + *trailer = '\0'; + if(ATOB_ConvertAsciiToItem(&crlDER, begin)) + goto fail; + + SECITEM_FreeItem(&filedata, PR_FALSE); + } + else + /* assume DER */ + crlDER = filedata; + + PR_Close(infile); + return nss_cache_crl(&crlDER); + +fail: + PR_Close(infile); + SECITEM_FreeItem(&filedata, PR_FALSE); + return SECFailure; +} + +static CURLcode nss_load_key(struct connectdata *conn, int sockindex, + char *key_file) +{ + PK11SlotInfo *slot; + SECStatus status; + CURLcode rv; + struct ssl_connect_data *ssl = conn->ssl; + (void)sockindex; /* unused */ + + rv = nss_create_object(ssl, CKO_PRIVATE_KEY, key_file, FALSE); + if(CURLE_OK != rv) { + PR_SetError(SEC_ERROR_BAD_KEY, 0); + return rv; + } + + slot = PK11_FindSlotByName("PEM Token #1"); + if(!slot) + return CURLE_SSL_CERTPROBLEM; + + /* This will force the token to be seen as re-inserted */ + SECMOD_WaitForAnyTokenEvent(mod, 0, 0); + PK11_IsPresent(slot); + + status = PK11_Authenticate(slot, PR_TRUE, + conn->data->set.str[STRING_KEY_PASSWD]); + PK11_FreeSlot(slot); + return (SECSuccess == status) + ? CURLE_OK + : CURLE_SSL_CERTPROBLEM; +} + +static int display_error(struct connectdata *conn, PRInt32 err, + const char *filename) +{ + switch(err) { + case SEC_ERROR_BAD_PASSWORD: + failf(conn->data, "Unable to load client key: Incorrect password"); + return 1; + case SEC_ERROR_UNKNOWN_CERT: + failf(conn->data, "Unable to load certificate %s", filename); + return 1; + default: + break; + } + return 0; /* The caller will print a generic error */ +} + +static CURLcode cert_stuff(struct connectdata *conn, int sockindex, + char *cert_file, char *key_file) +{ + struct SessionHandle *data = conn->data; + CURLcode rv; + + if(cert_file) { + rv = nss_load_cert(&conn->ssl[sockindex], cert_file, PR_FALSE); + if(CURLE_OK != rv) { + const PRErrorCode err = PR_GetError(); + if(!display_error(conn, err, cert_file)) { + const char *err_name = nss_error_to_name(err); + failf(data, "unable to load client cert: %d (%s)", err, err_name); + } + + return rv; + } + } + + if(key_file || (is_file(cert_file))) { + if(key_file) + rv = nss_load_key(conn, sockindex, key_file); + else + /* In case the cert file also has the key */ + rv = nss_load_key(conn, sockindex, cert_file); + if(CURLE_OK != rv) { + const PRErrorCode err = PR_GetError(); + if(!display_error(conn, err, key_file)) { + const char *err_name = nss_error_to_name(err); + failf(data, "unable to load client key: %d (%s)", err, err_name); + } + + return rv; + } + } + + return CURLE_OK; +} + +static char * nss_get_password(PK11SlotInfo * slot, PRBool retry, void *arg) +{ + (void)slot; /* unused */ + if(retry || NULL == arg) + return NULL; + else + return (char *)PORT_Strdup((char *)arg); +} + +/* bypass the default SSL_AuthCertificate() hook in case we do not want to + * verify peer */ +static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig, + PRBool isServer) +{ + struct connectdata *conn = (struct connectdata *)arg; + if(!conn->data->set.ssl.verifypeer) { + infof(conn->data, "skipping SSL peer certificate verification\n"); + return SECSuccess; + } + + return SSL_AuthCertificate(CERT_GetDefaultCertDB(), fd, checksig, isServer); +} + +/** + * Inform the application that the handshake is complete. + */ +static void HandshakeCallback(PRFileDesc *sock, void *arg) +{ + (void)sock; + (void)arg; +} + +static void display_cert_info(struct SessionHandle *data, + CERTCertificate *cert) +{ + char *subject, *issuer, *common_name; + PRExplodedTime printableTime; + char timeString[256]; + PRTime notBefore, notAfter; + + subject = CERT_NameToAscii(&cert->subject); + issuer = CERT_NameToAscii(&cert->issuer); + common_name = CERT_GetCommonName(&cert->subject); + infof(data, "\tsubject: %s\n", subject); + + CERT_GetCertTimes(cert, ¬Before, ¬After); + PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime); + PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); + infof(data, "\tstart date: %s\n", timeString); + PR_ExplodeTime(notAfter, PR_GMTParameters, &printableTime); + PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); + infof(data, "\texpire date: %s\n", timeString); + infof(data, "\tcommon name: %s\n", common_name); + infof(data, "\tissuer: %s\n", issuer); + + PR_Free(subject); + PR_Free(issuer); + PR_Free(common_name); +} + +static void display_conn_info(struct connectdata *conn, PRFileDesc *sock) +{ + SSLChannelInfo channel; + SSLCipherSuiteInfo suite; + CERTCertificate *cert; + + if(SSL_GetChannelInfo(sock, &channel, sizeof channel) == + SECSuccess && channel.length == sizeof channel && + channel.cipherSuite) { + if(SSL_GetCipherSuiteInfo(channel.cipherSuite, + &suite, sizeof suite) == SECSuccess) { + infof(conn->data, "SSL connection using %s\n", suite.cipherSuiteName); + } + } + + infof(conn->data, "Server certificate:\n"); + + cert = SSL_PeerCertificate(sock); + display_cert_info(conn->data, cert); + CERT_DestroyCertificate(cert); + + return; +} + +static SECStatus BadCertHandler(void *arg, PRFileDesc *sock) +{ + struct connectdata *conn = (struct connectdata *)arg; + struct SessionHandle *data = conn->data; + PRErrorCode err = PR_GetError(); + CERTCertificate *cert; + + /* remember the cert verification result */ + data->set.ssl.certverifyresult = err; + + if(err == SSL_ERROR_BAD_CERT_DOMAIN && !data->set.ssl.verifyhost) + /* we are asked not to verify the host name */ + return SECSuccess; + + /* print only info about the cert, the error is printed off the callback */ + cert = SSL_PeerCertificate(sock); + if(cert) { + infof(data, "Server certificate:\n"); + display_cert_info(data, cert); + CERT_DestroyCertificate(cert); + } + + return SECFailure; +} + +/** + * + * Check that the Peer certificate's issuer certificate matches the one found + * by issuer_nickname. This is not exactly the way OpenSSL and GNU TLS do the + * issuer check, so we provide comments that mimic the OpenSSL + * X509_check_issued function (in x509v3/v3_purp.c) + */ +static SECStatus check_issuer_cert(PRFileDesc *sock, + char *issuer_nickname) +{ + CERTCertificate *cert,*cert_issuer,*issuer; + SECStatus res=SECSuccess; + void *proto_win = NULL; + + /* + PRArenaPool *tmpArena = NULL; + CERTAuthKeyID *authorityKeyID = NULL; + SECITEM *caname = NULL; + */ + + cert = SSL_PeerCertificate(sock); + cert_issuer = CERT_FindCertIssuer(cert,PR_Now(),certUsageObjectSigner); + + proto_win = SSL_RevealPinArg(sock); + issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win); + + if((!cert_issuer) || (!issuer)) + res = SECFailure; + else if(SECITEM_CompareItem(&cert_issuer->derCert, + &issuer->derCert)!=SECEqual) + res = SECFailure; + + CERT_DestroyCertificate(cert); + CERT_DestroyCertificate(issuer); + CERT_DestroyCertificate(cert_issuer); + return res; +} + +/** + * + * Callback to pick the SSL client certificate. + */ +static SECStatus SelectClientCert(void *arg, PRFileDesc *sock, + struct CERTDistNamesStr *caNames, + struct CERTCertificateStr **pRetCert, + struct SECKEYPrivateKeyStr **pRetKey) +{ + struct ssl_connect_data *connssl = (struct ssl_connect_data *)arg; + struct SessionHandle *data = connssl->data; + const char *nickname = connssl->client_nickname; + + if(connssl->obj_clicert) { + /* use the cert/key provided by PEM reader */ + static const char pem_slotname[] = "PEM Token #1"; + SECItem cert_der = { 0, NULL, 0 }; + void *proto_win = SSL_RevealPinArg(sock); + struct CERTCertificateStr *cert; + struct SECKEYPrivateKeyStr *key; + + PK11SlotInfo *slot = PK11_FindSlotByName(pem_slotname); + if(NULL == slot) { + failf(data, "NSS: PK11 slot not found: %s", pem_slotname); + return SECFailure; + } + + if(PK11_ReadRawAttribute(PK11_TypeGeneric, connssl->obj_clicert, CKA_VALUE, + &cert_der) != SECSuccess) { + failf(data, "NSS: CKA_VALUE not found in PK11 generic object"); + PK11_FreeSlot(slot); + return SECFailure; + } + + cert = PK11_FindCertFromDERCertItem(slot, &cert_der, proto_win); + SECITEM_FreeItem(&cert_der, PR_FALSE); + if(NULL == cert) { + failf(data, "NSS: client certificate from file not found"); + PK11_FreeSlot(slot); + return SECFailure; + } + + key = PK11_FindPrivateKeyFromCert(slot, cert, NULL); + PK11_FreeSlot(slot); + if(NULL == key) { + failf(data, "NSS: private key from file not found"); + CERT_DestroyCertificate(cert); + return SECFailure; + } + + infof(data, "NSS: client certificate from file\n"); + display_cert_info(data, cert); + + *pRetCert = cert; + *pRetKey = key; + return SECSuccess; + } + + /* use the default NSS hook */ + if(SECSuccess != NSS_GetClientAuthData((void *)nickname, sock, caNames, + pRetCert, pRetKey) + || NULL == *pRetCert) { + + if(NULL == nickname) + failf(data, "NSS: client certificate not found (nickname not " + "specified)"); + else + failf(data, "NSS: client certificate not found: %s", nickname); + + return SECFailure; + } + + /* get certificate nickname if any */ + nickname = (*pRetCert)->nickname; + if(NULL == nickname) + nickname = "[unknown]"; + + if(NULL == *pRetKey) { + failf(data, "NSS: private key not found for certificate: %s", nickname); + return SECFailure; + } + + infof(data, "NSS: using client certificate: %s\n", nickname); + display_cert_info(data, *pRetCert); + return SECSuccess; +} + +/* This function is supposed to decide, which error codes should be used + * to conclude server is TLS intolerant. + * + * taken from xulrunner - nsNSSIOLayer.cpp + */ +static PRBool +isTLSIntoleranceError(PRInt32 err) +{ + switch (err) { + case SSL_ERROR_BAD_MAC_ALERT: + case SSL_ERROR_BAD_MAC_READ: + case SSL_ERROR_HANDSHAKE_FAILURE_ALERT: + case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT: + case SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE: + case SSL_ERROR_ILLEGAL_PARAMETER_ALERT: + case SSL_ERROR_NO_CYPHER_OVERLAP: + case SSL_ERROR_BAD_SERVER: + case SSL_ERROR_BAD_BLOCK_PADDING: + case SSL_ERROR_UNSUPPORTED_VERSION: + case SSL_ERROR_PROTOCOL_VERSION_ALERT: + case SSL_ERROR_RX_MALFORMED_FINISHED: + case SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE: + case SSL_ERROR_DECODE_ERROR_ALERT: + case SSL_ERROR_RX_UNKNOWN_ALERT: + return PR_TRUE; + default: + return PR_FALSE; + } +} + +static CURLcode nss_init_core(struct SessionHandle *data, const char *cert_dir) +{ +#ifdef HAVE_NSS_INITCONTEXT + NSSInitParameters initparams; + + if(nss_context != NULL) + return CURLE_OK; + + memset((void *) &initparams, '\0', sizeof(initparams)); + initparams.length = sizeof(initparams); +#else /* HAVE_NSS_INITCONTEXT */ + SECStatus rv; + + if(NSS_IsInitialized()) + return CURLE_OK; +#endif + + if(cert_dir) { + const bool use_sql = NSS_VersionCheck("3.12.0"); + char *certpath = aprintf("%s%s", use_sql ? "sql:" : "", cert_dir); + if(!certpath) + return CURLE_OUT_OF_MEMORY; + + infof(data, "Initializing NSS with certpath: %s\n", certpath); +#ifdef HAVE_NSS_INITCONTEXT + nss_context = NSS_InitContext(certpath, "", "", "", &initparams, + NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); + free(certpath); + + if(nss_context != NULL) + return CURLE_OK; +#else /* HAVE_NSS_INITCONTEXT */ + rv = NSS_Initialize(certpath, "", "", "", NSS_INIT_READONLY); + free(certpath); + + if(rv == SECSuccess) + return CURLE_OK; +#endif + + infof(data, "Unable to initialize NSS database\n"); + } + + infof(data, "Initializing NSS with certpath: none\n"); +#ifdef HAVE_NSS_INITCONTEXT + nss_context = NSS_InitContext("", "", "", "", &initparams, NSS_INIT_READONLY + | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN + | NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD); + if(nss_context != NULL) + return CURLE_OK; +#else /* HAVE_NSS_INITCONTEXT */ + if(NSS_NoDB_Init(NULL) == SECSuccess) + return CURLE_OK; +#endif + + infof(data, "Unable to initialize NSS\n"); + return CURLE_SSL_CACERT_BADFILE; +} + +static CURLcode nss_init(struct SessionHandle *data) +{ + char *cert_dir; + struct_stat st; + CURLcode rv; + + if(initialized) + return CURLE_OK; + + /* First we check if $SSL_DIR points to a valid dir */ + cert_dir = getenv("SSL_DIR"); + if(cert_dir) { + if((stat(cert_dir, &st) != 0) || + (!S_ISDIR(st.st_mode))) { + cert_dir = NULL; + } + } + + /* Now we check if the default location is a valid dir */ + if(!cert_dir) { + if((stat(SSL_DIR, &st) == 0) && + (S_ISDIR(st.st_mode))) { + cert_dir = (char *)SSL_DIR; + } + } + + rv = nss_init_core(data, cert_dir); + if(rv) + return rv; + + if(num_enabled_ciphers() == 0) + NSS_SetDomesticPolicy(); + + initialized = 1; + return CURLE_OK; +} + +/** + * Global SSL init + * + * @retval 0 error initializing SSL + * @retval 1 SSL initialized successfully + */ +int Curl_nss_init(void) +{ + /* curl_global_init() is not thread-safe so this test is ok */ + if(nss_initlock == NULL) { + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 256); + nss_initlock = PR_NewLock(); + nss_crllock = PR_NewLock(); + } + + /* We will actually initialize NSS later */ + + return 1; +} + +CURLcode Curl_nss_force_init(struct SessionHandle *data) +{ + CURLcode rv; + if(!nss_initlock) { + failf(data, + "unable to initialize NSS, curl_global_init() should have been " + "called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL"); + return CURLE_FAILED_INIT; + } + + PR_Lock(nss_initlock); + rv = nss_init(data); + PR_Unlock(nss_initlock); + return rv; +} + +/* Global cleanup */ +void Curl_nss_cleanup(void) +{ + /* This function isn't required to be threadsafe and this is only done + * as a safety feature. + */ + PR_Lock(nss_initlock); + if(initialized) { + /* Free references to client certificates held in the SSL session cache. + * Omitting this hampers destruction of the security module owning + * the certificates. */ + SSL_ClearSessionCache(); + + if(mod && SECSuccess == SECMOD_UnloadUserModule(mod)) { + SECMOD_DestroyModule(mod); + mod = NULL; + } +#ifdef HAVE_NSS_INITCONTEXT + NSS_ShutdownContext(nss_context); + nss_context = NULL; +#else /* HAVE_NSS_INITCONTEXT */ + NSS_Shutdown(); +#endif + } + PR_Unlock(nss_initlock); + + PR_DestroyLock(nss_initlock); + PR_DestroyLock(nss_crllock); + nss_initlock = NULL; + + initialized = 0; +} + +/* + * This function uses SSL_peek to determine connection status. + * + * Return codes: + * 1 means the connection is still in place + * 0 means the connection has been closed + * -1 means the connection status is unknown + */ +int +Curl_nss_check_cxn(struct connectdata *conn) +{ + int rc; + char buf; + + rc = + PR_Recv(conn->ssl[FIRSTSOCKET].handle, (void *)&buf, 1, PR_MSG_PEEK, + PR_SecondsToInterval(1)); + if(rc > 0) + return 1; /* connection still in place */ + + if(rc == 0) + return 0; /* connection has been closed */ + + return -1; /* connection status unknown */ +} + +/* + * This function is called when an SSL connection is closed. + */ +void Curl_nss_close(struct connectdata *conn, int sockindex) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + if(connssl->handle) { + /* NSS closes the socket we previously handed to it, so we must mark it + as closed to avoid double close */ + fake_sclose(conn->sock[sockindex]); + conn->sock[sockindex] = CURL_SOCKET_BAD; + if(connssl->client_nickname != NULL) { + free(connssl->client_nickname); + connssl->client_nickname = NULL; + + /* force NSS to ask again for a client cert when connecting + * next time to the same server */ + SSL_InvalidateSession(connssl->handle); + } + /* destroy all NSS objects in order to avoid failure of NSS shutdown */ + Curl_llist_destroy(connssl->obj_list, NULL); + connssl->obj_list = NULL; + connssl->obj_clicert = NULL; + + PR_Close(connssl->handle); + connssl->handle = NULL; + } +} + +/* + * This function is called when the 'data' struct is going away. Close + * down everything and free all resources! + */ +int Curl_nss_close_all(struct SessionHandle *data) +{ + (void)data; + return 0; +} + +/* return true if NSS can provide error code (and possibly msg) for the + error */ +static bool is_nss_error(CURLcode err) +{ + switch(err) { + case CURLE_PEER_FAILED_VERIFICATION: + case CURLE_SSL_CACERT: + case CURLE_SSL_CACERT_BADFILE: + case CURLE_SSL_CERTPROBLEM: + case CURLE_SSL_CONNECT_ERROR: + case CURLE_SSL_CRL_BADFILE: + case CURLE_SSL_ISSUER_ERROR: + return true; + + default: + return false; + } +} + +/* return true if the given error code is related to a client certificate */ +static bool is_cc_error(PRInt32 err) +{ + switch(err) { + case SSL_ERROR_BAD_CERT_ALERT: + case SSL_ERROR_EXPIRED_CERT_ALERT: + case SSL_ERROR_REVOKED_CERT_ALERT: + return true; + + default: + return false; + } +} + +static Curl_recv nss_recv; +static Curl_send nss_send; + +static CURLcode nss_load_ca_certificates(struct connectdata *conn, + int sockindex) +{ + struct SessionHandle *data = conn->data; + const char *cafile = data->set.ssl.CAfile; + const char *capath = data->set.ssl.CApath; + + if(cafile) { + CURLcode rv = nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE); + if(CURLE_OK != rv) + return rv; + } + + if(capath) { + struct_stat st; + if(stat(capath, &st) == -1) + return CURLE_SSL_CACERT_BADFILE; + + if(S_ISDIR(st.st_mode)) { + PRDirEntry *entry; + PRDir *dir = PR_OpenDir(capath); + if(!dir) + return CURLE_SSL_CACERT_BADFILE; + + while((entry = PR_ReadDir(dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN))) { + char *fullpath = aprintf("%s/%s", capath, entry->name); + if(!fullpath) { + PR_CloseDir(dir); + return CURLE_OUT_OF_MEMORY; + } + + if(CURLE_OK != nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE)) + /* This is purposefully tolerant of errors so non-PEM files can + * be in the same directory */ + infof(data, "failed to load '%s' from CURLOPT_CAPATH\n", fullpath); + + free(fullpath); + } + + PR_CloseDir(dir); + } + else + infof(data, "warning: CURLOPT_CAPATH not a directory (%s)\n", capath); + } + + infof(data, " CAfile: %s\n CApath: %s\n", + cafile ? cafile : "none", + capath ? capath : "none"); + + return CURLE_OK; +} + +CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) +{ + PRErrorCode err = 0; + PRFileDesc *model = NULL; + PRBool ssl2 = PR_FALSE; + PRBool ssl3 = PR_FALSE; + PRBool tlsv1 = PR_FALSE; + PRBool ssl_no_cache; + PRBool ssl_cbc_random_iv; + struct SessionHandle *data = conn->data; + curl_socket_t sockfd = conn->sock[sockindex]; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + CURLcode curlerr; + const int *cipher_to_enable; + PRSocketOptionData sock_opt; + long time_left; + PRUint32 timeout; + + if(connssl->state == ssl_connection_complete) + return CURLE_OK; + + connssl->data = data; + + /* list of all NSS objects we need to destroy in Curl_nss_close() */ + connssl->obj_list = Curl_llist_alloc(nss_destroy_object); + if(!connssl->obj_list) + return CURLE_OUT_OF_MEMORY; + + /* FIXME. NSS doesn't support multiple databases open at the same time. */ + PR_Lock(nss_initlock); + curlerr = nss_init(conn->data); + if(CURLE_OK != curlerr) { + PR_Unlock(nss_initlock); + goto error; + } + + curlerr = CURLE_SSL_CONNECT_ERROR; + + if(!mod) { + char *configstring = aprintf("library=%s name=PEM", pem_library); + if(!configstring) { + PR_Unlock(nss_initlock); + goto error; + } + mod = SECMOD_LoadUserModule(configstring, NULL, PR_FALSE); + free(configstring); + + if(!mod || !mod->loaded) { + if(mod) { + SECMOD_DestroyModule(mod); + mod = NULL; + } + infof(data, "WARNING: failed to load NSS PEM library %s. Using " + "OpenSSL PEM certificates will not work.\n", pem_library); + } + } + + PK11_SetPasswordFunc(nss_get_password); + PR_Unlock(nss_initlock); + + model = PR_NewTCPSocket(); + if(!model) + goto error; + model = SSL_ImportFD(NULL, model); + + /* make the socket nonblocking */ + sock_opt.option = PR_SockOpt_Nonblocking; + sock_opt.value.non_blocking = PR_TRUE; + if(PR_SetSocketOption(model, &sock_opt) != PR_SUCCESS) + goto error; + + if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) + goto error; + if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) + goto error; + if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess) + goto error; + + /* do not use SSL cache if we are not going to verify peer */ + ssl_no_cache = (data->set.ssl.verifypeer) ? PR_FALSE : PR_TRUE; + if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess) + goto error; + + switch (data->set.ssl.version) { + default: + case CURL_SSLVERSION_DEFAULT: + ssl3 = PR_TRUE; + if(data->state.ssl_connect_retry) + infof(data, "TLS disabled due to previous handshake failure\n"); + else + tlsv1 = PR_TRUE; + break; + case CURL_SSLVERSION_TLSv1: + tlsv1 = PR_TRUE; + break; + case CURL_SSLVERSION_SSLv2: + ssl2 = PR_TRUE; + break; + case CURL_SSLVERSION_SSLv3: + ssl3 = PR_TRUE; + break; + } + + if(SSL_OptionSet(model, SSL_ENABLE_SSL2, ssl2) != SECSuccess) + goto error; + if(SSL_OptionSet(model, SSL_ENABLE_SSL3, ssl3) != SECSuccess) + goto error; + if(SSL_OptionSet(model, SSL_ENABLE_TLS, tlsv1) != SECSuccess) + goto error; + + if(SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, ssl2) != SECSuccess) + goto error; + + ssl_cbc_random_iv = !data->set.ssl_enable_beast; +#ifdef SSL_CBC_RANDOM_IV + /* unless the user explicitly asks to allow the protocol vulnerability, we + use the work-around */ + if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess) + infof(data, "warning: failed to set SSL_CBC_RANDOM_IV = %d\n", + ssl_cbc_random_iv); +#else + if(ssl_cbc_random_iv) + infof(data, "warning: support for SSL_CBC_RANDOM_IV not compiled in\n"); +#endif + + /* reset the flag to avoid an infinite loop */ + data->state.ssl_connect_retry = FALSE; + + /* enable all ciphers from enable_ciphers_by_default */ + cipher_to_enable = enable_ciphers_by_default; + while(SSL_NULL_WITH_NULL_NULL != *cipher_to_enable) { + if(SSL_CipherPrefSet(model, *cipher_to_enable, PR_TRUE) != SECSuccess) { + curlerr = CURLE_SSL_CIPHER; + goto error; + } + cipher_to_enable++; + } + + if(data->set.ssl.cipher_list) { + if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) { + curlerr = CURLE_SSL_CIPHER; + goto error; + } + } + + if(!data->set.ssl.verifypeer && data->set.ssl.verifyhost) + infof(data, "warning: ignoring value of ssl.verifyhost\n"); + + /* bypass the default SSL_AuthCertificate() hook in case we do not want to + * verify peer */ + if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, conn) != SECSuccess) + goto error; + + data->set.ssl.certverifyresult=0; /* not checked yet */ + if(SSL_BadCertHook(model, BadCertHandler, conn) != SECSuccess) + goto error; + + if(SSL_HandshakeCallback(model, HandshakeCallback, NULL) != SECSuccess) + goto error; + + if(data->set.ssl.verifypeer) { + const CURLcode rv = nss_load_ca_certificates(conn, sockindex); + if(CURLE_OK != rv) { + curlerr = rv; + goto error; + } + } + + if(data->set.ssl.CRLfile) { + if(SECSuccess != nss_load_crl(data->set.ssl.CRLfile)) { + curlerr = CURLE_SSL_CRL_BADFILE; + goto error; + } + infof(data, + " CRLfile: %s\n", + data->set.ssl.CRLfile ? data->set.ssl.CRLfile : "none"); + } + + if(data->set.str[STRING_CERT]) { + char *nickname = dup_nickname(data, STRING_CERT); + if(nickname) { + /* we are not going to use libnsspem.so to read the client cert */ + connssl->obj_clicert = NULL; + } + else { + CURLcode rv = cert_stuff(conn, sockindex, data->set.str[STRING_CERT], + data->set.str[STRING_KEY]); + if(CURLE_OK != rv) { + /* failf() is already done in cert_stuff() */ + curlerr = rv; + goto error; + } + } + + /* store the nickname for SelectClientCert() called during handshake */ + connssl->client_nickname = nickname; + } + else + connssl->client_nickname = NULL; + + if(SSL_GetClientAuthDataHook(model, SelectClientCert, + (void *)connssl) != SECSuccess) { + curlerr = CURLE_SSL_CERTPROBLEM; + goto error; + } + + /* Import our model socket onto the existing file descriptor */ + connssl->handle = PR_ImportTCPSocket(sockfd); + connssl->handle = SSL_ImportFD(model, connssl->handle); + if(!connssl->handle) + goto error; + + PR_Close(model); /* We don't need this any more */ + model = NULL; + + /* This is the password associated with the cert that we're using */ + if(data->set.str[STRING_KEY_PASSWD]) { + SSL_SetPKCS11PinArg(connssl->handle, data->set.str[STRING_KEY_PASSWD]); + } + + /* Force handshake on next I/O */ + SSL_ResetHandshake(connssl->handle, /* asServer */ PR_FALSE); + + SSL_SetURL(connssl->handle, conn->host.name); + + /* check timeout situation */ + time_left = Curl_timeleft(data, NULL, TRUE); + if(time_left < 0L) { + failf(data, "timed out before SSL handshake"); + curlerr = CURLE_OPERATION_TIMEDOUT; + goto error; + } + timeout = PR_MillisecondsToInterval((PRUint32) time_left); + + /* Force the handshake now */ + if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) { + if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) + curlerr = CURLE_PEER_FAILED_VERIFICATION; + else if(conn->data->set.ssl.certverifyresult!=0) + curlerr = CURLE_SSL_CACERT; + goto error; + } + + connssl->state = ssl_connection_complete; + conn->recv[sockindex] = nss_recv; + conn->send[sockindex] = nss_send; + + display_conn_info(conn, connssl->handle); + + if(data->set.str[STRING_SSL_ISSUERCERT]) { + SECStatus ret = SECFailure; + char *nickname = dup_nickname(data, STRING_SSL_ISSUERCERT); + if(nickname) { + /* we support only nicknames in case of STRING_SSL_ISSUERCERT for now */ + ret = check_issuer_cert(connssl->handle, nickname); + free(nickname); + } + + if(SECFailure == ret) { + infof(data,"SSL certificate issuer check failed\n"); + curlerr = CURLE_SSL_ISSUER_ERROR; + goto error; + } + else { + infof(data, "SSL certificate issuer check ok\n"); + } + } + + return CURLE_OK; + + error: + /* reset the flag to avoid an infinite loop */ + data->state.ssl_connect_retry = FALSE; + + if(is_nss_error(curlerr)) { + /* read NSPR error code */ + err = PR_GetError(); + if(is_cc_error(err)) + curlerr = CURLE_SSL_CERTPROBLEM; + + /* print the error number and error string */ + infof(data, "NSS error %d (%s)\n", err, nss_error_to_name(err)); + + /* print a human-readable message describing the error if available */ + nss_print_error_message(data, err); + } + + if(model) + PR_Close(model); + + /* cleanup on connection failure */ + Curl_llist_destroy(connssl->obj_list, NULL); + connssl->obj_list = NULL; + + if(ssl3 && tlsv1 && isTLSIntoleranceError(err)) { + /* schedule reconnect through Curl_retry_request() */ + data->state.ssl_connect_retry = TRUE; + infof(data, "Error in TLS handshake, trying SSLv3...\n"); + return CURLE_OK; + } + + return curlerr; +} + +static ssize_t nss_send(struct connectdata *conn, /* connection data */ + int sockindex, /* socketindex */ + const void *mem, /* send this data */ + size_t len, /* amount to write */ + CURLcode *curlcode) +{ + int rc; + + rc = PR_Send(conn->ssl[sockindex].handle, mem, (int)len, 0, -1); + + if(rc < 0) { + PRInt32 err = PR_GetError(); + if(err == PR_WOULD_BLOCK_ERROR) + *curlcode = CURLE_AGAIN; + else { + /* print the error number and error string */ + const char *err_name = nss_error_to_name(err); + infof(conn->data, "SSL write: error %d (%s)\n", err, err_name); + + /* print a human-readable message describing the error if available */ + nss_print_error_message(conn->data, err); + + *curlcode = (is_cc_error(err)) + ? CURLE_SSL_CERTPROBLEM + : CURLE_SEND_ERROR; + } + return -1; + } + return rc; /* number of bytes */ +} + +static ssize_t nss_recv(struct connectdata * conn, /* connection data */ + int num, /* socketindex */ + char *buf, /* store read data here */ + size_t buffersize, /* max amount to read */ + CURLcode *curlcode) +{ + ssize_t nread; + + nread = PR_Recv(conn->ssl[num].handle, buf, (int)buffersize, 0, -1); + if(nread < 0) { + /* failed SSL read */ + PRInt32 err = PR_GetError(); + + if(err == PR_WOULD_BLOCK_ERROR) + *curlcode = CURLE_AGAIN; + else { + /* print the error number and error string */ + const char *err_name = nss_error_to_name(err); + infof(conn->data, "SSL read: errno %d (%s)\n", err, err_name); + + /* print a human-readable message describing the error if available */ + nss_print_error_message(conn->data, err); + + *curlcode = (is_cc_error(err)) + ? CURLE_SSL_CERTPROBLEM + : CURLE_RECV_ERROR; + } + return -1; + } + return nread; +} + +size_t Curl_nss_version(char *buffer, size_t size) +{ + return snprintf(buffer, size, "NSS/%s", NSS_VERSION); +} + +int Curl_nss_seed(struct SessionHandle *data) +{ + /* TODO: implement? */ + (void) data; + return 0; +} + +void Curl_nss_random(struct SessionHandle *data, + unsigned char *entropy, + size_t length) +{ + Curl_nss_seed(data); /* Initiate the seed if not already done */ + PK11_GenerateRandom(entropy, curlx_uztosi(length)); +} + +void Curl_nss_md5sum(unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *md5sum, /* output */ + size_t md5len) +{ + PK11Context *MD5pw = PK11_CreateDigestContext(SEC_OID_MD5); + unsigned int MD5out; + PK11_DigestOp(MD5pw, tmp, curlx_uztoui(tmplen)); + PK11_DigestFinal(MD5pw, md5sum, &MD5out, curlx_uztoui(md5len)); + PK11_DestroyContext(MD5pw, PR_TRUE); +} + +#endif /* USE_NSS */ diff --git a/lib/curl_nwlib.c b/lib/curl_nwlib.c new file mode 100644 index 000000000..f63f16b83 --- /dev/null +++ b/lib/curl_nwlib.c @@ -0,0 +1,329 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef NETWARE /* Novell NetWare */ + +#ifdef __NOVELL_LIBC__ +/* For native LibC-based NLM we need to register as a real lib. */ +#include +#include +#include +#include +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +typedef struct +{ + int _errno; + void *twentybytes; +} libthreaddata_t; + +typedef struct +{ + int x; + int y; + int z; + void *tenbytes; + NXKey_t perthreadkey; /* if -1, no key obtained... */ + NXMutex_t *lock; +} libdata_t; + +int gLibId = -1; +void *gLibHandle = (void *) NULL; +rtag_t gAllocTag = (rtag_t) NULL; +NXMutex_t *gLibLock = (NXMutex_t *) NULL; + +/* internal library function prototypes... */ +int DisposeLibraryData( void * ); +void DisposeThreadData( void * ); +int GetOrSetUpData( int id, libdata_t **data, libthreaddata_t **threaddata ); + + +int _NonAppStart( void *NLMHandle, + void *errorScreen, + const char *cmdLine, + const char *loadDirPath, + size_t uninitializedDataLength, + void *NLMFileHandle, + int (*readRoutineP)( int conn, + void *fileHandle, size_t offset, + size_t nbytes, + size_t *bytesRead, + void *buffer ), + size_t customDataOffset, + size_t customDataSize, + int messageCount, + const char **messages ) +{ + NX_LOCK_INFO_ALLOC(liblock, "Per-Application Data Lock", 0); + +#ifndef __GNUC__ +#pragma unused(cmdLine) +#pragma unused(loadDirPath) +#pragma unused(uninitializedDataLength) +#pragma unused(NLMFileHandle) +#pragma unused(readRoutineP) +#pragma unused(customDataOffset) +#pragma unused(customDataSize) +#pragma unused(messageCount) +#pragma unused(messages) +#endif + + /* + * Here we process our command line, post errors (to the error screen), + * perform initializations and anything else we need to do before being able + * to accept calls into us. If we succeed, we return non-zero and the NetWare + * Loader will leave us up, otherwise we fail to load and get dumped. + */ + gAllocTag = AllocateResourceTag(NLMHandle, + " memory allocations", + AllocSignature); + + if(!gAllocTag) { + OutputToScreen(errorScreen, "Unable to allocate resource tag for " + "library memory allocations.\n"); + return -1; + } + + gLibId = register_library(DisposeLibraryData); + + if(gLibId < -1) { + OutputToScreen(errorScreen, "Unable to register library with kernel.\n"); + return -1; + } + + gLibHandle = NLMHandle; + + gLibLock = NXMutexAlloc(0, 0, &liblock); + + if(!gLibLock) { + OutputToScreen(errorScreen, "Unable to allocate library data lock.\n"); + return -1; + } + + return 0; +} + +/* + * Here we clean up any resources we allocated. Resource tags is a big part + * of what we created, but NetWare doesn't ask us to free those. + */ +void _NonAppStop( void ) +{ + (void) unregister_library(gLibId); + NXMutexFree(gLibLock); +} + +/* + * This function cannot be the first in the file for if the file is linked + * first, then the check-unload function's offset will be nlmname.nlm+0 + * which is how to tell that there isn't one. When the check function is + * first in the linked objects, it is ambiguous. For this reason, we will + * put it inside this file after the stop function. + * + * Here we check to see if it's alright to ourselves to be unloaded. If not, + * we return a non-zero value. Right now, there isn't any reason not to allow + * it. + */ +int _NonAppCheckUnload( void ) +{ + return 0; +} + +int GetOrSetUpData(int id, libdata_t **appData, + libthreaddata_t **threadData ) +{ + int err; + libdata_t *app_data; + libthreaddata_t *thread_data; + NXKey_t key; + NX_LOCK_INFO_ALLOC(liblock, "Application Data Lock", 0); + + err = 0; + thread_data = (libthreaddata_t *) NULL; + + /* + * Attempt to get our data for the application calling us. This is where we + * store whatever application-specific information we need to carry in + * support of calling applications. + */ + app_data = (libdata_t *) get_app_data(id); + + if(!app_data) { + /* + * This application hasn't called us before; set up application AND + * per-thread data. Of course, just in case a thread from this same + * application is calling us simultaneously, we better lock our application + * data-creation mutex. We also need to recheck for data after we acquire + * the lock because WE might be that other thread that was too late to + * create the data and the first thread in will have created it. + */ + NXLock(gLibLock); + + if(!(app_data = (libdata_t *) get_app_data(id))) { + app_data = malloc(sizeof(libdata_t)); + + if(app_data) { + memset(app_data, 0, sizeof(libdata_t)); + + app_data->tenbytes = malloc(10); + app_data->lock = NXMutexAlloc(0, 0, &liblock); + + if(!app_data->tenbytes || !app_data->lock) { + if(app_data->lock) + NXMutexFree(app_data->lock); + + free(app_data); + app_data = (libdata_t *) NULL; + err = ENOMEM; + } + + if(app_data) { + /* + * Here we burn in the application data that we were trying to get + * by calling get_app_data(). Next time we call the first function, + * we'll get this data we're just now setting. We also go on here to + * establish the per-thread data for the calling thread, something + * we'll have to do on each application thread the first time + * it calls us. + */ + err = set_app_data(gLibId, app_data); + + if(err) { + free(app_data); + app_data = (libdata_t *) NULL; + err = ENOMEM; + } + else { + /* create key for thread-specific data... */ + err = NXKeyCreate(DisposeThreadData, (void *) NULL, &key); + + if(err) /* (no more keys left?) */ + key = -1; + + app_data->perthreadkey = key; + } + } + } + } + + NXUnlock(gLibLock); + } + + if(app_data) { + key = app_data->perthreadkey; + + if(key != -1 /* couldn't create a key? no thread data */ + && !(err = NXKeyGetValue(key, (void **) &thread_data)) + && !thread_data) { + /* + * Allocate the per-thread data for the calling thread. Regardless of + * whether there was already application data or not, this may be the + * first call by a new thread. The fact that we allocation 20 bytes on + * a pointer is not very important, this just helps to demonstrate that + * we can have arbitrarily complex per-thread data. + */ + thread_data = malloc(sizeof(libthreaddata_t)); + + if(thread_data) { + thread_data->_errno = 0; + thread_data->twentybytes = malloc(20); + + if(!thread_data->twentybytes) { + free(thread_data); + thread_data = (libthreaddata_t *) NULL; + err = ENOMEM; + } + + if((err = NXKeySetValue(key, thread_data))) { + free(thread_data->twentybytes); + free(thread_data); + thread_data = (libthreaddata_t *) NULL; + } + } + } + } + + if(appData) + *appData = app_data; + + if(threadData) + *threadData = thread_data; + + return err; +} + +int DisposeLibraryData( void *data ) +{ + if(data) { + void *tenbytes = ((libdata_t *) data)->tenbytes; + + if(tenbytes) + free(tenbytes); + + free(data); + } + + return 0; +} + +void DisposeThreadData( void *data ) +{ + if(data) { + void *twentybytes = ((libthreaddata_t *) data)->twentybytes; + + if(twentybytes) + free(twentybytes); + + free(data); + } +} + +#else /* __NOVELL_LIBC__ */ +/* For native CLib-based NLM seems we can do a bit more simple. */ +#include + +int main ( void ) +{ + /* initialize any globals here... */ + + /* do this if any global initializing was done + SynchronizeStart(); + */ + ExitThread (TSR_THREAD, 0); + return 0; +} + +#endif /* __NOVELL_LIBC__ */ + +#else /* NETWARE */ + +#ifdef __POCC__ +# pragma warn(disable:2024) /* Disable warning #2024: Empty input file */ +#endif + +#endif /* NETWARE */ diff --git a/lib/curl_nwos.c b/lib/curl_nwos.c new file mode 100644 index 000000000..23ff2a717 --- /dev/null +++ b/lib/curl_nwos.c @@ -0,0 +1,88 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef NETWARE /* Novell NetWare */ + +#ifdef __NOVELL_LIBC__ +/* For native LibC-based NLM we need to do nothing. */ +int netware_init ( void ) +{ + return 0; +} + +#else /* __NOVELL_LIBC__ */ + +/* For native CLib-based NLM we need to initialize the LONG namespace. */ +#include +#include +#include +/* Make the CLIB Ctx stuff link */ +#include +NETDB_DEFINE_CONTEXT +/* Make the CLIB Inet stuff link */ +#include +#include +NETINET_DEFINE_CONTEXT + +int netware_init ( void ) +{ + int rc = 0; + unsigned int myHandle = GetNLMHandle(); + /* import UnAugmentAsterisk dynamically for NW4.x compatibility */ + void (*pUnAugmentAsterisk)(int) = (void(*)(int)) + ImportSymbol(myHandle, "UnAugmentAsterisk"); + /* import UseAccurateCaseForPaths dynamically for NW3.x compatibility */ + void (*pUseAccurateCaseForPaths)(int) = (void(*)(int)) + ImportSymbol(myHandle, "UseAccurateCaseForPaths"); + if(pUnAugmentAsterisk) + pUnAugmentAsterisk(1); + if(pUseAccurateCaseForPaths) + pUseAccurateCaseForPaths(1); + UnimportSymbol(myHandle, "UnAugmentAsterisk"); + UnimportSymbol(myHandle, "UseAccurateCaseForPaths"); + /* set long name space */ + if((SetCurrentNameSpace(4) == 255)) { + rc = 1; + } + if((SetTargetNameSpace(4) == 255)) { + rc = rc + 2; + } + return rc; +} + +/* dummy function to satisfy newer prelude */ +int __init_environment ( void ) +{ + return 0; +} + +/* dummy function to satisfy newer prelude */ +int __deinit_environment ( void ) +{ + return 0; +} + +#endif /* __NOVELL_LIBC__ */ + +#endif /* NETWARE */ diff --git a/lib/curl_openldap.c b/lib/curl_openldap.c new file mode 100644 index 000000000..b10d31e18 --- /dev/null +++ b/lib/curl_openldap.c @@ -0,0 +1,652 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2010, Howard Chu, + * Copyright (C) 2011 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP) + +/* + * Notice that USE_OPENLDAP is only a source code selection switch. When + * libcurl is built with USE_OPENLDAP defined the libcurl source code that + * gets compiled is the code from curl_openldap.c, otherwise the code that + * gets compiled is the code from curl_ldap.c. + * + * When USE_OPENLDAP is defined a recent version of the OpenLDAP library + * might be required for compilation and runtime. In order to use ancient + * OpenLDAP library versions, USE_OPENLDAP shall not be defined. + */ + +#include + +#include "curl_urldata.h" +#include +#include "curl_sendf.h" +#include "curl_sslgen.h" +#include "curl_transfer.h" +#include "curl_ldap.h" +#include "curl_memory.h" +#include "curl_base64.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memdebug.h" + +#ifndef _LDAP_PVT_H +extern int ldap_pvt_url_scheme2proto(const char *); +extern int ldap_init_fd(ber_socket_t fd, int proto, const char *url, + LDAP **ld); +#endif + +static CURLcode ldap_setup(struct connectdata *conn); +static CURLcode ldap_do(struct connectdata *conn, bool *done); +static CURLcode ldap_done(struct connectdata *conn, CURLcode, bool); +static CURLcode ldap_connect(struct connectdata *conn, bool *done); +static CURLcode ldap_connecting(struct connectdata *conn, bool *done); +static CURLcode ldap_disconnect(struct connectdata *conn, bool dead); + +static Curl_recv ldap_recv; + +/* + * LDAP protocol handler. + */ + +const struct Curl_handler Curl_handler_ldap = { + "LDAP", /* scheme */ + ldap_setup, /* setup_connection */ + ldap_do, /* do_it */ + ldap_done, /* done */ + ZERO_NULL, /* do_more */ + ldap_connect, /* connect_it */ + ldap_connecting, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ldap_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_LDAP, /* defport */ + CURLPROTO_LDAP, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +#ifdef USE_SSL +/* + * LDAPS protocol handler. + */ + +const struct Curl_handler Curl_handler_ldaps = { + "LDAPS", /* scheme */ + ldap_setup, /* setup_connection */ + ldap_do, /* do_it */ + ldap_done, /* done */ + ZERO_NULL, /* do_more */ + ldap_connect, /* connect_it */ + ldap_connecting, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ldap_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_LDAPS, /* defport */ + CURLPROTO_LDAP, /* protocol */ + PROTOPT_SSL /* flags */ +}; +#endif + +static const char *url_errs[] = { + "success", + "out of memory", + "bad parameter", + "unrecognized scheme", + "unbalanced delimiter", + "bad URL", + "bad host or port", + "bad or missing attributes", + "bad or missing scope", + "bad or missing filter", + "bad or missing extensions" +}; + +typedef struct ldapconninfo { + LDAP *ld; + Curl_recv *recv; /* for stacking SSL handler */ + Curl_send *send; + int proto; + int msgid; + bool ssldone; + bool sslinst; + bool didbind; +} ldapconninfo; + +typedef struct ldapreqinfo { + int msgid; + int nument; +} ldapreqinfo; + +static CURLcode ldap_setup(struct connectdata *conn) +{ + ldapconninfo *li; + LDAPURLDesc *lud; + struct SessionHandle *data=conn->data; + int rc, proto; + CURLcode status; + + rc = ldap_url_parse(data->change.url, &lud); + if(rc != LDAP_URL_SUCCESS) { + const char *msg = "url parsing problem"; + status = CURLE_URL_MALFORMAT; + if(rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) { + if(rc == LDAP_URL_ERR_MEM) + status = CURLE_OUT_OF_MEMORY; + msg = url_errs[rc]; + } + failf(conn->data, "LDAP local: %s", msg); + return status; + } + proto = ldap_pvt_url_scheme2proto(lud->lud_scheme); + ldap_free_urldesc(lud); + + li = calloc(1, sizeof(ldapconninfo)); + if(!li) + return CURLE_OUT_OF_MEMORY; + li->proto = proto; + conn->proto.generic = li; + conn->bits.close = FALSE; + /* TODO: + * - provide option to choose SASL Binds instead of Simple + */ + return CURLE_OK; +} + +#ifdef USE_SSL +static Sockbuf_IO ldapsb_tls; +#endif + +static CURLcode ldap_connect(struct connectdata *conn, bool *done) +{ + ldapconninfo *li = conn->proto.generic; + struct SessionHandle *data=conn->data; + int rc, proto = LDAP_VERSION3; + char hosturl[1024], *ptr; + + strcpy(hosturl, "ldap"); + ptr = hosturl+4; + if(conn->handler->flags & PROTOPT_SSL) + *ptr++ = 's'; + snprintf(ptr, sizeof(hosturl)-(ptr-hosturl), "://%s:%d", + conn->host.name, conn->remote_port); + + rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld); + if(rc) { + failf(data, "LDAP local: Cannot connect to %s, %s", + hosturl, ldap_err2string(rc)); + return CURLE_COULDNT_CONNECT; + } + + ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); + +#ifdef USE_SSL + if(conn->handler->flags & PROTOPT_SSL) { + CURLcode res; + if(data->state.used_interface == Curl_if_easy) { + res = Curl_ssl_connect(conn, FIRSTSOCKET); + if(res) + return res; + li->ssldone = TRUE; + } + else { + res = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &li->ssldone); + if(res) + return res; + } + } +#endif + + if(data->state.used_interface == Curl_if_easy) + return ldap_connecting(conn, done); + + return CURLE_OK; +} + +static CURLcode ldap_connecting(struct connectdata *conn, bool *done) +{ + ldapconninfo *li = conn->proto.generic; + struct SessionHandle *data=conn->data; + LDAPMessage *result = NULL; + struct timeval tv = {0,1}, *tvp; + int rc, err; + char *info = NULL; + +#ifdef USE_SSL + if(conn->handler->flags & PROTOPT_SSL) { + /* Is the SSL handshake complete yet? */ + if(!li->ssldone) { + CURLcode res = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, + &li->ssldone); + if(res || !li->ssldone) + return res; + } + /* Have we installed the libcurl SSL handlers into the sockbuf yet? */ + if(!li->sslinst) { + Sockbuf *sb; + ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); + ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, conn); + li->sslinst = TRUE; + li->recv = conn->recv[FIRSTSOCKET]; + li->send = conn->send[FIRSTSOCKET]; + } + } +#endif + + if(data->state.used_interface == Curl_if_easy) + tvp = NULL; /* let ldap_result block indefinitely */ + else + tvp = &tv; + +retry: + if(!li->didbind) { + char *binddn; + struct berval passwd; + + if(conn->bits.user_passwd) { + binddn = conn->user; + passwd.bv_val = conn->passwd; + passwd.bv_len = strlen(passwd.bv_val); + } + else { + binddn = NULL; + passwd.bv_val = NULL; + passwd.bv_len = 0; + } + rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, + NULL, NULL, &li->msgid); + if(rc) + return CURLE_LDAP_CANNOT_BIND; + li->didbind = TRUE; + if(tvp) + return CURLE_OK; + } + + rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, tvp, &result); + if(rc < 0) { + failf(data, "LDAP local: bind ldap_result %s", ldap_err2string(rc)); + return CURLE_LDAP_CANNOT_BIND; + } + if(rc == 0) { + /* timed out */ + return CURLE_OK; + } + rc = ldap_parse_result(li->ld, result, &err, NULL, &info, NULL, NULL, 1); + if(rc) { + failf(data, "LDAP local: bind ldap_parse_result %s", ldap_err2string(rc)); + return CURLE_LDAP_CANNOT_BIND; + } + /* Try to fallback to LDAPv2? */ + if(err == LDAP_PROTOCOL_ERROR) { + int proto; + ldap_get_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); + if(proto == LDAP_VERSION3) { + if(info) { + ldap_memfree(info); + info = NULL; + } + proto = LDAP_VERSION2; + ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); + li->didbind = FALSE; + goto retry; + } + } + + if(err) { + failf(data, "LDAP remote: bind failed %s %s", ldap_err2string(rc), + info ? info : ""); + if(info) + ldap_memfree(info); + return CURLE_LOGIN_DENIED; + } + + if(info) + ldap_memfree(info); + conn->recv[FIRSTSOCKET] = ldap_recv; + *done = TRUE; + return CURLE_OK; +} + +static CURLcode ldap_disconnect(struct connectdata *conn, bool dead_connection) +{ + ldapconninfo *li = conn->proto.generic; + (void) dead_connection; + + if(li) { + if(li->ld) { + ldap_unbind_ext(li->ld, NULL, NULL); + li->ld = NULL; + } + conn->proto.generic = NULL; + free(li); + } + return CURLE_OK; +} + +static CURLcode ldap_do(struct connectdata *conn, bool *done) +{ + ldapconninfo *li = conn->proto.generic; + ldapreqinfo *lr; + CURLcode status = CURLE_OK; + int rc = 0; + LDAPURLDesc *ludp = NULL; + int msgid; + struct SessionHandle *data=conn->data; + + conn->bits.close = FALSE; + + infof(data, "LDAP local: %s\n", data->change.url); + + rc = ldap_url_parse(data->change.url, &ludp); + if(rc != LDAP_URL_SUCCESS) { + const char *msg = "url parsing problem"; + status = CURLE_URL_MALFORMAT; + if(rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) { + if(rc == LDAP_URL_ERR_MEM) + status = CURLE_OUT_OF_MEMORY; + msg = url_errs[rc]; + } + failf(conn->data, "LDAP local: %s", msg); + return status; + } + + rc = ldap_search_ext(li->ld, ludp->lud_dn, ludp->lud_scope, + ludp->lud_filter, ludp->lud_attrs, 0, + NULL, NULL, NULL, 0, &msgid); + ldap_free_urldesc(ludp); + if(rc != LDAP_SUCCESS) { + failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); + return CURLE_LDAP_SEARCH_FAILED; + } + lr = calloc(1,sizeof(ldapreqinfo)); + if(!lr) + return CURLE_OUT_OF_MEMORY; + lr->msgid = msgid; + data->state.proto.generic = lr; + Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, NULL, -1, NULL); + *done = TRUE; + return CURLE_OK; +} + +static CURLcode ldap_done(struct connectdata *conn, CURLcode res, + bool premature) +{ + ldapreqinfo *lr = conn->data->state.proto.generic; + (void)res; + (void)premature; + + if(lr) { + /* if there was a search in progress, abandon it */ + if(lr->msgid) { + ldapconninfo *li = conn->proto.generic; + ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL); + lr->msgid = 0; + } + conn->data->state.proto.generic = NULL; + free(lr); + } + return CURLE_OK; +} + +static ssize_t ldap_recv(struct connectdata *conn, int sockindex, char *buf, + size_t len, CURLcode *err) +{ + ldapconninfo *li = conn->proto.generic; + struct SessionHandle *data=conn->data; + ldapreqinfo *lr = data->state.proto.generic; + int rc, ret; + LDAPMessage *result = NULL; + LDAPMessage *ent; + BerElement *ber = NULL; + struct timeval tv = {0,1}; + (void)len; + (void)buf; + (void)sockindex; + + rc = ldap_result(li->ld, lr->msgid, LDAP_MSG_RECEIVED, &tv, &result); + if(rc < 0) { + failf(data, "LDAP local: search ldap_result %s", ldap_err2string(rc)); + *err = CURLE_RECV_ERROR; + return -1; + } + + *err = CURLE_AGAIN; + ret = -1; + + /* timed out */ + if(result == NULL) + return ret; + + for(ent = ldap_first_message(li->ld, result); ent; + ent = ldap_next_message(li->ld, ent)) { + struct berval bv, *bvals, **bvp = &bvals; + int binary = 0, msgtype; + + msgtype = ldap_msgtype(ent); + if(msgtype == LDAP_RES_SEARCH_RESULT) { + int code; + char *info = NULL; + rc = ldap_parse_result(li->ld, ent, &code, NULL, &info, NULL, NULL, 0); + if(rc) { + failf(data, "LDAP local: search ldap_parse_result %s", + ldap_err2string(rc)); + *err = CURLE_LDAP_SEARCH_FAILED; + } + else if(code && code != LDAP_SIZELIMIT_EXCEEDED) { + failf(data, "LDAP remote: search failed %s %s", ldap_err2string(rc), + info ? info : ""); + *err = CURLE_LDAP_SEARCH_FAILED; + } + else { + /* successful */ + if(code == LDAP_SIZELIMIT_EXCEEDED) + infof(data, "There are more than %d entries\n", lr->nument); + data->req.size = data->req.bytecount; + *err = CURLE_OK; + ret = 0; + } + lr->msgid = 0; + ldap_memfree(info); + break; + } + else if(msgtype != LDAP_RES_SEARCH_ENTRY) + continue; + + lr->nument++; + rc = ldap_get_dn_ber(li->ld, ent, &ber, &bv); + if(rc < 0) { + /* TODO: verify that this is really how this return code should be + handled */ + *err = CURLE_RECV_ERROR; + return -1; + } + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, bv.bv_len); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); + data->req.bytecount += bv.bv_len + 5; + + for(rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp); + rc == LDAP_SUCCESS; + rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp)) { + int i; + + if(bv.bv_val == NULL) break; + + if(bv.bv_len > 7 && !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7)) + binary = 1; + else + binary = 0; + + for(i=0; bvals[i].bv_val != NULL; i++) { + int binval = 0; + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, + bv.bv_len); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)":", 1); + data->req.bytecount += bv.bv_len + 2; + + if(!binary) { + /* check for leading or trailing whitespace */ + if(ISSPACE(bvals[i].bv_val[0]) || + ISSPACE(bvals[i].bv_val[bvals[i].bv_len-1])) + binval = 1; + else { + /* check for unprintable characters */ + unsigned int j; + for(j=0; jreq.bytecount += 2; + if(val_b64_sz > 0) { + Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, val_b64_sz); + free(val_b64); + data->req.bytecount += val_b64_sz; + } + } + else { + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)" ", 1); + Curl_client_write(conn, CLIENTWRITE_BODY, bvals[i].bv_val, + bvals[i].bv_len); + data->req.bytecount += bvals[i].bv_len + 1; + } + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); + data->req.bytecount++; + } + ber_memfree(bvals); + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); + data->req.bytecount++; + } + Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); + data->req.bytecount++; + ber_free(ber, 0); + } + ldap_msgfree(result); + return ret; +} + +#ifdef USE_SSL +static int +ldapsb_tls_setup(Sockbuf_IO_Desc *sbiod, void *arg) +{ + sbiod->sbiod_pvt = arg; + return 0; +} + +static int +ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod) +{ + sbiod->sbiod_pvt = NULL; + return 0; +} + +/* We don't need to do anything because libcurl does it already */ +static int +ldapsb_tls_close(Sockbuf_IO_Desc *sbiod) +{ + (void)sbiod; + return 0; +} + +static int +ldapsb_tls_ctrl(Sockbuf_IO_Desc *sbiod, int opt, void *arg) +{ + (void)arg; + if(opt == LBER_SB_OPT_DATA_READY) { + struct connectdata *conn = sbiod->sbiod_pvt; + return Curl_ssl_data_pending(conn, FIRSTSOCKET); + } + return 0; +} + +static ber_slen_t +ldapsb_tls_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct connectdata *conn = sbiod->sbiod_pvt; + ldapconninfo *li = conn->proto.generic; + ber_slen_t ret; + CURLcode err = CURLE_RECV_ERROR; + + ret = li->recv(conn, FIRSTSOCKET, buf, len, &err); + if(ret < 0 && err == CURLE_AGAIN) { + SET_SOCKERRNO(EWOULDBLOCK); + } + return ret; +} + +static ber_slen_t +ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) +{ + struct connectdata *conn = sbiod->sbiod_pvt; + ldapconninfo *li = conn->proto.generic; + ber_slen_t ret; + CURLcode err = CURLE_SEND_ERROR; + + ret = li->send(conn, FIRSTSOCKET, buf, len, &err); + if(ret < 0 && err == CURLE_AGAIN) { + SET_SOCKERRNO(EWOULDBLOCK); + } + return ret; +} + +static Sockbuf_IO ldapsb_tls = +{ + ldapsb_tls_setup, + ldapsb_tls_remove, + ldapsb_tls_ctrl, + ldapsb_tls_read, + ldapsb_tls_write, + ldapsb_tls_close +}; +#endif /* USE_SSL */ + +#endif /* !CURL_DISABLE_LDAP && USE_OPENLDAP */ diff --git a/lib/curl_parsedate.c b/lib/curl_parsedate.c new file mode 100644 index 000000000..a50b6074e --- /dev/null +++ b/lib/curl_parsedate.c @@ -0,0 +1,580 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ +/* + A brief summary of the date string formats this parser groks: + + RFC 2616 3.3.1 + + Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 + Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 + Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + + we support dates without week day name: + + 06 Nov 1994 08:49:37 GMT + 06-Nov-94 08:49:37 GMT + Nov 6 08:49:37 1994 + + without the time zone: + + 06 Nov 1994 08:49:37 + 06-Nov-94 08:49:37 + + weird order: + + 1994 Nov 6 08:49:37 (GNU date fails) + GMT 08:49:37 06-Nov-94 Sunday + 94 6 Nov 08:49:37 (GNU date fails) + + time left out: + + 1994 Nov 6 + 06-Nov-94 + Sun Nov 6 94 + + unusual separators: + + 1994.Nov.6 + Sun/Nov/6/94/GMT + + commonly used time zone names: + + Sun, 06 Nov 1994 08:49:37 CET + 06 Nov 1994 08:49:37 EST + + time zones specified using RFC822 style: + + Sun, 12 Sep 2004 15:05:58 -0700 + Sat, 11 Sep 2004 21:32:11 +0200 + + compact numerical date strings: + + 20040912 15:05:58 -0700 + 20040911 +0200 + +*/ + +#include "curl_setup.h" + +#ifdef HAVE_LIMITS_H +#include +#endif + +#include +#include "curl_rawstr.h" +#include "curl_warnless.h" +#include "curl_parsedate.h" + +const char * const Curl_wkday[] = +{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; +static const char * const weekday[] = +{ "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday", "Sunday" }; +const char * const Curl_month[]= +{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +struct tzinfo { + char name[5]; + int offset; /* +/- in minutes */ +}; + +/* + * parsedate() + * + * Returns: + * + * PARSEDATE_OK - a fine conversion + * PARSEDATE_FAIL - failed to convert + * PARSEDATE_LATER - time overflow at the far end of time_t + * PARSEDATE_SOONER - time underflow at the low end of time_t + */ + +static int parsedate(const char *date, time_t *output); + +#define PARSEDATE_OK 0 +#define PARSEDATE_FAIL -1 +#define PARSEDATE_LATER 1 +#define PARSEDATE_SOONER 2 + +/* Here's a bunch of frequently used time zone names. These were supported + by the old getdate parser. */ +#define tDAYZONE -60 /* offset for daylight savings time */ +static const struct tzinfo tz[]= { + {"GMT", 0}, /* Greenwich Mean */ + {"UTC", 0}, /* Universal (Coordinated) */ + {"WET", 0}, /* Western European */ + {"BST", 0 tDAYZONE}, /* British Summer */ + {"WAT", 60}, /* West Africa */ + {"AST", 240}, /* Atlantic Standard */ + {"ADT", 240 tDAYZONE}, /* Atlantic Daylight */ + {"EST", 300}, /* Eastern Standard */ + {"EDT", 300 tDAYZONE}, /* Eastern Daylight */ + {"CST", 360}, /* Central Standard */ + {"CDT", 360 tDAYZONE}, /* Central Daylight */ + {"MST", 420}, /* Mountain Standard */ + {"MDT", 420 tDAYZONE}, /* Mountain Daylight */ + {"PST", 480}, /* Pacific Standard */ + {"PDT", 480 tDAYZONE}, /* Pacific Daylight */ + {"YST", 540}, /* Yukon Standard */ + {"YDT", 540 tDAYZONE}, /* Yukon Daylight */ + {"HST", 600}, /* Hawaii Standard */ + {"HDT", 600 tDAYZONE}, /* Hawaii Daylight */ + {"CAT", 600}, /* Central Alaska */ + {"AHST", 600}, /* Alaska-Hawaii Standard */ + {"NT", 660}, /* Nome */ + {"IDLW", 720}, /* International Date Line West */ + {"CET", -60}, /* Central European */ + {"MET", -60}, /* Middle European */ + {"MEWT", -60}, /* Middle European Winter */ + {"MEST", -60 tDAYZONE}, /* Middle European Summer */ + {"CEST", -60 tDAYZONE}, /* Central European Summer */ + {"MESZ", -60 tDAYZONE}, /* Middle European Summer */ + {"FWT", -60}, /* French Winter */ + {"FST", -60 tDAYZONE}, /* French Summer */ + {"EET", -120}, /* Eastern Europe, USSR Zone 1 */ + {"WAST", -420}, /* West Australian Standard */ + {"WADT", -420 tDAYZONE}, /* West Australian Daylight */ + {"CCT", -480}, /* China Coast, USSR Zone 7 */ + {"JST", -540}, /* Japan Standard, USSR Zone 8 */ + {"EAST", -600}, /* Eastern Australian Standard */ + {"EADT", -600 tDAYZONE}, /* Eastern Australian Daylight */ + {"GST", -600}, /* Guam Standard, USSR Zone 9 */ + {"NZT", -720}, /* New Zealand */ + {"NZST", -720}, /* New Zealand Standard */ + {"NZDT", -720 tDAYZONE}, /* New Zealand Daylight */ + {"IDLE", -720}, /* International Date Line East */ + /* Next up: Military timezone names. RFC822 allowed these, but (as noted in + RFC 1123) had their signs wrong. Here we use the correct signs to match + actual military usage. + */ + {"A", +1 * 60}, /* Alpha */ + {"B", +2 * 60}, /* Bravo */ + {"C", +3 * 60}, /* Charlie */ + {"D", +4 * 60}, /* Delta */ + {"E", +5 * 60}, /* Echo */ + {"F", +6 * 60}, /* Foxtrot */ + {"G", +7 * 60}, /* Golf */ + {"H", +8 * 60}, /* Hotel */ + {"I", +9 * 60}, /* India */ + /* "J", Juliet is not used as a timezone, to indicate the observer's local + time */ + {"K", +10 * 60}, /* Kilo */ + {"L", +11 * 60}, /* Lima */ + {"M", +12 * 60}, /* Mike */ + {"N", -1 * 60}, /* November */ + {"O", -2 * 60}, /* Oscar */ + {"P", -3 * 60}, /* Papa */ + {"Q", -4 * 60}, /* Quebec */ + {"R", -5 * 60}, /* Romeo */ + {"S", -6 * 60}, /* Sierra */ + {"T", -7 * 60}, /* Tango */ + {"U", -8 * 60}, /* Uniform */ + {"V", -9 * 60}, /* Victor */ + {"W", -10 * 60}, /* Whiskey */ + {"X", -11 * 60}, /* X-ray */ + {"Y", -12 * 60}, /* Yankee */ + {"Z", 0}, /* Zulu, zero meridian, a.k.a. UTC */ +}; + +/* returns: + -1 no day + 0 monday - 6 sunday +*/ + +static int checkday(const char *check, size_t len) +{ + int i; + const char * const *what; + bool found= FALSE; + if(len > 3) + what = &weekday[0]; + else + what = &Curl_wkday[0]; + for(i=0; i<7; i++) { + if(Curl_raw_equal(check, what[0])) { + found=TRUE; + break; + } + what++; + } + return found?i:-1; +} + +static int checkmonth(const char *check) +{ + int i; + const char * const *what; + bool found= FALSE; + + what = &Curl_month[0]; + for(i=0; i<12; i++) { + if(Curl_raw_equal(check, what[0])) { + found=TRUE; + break; + } + what++; + } + return found?i:-1; /* return the offset or -1, no real offset is -1 */ +} + +/* return the time zone offset between GMT and the input one, in number + of seconds or -1 if the timezone wasn't found/legal */ + +static int checktz(const char *check) +{ + unsigned int i; + const struct tzinfo *what; + bool found= FALSE; + + what = tz; + for(i=0; i< sizeof(tz)/sizeof(tz[0]); i++) { + if(Curl_raw_equal(check, what->name)) { + found=TRUE; + break; + } + what++; + } + return found?what->offset*60:-1; +} + +static void skip(const char **date) +{ + /* skip everything that aren't letters or digits */ + while(**date && !ISALNUM(**date)) + (*date)++; +} + +enum assume { + DATE_MDAY, + DATE_YEAR, + DATE_TIME +}; + +/* this is a clone of 'struct tm' but with all fields we don't need or use + cut out */ +struct my_tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; +}; + +/* struct tm to time since epoch in GMT time zone. + * This is similar to the standard mktime function but for GMT only, and + * doesn't suffer from the various bugs and portability problems that + * some systems' implementations have. + */ +static time_t my_timegm(struct my_tm *tm) +{ + static const int month_days_cumulative [12] = + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + int month, year, leap_days; + + if(tm->tm_year < 70) + /* we don't support years before 1970 as they will cause this function + to return a negative value */ + return -1; + + year = tm->tm_year + 1900; + month = tm->tm_mon; + if(month < 0) { + year += (11 - month) / 12; + month = 11 - (11 - month) % 12; + } + else if(month >= 12) { + year -= month / 12; + month = month % 12; + } + + leap_days = year - (tm->tm_mon <= 1); + leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400) + - (1969 / 4) + (1969 / 100) - (1969 / 400)); + + return ((((time_t) (year - 1970) * 365 + + leap_days + month_days_cumulative [month] + tm->tm_mday - 1) * 24 + + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec; +} + +/* + * parsedate() + * + * Returns: + * + * PARSEDATE_OK - a fine conversion + * PARSEDATE_FAIL - failed to convert + * PARSEDATE_LATER - time overflow at the far end of time_t + * PARSEDATE_SOONER - time underflow at the low end of time_t + */ + +static int parsedate(const char *date, time_t *output) +{ + time_t t = 0; + int wdaynum=-1; /* day of the week number, 0-6 (mon-sun) */ + int monnum=-1; /* month of the year number, 0-11 */ + int mdaynum=-1; /* day of month, 1 - 31 */ + int hournum=-1; + int minnum=-1; + int secnum=-1; + int yearnum=-1; + int tzoff=-1; + struct my_tm tm; + enum assume dignext = DATE_MDAY; + const char *indate = date; /* save the original pointer */ + int part = 0; /* max 6 parts */ + + while(*date && (part < 6)) { + bool found=FALSE; + + skip(&date); + + if(ISALPHA(*date)) { + /* a name coming up */ + char buf[32]=""; + size_t len; + sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]", + buf); + len = strlen(buf); + + if(wdaynum == -1) { + wdaynum = checkday(buf, len); + if(wdaynum != -1) + found = TRUE; + } + if(!found && (monnum == -1)) { + monnum = checkmonth(buf); + if(monnum != -1) + found = TRUE; + } + + if(!found && (tzoff == -1)) { + /* this just must be a time zone string */ + tzoff = checktz(buf); + if(tzoff != -1) + found = TRUE; + } + + if(!found) + return PARSEDATE_FAIL; /* bad string */ + + date += len; + } + else if(ISDIGIT(*date)) { + /* a digit */ + int val; + char *end; + if((secnum == -1) && + (3 == sscanf(date, "%02d:%02d:%02d", &hournum, &minnum, &secnum))) { + /* time stamp! */ + date += 8; + } + else if((secnum == -1) && + (2 == sscanf(date, "%02d:%02d", &hournum, &minnum))) { + /* time stamp without seconds */ + date += 5; + secnum = 0; + } + else { + long lval; + int error; + int old_errno; + + old_errno = ERRNO; + SET_ERRNO(0); + lval = strtol(date, &end, 10); + error = ERRNO; + if(error != old_errno) + SET_ERRNO(old_errno); + + if(error) + return PARSEDATE_FAIL; + + if((lval > (long)INT_MAX) || (lval < (long)INT_MIN)) + return PARSEDATE_FAIL; + + val = curlx_sltosi(lval); + + if((tzoff == -1) && + ((end - date) == 4) && + (val <= 1400) && + (indate< date) && + ((date[-1] == '+' || date[-1] == '-'))) { + /* four digits and a value less than or equal to 1400 (to take into + account all sorts of funny time zone diffs) and it is preceded + with a plus or minus. This is a time zone indication. 1400 is + picked since +1300 is frequently used and +1400 is mentioned as + an edge number in the document "ISO C 200X Proposal: Timezone + Functions" at http://david.tribble.com/text/c0xtimezone.html If + anyone has a more authoritative source for the exact maximum time + zone offsets, please speak up! */ + found = TRUE; + tzoff = (val/100 * 60 + val%100)*60; + + /* the + and - prefix indicates the local time compared to GMT, + this we need ther reversed math to get what we want */ + tzoff = date[-1]=='+'?-tzoff:tzoff; + } + + if(((end - date) == 8) && + (yearnum == -1) && + (monnum == -1) && + (mdaynum == -1)) { + /* 8 digits, no year, month or day yet. This is YYYYMMDD */ + found = TRUE; + yearnum = val/10000; + monnum = (val%10000)/100-1; /* month is 0 - 11 */ + mdaynum = val%100; + } + + if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { + if((val > 0) && (val<32)) { + mdaynum = val; + found = TRUE; + } + dignext = DATE_YEAR; + } + + if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) { + yearnum = val; + found = TRUE; + if(yearnum < 1900) { + if(yearnum > 70) + yearnum += 1900; + else + yearnum += 2000; + } + if(mdaynum == -1) + dignext = DATE_MDAY; + } + + if(!found) + return PARSEDATE_FAIL; + + date = end; + } + } + + part++; + } + + if(-1 == secnum) + secnum = minnum = hournum = 0; /* no time, make it zero */ + + if((-1 == mdaynum) || + (-1 == monnum) || + (-1 == yearnum)) + /* lacks vital info, fail */ + return PARSEDATE_FAIL; + +#if SIZEOF_TIME_T < 5 + /* 32 bit time_t can only hold dates to the beginning of 2038 */ + if(yearnum > 2037) { + *output = 0x7fffffff; + return PARSEDATE_LATER; + } +#endif + + if(yearnum < 1970) { + *output = 0; + return PARSEDATE_SOONER; + } + + if((mdaynum > 31) || (monnum > 11) || + (hournum > 23) || (minnum > 59) || (secnum > 60)) + return PARSEDATE_FAIL; /* clearly an illegal date */ + + tm.tm_sec = secnum; + tm.tm_min = minnum; + tm.tm_hour = hournum; + tm.tm_mday = mdaynum; + tm.tm_mon = monnum; + tm.tm_year = yearnum - 1900; + + /* my_timegm() returns a time_t. time_t is often 32 bits, even on many + architectures that feature 64 bit 'long'. + + Some systems have 64 bit time_t and deal with years beyond 2038. However, + even on some of the systems with 64 bit time_t mktime() returns -1 for + dates beyond 03:14:07 UTC, January 19, 2038. (Such as AIX 5100-06) + */ + t = my_timegm(&tm); + + /* time zone adjust (cast t to int to compare to negative one) */ + if(-1 != (int)t) { + + /* Add the time zone diff between local time zone and GMT. */ + long delta = (long)(tzoff!=-1?tzoff:0); + + if((delta>0) && (t + delta < t)) + return -1; /* time_t overflow */ + + t += delta; + } + + *output = t; + + return PARSEDATE_OK; +} + +time_t curl_getdate(const char *p, const time_t *now) +{ + time_t parsed; + int rc = parsedate(p, &parsed); + (void)now; /* legacy argument from the past that we ignore */ + + switch(rc) { + case PARSEDATE_OK: + case PARSEDATE_LATER: + case PARSEDATE_SOONER: + return parsed; + } + /* everything else is fail */ + return -1; +} + +/* + * Curl_gmtime() is a gmtime() replacement for portability. Do not use the + * gmtime_r() or gmtime() functions anywhere else but here. + * + * To make sure no such function calls slip in, we define them to cause build + * errors, which is why we use the name within parentheses in this function. + * + */ + +CURLcode Curl_gmtime(time_t intime, struct tm *store) +{ + const struct tm *tm; +#ifdef HAVE_GMTIME_R + /* thread-safe version */ + tm = (struct tm *)gmtime_r(&intime, store); +#else + tm = gmtime(&intime); + if(tm) + *store = *tm; /* copy the pointed struct to the local copy */ +#endif + + if(!tm) + return CURLE_BAD_FUNCTION_ARGUMENT; + return CURLE_OK; +} diff --git a/lib/curl_pingpong.c b/lib/curl_pingpong.c new file mode 100644 index 000000000..d28e78aa2 --- /dev/null +++ b/lib/curl_pingpong.c @@ -0,0 +1,538 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + * 'pingpong' is for generic back-and-forth support functions used by FTP, + * IMAP, POP3, SMTP and whatever more that likes them. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_select.h" +#include "curl_progress.h" +#include "curl_speedcheck.h" +#include "curl_pingpong.h" +#include "curl_multiif.h" +#include "curl_non_ascii.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifdef USE_PINGPONG + +/* Returns timeout in ms. 0 or negative number means the timeout has already + triggered */ +long Curl_pp_state_timeout(struct pingpong *pp) +{ + struct connectdata *conn = pp->conn; + struct SessionHandle *data=conn->data; + long timeout_ms; /* in milliseconds */ + long timeout2_ms; /* in milliseconds */ + long response_time= (data->set.server_response_timeout)? + data->set.server_response_timeout: pp->response_time; + + /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine + remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is + supposed to govern the response for any given server response, not for + the time from connect to the given server response. */ + + /* Without a requested timeout, we only wait 'response_time' seconds for the + full response to arrive before we bail out */ + timeout_ms = response_time - + Curl_tvdiff(Curl_tvnow(), pp->response); /* spent time */ + + if(data->set.timeout) { + /* if timeout is requested, find out how much remaining time we have */ + timeout2_ms = data->set.timeout - /* timeout time */ + Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ + + /* pick the lowest number */ + timeout_ms = CURLMIN(timeout_ms, timeout2_ms); + } + + return timeout_ms; +} + + +/* + * Curl_pp_multi_statemach() + * + * called repeatedly until done when the multi interface is used. + */ +CURLcode Curl_pp_multi_statemach(struct pingpong *pp) +{ + struct connectdata *conn = pp->conn; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc; + struct SessionHandle *data=conn->data; + CURLcode result = CURLE_OK; + long timeout_ms = Curl_pp_state_timeout(pp); + + if(timeout_ms <= 0) { + failf(data, "server response timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + 0); + + if(rc == -1) { + failf(data, "select/poll error"); + return CURLE_OUT_OF_MEMORY; + } + else if(rc != 0) + result = pp->statemach_act(conn); + + /* if rc == 0, then select() timed out */ + + return result; +} + +/* + * Curl_pp_easy_statemach() + * + * called repeatedly until done when the easy interface is used. + */ +CURLcode Curl_pp_easy_statemach(struct pingpong *pp) +{ + struct connectdata *conn = pp->conn; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int rc; + long interval_ms; + long timeout_ms = Curl_pp_state_timeout(pp); + struct SessionHandle *data=conn->data; + CURLcode result; + + if(timeout_ms <=0 ) { + failf(data, "server response timeout"); + return CURLE_OPERATION_TIMEDOUT; /* already too little time */ + } + + interval_ms = 1000; /* use 1 second timeout intervals */ + if(timeout_ms < interval_ms) + interval_ms = timeout_ms; + + rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ + pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + interval_ms); + + if(Curl_pgrsUpdate(conn)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, Curl_tvnow()); + + if(result) + ; + else if(rc == -1) { + failf(data, "select/poll error"); + result = CURLE_OUT_OF_MEMORY; + } + else if(rc) + result = pp->statemach_act(conn); + + return result; +} + +/* initialize stuff to prepare for reading a fresh new response */ +void Curl_pp_init(struct pingpong *pp) +{ + struct connectdata *conn = pp->conn; + pp->nread_resp = 0; + pp->linestart_resp = conn->data->state.buffer; + pp->pending_resp = TRUE; + pp->response = Curl_tvnow(); /* start response time-out now! */ +} + + + +/*********************************************************************** + * + * Curl_pp_vsendf() + * + * Send the formated string as a command to a pingpong server. Note that + * the string should not have any CRLF appended, as this function will + * append the necessary things itself. + * + * made to never block + */ +CURLcode Curl_pp_vsendf(struct pingpong *pp, + const char *fmt, + va_list args) +{ + ssize_t bytes_written; + size_t write_len; + char *fmt_crlf; + char *s; + CURLcode error; + struct connectdata *conn = pp->conn; + struct SessionHandle *data = conn->data; + +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + enum protection_level data_sec = conn->data_prot; +#endif + + DEBUGASSERT(pp->sendleft == 0); + DEBUGASSERT(pp->sendsize == 0); + DEBUGASSERT(pp->sendthis == NULL); + + fmt_crlf = aprintf("%s\r\n", fmt); /* append a trailing CRLF */ + if(!fmt_crlf) + return CURLE_OUT_OF_MEMORY; + + s = vaprintf(fmt_crlf, args); /* trailing CRLF appended */ + free(fmt_crlf); + if(!s) + return CURLE_OUT_OF_MEMORY; + + bytes_written = 0; + write_len = strlen(s); + + Curl_pp_init(pp); + + error = Curl_convert_to_network(data, s, write_len); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(error) { + free(s); + return error; + } + +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + conn->data_prot = PROT_CMD; +#endif + error = Curl_write(conn, conn->sock[FIRSTSOCKET], s, write_len, + &bytes_written); +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); + conn->data_prot = data_sec; +#endif + + if(error) { + free(s); + return error; + } + + if(conn->data->set.verbose) + Curl_debug(conn->data, CURLINFO_HEADER_OUT, + s, (size_t)bytes_written, conn); + + if(bytes_written != (ssize_t)write_len) { + /* the whole chunk was not sent, keep it around and adjust sizes */ + pp->sendthis = s; + pp->sendsize = write_len; + pp->sendleft = write_len - bytes_written; + } + else { + free(s); + pp->sendthis = NULL; + pp->sendleft = pp->sendsize = 0; + pp->response = Curl_tvnow(); + } + + return CURLE_OK; +} + + +/*********************************************************************** + * + * Curl_pp_sendf() + * + * Send the formated string as a command to a pingpong server. Note that + * the string should not have any CRLF appended, as this function will + * append the necessary things itself. + * + * made to never block + */ +CURLcode Curl_pp_sendf(struct pingpong *pp, + const char *fmt, ...) +{ + CURLcode res; + va_list ap; + va_start(ap, fmt); + + res = Curl_pp_vsendf(pp, fmt, ap); + + va_end(ap); + + return res; +} + +/* + * Curl_pp_readresp() + * + * Reads a piece of a server response. + */ +CURLcode Curl_pp_readresp(curl_socket_t sockfd, + struct pingpong *pp, + int *code, /* return the server code if done */ + size_t *size) /* size of the response */ +{ + ssize_t perline; /* count bytes per line */ + bool keepon=TRUE; + ssize_t gotbytes; + char *ptr; + struct connectdata *conn = pp->conn; + struct SessionHandle *data = conn->data; + char * const buf = data->state.buffer; + CURLcode result = CURLE_OK; + + *code = 0; /* 0 for errors or not done */ + *size = 0; + + ptr=buf + pp->nread_resp; + + /* number of bytes in the current line, so far */ + perline = (ssize_t)(ptr-pp->linestart_resp); + + keepon=TRUE; + + while((pp->nread_respcache) { + /* we had data in the "cache", copy that instead of doing an actual + * read + * + * pp->cache_size is cast to ssize_t here. This should be safe, because + * it would have been populated with something of size int to begin + * with, even though its datatype may be larger than an int. + */ + DEBUGASSERT((ptr+pp->cache_size) <= (buf+BUFSIZE+1)); + memcpy(ptr, pp->cache, pp->cache_size); + gotbytes = (ssize_t)pp->cache_size; + free(pp->cache); /* free the cache */ + pp->cache = NULL; /* clear the pointer */ + pp->cache_size = 0; /* zero the size just in case */ + } + else { + int res; +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + enum protection_level prot = conn->data_prot; + conn->data_prot = PROT_CLEAR; +#endif + DEBUGASSERT((ptr+BUFSIZE-pp->nread_resp) <= (buf+BUFSIZE+1)); + res = Curl_read(conn, sockfd, ptr, BUFSIZE-pp->nread_resp, + &gotbytes); +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); + conn->data_prot = prot; +#endif + if(res == CURLE_AGAIN) + return CURLE_OK; /* return */ + + if((res == CURLE_OK) && (gotbytes > 0)) + /* convert from the network encoding */ + res = Curl_convert_from_network(data, ptr, gotbytes); + /* Curl_convert_from_network calls failf if unsuccessful */ + + if(CURLE_OK != res) { + result = (CURLcode)res; /* Set outer result variable to this error. */ + keepon = FALSE; + } + } + + if(!keepon) + ; + else if(gotbytes <= 0) { + keepon = FALSE; + result = CURLE_RECV_ERROR; + failf(data, "response reading failed"); + } + else { + /* we got a whole chunk of data, which can be anything from one + * byte to a set of lines and possible just a piece of the last + * line */ + ssize_t i; + ssize_t clipamount = 0; + bool restart = FALSE; + + data->req.headerbytecount += (long)gotbytes; + + pp->nread_resp += gotbytes; + for(i = 0; i < gotbytes; ptr++, i++) { + perline++; + if(*ptr=='\n') { + /* a newline is CRLF in pp-talk, so the CR is ignored as + 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, + pp->linestart_resp, (size_t)perline, conn); + + /* + * We pass all response-lines to the callback function registered + * for "headers". The response lines can be seen as a kind of + * headers. + */ + result = Curl_client_write(conn, CLIENTWRITE_HEADER, + pp->linestart_resp, perline); + if(result) + return result; + + if(pp->endofresp(pp, code)) { + /* This is the end of the last line, copy the last line to the + start of the buffer and zero terminate, for old times sake (and + krb4)! */ + char *meow; + int n; + for(meow=pp->linestart_resp, n=0; meowlinestart_resp = ptr+1; /* advance pointer */ + i++; /* skip this before getting out */ + + *size = pp->nread_resp; /* size of the response */ + pp->nread_resp = 0; /* restart */ + break; + } + perline=0; /* line starts over here */ + pp->linestart_resp = ptr+1; + } + } + + if(!keepon && (i != gotbytes)) { + /* We found the end of the response lines, but we didn't parse the + full chunk of data we have read from the server. We therefore need + to store the rest of the data to be checked on the next invoke as + it may actually contain another end of response already! */ + clipamount = gotbytes - i; + restart = TRUE; + DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing " + "server response left\n", + (int)clipamount)); + } + else if(keepon) { + + if((perline == gotbytes) && (gotbytes > BUFSIZE/2)) { + /* We got an excessive line without newlines and we need to deal + with it. We keep the first bytes of the line then we throw + away the rest. */ + infof(data, "Excessive server response line length received, " + "%zd bytes. Stripping\n", gotbytes); + restart = TRUE; + + /* we keep 40 bytes since all our pingpong protocols are only + interested in the first piece */ + clipamount = 40; + } + else if(pp->nread_resp > BUFSIZE/2) { + /* We got a large chunk of data and there's potentially still + trailing data to take care of, so we put any such part in the + "cache", clear the buffer to make space and restart. */ + clipamount = perline; + restart = TRUE; + } + } + else if(i == gotbytes) + restart = TRUE; + + if(clipamount) { + pp->cache_size = clipamount; + pp->cache = malloc(pp->cache_size); + if(pp->cache) + memcpy(pp->cache, pp->linestart_resp, pp->cache_size); + else + return CURLE_OUT_OF_MEMORY; + } + if(restart) { + /* now reset a few variables to start over nicely from the start of + the big buffer */ + pp->nread_resp = 0; /* start over from scratch in the buffer */ + ptr = pp->linestart_resp = buf; + perline = 0; + } + + } /* there was data */ + + } /* while there's buffer left and loop is requested */ + + pp->pending_resp = FALSE; + + return result; +} + +int Curl_pp_getsock(struct pingpong *pp, + curl_socket_t *socks, + int numsocks) +{ + struct connectdata *conn = pp->conn; + + if(!numsocks) + return GETSOCK_BLANK; + + socks[0] = conn->sock[FIRSTSOCKET]; + + if(pp->sendleft) { + /* write mode */ + return GETSOCK_WRITESOCK(0); + } + + /* read mode */ + return GETSOCK_READSOCK(0); +} + +CURLcode Curl_pp_flushsend(struct pingpong *pp) +{ + /* we have a piece of a command still left to send */ + struct connectdata *conn = pp->conn; + ssize_t written; + CURLcode result = CURLE_OK; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + + result = Curl_write(conn, sock, pp->sendthis + pp->sendsize - + pp->sendleft, pp->sendleft, &written); + if(result) + return result; + + if(written != (ssize_t)pp->sendleft) { + /* only a fraction was sent */ + pp->sendleft -= written; + } + else { + free(pp->sendthis); + pp->sendthis=NULL; + pp->sendleft = pp->sendsize = 0; + pp->response = Curl_tvnow(); + } + return CURLE_OK; +} + +CURLcode Curl_pp_disconnect(struct pingpong *pp) +{ + if(pp->cache) { + free(pp->cache); + pp->cache = NULL; + } + return CURLE_OK; +} + + + +#endif diff --git a/lib/curl_polarssl.c b/lib/curl_polarssl.c new file mode 100644 index 000000000..81c70264f --- /dev/null +++ b/lib/curl_polarssl.c @@ -0,0 +1,596 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2010, 2011, Hoi-Ho Chan, + * Copyright (C) 2012 - 2013, 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 + * 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. + * + ***************************************************************************/ + +/* + * Source file for all PolarSSL-specific code for the TLS/SSL layer. No code + * but curl_sslgen.c should ever call or use these functions. + * + */ + +#include "curl_setup.h" + +#ifdef USE_POLARSSL + +#include +#include +#include +#include +#include +#include + +#include +#include + +#if POLARSSL_VERSION_NUMBER<0x01000000 +/* + Earlier versions of polarssl had no WANT_READ or WANT_WRITE, only TRY_AGAIN +*/ +#define POLARSSL_ERR_NET_WANT_READ POLARSSL_ERR_NET_TRY_AGAIN +#define POLARSSL_ERR_NET_WANT_WRITE POLARSSL_ERR_NET_TRY_AGAIN +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_inet_pton.h" +#include "curl_polarssl.h" +#include "curl_sslgen.h" +#include "curl_parsedate.h" +#include "curl_connect.h" /* for the connect timeout */ +#include "curl_select.h" +#include "curl_rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* version dependent differences */ +#if POLARSSL_VERSION_NUMBER < 0x01010000 +/* the old way */ +#define HAVEGE_RANDOM havege_rand +#else +/* from 1.1.0 */ +#define HAVEGE_RANDOM havege_random +#endif + +/* Define this to enable lots of debugging for PolarSSL */ +#undef POLARSSL_DEBUG + +#ifdef POLARSSL_DEBUG +static void polarssl_debug(void *context, int level, char *line) +{ + struct SessionHandle *data = NULL; + + if(!context) + return; + + data = (struct SessionHandle *)context; + + infof(data, "%s\n", line); +} +#else +#endif + +static Curl_recv polarssl_recv; +static Curl_send polarssl_send; + + +static CURLcode +polarssl_connect_step1(struct connectdata *conn, + int sockindex) +{ + struct SessionHandle *data = conn->data; + struct ssl_connect_data* connssl = &conn->ssl[sockindex]; + + bool sni = TRUE; /* default is SNI enabled */ + int ret = -1; +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + void *old_session = NULL; + size_t old_session_size = 0; + + /* PolarSSL only supports SSLv3 and TLSv1 */ + if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) { + failf(data, "PolarSSL does not support SSLv2"); + return CURLE_SSL_CONNECT_ERROR; + } + else if(data->set.ssl.version == CURL_SSLVERSION_SSLv3) + sni = FALSE; /* SSLv3 has no SNI */ + + havege_init(&connssl->hs); + + /* Load the trusted CA */ + memset(&connssl->cacert, 0, sizeof(x509_cert)); + + if(data->set.str[STRING_SSL_CAFILE]) { + ret = x509parse_crtfile(&connssl->cacert, + data->set.str[STRING_SSL_CAFILE]); + + if(ret<0) { + failf(data, "Error reading ca cert file %s: -0x%04X", + data->set.str[STRING_SSL_CAFILE], ret); + + if(data->set.ssl.verifypeer) + return CURLE_SSL_CACERT_BADFILE; + } + } + + /* Load the client certificate */ + memset(&connssl->clicert, 0, sizeof(x509_cert)); + + if(data->set.str[STRING_CERT]) { + ret = x509parse_crtfile(&connssl->clicert, + data->set.str[STRING_CERT]); + + if(ret) { + failf(data, "Error reading client cert file %s: -0x%04X", + data->set.str[STRING_CERT], -ret); + return CURLE_SSL_CERTPROBLEM; + } + } + + /* Load the client private key */ + if(data->set.str[STRING_KEY]) { + ret = x509parse_keyfile(&connssl->rsa, + data->set.str[STRING_KEY], + data->set.str[STRING_KEY_PASSWD]); + + if(ret) { + failf(data, "Error reading private key %s: -0x%04X", + data->set.str[STRING_KEY], -ret); + return CURLE_SSL_CERTPROBLEM; + } + } + + /* Load the CRL */ + memset(&connssl->crl, 0, sizeof(x509_crl)); + + if(data->set.str[STRING_SSL_CRLFILE]) { + ret = x509parse_crlfile(&connssl->crl, + data->set.str[STRING_SSL_CRLFILE]); + + if(ret) { + failf(data, "Error reading CRL file %s: -0x%04X", + data->set.str[STRING_SSL_CRLFILE], -ret); + return CURLE_SSL_CRL_BADFILE; + } + } + + infof(data, "PolarSSL: Connecting to %s:%d\n", + conn->host.name, conn->remote_port); + + if(ssl_init(&connssl->ssl)) { + failf(data, "PolarSSL: ssl_init failed"); + return CURLE_SSL_CONNECT_ERROR; + } + + ssl_set_endpoint(&connssl->ssl, SSL_IS_CLIENT); + ssl_set_authmode(&connssl->ssl, SSL_VERIFY_OPTIONAL); + + ssl_set_rng(&connssl->ssl, HAVEGE_RANDOM, + &connssl->hs); + ssl_set_bio(&connssl->ssl, + net_recv, &conn->sock[sockindex], + net_send, &conn->sock[sockindex]); + + +#if POLARSSL_VERSION_NUMBER<0x01000000 + ssl_set_ciphers(&connssl->ssl, ssl_default_ciphers); +#else + ssl_set_ciphersuites(&connssl->ssl, ssl_default_ciphersuites); +#endif + if(!Curl_ssl_getsessionid(conn, &old_session, &old_session_size)) { + memcpy(&connssl->ssn, old_session, old_session_size); + infof(data, "PolarSSL re-using session\n"); + } + +/* PolarSSL SVN revision r1316 to r1317, matching <1.2.0 is to cover Ubuntu's + 1.1.4 version and the like */ +#if POLARSSL_VERSION_NUMBER<0x01020000 + ssl_set_session(&connssl->ssl, 1, 600, + &connssl->ssn); +#else + ssl_set_session(&connssl->ssl, + &connssl->ssn); +#endif + + ssl_set_ca_chain(&connssl->ssl, + &connssl->cacert, + &connssl->crl, + conn->host.name); + + ssl_set_own_cert(&connssl->ssl, + &connssl->clicert, &connssl->rsa); + + if(!Curl_inet_pton(AF_INET, conn->host.name, &addr) && +#ifdef ENABLE_IPV6 + !Curl_inet_pton(AF_INET6, conn->host.name, &addr) && +#endif + sni && ssl_set_hostname(&connssl->ssl, conn->host.name)) { + infof(data, "WARNING: failed to configure " + "server name indication (SNI) TLS extension\n"); + } + +#ifdef POLARSSL_DEBUG + ssl_set_dbg(&connssl->ssl, polarssl_debug, data); +#endif + + connssl->connecting_state = ssl_connect_2; + + return CURLE_OK; +} + +static CURLcode +polarssl_connect_step2(struct connectdata *conn, + int sockindex) +{ + int ret; + struct SessionHandle *data = conn->data; + struct ssl_connect_data* connssl = &conn->ssl[sockindex]; + char buffer[1024]; + + conn->recv[sockindex] = polarssl_recv; + conn->send[sockindex] = polarssl_send; + + for(;;) { + if(!(ret = ssl_handshake(&connssl->ssl))) + break; + else if(ret != POLARSSL_ERR_NET_WANT_READ && + ret != POLARSSL_ERR_NET_WANT_WRITE) { + failf(data, "ssl_handshake returned -0x%04X", -ret); + return CURLE_SSL_CONNECT_ERROR; + } + else { + if(ret == POLARSSL_ERR_NET_WANT_READ) { + connssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + if(ret == POLARSSL_ERR_NET_WANT_WRITE) { + connssl->connecting_state = ssl_connect_2_writing; + return CURLE_OK; + } + failf(data, "SSL_connect failed with error %d.", ret); + return CURLE_SSL_CONNECT_ERROR; + + } + } + + infof(data, "PolarSSL: Handshake complete, cipher is %s\n", +#if POLARSSL_VERSION_NUMBER<0x01000000 + ssl_get_cipher(&conn->ssl[sockindex].ssl) +#elif POLARSSL_VERSION_NUMBER >= 0x01010000 + ssl_get_ciphersuite(&conn->ssl[sockindex].ssl) +#else + ssl_get_ciphersuite_name(&conn->ssl[sockindex].ssl) +#endif + ); + + ret = ssl_get_verify_result(&conn->ssl[sockindex].ssl); + + if(ret && data->set.ssl.verifypeer) { + if(ret & BADCERT_EXPIRED) + failf(data, "Cert verify failed: BADCERT_EXPIRED"); + + if(ret & BADCERT_REVOKED) { + failf(data, "Cert verify failed: BADCERT_REVOKED"); + return CURLE_SSL_CACERT; + } + + if(ret & BADCERT_CN_MISMATCH) + failf(data, "Cert verify failed: BADCERT_CN_MISMATCH"); + + if(ret & BADCERT_NOT_TRUSTED) + failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED"); + + return CURLE_PEER_FAILED_VERIFICATION; + } + +/* PolarSSL SVN revision r1316 to r1317, matching <1.2.0 is to cover Ubuntu's + 1.1.4 version and the like */ +#if POLARSSL_VERSION_NUMBER<0x01020000 + if(conn->ssl[sockindex].ssl.peer_cert) { +#else + if(ssl_get_peer_cert(&(connssl->ssl))) { +#endif + /* If the session was resumed, there will be no peer certs */ + memset(buffer, 0, sizeof(buffer)); + +/* PolarSSL SVN revision r1316 to r1317, matching <1.2.0 is to cover Ubuntu's + 1.1.4 version and the like */ +#if POLARSSL_VERSION_NUMBER<0x01020000 + if(x509parse_cert_info(buffer, sizeof(buffer), (char *)"* ", + conn->ssl[sockindex].ssl.peer_cert) != -1) +#else + if(x509parse_cert_info(buffer, sizeof(buffer), (char *)"* ", + ssl_get_peer_cert(&(connssl->ssl))) != -1) +#endif + infof(data, "Dumping cert info:\n%s\n", buffer); + } + + connssl->connecting_state = ssl_connect_3; + infof(data, "SSL connected\n"); + + return CURLE_OK; +} + +static CURLcode +polarssl_connect_step3(struct connectdata *conn, + int sockindex) +{ + CURLcode retcode = CURLE_OK; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct SessionHandle *data = conn->data; + void *old_ssl_sessionid = NULL; + ssl_session *our_ssl_sessionid = &conn->ssl[sockindex].ssn ; + int incache; + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + + /* Save the current session data for possible re-use */ + incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL)); + if(incache) { + if(old_ssl_sessionid != our_ssl_sessionid) { + infof(data, "old SSL session ID is stale, removing\n"); + Curl_ssl_delsessionid(conn, old_ssl_sessionid); + incache = FALSE; + } + } + if(!incache) { + void *new_session = malloc(sizeof(ssl_session)); + + if(new_session) { + memcpy(new_session, our_ssl_sessionid, + sizeof(ssl_session)); + + retcode = Curl_ssl_addsessionid(conn, new_session, + sizeof(ssl_session)); + } + else { + retcode = CURLE_OUT_OF_MEMORY; + } + + if(retcode) { + failf(data, "failed to store ssl session"); + return retcode; + } + } + + connssl->connecting_state = ssl_connect_done; + + return CURLE_OK; +} + +static ssize_t polarssl_send(struct connectdata *conn, + int sockindex, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + int ret = -1; + + ret = ssl_write(&conn->ssl[sockindex].ssl, + (unsigned char *)mem, len); + + if(ret < 0) { + *curlcode = (ret == POLARSSL_ERR_NET_WANT_WRITE) ? + CURLE_AGAIN : CURLE_SEND_ERROR; + ret = -1; + } + + return ret; +} + +void Curl_polarssl_close_all(struct SessionHandle *data) +{ + (void)data; +} + +void Curl_polarssl_close(struct connectdata *conn, int sockindex) +{ + rsa_free(&conn->ssl[sockindex].rsa); + x509_free(&conn->ssl[sockindex].clicert); + x509_free(&conn->ssl[sockindex].cacert); + x509_crl_free(&conn->ssl[sockindex].crl); + ssl_free(&conn->ssl[sockindex].ssl); +} + +static ssize_t polarssl_recv(struct connectdata *conn, + int num, + char *buf, + size_t buffersize, + CURLcode *curlcode) +{ + int ret = -1; + ssize_t len = -1; + + memset(buf, 0, buffersize); + ret = ssl_read(&conn->ssl[num].ssl, (unsigned char *)buf, buffersize); + + if(ret <= 0) { + if(ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) + return 0; + + *curlcode = (ret == POLARSSL_ERR_NET_WANT_READ) ? + CURLE_AGAIN : CURLE_RECV_ERROR; + return -1; + } + + len = ret; + + return len; +} + +void Curl_polarssl_session_free(void *ptr) +{ + free(ptr); +} + +size_t Curl_polarssl_version(char *buffer, size_t size) +{ + unsigned int version = version_get_number(); + return snprintf(buffer, size, "PolarSSL/%d.%d.%d", version>>24, + (version>>16)&0xff, (version>>8)&0xff); +} + +static CURLcode +polarssl_connect_common(struct connectdata *conn, + int sockindex, + bool nonblocking, + bool *done) +{ + CURLcode retcode; + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + curl_socket_t sockfd = conn->sock[sockindex]; + long timeout_ms; + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1==connssl->connecting_state) { + /* Find out how much more time we're allowed */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + retcode = polarssl_connect_step1(conn, sockindex); + if(retcode) + return retcode; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading + || connssl->connecting_state == ssl_connect_2_writing) { + + curl_socket_t writefd = ssl_connect_2_writing== + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading== + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if + * this connection is part of a multi handle and this loop would + * execute again. This permits the owner of a multi handle to + * abort a connection attempt before step2 has completed while + * ensuring that a client using select() or epoll() will always + * have a valid fdset to wait on. + */ + retcode = polarssl_connect_step2(conn, sockindex); + if(retcode || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return retcode; + + } /* repeat step2 until all transactions are done. */ + + if(ssl_connect_3==connssl->connecting_state) { + retcode = polarssl_connect_step3(conn, sockindex); + if(retcode) + return retcode; + } + + if(ssl_connect_done==connssl->connecting_state) { + connssl->state = ssl_connection_complete; + conn->recv[sockindex] = polarssl_recv; + conn->send[sockindex] = polarssl_send; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + +CURLcode +Curl_polarssl_connect_nonblocking(struct connectdata *conn, + int sockindex, + bool *done) +{ + return polarssl_connect_common(conn, sockindex, TRUE, done); +} + + +CURLcode +Curl_polarssl_connect(struct connectdata *conn, + int sockindex) +{ + CURLcode retcode; + bool done = FALSE; + + retcode = polarssl_connect_common(conn, sockindex, FALSE, &done); + if(retcode) + return retcode; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +#endif diff --git a/lib/curl_pop3.c b/lib/curl_pop3.c new file mode 100644 index 000000000..0d157f00b --- /dev/null +++ b/lib/curl_pop3.c @@ -0,0 +1,1764 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + * RFC1734 POP3 Authentication + * RFC1939 POP3 protocol + * RFC2195 CRAM-MD5 authentication + * RFC2384 POP URL Scheme + * RFC2449 POP3 Extension Mechanism + * RFC2595 Using TLS with IMAP, POP3 and ACAP + * RFC2831 DIGEST-MD5 authentication + * RFC4616 PLAIN authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_POP3 + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_if2ip.h" +#include "curl_hostip.h" +#include "curl_progress.h" +#include "curl_transfer.h" +#include "curl_escape.h" +#include "curl_http.h" /* for HTTP proxy tunnel stuff */ +#include "curl_socks.h" +#include "curl_pop3.h" + +#include "curl_strtoofft.h" +#include "curl_strequal.h" +#include "curl_sslgen.h" +#include "curl_connect.h" +#include "curl_strerror.h" +#include "curl_select.h" +#include "curl_multiif.h" +#include "curl_url.h" +#include "curl_rawstr.h" +#include "curl_sasl.h" +#include "curl_md5.h" +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* Local API functions */ +static CURLcode pop3_parse_url_path(struct connectdata *conn); +static CURLcode pop3_parse_custom_request(struct connectdata *conn); +static CURLcode pop3_regular_transfer(struct connectdata *conn, bool *done); +static CURLcode pop3_do(struct connectdata *conn, bool *done); +static CURLcode pop3_done(struct connectdata *conn, CURLcode status, + bool premature); +static CURLcode pop3_connect(struct connectdata *conn, bool *done); +static CURLcode pop3_disconnect(struct connectdata *conn, bool dead); +static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done); +static int pop3_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); +static CURLcode pop3_doing(struct connectdata *conn, bool *dophase_done); +static CURLcode pop3_setup_connection(struct connectdata *conn); + +/* + * POP3 protocol handler. + */ + +const struct Curl_handler Curl_handler_pop3 = { + "POP3", /* scheme */ + pop3_setup_connection, /* setup_connection */ + pop3_do, /* do_it */ + pop3_done, /* done */ + ZERO_NULL, /* do_more */ + pop3_connect, /* connect_it */ + pop3_multi_statemach, /* connecting */ + pop3_doing, /* doing */ + pop3_getsock, /* proto_getsock */ + pop3_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + pop3_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_POP3, /* defport */ + CURLPROTO_POP3, /* protocol */ + PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY /* flags */ +}; + +#ifdef USE_SSL +/* + * POP3S protocol handler. + */ + +const struct Curl_handler Curl_handler_pop3s = { + "POP3S", /* scheme */ + pop3_setup_connection, /* setup_connection */ + pop3_do, /* do_it */ + pop3_done, /* done */ + ZERO_NULL, /* do_more */ + pop3_connect, /* connect_it */ + pop3_multi_statemach, /* connecting */ + pop3_doing, /* doing */ + pop3_getsock, /* proto_getsock */ + pop3_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + pop3_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_POP3S, /* defport */ + CURLPROTO_POP3 | CURLPROTO_POP3S, /* protocol */ + PROTOPT_CLOSEACTION | PROTOPT_SSL + | PROTOPT_NOURLQUERY /* flags */ +}; +#endif + +#ifndef CURL_DISABLE_HTTP +/* + * HTTP-proxyed POP3 protocol handler. + */ + +static const struct Curl_handler Curl_handler_pop3_proxy = { + "POP3", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_POP3, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +#ifdef USE_SSL +/* + * HTTP-proxyed POP3S protocol handler. + */ + +static const struct Curl_handler Curl_handler_pop3s_proxy = { + "POP3S", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_POP3S, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; +#endif +#endif + +/* Function that checks for an ending pop3 status code at the start of the + given string, but also detects the APOP timestamp from the server greeting + as well as the supported authentication types and allowed SASL mechanisms + from the CAPA response. */ +static int pop3_endofresp(struct pingpong *pp, int *resp) +{ + char *line = pp->linestart_resp; + size_t len = strlen(pp->linestart_resp); + struct connectdata *conn = pp->conn; + struct pop3_conn *pop3c = &conn->proto.pop3c; + size_t wordlen; + size_t i; + + /* Do we have an error response? */ + if(len >= 4 && !memcmp("-ERR", line, 4)) { + *resp = '-'; + + return FALSE; + } + + /* Are we processing servergreet responses */ + if(pop3c->state == POP3_SERVERGREET) { + /* Look for the APOP timestamp */ + if(len >= 3 && line[len - 3] == '>') { + for(i = 0; i < len - 3; ++i) { + if(line[i] == '<') { + /* Calculate the length of the timestamp */ + size_t timestamplen = len - 2 - i; + + /* Allocate some memory for the timestamp */ + pop3c->apoptimestamp = (char *)calloc(1, timestamplen + 1); + + if(!pop3c->apoptimestamp) + break; + + /* Copy the timestamp */ + memcpy(pop3c->apoptimestamp, line + i, timestamplen); + pop3c->apoptimestamp[timestamplen] = '\0'; + break; + } + } + } + } + /* Are we processing CAPA command responses? */ + else if(pop3c->state == POP3_CAPA) { + + /* Do we have the terminating character? */ + if(len >= 1 && !memcmp(line, ".", 1)) { + *resp = '+'; + + return TRUE; + } + + /* Does the server support clear text? */ + if(len >= 4 && !memcmp(line, "USER", 4)) { + pop3c->authtypes |= POP3_TYPE_CLEARTEXT; + return FALSE; + } + + /* Does the server support APOP? */ + if(len >= 4 && !memcmp(line, "APOP", 4)) { + pop3c->authtypes |= POP3_TYPE_APOP; + return FALSE; + } + + /* Does the server support SASL? */ + if(len < 4 || memcmp(line, "SASL", 4)) + return FALSE; + + pop3c->authtypes |= POP3_TYPE_SASL; + + /* Advance past the SASL keyword */ + line += 4; + len -= 4; + + /* Loop through the data line */ + for(;;) { + while(len && + (*line == ' ' || *line == '\t' || + *line == '\r' || *line == '\n')) { + + if(*line == '\n') + return FALSE; + + line++; + len--; + } + + if(!len) + break; + + /* Extract the word */ + for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && + line[wordlen] != '\t' && line[wordlen] != '\r' && + line[wordlen] != '\n';) + wordlen++; + + /* Test the word for a matching authentication mechanism */ + if(wordlen == 5 && !memcmp(line, "LOGIN", 5)) + pop3c->authmechs |= SASL_MECH_LOGIN; + else if(wordlen == 5 && !memcmp(line, "PLAIN", 5)) + pop3c->authmechs |= SASL_MECH_PLAIN; + else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8)) + pop3c->authmechs |= SASL_MECH_CRAM_MD5; + else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10)) + pop3c->authmechs |= SASL_MECH_DIGEST_MD5; + else if(wordlen == 6 && !memcmp(line, "GSSAPI", 6)) + pop3c->authmechs |= SASL_MECH_GSSAPI; + else if(wordlen == 8 && !memcmp(line, "EXTERNAL", 8)) + pop3c->authmechs |= SASL_MECH_EXTERNAL; + else if(wordlen == 4 && !memcmp(line, "NTLM", 4)) + pop3c->authmechs |= SASL_MECH_NTLM; + + line += wordlen; + len -= wordlen; + } + } + + if((len < 1 || memcmp("+", line, 1)) && + (len < 3 || memcmp("+OK", line, 3))) + return FALSE; /* Nothing for us */ + + /* Otherwise it's a positive response */ + *resp = '+'; + + return TRUE; +} + +/* This is the ONLY way to change POP3 state! */ +static void state(struct connectdata *conn, pop3state newstate) +{ + struct pop3_conn *pop3c = &conn->proto.pop3c; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[] = { + "STOP", + "SERVERGREET", + "STARTTLS", + "CAPA", + "AUTH_PLAIN", + "AUTH_LOGIN", + "AUTH_LOGIN_PASSWD", + "AUTH_CRAMMD5", + "AUTH_DIGESTMD5", + "AUTH_DIGESTMD5_RESP", + "AUTH_NTLM", + "AUTH_NTLM_TYPE2MSG", + "AUTH", + "APOP", + "USER", + "PASS", + "COMMAND", + "QUIT", + /* LAST */ + }; + + if(pop3c->state != newstate) + infof(conn->data, "POP3 %p state change from %s to %s\n", + pop3c, names[pop3c->state], names[newstate]); +#endif + + pop3c->state = newstate; +} + +static CURLcode pop3_state_capa(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + + pop3c->authmechs = 0; /* No known authentication mechanisms yet */ + pop3c->authused = 0; /* Clear the authentication mechanism used */ + + /* Check we have a username and password to authenticate with and end the + connect phase if we don't */ + if(!conn->bits.user_passwd) { + state(conn, POP3_STOP); + + return result; + } + + /* Send the CAPA command */ + result = Curl_pp_sendf(&pop3c->pp, "CAPA"); + + if(result) + return result; + + state(conn, POP3_CAPA); + + return CURLE_OK; +} + +static CURLcode pop3_state_user(struct connectdata *conn) +{ + CURLcode result; + struct FTP *pop3 = conn->data->state.proto.pop3; + + /* Send the USER command */ + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "USER %s", + pop3->user ? pop3->user : ""); + if(result) + return result; + + state(conn, POP3_USER); + + return CURLE_OK; +} + +#ifndef CURL_DISABLE_CRYPTO_AUTH +static CURLcode pop3_state_apop(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + size_t i; + MD5_context *ctxt; + unsigned char digest[MD5_DIGEST_LEN]; + char secret[2 * MD5_DIGEST_LEN + 1]; + + ctxt = Curl_MD5_init(Curl_DIGEST_MD5); + if(!ctxt) + return CURLE_OUT_OF_MEMORY; + + Curl_MD5_update(ctxt, (const unsigned char *) pop3c->apoptimestamp, + curlx_uztoui(strlen(pop3c->apoptimestamp))); + + Curl_MD5_update(ctxt, (const unsigned char *) conn->passwd, + curlx_uztoui(strlen(conn->passwd))); + + /* Finalise the digest */ + Curl_MD5_final(ctxt, digest); + + /* Convert the calculated 16 octet digest into a 32 byte hex string */ + for(i = 0; i < MD5_DIGEST_LEN; i++) + snprintf(&secret[2 * i], 3, "%02x", digest[i]); + + result = Curl_pp_sendf(&pop3c->pp, "APOP %s %s", conn->user, secret); + + if(!result) + state(conn, POP3_APOP); + + return result; +} +#endif + +static CURLcode pop3_authenticate(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + const char *mech = NULL; + pop3state authstate = POP3_STOP; + + /* Check supported authentication mechanisms by decreasing order of + security */ +#ifndef CURL_DISABLE_CRYPTO_AUTH + if(pop3c->authmechs & SASL_MECH_DIGEST_MD5) { + mech = "DIGEST-MD5"; + authstate = POP3_AUTH_DIGESTMD5; + pop3c->authused = SASL_MECH_DIGEST_MD5; + } + else if(pop3c->authmechs & SASL_MECH_CRAM_MD5) { + mech = "CRAM-MD5"; + authstate = POP3_AUTH_CRAMMD5; + pop3c->authused = SASL_MECH_CRAM_MD5; + } + else +#endif +#ifdef USE_NTLM + if(pop3c->authmechs & SASL_MECH_NTLM) { + mech = "NTLM"; + authstate = POP3_AUTH_NTLM; + pop3c->authused = SASL_MECH_NTLM; + } + else +#endif + if(pop3c->authmechs & SASL_MECH_LOGIN) { + mech = "LOGIN"; + authstate = POP3_AUTH_LOGIN; + pop3c->authused = SASL_MECH_LOGIN; + } + else if(pop3c->authmechs & SASL_MECH_PLAIN) { + mech = "PLAIN"; + authstate = POP3_AUTH_PLAIN; + pop3c->authused = SASL_MECH_PLAIN; + } + else { + infof(conn->data, "No known SASL authentication mechanisms supported!\n"); + result = CURLE_LOGIN_DENIED; /* Other mechanisms not supported */ + } + + if(!result) { + result = Curl_pp_sendf(&pop3c->pp, "AUTH %s", mech); + + if(!result) + state(conn, authstate); + } + + return result; +} + +/* For the POP3 "protocol connect" and "doing" phases only */ +static int pop3_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks) +{ + return Curl_pp_getsock(&conn->proto.pop3c.pp, socks, numsocks); +} + +#ifdef USE_SSL +static void pop3_to_pop3s(struct connectdata *conn) +{ + conn->handler = &Curl_handler_pop3s; +} +#else +#define pop3_to_pop3s(x) Curl_nop_stmt +#endif + +/* For the initial server greeting */ +static CURLcode pop3_state_servergreet_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct pop3_conn *pop3c = &conn->proto.pop3c; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Got unexpected pop3-server response"); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { + /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch + to TLS connection now */ + result = Curl_pp_sendf(&pop3c->pp, "STLS"); + state(conn, POP3_STARTTLS); + } + else + result = pop3_state_capa(conn); + + return result; +} + +/* For STARTTLS responses */ +static CURLcode pop3_state_starttls_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + if(data->set.use_ssl != CURLUSESSL_TRY) { + failf(data, "STARTTLS denied. %c", pop3code); + result = CURLE_USE_SSL_FAILED; + } + else + result = pop3_state_capa(conn); + } + else { + /* Curl_ssl_connect is BLOCKING */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(CURLE_OK == result) { + pop3_to_pop3s(conn); + result = pop3_state_capa(conn); + } + } + + return result; +} + +/* For CAPA responses */ +static CURLcode pop3_state_capa_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') + result = pop3_state_user(conn); + else { + /* Check supported authentication types by decreasing order of security */ + if(conn->proto.pop3c.authtypes & POP3_TYPE_SASL) + result = pop3_authenticate(conn); +#ifndef CURL_DISABLE_CRYPTO_AUTH + else if(conn->proto.pop3c.authtypes & POP3_TYPE_APOP) + result = pop3_state_apop(conn); +#endif + else if(conn->proto.pop3c.authtypes & POP3_TYPE_CLEARTEXT) + result = pop3_state_user(conn); + else { + infof(conn->data, "No known authentication types supported!\n"); + result = CURLE_LOGIN_DENIED; /* Other types not supported */ + } + } + + return result; +} + +/* For AUTH PLAIN responses */ +static CURLcode pop3_state_auth_plain_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *plainauth = NULL; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied. %c", pop3code); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the authorisation message */ + result = Curl_sasl_create_plain_message(data, conn->user, conn->passwd, + &plainauth, &len); + + /* Send the message */ + if(!result) { + if(plainauth) { + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", plainauth); + + if(!result) + state(conn, POP3_AUTH); + } + + Curl_safefree(plainauth); + } + } + + return result; +} + +/* For AUTH LOGIN responses */ +static CURLcode pop3_state_auth_login_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *authuser = NULL; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied: %d", pop3code); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the user message */ + result = Curl_sasl_create_login_message(data, conn->user, + &authuser, &len); + + /* Send the user */ + if(!result) { + if(authuser) { + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", authuser); + + if(!result) + state(conn, POP3_AUTH_LOGIN_PASSWD); + } + + Curl_safefree(authuser); + } + } + + return result; +} + +/* For AUTH LOGIN user entry responses */ +static CURLcode pop3_state_auth_login_password_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *authpasswd = NULL; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied: %d", pop3code); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the password message */ + result = Curl_sasl_create_login_message(data, conn->passwd, + &authpasswd, &len); + + /* Send the password */ + if(!result) { + if(authpasswd) { + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", authpasswd); + + if(!result) + state(conn, POP3_AUTH); + } + + Curl_safefree(authpasswd); + } + } + + return result; +} + +#ifndef CURL_DISABLE_CRYPTO_AUTH +/* For AUTH CRAM-MD5 responses */ +static CURLcode pop3_state_auth_cram_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + char *chlg64 = data->state.buffer; + size_t len = 0; + char *rplyb64 = NULL; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied: %d", pop3code); + return CURLE_LOGIN_DENIED; + } + + /* Get the challenge */ + for(chlg64 += 2; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++) + ; + + /* Terminate the challenge */ + if(*chlg64 != '=') { + for(len = strlen(chlg64); len--;) + if(chlg64[len] != '\r' && chlg64[len] != '\n' && chlg64[len] != ' ' && + chlg64[len] != '\t') + break; + + if(++len) { + chlg64[len] = '\0'; + } + } + + /* Create the response message */ + result = Curl_sasl_create_cram_md5_message(data, chlg64, conn->user, + conn->passwd, &rplyb64, &len); + + /* Send the response */ + if(!result) { + if(rplyb64) { + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", rplyb64); + + if(!result) + state(conn, POP3_AUTH); + } + + Curl_safefree(rplyb64); + } + + return result; +} + +/* For AUTH DIGEST-MD5 challenge responses */ +static CURLcode pop3_state_auth_digest_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + char *chlg64 = data->state.buffer; + size_t len = 0; + char *rplyb64 = NULL; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied: %d", pop3code); + return CURLE_LOGIN_DENIED; + } + + /* Get the challenge */ + for(chlg64 += 2; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++) + ; + + /* Create the response message */ + result = Curl_sasl_create_digest_md5_message(data, chlg64, conn->user, + conn->passwd, "pop", + &rplyb64, &len); + + /* Send the response */ + if(!result) { + if(rplyb64) { + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", rplyb64); + + if(!result) + state(conn, POP3_AUTH_DIGESTMD5_RESP); + } + + Curl_safefree(rplyb64); + } + + return result; +} + +/* For AUTH DIGEST-MD5 challenge-response responses */ +static CURLcode pop3_state_auth_digest_resp_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Authentication failed: %d", pop3code); + result = CURLE_LOGIN_DENIED; + } + else { + /* Send an empty response */ + result = Curl_pp_sendf(&conn->proto.pop3c.pp, ""); + + if(!result) + state(conn, POP3_AUTH); + } + + return result; +} +#endif + +#ifdef USE_NTLM +/* For AUTH NTLM responses */ +static CURLcode pop3_state_auth_ntlm_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *type1msg = NULL; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied: %d", pop3code); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the type-1 message */ + result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, + &conn->ntlm, + &type1msg, &len); + + /* Send the message */ + if(!result) { + if(type1msg) { + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", type1msg); + + if(!result) + state(conn, POP3_AUTH_NTLM_TYPE2MSG); + } + + Curl_safefree(type1msg); + } + } + + return result; +} + +/* For NTLM type-2 responses (sent in reponse to our type-1 message) */ +static CURLcode pop3_state_auth_ntlm_type2msg_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *type3msg = NULL; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied: %d", pop3code); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the type-3 message */ + result = Curl_sasl_create_ntlm_type3_message(data, + data->state.buffer + 2, + conn->user, conn->passwd, + &conn->ntlm, + &type3msg, &len); + + /* Send the message */ + if(!result) { + if(type3msg) { + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", type3msg); + + if(!result) + state(conn, POP3_AUTH); + } + + Curl_safefree(type3msg); + } + } + + return result; +} +#endif + +/* For final responses to the AUTH sequence */ +static CURLcode pop3_state_auth_final_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Authentication failed: %d", pop3code); + result = CURLE_LOGIN_DENIED; + } + + /* End of connect phase */ + state(conn, POP3_STOP); + + return result; +} + +static CURLcode pop3_state_apop_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Authentication failed: %d", pop3code); + result = CURLE_LOGIN_DENIED; + } + + /* End of connect phase */ + state(conn, POP3_STOP); + + return result; +} + +/* For USER responses */ +static CURLcode pop3_state_user_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct FTP *pop3 = data->state.proto.pop3; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied. %c", pop3code); + result = CURLE_LOGIN_DENIED; + } + else + /* Send the PASS command */ + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "PASS %s", + pop3->passwd ? pop3->passwd : ""); + if(result) + return result; + + state(conn, POP3_PASS); + + return result; +} + +/* For PASS responses */ +static CURLcode pop3_state_pass_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + failf(data, "Access denied. %c", pop3code); + result = CURLE_LOGIN_DENIED; + } + + /* End of connect phase */ + state(conn, POP3_STOP); + + return result; +} + +/* Start the DO phase for the command */ +static CURLcode pop3_command(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + const char *command = NULL; + + /* Calculate the default command */ + if(pop3c->mailbox[0] == '\0' || conn->data->set.ftp_list_only) { + command = "LIST"; + + if(pop3c->mailbox[0] != '\0') { + /* Message specific LIST so skip the BODY transfer */ + struct FTP *pop3 = conn->data->state.proto.pop3; + pop3->transfer = FTPTRANSFER_INFO; + } + } + else + command = "RETR"; + + /* Send the command */ + if(pop3c->mailbox[0] != '\0') + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s %s", + (pop3c->custom && pop3c->custom[0] != '\0' ? + pop3c->custom : command), pop3c->mailbox); + else + result = Curl_pp_sendf(&conn->proto.pop3c.pp, + (pop3c->custom && pop3c->custom[0] != '\0' ? + pop3c->custom : command)); + + if(result) + return result; + + state(conn, POP3_COMMAND); + + return result; +} + +/* For command responses */ +static CURLcode pop3_state_command_resp(struct connectdata *conn, + int pop3code, + pop3state instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct FTP *pop3 = data->state.proto.pop3; + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pingpong *pp = &pop3c->pp; + + (void)instate; /* no use for this yet */ + + if(pop3code != '+') { + state(conn, POP3_STOP); + return CURLE_RECV_ERROR; + } + + /* This 'OK' line ends with a CR LF pair which is the two first bytes of the + EOB string so count this is two matching bytes. This is necessary to make + the code detect the EOB if the only data than comes now is %2e CR LF like + when there is no body to return. */ + pop3c->eob = 2; + + /* But since this initial CR LF pair is not part of the actual body, we set + the strip counter here so that these bytes won't be delivered. */ + pop3c->strip = 2; + + /* POP3 download */ + Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, pop3->bytecountp, + -1, NULL); /* no upload here */ + + if(pp->cache) { + /* The header "cache" contains a bunch of data that is actually body + content so send it as such. Note that there may even be additional + "headers" after the body */ + + if(!data->set.opt_no_body) { + result = Curl_pop3_write(conn, pp->cache, pp->cache_size); + if(result) + return result; + } + + /* Free the cache */ + Curl_safefree(pp->cache); + + /* Reset the cache size */ + pp->cache_size = 0; + } + + /* End of do phase */ + state(conn, POP3_STOP); + + return result; +} + +static CURLcode pop3_statemach_act(struct connectdata *conn) +{ + CURLcode result; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + int pop3code; + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pingpong *pp = &pop3c->pp; + size_t nread = 0; + + /* Flush any data that needs to be sent */ + if(pp->sendleft) + return Curl_pp_flushsend(pp); + + /* Read the response from the server */ + result = Curl_pp_readresp(sock, pp, &pop3code, &nread); + if(result) + return result; + + if(pop3code) { + /* We have now received a full POP3 server response */ + switch(pop3c->state) { + case POP3_SERVERGREET: + result = pop3_state_servergreet_resp(conn, pop3code, pop3c->state); + break; + + case POP3_STARTTLS: + result = pop3_state_starttls_resp(conn, pop3code, pop3c->state); + break; + + case POP3_CAPA: + result = pop3_state_capa_resp(conn, pop3code, pop3c->state); + break; + + case POP3_AUTH_PLAIN: + result = pop3_state_auth_plain_resp(conn, pop3code, pop3c->state); + break; + + case POP3_AUTH_LOGIN: + result = pop3_state_auth_login_resp(conn, pop3code, pop3c->state); + break; + + case POP3_AUTH_LOGIN_PASSWD: + result = pop3_state_auth_login_password_resp(conn, pop3code, + pop3c->state); + break; + +#ifndef CURL_DISABLE_CRYPTO_AUTH + case POP3_AUTH_CRAMMD5: + result = pop3_state_auth_cram_resp(conn, pop3code, pop3c->state); + break; + + case POP3_AUTH_DIGESTMD5: + result = pop3_state_auth_digest_resp(conn, pop3code, pop3c->state); + break; + + case POP3_AUTH_DIGESTMD5_RESP: + result = pop3_state_auth_digest_resp_resp(conn, pop3code, pop3c->state); + break; +#endif + +#ifdef USE_NTLM + case POP3_AUTH_NTLM: + result = pop3_state_auth_ntlm_resp(conn, pop3code, pop3c->state); + break; + + case POP3_AUTH_NTLM_TYPE2MSG: + result = pop3_state_auth_ntlm_type2msg_resp(conn, pop3code, + pop3c->state); + break; +#endif + + case POP3_AUTH: + result = pop3_state_auth_final_resp(conn, pop3code, pop3c->state); + break; + + case POP3_APOP: + result = pop3_state_apop_resp(conn, pop3code, pop3c->state); + break; + + case POP3_USER: + result = pop3_state_user_resp(conn, pop3code, pop3c->state); + break; + + case POP3_PASS: + result = pop3_state_pass_resp(conn, pop3code, pop3c->state); + break; + + case POP3_COMMAND: + result = pop3_state_command_resp(conn, pop3code, pop3c->state); + break; + + case POP3_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + state(conn, POP3_STOP); + break; + } + } + + return result; +} + +/* Called repeatedly until done from curl_multi.c */ +static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done) +{ + struct pop3_conn *pop3c = &conn->proto.pop3c; + CURLcode result = Curl_pp_multi_statemach(&pop3c->pp); + + *done = (pop3c->state == POP3_STOP) ? TRUE : FALSE; + + return result; +} + +static CURLcode pop3_easy_statemach(struct connectdata *conn) +{ + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pingpong *pp = &pop3c->pp; + CURLcode result = CURLE_OK; + + while(pop3c->state != POP3_STOP) { + result = Curl_pp_easy_statemach(pp); + if(result) + break; + } + + return result; +} + +/* Allocate and initialize the POP3 struct for the current SessionHandle if + required */ +static CURLcode pop3_init(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct FTP *pop3 = data->state.proto.pop3; + + if(!pop3) { + pop3 = data->state.proto.pop3 = calloc(sizeof(struct FTP), 1); + if(!pop3) + return CURLE_OUT_OF_MEMORY; + } + + /* Get some initial data into the pop3 struct */ + pop3->bytecountp = &data->req.bytecount; + + /* No need to duplicate user+password, the connectdata struct won't change + during a session, but we re-init them here since on subsequent inits + since the conn struct may have changed or been replaced. + */ + pop3->user = conn->user; + pop3->passwd = conn->passwd; + + return CURLE_OK; +} + +/*********************************************************************** + * + * pop3_connect() + * + * This function should do everything that is to be considered a part of the + * connection phase. + * + * The variable 'done' points to will be TRUE if the protocol-layer connect + * phase is done when this function returns, or FALSE is not. When called as + * a part of the easy interface, it will always be TRUE. + */ +static CURLcode pop3_connect(struct connectdata *conn, bool *done) +{ + CURLcode result; + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct SessionHandle *data = conn->data; + struct pingpong *pp = &pop3c->pp; + + *done = FALSE; /* default to not done yet */ + + /* If there already is a protocol-specific struct allocated for this + sessionhandle, deal with it */ + Curl_reset_reqproto(conn); + + result = pop3_init(conn); + if(CURLE_OK != result) + return result; + + /* We always support persistent connections on pop3 */ + conn->bits.close = FALSE; + + pp->response_time = RESP_TIMEOUT; /* set default response time-out */ + pp->statemach_act = pop3_statemach_act; + pp->endofresp = pop3_endofresp; + pp->conn = conn; + + if(conn->handler->flags & PROTOPT_SSL) { + /* POP3S is simply pop3 with SSL for the control channel */ + /* so perform the SSL initialization for this socket */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(result) + return result; + } + + /* Initialise the response reader stuff */ + Curl_pp_init(pp); + + /* Start off waiting for the server greeting response */ + state(conn, POP3_SERVERGREET); + + if(data->state.used_interface == Curl_if_multi) + result = pop3_multi_statemach(conn, done); + else { + result = pop3_easy_statemach(conn); + if(!result) + *done = TRUE; + } + + return result; +} + +/*********************************************************************** + * + * pop3_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode pop3_done(struct connectdata *conn, CURLcode status, + bool premature) +{ + struct SessionHandle *data = conn->data; + struct FTP *pop3 = data->state.proto.pop3; + struct pop3_conn *pop3c = &conn->proto.pop3c; + CURLcode result = CURLE_OK; + + (void)premature; + + if(!pop3) + /* When the easy handle is removed from the multi while libcurl is still + * trying to resolve the host name, it seems that the pop3 struct is not + * yet initialized, but the removal action calls Curl_done() which calls + * this function. So we simply return success if no pop3 pointer is set. + */ + return CURLE_OK; + + if(status) { + conn->bits.close = TRUE; /* marked for closure */ + result = status; /* use the already set error code */ + } + + /* Cleanup our do based variables */ + Curl_safefree(pop3c->mailbox); + Curl_safefree(pop3c->custom); + + /* Clear the transfer mode for the next connection */ + pop3->transfer = FTPTRANSFER_BODY; + + return result; +} + +/*********************************************************************** + * + * pop3_perform() + * + * This is the actual DO function for POP3. Get a file/directory according to + * the options previously setup. + */ +static CURLcode pop3_perform(struct connectdata *conn, bool *connected, + bool *dophase_done) +{ + /* This is POP3 and no proxy */ + CURLcode result = CURLE_OK; + + DEBUGF(infof(conn->data, "DO phase starts\n")); + + if(conn->data->set.opt_no_body) { + /* Requested no body means no transfer */ + struct FTP *pop3 = conn->data->state.proto.pop3; + pop3->transfer = FTPTRANSFER_INFO; + } + + *dophase_done = FALSE; /* not done yet */ + + /* Start the first command in the DO phase */ + result = pop3_command(conn); + if(result) + return result; + + /* Run the state-machine */ + if(conn->data->state.used_interface == Curl_if_multi) + result = pop3_multi_statemach(conn, dophase_done); + else { + result = pop3_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect[FIRSTSOCKET]; + + if(*dophase_done) + DEBUGF(infof(conn->data, "DO phase is complete\n")); + + return result; +} + +/*********************************************************************** + * + * pop3_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (pop3_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode pop3_do(struct connectdata *conn, bool *done) +{ + CURLcode retcode = CURLE_OK; + + *done = FALSE; /* default to false */ + + /* + Since connections can be re-used between SessionHandles, this might be a + connection already existing but on a fresh SessionHandle struct so we must + make sure we have a good 'struct POP3' to play with. For new connections, + the struct POP3 is allocated and setup in the pop3_connect() function. + */ + Curl_reset_reqproto(conn); + retcode = pop3_init(conn); + if(retcode) + return retcode; + + /* Parse the URL path */ + retcode = pop3_parse_url_path(conn); + if(retcode) + return retcode; + + /* Parse the custom request */ + retcode = pop3_parse_custom_request(conn); + if(retcode) + return retcode; + + retcode = pop3_regular_transfer(conn, done); + + return retcode; +} + +/*********************************************************************** + * + * pop3_quit() + * + * This should be called before calling sclose(). We should then wait for the + * response from the server before returning. The calling code should then try + * to close the connection. + */ +static CURLcode pop3_quit(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + result = Curl_pp_sendf(&conn->proto.pop3c.pp, "QUIT", NULL); + if(result) + return result; + + state(conn, POP3_QUIT); + + result = pop3_easy_statemach(conn); + + return result; +} + +/*********************************************************************** + * + * pop3_disconnect() + * + * Disconnect from an POP3 server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode pop3_disconnect(struct connectdata *conn, + bool dead_connection) +{ + struct pop3_conn *pop3c = &conn->proto.pop3c; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to */ + + /* The POP3 session may or may not have been allocated/setup at this + point! */ + if(!dead_connection && pop3c->pp.conn) + (void)pop3_quit(conn); /* ignore errors on the LOGOUT */ + + /* Disconnect from the server */ + Curl_pp_disconnect(&pop3c->pp); + + /* Cleanup the SASL module */ + Curl_sasl_cleanup(conn, pop3c->authused); + + /* Cleanup our connection based variables */ + Curl_safefree(pop3c->apoptimestamp); + + return CURLE_OK; +} + +/*********************************************************************** + * + * pop3_parse_url_path() + * + * Parse the URL path into separate path components. + */ +static CURLcode pop3_parse_url_path(struct connectdata *conn) +{ + /* The POP3 struct is already initialised in pop3_connect() */ + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct SessionHandle *data = conn->data; + const char *path = data->state.path; + + /* URL decode the path and use this mailbox */ + return Curl_urldecode(data, path, 0, &pop3c->mailbox, NULL, TRUE); +} + +static CURLcode pop3_parse_custom_request(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct pop3_conn *pop3c = &conn->proto.pop3c; + struct SessionHandle *data = conn->data; + const char *custom = conn->data->set.str[STRING_CUSTOMREQUEST]; + + /* URL decode the custom request */ + if(custom) + result = Curl_urldecode(data, custom, 0, &pop3c->custom, NULL, TRUE); + + return result; +} + +/* Call this when the DO phase has completed */ +static CURLcode pop3_dophase_done(struct connectdata *conn, bool connected) +{ + struct FTP *pop3 = conn->data->state.proto.pop3; + + (void)connected; + + if(pop3->transfer != FTPTRANSFER_BODY) + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + return CURLE_OK; +} + +/* Called from curl_multi.c while DOing */ +static CURLcode pop3_doing(struct connectdata *conn, bool *dophase_done) +{ + CURLcode result = pop3_multi_statemach(conn, dophase_done); + + if(result) + DEBUGF(infof(conn->data, "DO phase failed\n")); + else { + if(*dophase_done) { + result = pop3_dophase_done(conn, FALSE /* not connected */); + + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + } + + return result; +} + +/*********************************************************************** + * + * pop3_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + */ +static CURLcode pop3_regular_transfer(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + bool connected = FALSE; + struct SessionHandle *data = conn->data; + + /* Make sure size is unknown at this point */ + data->req.size = -1; + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, 0); + Curl_pgrsSetDownloadSize(data, 0); + + result = pop3_perform(conn, &connected, dophase_done); + + if(CURLE_OK == result) { + if(!*dophase_done) + /* The DO phase has not completed yet */ + return CURLE_OK; + + result = pop3_dophase_done(conn, connected); + if(result) + return result; + } + + return result; +} + +static CURLcode pop3_setup_connection(struct connectdata * conn) +{ + struct SessionHandle *data = conn->data; + + if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { + /* Unless we have asked to tunnel pop3 operations through the proxy, we + switch and use HTTP operations only */ +#ifndef CURL_DISABLE_HTTP + if(conn->handler == &Curl_handler_pop3) + conn->handler = &Curl_handler_pop3_proxy; + else { +#ifdef USE_SSL + conn->handler = &Curl_handler_pop3s_proxy; +#else + failf(data, "POP3S not supported!"); + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + + /* We explicitly mark this connection as persistent here as we're doing + POP3 over HTTP and thus we accidentally avoid setting this value + otherwise */ + conn->bits.close = FALSE; +#else + failf(data, "POP3 over http proxy requires HTTP support built-in!"); + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + + data->state.path++; /* don't include the initial slash */ + + return CURLE_OK; +} + +/* This function scans the body after the end-of-body and writes everything + until the end is found */ +CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) +{ + /* This code could be made into a special function in the handler struct */ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct SingleRequest *k = &data->req; + + struct pop3_conn *pop3c = &conn->proto.pop3c; + bool strip_dot = FALSE; + size_t last = 0; + size_t i; + + /* Search through the buffer looking for the end-of-body marker which is + 5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches + the eob so the server will have prefixed it with an extra dot which we + need to strip out. Additionally the marker could of course be spread out + over 5 different data chunks */ + for(i = 0; i < nread; i++) { + size_t prev = pop3c->eob; + + switch(str[i]) { + case 0x0d: + if(pop3c->eob == 0) { + pop3c->eob++; + + if(i) { + /* Write out the body part that didn't match */ + result = Curl_client_write(conn, CLIENTWRITE_BODY, &str[last], + i - last); + + if(result) + return result; + + last = i; + } + } + else if(pop3c->eob == 3) + pop3c->eob++; + else + /* If the character match wasn't at position 0 or 3 then restart the + pattern matching */ + pop3c->eob = 1; + break; + + case 0x0a: + if(pop3c->eob == 1 || pop3c->eob == 4) + pop3c->eob++; + else + /* If the character match wasn't at position 1 or 4 then start the + search again */ + pop3c->eob = 0; + break; + + case 0x2e: + if(pop3c->eob == 2) + pop3c->eob++; + else if(pop3c->eob == 3) { + /* We have an extra dot after the CRLF which we need to strip off */ + strip_dot = TRUE; + pop3c->eob = 0; + } + else + /* If the character match wasn't at position 2 then start the search + again */ + pop3c->eob = 0; + break; + + default: + pop3c->eob = 0; + break; + } + + /* Did we have a partial match which has subsequently failed? */ + if(prev && prev >= pop3c->eob) { + /* Strip can only be non-zero for the very first mismatch after CRLF + and then both prev and strip are equal and nothing will be output + below */ + while(prev && pop3c->strip) { + prev--; + pop3c->strip--; + } + + if(prev) { + /* If the partial match was the CRLF and dot then only write the CRLF + as the server would have inserted the dot */ + result = Curl_client_write(conn, CLIENTWRITE_BODY, (char*)POP3_EOB, + strip_dot ? prev - 1 : prev); + + if(result) + return result; + + last = i; + strip_dot = FALSE; + } + } + } + + if(pop3c->eob == POP3_EOB_LEN) { + /* We have a full match so the transfer is done, however we must transfer + the CRLF at the start of the EOB as this is considered to be part of the + message as per RFC-1939, sect. 3 */ + result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)POP3_EOB, 2); + + k->keepon &= ~KEEP_RECV; + pop3c->eob = 0; + + return result; + } + + if(pop3c->eob) + /* While EOB is matching nothing should be output */ + return CURLE_OK; + + if(nread - last) { + result = Curl_client_write(conn, CLIENTWRITE_BODY, &str[last], + nread - last); + } + + return result; +} + +#endif /* CURL_DISABLE_POP3 */ diff --git a/lib/curl_progress.c b/lib/curl_progress.c new file mode 100644 index 000000000..88f802d0a --- /dev/null +++ b/lib/curl_progress.c @@ -0,0 +1,474 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_progress.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero + byte) */ +static void time2str(char *r, curl_off_t seconds) +{ + curl_off_t d, h, m, s; + if(seconds <= 0) { + strcpy(r, "--:--:--"); + return; + } + h = seconds / CURL_OFF_T_C(3600); + if(h <= CURL_OFF_T_C(99)) { + m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); + s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); + snprintf(r, 9, "%2" FORMAT_OFF_T ":%02" FORMAT_OFF_T ":%02" FORMAT_OFF_T, + h, m, s); + } + else { + /* this equals to more than 99 hours, switch to a more suitable output + format to fit within the limits. */ + d = seconds / CURL_OFF_T_C(86400); + h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600); + if(d <= CURL_OFF_T_C(999)) + snprintf(r, 9, "%3" FORMAT_OFF_T "d %02" FORMAT_OFF_T "h", d, h); + else + snprintf(r, 9, "%7" FORMAT_OFF_T "d", d); + } +} + +/* The point of this function would be to return a string of the input data, + but never longer than 5 columns (+ one zero byte). + Add suffix k, M, G when suitable... */ +static char *max5data(curl_off_t bytes, char *max5) +{ +#define ONE_KILOBYTE CURL_OFF_T_C(1024) +#define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE) +#define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE) +#define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE) +#define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) + + if(bytes < CURL_OFF_T_C(100000)) + snprintf(max5, 6, "%5" FORMAT_OFF_T, bytes); + + else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) + snprintf(max5, 6, "%4" FORMAT_OFF_T "k", bytes/ONE_KILOBYTE); + + else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) + /* 'XX.XM' is good as long as we're less than 100 megs */ + snprintf(max5, 6, "%2" FORMAT_OFF_T ".%0" FORMAT_OFF_T "M", + bytes/ONE_MEGABYTE, + (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); + +#if (CURL_SIZEOF_CURL_OFF_T > 4) + + else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE) + /* 'XXXXM' is good until we're at 10000MB or above */ + snprintf(max5, 6, "%4" FORMAT_OFF_T "M", bytes/ONE_MEGABYTE); + + else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE) + /* 10000 MB - 100 GB, we show it as XX.XG */ + snprintf(max5, 6, "%2" FORMAT_OFF_T ".%0" FORMAT_OFF_T "G", + bytes/ONE_GIGABYTE, + (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) ); + + else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE) + /* up to 10000GB, display without decimal: XXXXG */ + snprintf(max5, 6, "%4" FORMAT_OFF_T "G", bytes/ONE_GIGABYTE); + + else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) + /* up to 10000TB, display without decimal: XXXXT */ + snprintf(max5, 6, "%4" FORMAT_OFF_T "T", bytes/ONE_TERABYTE); + + else + /* up to 10000PB, display without decimal: XXXXP */ + snprintf(max5, 6, "%4" FORMAT_OFF_T "P", bytes/ONE_PETABYTE); + + /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number + can hold, but our data type is signed so 8192PB will be the maximum. */ + +#else + + else + snprintf(max5, 6, "%4" FORMAT_OFF_T "M", bytes/ONE_MEGABYTE); + +#endif + + return max5; +} + +/* + + New proposed interface, 9th of February 2000: + + pgrsStartNow() - sets start time + pgrsSetDownloadSize(x) - known expected download size + pgrsSetUploadSize(x) - known expected upload size + pgrsSetDownloadCounter() - amount of data currently downloaded + pgrsSetUploadCounter() - amount of data currently uploaded + pgrsUpdate() - show progress + pgrsDone() - transfer complete + +*/ + +int Curl_pgrsDone(struct connectdata *conn) +{ + int rc; + struct SessionHandle *data = conn->data; + data->progress.lastshow=0; + rc = Curl_pgrsUpdate(conn); /* the final (forced) update */ + if(rc) + return rc; + + if(!(data->progress.flags & PGRS_HIDE) && + !data->progress.callback) + /* only output if we don't use a progress callback and we're not + * hidden */ + fprintf(data->set.err, "\n"); + + data->progress.speeder_c = 0; /* reset the progress meter display */ + return 0; +} + +/* reset all times except redirect, and reset the known transfer sizes */ +void Curl_pgrsResetTimesSizes(struct SessionHandle *data) +{ + data->progress.t_nslookup = 0.0; + data->progress.t_connect = 0.0; + data->progress.t_pretransfer = 0.0; + data->progress.t_starttransfer = 0.0; + + Curl_pgrsSetDownloadSize(data, 0); + Curl_pgrsSetUploadSize(data, 0); +} + +void Curl_pgrsTime(struct SessionHandle *data, timerid timer) +{ + struct timeval now = Curl_tvnow(); + + switch(timer) { + default: + case TIMER_NONE: + /* mistake filter */ + break; + case TIMER_STARTSINGLE: + /* This is set at the start of a single fetch */ + data->progress.t_startsingle = now; + break; + + case TIMER_STARTACCEPT: + data->progress.t_acceptdata = Curl_tvnow(); + break; + + case TIMER_NAMELOOKUP: + data->progress.t_nslookup = + Curl_tvdiff_secs(now, data->progress.t_startsingle); + break; + case TIMER_CONNECT: + data->progress.t_connect = + Curl_tvdiff_secs(now, data->progress.t_startsingle); + break; + case TIMER_APPCONNECT: + data->progress.t_appconnect = + Curl_tvdiff_secs(now, data->progress.t_startsingle); + break; + case TIMER_PRETRANSFER: + data->progress.t_pretransfer = + Curl_tvdiff_secs(now, data->progress.t_startsingle); + break; + case TIMER_STARTTRANSFER: + data->progress.t_starttransfer = + Curl_tvdiff_secs(now, data->progress.t_startsingle); + break; + case TIMER_POSTRANSFER: + /* this is the normal end-of-transfer thing */ + break; + case TIMER_REDIRECT: + data->progress.t_redirect = Curl_tvdiff_secs(now, data->progress.start); + break; + } +} + +void Curl_pgrsStartNow(struct SessionHandle *data) +{ + data->progress.speeder_c = 0; /* reset the progress meter display */ + data->progress.start = Curl_tvnow(); + /* clear all bits except HIDE and HEADERS_OUT */ + data->progress.flags &= PGRS_HIDE|PGRS_HEADERS_OUT; +} + +void Curl_pgrsSetDownloadCounter(struct SessionHandle *data, curl_off_t size) +{ + data->progress.downloaded = size; +} + +void Curl_pgrsSetUploadCounter(struct SessionHandle *data, curl_off_t size) +{ + data->progress.uploaded = size; +} + +void Curl_pgrsSetDownloadSize(struct SessionHandle *data, curl_off_t size) +{ + data->progress.size_dl = size; + if(size >= 0) + data->progress.flags |= PGRS_DL_SIZE_KNOWN; + else + data->progress.flags &= ~PGRS_DL_SIZE_KNOWN; +} + +void Curl_pgrsSetUploadSize(struct SessionHandle *data, curl_off_t size) +{ + data->progress.size_ul = size; + if(size >= 0) + data->progress.flags |= PGRS_UL_SIZE_KNOWN; + else + data->progress.flags &= ~PGRS_UL_SIZE_KNOWN; +} + +/* + * Curl_pgrsUpdate() returns 0 for success or the value returned by the + * progress callback! + */ +int Curl_pgrsUpdate(struct connectdata *conn) +{ + struct timeval now; + int result; + char max5[6][10]; + curl_off_t dlpercen=0; + curl_off_t ulpercen=0; + curl_off_t total_percen=0; + curl_off_t total_transfer; + curl_off_t total_expected_transfer; + curl_off_t timespent; + struct SessionHandle *data = conn->data; + int nowindex = data->progress.speeder_c% CURR_TIME; + int checkindex; + int countindex; /* amount of seconds stored in the speeder array */ + char time_left[10]; + char time_total[10]; + char time_spent[10]; + curl_off_t ulestimate=0; + curl_off_t dlestimate=0; + curl_off_t total_estimate; + bool shownow=FALSE; + + now = Curl_tvnow(); /* what time is it */ + + /* The time spent so far (from the start) */ + data->progress.timespent = + (double)(now.tv_sec - data->progress.start.tv_sec) + + (double)(now.tv_usec - data->progress.start.tv_usec)/1000000.0; + timespent = (curl_off_t)data->progress.timespent; + + /* The average download speed this far */ + data->progress.dlspeed = (curl_off_t) + ((double)data->progress.downloaded/ + (data->progress.timespent>0?data->progress.timespent:1)); + + /* The average upload speed this far */ + data->progress.ulspeed = (curl_off_t) + ((double)data->progress.uploaded/ + (data->progress.timespent>0?data->progress.timespent:1)); + + /* Calculations done at most once a second, unless end is reached */ + if(data->progress.lastshow != (long)now.tv_sec) { + shownow = TRUE; + + data->progress.lastshow = now.tv_sec; + + /* Let's do the "current speed" thing, which should use the fastest + of the dl/ul speeds. Store the faster speed at entry 'nowindex'. */ + data->progress.speeder[ nowindex ] = + data->progress.downloaded>data->progress.uploaded? + data->progress.downloaded:data->progress.uploaded; + + /* remember the exact time for this moment */ + data->progress.speeder_time [ nowindex ] = now; + + /* advance our speeder_c counter, which is increased every time we get + here and we expect it to never wrap as 2^32 is a lot of seconds! */ + data->progress.speeder_c++; + + /* figure out how many index entries of data we have stored in our speeder + array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of + transfer. Imagine, after one second we have filled in two entries, + after two seconds we've filled in three entries etc. */ + countindex = ((data->progress.speeder_c>=CURR_TIME)? + CURR_TIME:data->progress.speeder_c) - 1; + + /* first of all, we don't do this if there's no counted seconds yet */ + if(countindex) { + long span_ms; + + /* Get the index position to compare with the 'nowindex' position. + Get the oldest entry possible. While we have less than CURR_TIME + entries, the first entry will remain the oldest. */ + checkindex = (data->progress.speeder_c>=CURR_TIME)? + data->progress.speeder_c%CURR_TIME:0; + + /* Figure out the exact time for the time span */ + span_ms = Curl_tvdiff(now, + data->progress.speeder_time[checkindex]); + if(0 == span_ms) + span_ms=1; /* at least one millisecond MUST have passed */ + + /* Calculate the average speed the last 'span_ms' milliseconds */ + { + curl_off_t amount = data->progress.speeder[nowindex]- + data->progress.speeder[checkindex]; + + if(amount > CURL_OFF_T_C(4294967) /* 0xffffffff/1000 */) + /* the 'amount' value is bigger than would fit in 32 bits if + multiplied with 1000, so we use the double math for this */ + data->progress.current_speed = (curl_off_t) + ((double)amount/((double)span_ms/1000.0)); + else + /* the 'amount' value is small enough to fit within 32 bits even + when multiplied with 1000 */ + data->progress.current_speed = amount*CURL_OFF_T_C(1000)/span_ms; + } + } + else + /* the first second we use the main average */ + data->progress.current_speed = + (data->progress.ulspeed>data->progress.dlspeed)? + data->progress.ulspeed:data->progress.dlspeed; + + } /* Calculations end */ + + if(!(data->progress.flags & PGRS_HIDE)) { + + /* progress meter has not been shut off */ + + if(data->set.fprogress) { + /* There's a callback set, so we call that instead of writing + anything ourselves. This really is the way to go. */ + result= data->set.fprogress(data->set.progress_client, + (double)data->progress.size_dl, + (double)data->progress.downloaded, + (double)data->progress.size_ul, + (double)data->progress.uploaded); + if(result) + failf(data, "Callback aborted"); + return result; + } + + if(!shownow) + /* only show the internal progress meter once per second */ + return 0; + + /* If there's no external callback set, use internal code to show + progress */ + + if(!(data->progress.flags & PGRS_HEADERS_OUT)) { + if(data->state.resume_from) { + fprintf(data->set.err, + "** Resuming transfer from byte position %" FORMAT_OFF_T "\n", + data->state.resume_from); + } + fprintf(data->set.err, + " %% Total %% Received %% Xferd Average Speed " + "Time Time Time Current\n" + " Dload Upload " + "Total Spent Left Speed\n"); + data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */ + } + + /* Figure out the estimated time of arrival for the upload */ + if((data->progress.flags & PGRS_UL_SIZE_KNOWN) && + (data->progress.ulspeed > CURL_OFF_T_C(0))) { + ulestimate = data->progress.size_ul / data->progress.ulspeed; + + if(data->progress.size_ul > CURL_OFF_T_C(10000)) + ulpercen = data->progress.uploaded / + (data->progress.size_ul/CURL_OFF_T_C(100)); + else if(data->progress.size_ul > CURL_OFF_T_C(0)) + ulpercen = (data->progress.uploaded*100) / + data->progress.size_ul; + } + + /* ... and the download */ + if((data->progress.flags & PGRS_DL_SIZE_KNOWN) && + (data->progress.dlspeed > CURL_OFF_T_C(0))) { + dlestimate = data->progress.size_dl / data->progress.dlspeed; + + if(data->progress.size_dl > CURL_OFF_T_C(10000)) + dlpercen = data->progress.downloaded / + (data->progress.size_dl/CURL_OFF_T_C(100)); + else if(data->progress.size_dl > CURL_OFF_T_C(0)) + dlpercen = (data->progress.downloaded*100) / + data->progress.size_dl; + } + + /* Now figure out which of them is slower and use that one for the + total estimate! */ + total_estimate = ulestimate>dlestimate?ulestimate:dlestimate; + + /* create the three time strings */ + time2str(time_left, total_estimate > 0?(total_estimate - timespent):0); + time2str(time_total, total_estimate); + time2str(time_spent, timespent); + + /* Get the total amount of data expected to get transferred */ + total_expected_transfer = + (data->progress.flags & PGRS_UL_SIZE_KNOWN? + data->progress.size_ul:data->progress.uploaded)+ + (data->progress.flags & PGRS_DL_SIZE_KNOWN? + data->progress.size_dl:data->progress.downloaded); + + /* We have transferred this much so far */ + total_transfer = data->progress.downloaded + data->progress.uploaded; + + /* Get the percentage of data transferred so far */ + if(total_expected_transfer > CURL_OFF_T_C(10000)) + total_percen = total_transfer / + (total_expected_transfer/CURL_OFF_T_C(100)); + else if(total_expected_transfer > CURL_OFF_T_C(0)) + total_percen = (total_transfer*100) / total_expected_transfer; + + fprintf(data->set.err, + "\r" + "%3" FORMAT_OFF_T " %s " + "%3" FORMAT_OFF_T " %s " + "%3" FORMAT_OFF_T " %s %s %s %s %s %s %s", + total_percen, /* 3 letters */ /* total % */ + max5data(total_expected_transfer, max5[2]), /* total size */ + dlpercen, /* 3 letters */ /* rcvd % */ + max5data(data->progress.downloaded, max5[0]), /* rcvd size */ + ulpercen, /* 3 letters */ /* xfer % */ + max5data(data->progress.uploaded, max5[1]), /* xfer size */ + max5data(data->progress.dlspeed, max5[3]), /* avrg dl speed */ + max5data(data->progress.ulspeed, max5[4]), /* avrg ul speed */ + time_total, /* 8 letters */ /* total time */ + time_spent, /* 8 letters */ /* time spent */ + time_left, /* 8 letters */ /* time left */ + max5data(data->progress.current_speed, max5[5]) /* current speed */ + ); + + /* we flush the output stream to make it appear as soon as possible */ + fflush(data->set.err); + + } /* !(data->progress.flags & PGRS_HIDE) */ + + return 0; +} diff --git a/lib/curl_qssl.c b/lib/curl_qssl.c new file mode 100644 index 000000000..d140dc9eb --- /dev/null +++ b/lib/curl_qssl.c @@ -0,0 +1,501 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_QSOSSL + +#include + +#ifdef HAVE_LIMITS_H +# include +#endif + +#include +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_qssl.h" +#include "curl_sslgen.h" +#include "curl_connect.h" /* for the connect timeout */ +#include "curl_select.h" +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + + +int Curl_qsossl_init(void) + +{ + /* Nothing to do here. We must have connection data to initialize ssl, so + * defer. + */ + + return 1; +} + + +void Curl_qsossl_cleanup(void) + +{ + /* Nothing to do. */ +} + + +static CURLcode Curl_qsossl_init_session(struct SessionHandle * data) + +{ + int rc; + char * certname; + SSLInit initstr; + SSLInitApp initappstr; + + /* Initialize the job for SSL according to the current parameters. + * QsoSSL offers two ways to do it: SSL_Init_Application() that uses an + * application identifier to select certificates in the main certificate + * store, and SSL_Init() that uses named keyring files and a password. + * It is not possible to have different keyrings for the CAs and the + * local certificate. We thus use the certificate name to identify the + * keyring if given, else the CA file name. + * If the key file name is given, it is taken as the password for the + * keyring in certificate file. + * We first try to SSL_Init_Application(), then SSL_Init() if it failed. + */ + + certname = data->set.str[STRING_CERT]; + + if(!certname) { + certname = data->set.str[STRING_SSL_CAFILE]; + + if(!certname) + return CURLE_OK; /* Use previous setup. */ + } + + memset((char *) &initappstr, 0, sizeof initappstr); + initappstr.applicationID = certname; + initappstr.applicationIDLen = strlen(certname); + initappstr.protocol = SSL_VERSION_CURRENT; /* TLSV1 compat. SSLV[23]. */ + initappstr.sessionType = SSL_REGISTERED_AS_CLIENT; + rc = SSL_Init_Application(&initappstr); + + if(rc == SSL_ERROR_NOT_REGISTERED) { + initstr.keyringFileName = certname; + initstr.keyringPassword = data->set.str[STRING_KEY]; + initstr.cipherSuiteList = NULL; /* Use default. */ + initstr.cipherSuiteListLen = 0; + rc = SSL_Init(&initstr); + } + + switch (rc) { + + case 0: /* No error. */ + break; + + case SSL_ERROR_IO: + failf(data, "SSL_Init() I/O error: %s", strerror(errno)); + return CURLE_SSL_CONNECT_ERROR; + + case SSL_ERROR_BAD_CIPHER_SUITE: + return CURLE_SSL_CIPHER; + + case SSL_ERROR_KEYPASSWORD_EXPIRED: + case SSL_ERROR_NOT_REGISTERED: + return CURLE_SSL_CONNECT_ERROR; + + case SSL_ERROR_NO_KEYRING: + return CURLE_SSL_CACERT; + + case SSL_ERROR_CERT_EXPIRED: + return CURLE_SSL_CERTPROBLEM; + + default: + failf(data, "SSL_Init(): %s", SSL_Strerror(rc, NULL)); + return CURLE_SSL_CONNECT_ERROR; + } + + return CURLE_OK; +} + + +static CURLcode Curl_qsossl_create(struct connectdata * conn, int sockindex) + +{ + SSLHandle * h; + struct ssl_connect_data * connssl = &conn->ssl[sockindex]; + + h = SSL_Create(conn->sock[sockindex], SSL_ENCRYPT); + + if(!h) { + failf(conn->data, "SSL_Create() I/O error: %s", strerror(errno)); + return CURLE_SSL_CONNECT_ERROR; + } + + connssl->handle = h; + return CURLE_OK; +} + + +static int Curl_qsossl_trap_cert(SSLHandle * h) + +{ + return 1; /* Accept certificate. */ +} + + +static CURLcode Curl_qsossl_handshake(struct connectdata * conn, int sockindex) + +{ + int rc; + struct SessionHandle * data = conn->data; + struct ssl_connect_data * connssl = &conn->ssl[sockindex]; + SSLHandle * h = connssl->handle; + long timeout_ms; + + h->exitPgm = NULL; + + if(!data->set.ssl.verifyhost) + h->exitPgm = Curl_qsossl_trap_cert; + + /* figure out how long time we should wait at maximum */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* time-out, bail out, go home */ + failf(data, "Connection time-out"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* SSL_Handshake() timeout resolution is second, so round up. */ + h->timeout = (timeout_ms + 1000 - 1) / 1000; + + /* Set-up protocol. */ + + switch (data->set.ssl.version) { + + default: + case CURL_SSLVERSION_DEFAULT: + h->protocol = SSL_VERSION_CURRENT; /* TLSV1 compat. SSLV[23]. */ + break; + + case CURL_SSLVERSION_TLSv1: + h->protocol = TLS_VERSION_1; + break; + + case CURL_SSLVERSION_SSLv2: + h->protocol = SSL_VERSION_2; + break; + + case CURL_SSLVERSION_SSLv3: + h->protocol = SSL_VERSION_3; + break; + } + + rc = SSL_Handshake(h, SSL_HANDSHAKE_AS_CLIENT); + + switch (rc) { + + case 0: /* No error. */ + break; + + case SSL_ERROR_BAD_CERTIFICATE: + case SSL_ERROR_BAD_CERT_SIG: + case SSL_ERROR_NOT_TRUSTED_ROOT: + return CURLE_PEER_FAILED_VERIFICATION; + + case SSL_ERROR_BAD_CIPHER_SUITE: + case SSL_ERROR_NO_CIPHERS: + return CURLE_SSL_CIPHER; + + case SSL_ERROR_CERTIFICATE_REJECTED: + case SSL_ERROR_CERT_EXPIRED: + case SSL_ERROR_NO_CERTIFICATE: + return CURLE_SSL_CERTPROBLEM; + + case SSL_ERROR_IO: + failf(data, "SSL_Handshake() I/O error: %s", strerror(errno)); + return CURLE_SSL_CONNECT_ERROR; + + default: + failf(data, "SSL_Handshake(): %s", SSL_Strerror(rc, NULL)); + return CURLE_SSL_CONNECT_ERROR; + } + + return CURLE_OK; +} + + +static Curl_recv qsossl_recv; +static Curl_send qsossl_send; + +CURLcode Curl_qsossl_connect(struct connectdata * conn, int sockindex) + +{ + struct SessionHandle * data = conn->data; + struct ssl_connect_data * connssl = &conn->ssl[sockindex]; + int rc; + + rc = Curl_qsossl_init_session(data); + + if(rc == CURLE_OK) { + rc = Curl_qsossl_create(conn, sockindex); + + if(rc == CURLE_OK) + rc = Curl_qsossl_handshake(conn, sockindex); + else { + SSL_Destroy(connssl->handle); + connssl->handle = NULL; + connssl->use = FALSE; + connssl->state = ssl_connection_none; + } + } + if(rc == CURLE_OK) { + connssl->state = ssl_connection_complete; + conn->recv[sockindex] = qsossl_recv; + conn->send[sockindex] = qsossl_send; + } + + return rc; +} + + +static int Curl_qsossl_close_one(struct ssl_connect_data * conn, + struct SessionHandle * data) + +{ + int rc; + + if(!conn->handle) + return 0; + + rc = SSL_Destroy(conn->handle); + + if(rc) { + if(rc == SSL_ERROR_IO) { + failf(data, "SSL_Destroy() I/O error: %s", strerror(errno)); + return -1; + } + + /* An SSL error. */ + failf(data, "SSL_Destroy() returned error %s", SSL_Strerror(rc, NULL)); + return -1; + } + + conn->handle = NULL; + return 0; +} + + +void Curl_qsossl_close(struct connectdata *conn, int sockindex) + +{ + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + if(connssl->use) + (void) Curl_qsossl_close_one(connssl, data); +} + + +int Curl_qsossl_close_all(struct SessionHandle * data) + +{ + /* Unimplemented. */ + (void) data; + return 0; +} + + +int Curl_qsossl_shutdown(struct connectdata * conn, int sockindex) + +{ + struct ssl_connect_data * connssl = &conn->ssl[sockindex]; + struct SessionHandle *data = conn->data; + ssize_t nread; + int what; + int rc; + char buf[120]; + + if(!connssl->handle) + return 0; + + if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) + return 0; + + if(Curl_qsossl_close_one(connssl, data)) + return -1; + + rc = 0; + + what = Curl_socket_ready(conn->sock[sockindex], + CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); + + for(;;) { + if(what < 0) { + /* anything that gets here is fatally bad */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + rc = -1; + break; + } + + if(!what) { /* timeout */ + failf(data, "SSL shutdown timeout"); + break; + } + + /* Something to read, let's do it and hope that it is the close + notify alert from the server. No way to SSL_Read now, so use read(). */ + + nread = read(conn->sock[sockindex], buf, sizeof(buf)); + + if(nread < 0) { + failf(data, "read: %s", strerror(errno)); + rc = -1; + } + + if(nread <= 0) + break; + + what = Curl_socket_ready(conn->sock[sockindex], CURL_SOCKET_BAD, 0); + } + + return rc; +} + + +static ssize_t qsossl_send(struct connectdata * conn, int sockindex, + const void * mem, size_t len, CURLcode * curlcode) + +{ + /* SSL_Write() is said to return 'int' while write() and send() returns + 'size_t' */ + int rc; + + rc = SSL_Write(conn->ssl[sockindex].handle, (void *) mem, (int) len); + + if(rc < 0) { + switch(rc) { + + case SSL_ERROR_BAD_STATE: + /* The operation did not complete; the same SSL I/O function + should be called again later. This is basically an EWOULDBLOCK + equivalent. */ + *curlcode = CURLE_AGAIN; + return -1; + + case SSL_ERROR_IO: + switch (errno) { + case EWOULDBLOCK: + case EINTR: + *curlcode = CURLE_AGAIN; + return -1; + } + + failf(conn->data, "SSL_Write() I/O error: %s", strerror(errno)); + *curlcode = CURLE_SEND_ERROR; + return -1; + } + + /* An SSL error. */ + failf(conn->data, "SSL_Write() returned error %s", + SSL_Strerror(rc, NULL)); + *curlcode = CURLE_SEND_ERROR; + return -1; + } + + return (ssize_t) rc; /* number of bytes */ +} + + +static ssize_t qsossl_recv(struct connectdata * conn, int num, char * buf, + size_t buffersize, CURLcode * curlcode) + +{ + char error_buffer[120]; /* OpenSSL documents that this must be at + least 120 bytes long. */ + unsigned long sslerror; + int buffsize; + int nread; + + buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; + nread = SSL_Read(conn->ssl[num].handle, buf, buffsize); + + if(nread < 0) { + /* failed SSL_read */ + + switch (nread) { + + case SSL_ERROR_BAD_STATE: + /* there's data pending, re-invoke SSL_Read(). */ + *curlcode = CURLE_AGAIN; + return -1; + + case SSL_ERROR_IO: + switch (errno) { + case EWOULDBLOCK: + *curlcode = CURLE_AGAIN; + return -1; + } + + failf(conn->data, "SSL_Read() I/O error: %s", strerror(errno)); + *curlcode = CURLE_RECV_ERROR; + return -1; + + default: + failf(conn->data, "SSL read error: %s", SSL_Strerror(nread, NULL)); + *curlcode = CURLE_RECV_ERROR; + return -1; + } + } + return (ssize_t) nread; +} + + +size_t Curl_qsossl_version(char * buffer, size_t size) + +{ + strncpy(buffer, "IBM OS/400 SSL", size); + return strlen(buffer); +} + + +int Curl_qsossl_check_cxn(struct connectdata * cxn) + +{ + int err; + int errlen; + + /* The only thing that can be tested here is at the socket level. */ + + if(!cxn->ssl[FIRSTSOCKET].handle) + return 0; /* connection has been closed */ + + err = 0; + errlen = sizeof err; + + if(getsockopt(cxn->sock[FIRSTSOCKET], SOL_SOCKET, SO_ERROR, + (unsigned char *) &err, &errlen) || + errlen != sizeof err || err) + return 0; /* connection has been closed */ + + return -1; /* connection status unknown */ +} + +#endif /* USE_QSOSSL */ diff --git a/lib/curl_rawstr.c b/lib/curl_rawstr.c new file mode 100644 index 000000000..17fd1f3f5 --- /dev/null +++ b/lib/curl_rawstr.c @@ -0,0 +1,142 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_rawstr.h" + +/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because + its behavior is altered by the current locale. */ +char Curl_raw_toupper(char in) +{ + switch (in) { + case 'a': + return 'A'; + case 'b': + return 'B'; + case 'c': + return 'C'; + case 'd': + return 'D'; + case 'e': + return 'E'; + case 'f': + return 'F'; + case 'g': + return 'G'; + case 'h': + return 'H'; + case 'i': + return 'I'; + case 'j': + return 'J'; + case 'k': + return 'K'; + case 'l': + return 'L'; + case 'm': + return 'M'; + case 'n': + return 'N'; + case 'o': + return 'O'; + case 'p': + return 'P'; + case 'q': + return 'Q'; + case 'r': + return 'R'; + case 's': + return 'S'; + case 't': + return 'T'; + case 'u': + return 'U'; + case 'v': + return 'V'; + case 'w': + return 'W'; + case 'x': + return 'X'; + case 'y': + return 'Y'; + case 'z': + return 'Z'; + } + return in; +} + +/* + * Curl_raw_equal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * some further explanation to why this function is necessary. + * + * The function is capable of comparing a-z case insensitively even for + * non-ascii. + */ + +int Curl_raw_equal(const char *first, const char *second) +{ + while(*first && *second) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + /* get out of the loop as soon as they don't match */ + break; + first++; + second++; + } + /* we do the comparison here (possibly again), just to make sure that if the + loop above is skipped because one of the strings reached zero, we must not + return this as a successful match */ + return (Curl_raw_toupper(*first) == Curl_raw_toupper(*second)); +} + +int Curl_raw_nequal(const char *first, const char *second, size_t max) +{ + while(*first && *second && max) { + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) { + break; + } + max--; + first++; + second++; + } + if(0 == max) + return 1; /* they are equal this far */ + + return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); +} + +/* Copy an upper case version of the string from src to dest. The + * strings may overlap. No more than n characters of the string are copied + * (including any NUL) and the destination string will NOT be + * NUL-terminated if that limit is reached. + */ +void Curl_strntoupper(char *dest, const char *src, size_t n) +{ + if(n < 1) + return; + + do { + *dest++ = Curl_raw_toupper(*src); + } while(*src++ && --n); +} diff --git a/lib/curl_rtsp.c b/lib/curl_rtsp.c new file mode 100644 index 000000000..71e434c47 --- /dev/null +++ b/lib/curl_rtsp.c @@ -0,0 +1,807 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_RTSP + +#include "curl_urldata.h" +#include +#include "curl_transfer.h" +#include "curl_sendf.h" +#include "curl_multiif.h" +#include "curl_http.h" +#include "curl_url.h" +#include "curl_progress.h" +#include "curl_rtsp.h" +#include "curl_rawstr.h" +#include "curl_memory.h" +#include "curl_select.h" +#include "curl_connect.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + * TODO (general) + * -incoming server requests + * -server CSeq counter + * -digest authentication + * -connect thru proxy + * -pipelining? + */ + + +#define RTP_PKT_CHANNEL(p) ((int)((unsigned char)((p)[1]))) + +#define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \ + ((int)((unsigned char)((p)[3])))) + +/* protocol-specific functions set up to be called by the main engine */ +static CURLcode rtsp_do(struct connectdata *conn, bool *done); +static CURLcode rtsp_done(struct connectdata *conn, CURLcode, bool premature); +static CURLcode rtsp_connect(struct connectdata *conn, bool *done); +static CURLcode rtsp_disconnect(struct connectdata *conn, bool dead); + +static int rtsp_getsock_do(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); + +/* + * Parse and write out any available RTP data. + * + * nread: amount of data left after k->str. will be modified if RTP + * data is parsed and k->str is moved up + * readmore: whether or not the RTP parser needs more data right away + */ +static CURLcode rtsp_rtp_readwrite(struct SessionHandle *data, + struct connectdata *conn, + ssize_t *nread, + bool *readmore); + + +/* this returns the socket to wait for in the DO and DOING state for the multi + interface and then we're always _sending_ a request and thus we wait for + the single socket to become writable only */ +static int rtsp_getsock_do(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + /* write mode */ + (void)numsocks; /* unused, we trust it to be at least 1 */ + socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_WRITESOCK(0); +} + +static +CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len); + + +/* + * RTSP handler interface. + */ +const struct Curl_handler Curl_handler_rtsp = { + "RTSP", /* scheme */ + ZERO_NULL, /* setup_connection */ + rtsp_do, /* do_it */ + rtsp_done, /* done */ + ZERO_NULL, /* do_more */ + rtsp_connect, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + rtsp_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + rtsp_disconnect, /* disconnect */ + rtsp_rtp_readwrite, /* readwrite */ + PORT_RTSP, /* defport */ + CURLPROTO_RTSP, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +/* + * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not + * want to block the application forever while receiving a stream. Therefore, + * we cannot assume that an RTSP socket is dead just because it is readable. + * + * Instead, if it is readable, run Curl_getconnectinfo() to peek at the socket + * and distinguish between closed and data. + */ +bool Curl_rtsp_connisdead(struct connectdata *check) +{ + int sval; + bool ret_val = TRUE; + + sval = Curl_socket_ready(check->sock[FIRSTSOCKET], CURL_SOCKET_BAD, 0); + if(sval == 0) { + /* timeout */ + ret_val = FALSE; + } + else if(sval & CURL_CSELECT_ERR) { + /* socket is in an error state */ + ret_val = TRUE; + } + else if((sval & CURL_CSELECT_IN) && check->data) { + /* readable with no error. could be closed or could be alive but we can + only check if we have a proper SessionHandle for the connection */ + curl_socket_t connectinfo = Curl_getconnectinfo(check->data, &check); + if(connectinfo != CURL_SOCKET_BAD) + ret_val = FALSE; + } + + return ret_val; +} + +static CURLcode rtsp_connect(struct connectdata *conn, bool *done) +{ + CURLcode httpStatus; + struct SessionHandle *data = conn->data; + + httpStatus = Curl_http_connect(conn, done); + + /* Initialize the CSeq if not already done */ + if(data->state.rtsp_next_client_CSeq == 0) + data->state.rtsp_next_client_CSeq = 1; + if(data->state.rtsp_next_server_CSeq == 0) + data->state.rtsp_next_server_CSeq = 1; + + conn->proto.rtspc.rtp_channel = -1; + + return httpStatus; +} + +static CURLcode rtsp_disconnect(struct connectdata *conn, bool dead) +{ + (void) dead; + Curl_safefree(conn->proto.rtspc.rtp_buf); + return CURLE_OK; +} + + +static CURLcode rtsp_done(struct connectdata *conn, + CURLcode status, bool premature) +{ + struct SessionHandle *data = conn->data; + struct RTSP *rtsp = data->state.proto.rtsp; + CURLcode httpStatus; + long CSeq_sent; + long CSeq_recv; + + /* Bypass HTTP empty-reply checks on receive */ + if(data->set.rtspreq == RTSPREQ_RECEIVE) + premature = TRUE; + + httpStatus = Curl_http_done(conn, status, premature); + + if(rtsp) { + /* Check the sequence numbers */ + CSeq_sent = rtsp->CSeq_sent; + CSeq_recv = rtsp->CSeq_recv; + if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) { + failf(data, + "The CSeq of this request %ld did not match the response %ld", + CSeq_sent, CSeq_recv); + return CURLE_RTSP_CSEQ_ERROR; + } + else if(data->set.rtspreq == RTSPREQ_RECEIVE && + (conn->proto.rtspc.rtp_channel == -1)) { + infof(data, "Got an RTP Receive with a CSeq of %ld\n", CSeq_recv); + /* TODO CPC: Server -> Client logic here */ + } + } + + return httpStatus; +} + +static CURLcode rtsp_do(struct connectdata *conn, bool *done) +{ + struct SessionHandle *data = conn->data; + CURLcode result=CURLE_OK; + Curl_RtspReq rtspreq = data->set.rtspreq; + struct RTSP *rtsp; + struct HTTP *http; + Curl_send_buffer *req_buffer; + curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ + curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ + + const char *p_request = NULL; + const char *p_session_id = NULL; + const char *p_accept = NULL; + const char *p_accept_encoding = NULL; + const char *p_range = NULL; + const char *p_referrer = NULL; + const char *p_stream_uri = NULL; + const char *p_transport = NULL; + const char *p_uagent = NULL; + + *done = TRUE; + + Curl_reset_reqproto(conn); + + if(!data->state.proto.rtsp) { + /* Only allocate this struct if we don't already have it! */ + + rtsp = calloc(1, sizeof(struct RTSP)); + if(!rtsp) + return CURLE_OUT_OF_MEMORY; + data->state.proto.rtsp = rtsp; + } + else { + rtsp = data->state.proto.rtsp; + } + + http = &(rtsp->http_wrapper); + /* Assert that no one has changed the RTSP struct in an evil way */ + DEBUGASSERT((void *)http == (void *)rtsp); + + rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; + rtsp->CSeq_recv = 0; + + /* Setup the 'p_request' pointer to the proper p_request string + * Since all RTSP requests are included here, there is no need to + * support custom requests like HTTP. + **/ + DEBUGASSERT((rtspreq > RTSPREQ_NONE && rtspreq < RTSPREQ_LAST)); + data->set.opt_no_body = TRUE; /* most requests don't contain a body */ + switch(rtspreq) { + case RTSPREQ_NONE: + failf(data, "Got invalid RTSP request: RTSPREQ_NONE"); + return CURLE_BAD_FUNCTION_ARGUMENT; + case RTSPREQ_OPTIONS: + p_request = "OPTIONS"; + break; + case RTSPREQ_DESCRIBE: + p_request = "DESCRIBE"; + data->set.opt_no_body = FALSE; + break; + case RTSPREQ_ANNOUNCE: + p_request = "ANNOUNCE"; + break; + case RTSPREQ_SETUP: + p_request = "SETUP"; + break; + case RTSPREQ_PLAY: + p_request = "PLAY"; + break; + case RTSPREQ_PAUSE: + p_request = "PAUSE"; + break; + case RTSPREQ_TEARDOWN: + p_request = "TEARDOWN"; + break; + case RTSPREQ_GET_PARAMETER: + /* GET_PARAMETER's no_body status is determined later */ + p_request = "GET_PARAMETER"; + data->set.opt_no_body = FALSE; + break; + case RTSPREQ_SET_PARAMETER: + p_request = "SET_PARAMETER"; + break; + case RTSPREQ_RECORD: + p_request = "RECORD"; + break; + case RTSPREQ_RECEIVE: + p_request = ""; + /* Treat interleaved RTP as body*/ + data->set.opt_no_body = FALSE; + break; + case RTSPREQ_LAST: + failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + if(rtspreq == RTSPREQ_RECEIVE) { + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, + &http->readbytecount, -1, NULL); + + return result; + } + + p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; + if(!p_session_id && + (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { + failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", + p_request ? p_request : ""); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* TODO: auth? */ + /* TODO: proxy? */ + + /* Stream URI. Default to server '*' if not specified */ + if(data->set.str[STRING_RTSP_STREAM_URI]) { + p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; + } + else { + p_stream_uri = "*"; + } + + /* Transport Header for SETUP requests */ + p_transport = Curl_checkheaders(data, "Transport:"); + if(rtspreq == RTSPREQ_SETUP && !p_transport) { + /* New Transport: setting? */ + if(data->set.str[STRING_RTSP_TRANSPORT]) { + Curl_safefree(conn->allocptr.rtsp_transport); + + conn->allocptr.rtsp_transport = + aprintf("Transport: %s\r\n", + data->set.str[STRING_RTSP_TRANSPORT]); + if(!conn->allocptr.rtsp_transport) + return CURLE_OUT_OF_MEMORY; + } + else { + failf(data, + "Refusing to issue an RTSP SETUP without a Transport: header."); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + p_transport = conn->allocptr.rtsp_transport; + } + + /* Accept Headers for DESCRIBE requests */ + if(rtspreq == RTSPREQ_DESCRIBE) { + /* Accept Header */ + p_accept = Curl_checkheaders(data, "Accept:")? + NULL:"Accept: application/sdp\r\n"; + + /* Accept-Encoding header */ + if(!Curl_checkheaders(data, "Accept-Encoding:") && + data->set.str[STRING_ENCODING]) { + Curl_safefree(conn->allocptr.accept_encoding); + conn->allocptr.accept_encoding = + aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); + + if(!conn->allocptr.accept_encoding) + return CURLE_OUT_OF_MEMORY; + + p_accept_encoding = conn->allocptr.accept_encoding; + } + } + + /* The User-Agent string might have been allocated in curl_url.c already, + because it might have been used in the proxy connect, but if we have + got a header with the user-agent string specified, we erase the + previously made string here. */ + if(Curl_checkheaders(data, "User-Agent:") && conn->allocptr.uagent) { + Curl_safefree(conn->allocptr.uagent); + conn->allocptr.uagent = NULL; + } + else if(!Curl_checkheaders(data, "User-Agent:") && + data->set.str[STRING_USERAGENT]) { + p_uagent = conn->allocptr.uagent; + } + + /* Referrer */ + Curl_safefree(conn->allocptr.ref); + if(data->change.referer && !Curl_checkheaders(data, "Referer:")) + conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); + else + conn->allocptr.ref = NULL; + + p_referrer = conn->allocptr.ref; + + /* + * Range Header + * Only applies to PLAY, PAUSE, RECORD + * + * Go ahead and use the Range stuff supplied for HTTP + */ + if(data->state.use_range && + (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { + + /* Check to see if there is a range set in the custom headers */ + if(!Curl_checkheaders(data, "Range:") && data->state.range) { + Curl_safefree(conn->allocptr.rangeline); + conn->allocptr.rangeline = aprintf("Range: %s\r\n", data->state.range); + p_range = conn->allocptr.rangeline; + } + } + + /* + * Sanity check the custom headers + */ + if(Curl_checkheaders(data, "CSeq:")) { + failf(data, "CSeq cannot be set as a custom header."); + return CURLE_RTSP_CSEQ_ERROR; + } + if(Curl_checkheaders(data, "Session:")) { + failf(data, "Session ID cannot be set as a custom header."); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* Initialize a dynamic send buffer */ + req_buffer = Curl_add_buffer_init(); + + if(!req_buffer) + return CURLE_OUT_OF_MEMORY; + + result = + Curl_add_bufferf(req_buffer, + "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ + "CSeq: %ld\r\n", /* CSeq */ + (p_request ? p_request : ""), p_stream_uri, + rtsp->CSeq_sent); + if(result) + return result; + + /* + * Rather than do a normal alloc line, keep the session_id unformatted + * to make comparison easier + */ + if(p_session_id) { + result = Curl_add_bufferf(req_buffer, "Session: %s\r\n", p_session_id); + if(result) + return result; + } + + /* + * Shared HTTP-like options + */ + result = Curl_add_bufferf(req_buffer, + "%s" /* transport */ + "%s" /* accept */ + "%s" /* accept-encoding */ + "%s" /* range */ + "%s" /* referrer */ + "%s" /* user-agent */ + , + p_transport ? p_transport : "", + p_accept ? p_accept : "", + p_accept_encoding ? p_accept_encoding : "", + p_range ? p_range : "", + p_referrer ? p_referrer : "", + p_uagent ? p_uagent : ""); + if(result) + return result; + + if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { + result = Curl_add_timecondition(data, req_buffer); + if(result) + return result; + } + + result = Curl_add_custom_headers(conn, req_buffer); + if(result) + return result; + + if(rtspreq == RTSPREQ_ANNOUNCE || + rtspreq == RTSPREQ_SET_PARAMETER || + rtspreq == RTSPREQ_GET_PARAMETER) { + + if(data->set.upload) { + putsize = data->set.infilesize; + data->set.httpreq = HTTPREQ_PUT; + + } + else { + postsize = (data->set.postfieldsize != -1)? + data->set.postfieldsize: + (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); + data->set.httpreq = HTTPREQ_POST; + } + + if(putsize > 0 || postsize > 0) { + /* As stated in the http comments, it is probably not wise to + * actually set a custom Content-Length in the headers */ + if(!Curl_checkheaders(data, "Content-Length:")) { + result = Curl_add_bufferf(req_buffer, + "Content-Length: %" FORMAT_OFF_T"\r\n", + (data->set.upload ? putsize : postsize)); + if(result) + return result; + } + + if(rtspreq == RTSPREQ_SET_PARAMETER || + rtspreq == RTSPREQ_GET_PARAMETER) { + if(!Curl_checkheaders(data, "Content-Type:")) { + result = Curl_add_bufferf(req_buffer, + "Content-Type: text/parameters\r\n"); + if(result) + return result; + } + } + + if(rtspreq == RTSPREQ_ANNOUNCE) { + if(!Curl_checkheaders(data, "Content-Type:")) { + result = Curl_add_bufferf(req_buffer, + "Content-Type: application/sdp\r\n"); + if(result) + return result; + } + } + + data->state.expect100header = FALSE; /* RTSP posts are simple/small */ + } + else if(rtspreq == RTSPREQ_GET_PARAMETER) { + /* Check for an empty GET_PARAMETER (heartbeat) request */ + data->set.httpreq = HTTPREQ_HEAD; + data->set.opt_no_body = TRUE; + } + } + + /* RTSP never allows chunked transfer */ + data->req.forbidchunk = TRUE; + /* Finish the request buffer */ + result = Curl_add_buffer(req_buffer, "\r\n", 2); + if(result) + return result; + + if(postsize > 0) { + result = Curl_add_buffer(req_buffer, data->set.postfields, + (size_t)postsize); + if(result) + return result; + } + + /* issue the request */ + result = Curl_add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, FIRSTSOCKET); + if(result) { + failf(data, "Failed sending RTSP request"); + return result; + } + + Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, + putsize?FIRSTSOCKET:-1, + putsize?&http->writebytecount:NULL); + + /* Increment the CSeq on success */ + data->state.rtsp_next_client_CSeq++; + + if(http->writebytecount) { + /* if a request-body has been sent off, we make sure this progress is + noted properly */ + Curl_pgrsSetUploadCounter(data, http->writebytecount); + if(Curl_pgrsUpdate(conn)) + result = CURLE_ABORTED_BY_CALLBACK; + } + + return result; +} + + +static CURLcode rtsp_rtp_readwrite(struct SessionHandle *data, + struct connectdata *conn, + ssize_t *nread, + bool *readmore) { + struct SingleRequest *k = &data->req; + struct rtsp_conn *rtspc = &(conn->proto.rtspc); + + char *rtp; /* moving pointer to rtp data */ + ssize_t rtp_dataleft; /* how much data left to parse in this round */ + char *scratch; + CURLcode result; + + if(rtspc->rtp_buf) { + /* There was some leftover data the last time. Merge buffers */ + char *newptr = realloc(rtspc->rtp_buf, rtspc->rtp_bufsize + *nread); + if(!newptr) { + Curl_safefree(rtspc->rtp_buf); + rtspc->rtp_buf = NULL; + rtspc->rtp_bufsize = 0; + return CURLE_OUT_OF_MEMORY; + } + rtspc->rtp_buf = newptr; + memcpy(rtspc->rtp_buf + rtspc->rtp_bufsize, k->str, *nread); + rtspc->rtp_bufsize += *nread; + rtp = rtspc->rtp_buf; + rtp_dataleft = rtspc->rtp_bufsize; + } + else { + /* Just parse the request buffer directly */ + rtp = k->str; + rtp_dataleft = *nread; + } + + while((rtp_dataleft > 0) && + (rtp[0] == '$')) { + if(rtp_dataleft > 4) { + int rtp_length; + + /* Parse the header */ + /* The channel identifier immediately follows and is 1 byte */ + rtspc->rtp_channel = RTP_PKT_CHANNEL(rtp); + + /* The length is two bytes */ + rtp_length = RTP_PKT_LENGTH(rtp); + + if(rtp_dataleft < rtp_length + 4) { + /* Need more - incomplete payload*/ + *readmore = TRUE; + break; + } + else { + /* We have the full RTP interleaved packet + * Write out the header including the leading '$' */ + DEBUGF(infof(data, "RTP write channel %d rtp_length %d\n", + rtspc->rtp_channel, rtp_length)); + result = rtp_client_write(conn, &rtp[0], rtp_length + 4); + if(result) { + failf(data, "Got an error writing an RTP packet"); + *readmore = FALSE; + Curl_safefree(rtspc->rtp_buf); + rtspc->rtp_buf = NULL; + rtspc->rtp_bufsize = 0; + return result; + } + + /* Move forward in the buffer */ + rtp_dataleft -= rtp_length + 4; + rtp += rtp_length + 4; + + if(data->set.rtspreq == RTSPREQ_RECEIVE) { + /* If we are in a passive receive, give control back + * to the app as often as we can. + */ + k->keepon &= ~KEEP_RECV; + } + } + } + else { + /* Need more - incomplete header */ + *readmore = TRUE; + break; + } + } + + if(rtp_dataleft != 0 && rtp[0] == '$') { + DEBUGF(infof(data, "RTP Rewinding %zu %s\n", rtp_dataleft, + *readmore ? "(READMORE)" : "")); + + /* Store the incomplete RTP packet for a "rewind" */ + scratch = malloc(rtp_dataleft); + if(!scratch) { + Curl_safefree(rtspc->rtp_buf); + rtspc->rtp_buf = NULL; + rtspc->rtp_bufsize = 0; + return CURLE_OUT_OF_MEMORY; + } + memcpy(scratch, rtp, rtp_dataleft); + Curl_safefree(rtspc->rtp_buf); + rtspc->rtp_buf = scratch; + rtspc->rtp_bufsize = rtp_dataleft; + + /* As far as the transfer is concerned, this data is consumed */ + *nread = 0; + return CURLE_OK; + } + else { + /* Fix up k->str to point just after the last RTP packet */ + k->str += *nread - rtp_dataleft; + + /* either all of the data has been read or... + * rtp now points at the next byte to parse + */ + if(rtp_dataleft > 0) + DEBUGASSERT(k->str[0] == rtp[0]); + + DEBUGASSERT(rtp_dataleft <= *nread); /* sanity check */ + + *nread = rtp_dataleft; + } + + /* If we get here, we have finished with the leftover/merge buffer */ + Curl_safefree(rtspc->rtp_buf); + rtspc->rtp_buf = NULL; + rtspc->rtp_bufsize = 0; + + return CURLE_OK; +} + +static +CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len) +{ + struct SessionHandle *data = conn->data; + size_t wrote; + curl_write_callback writeit; + + if(len == 0) { + failf (data, "Cannot write a 0 size RTP packet."); + return CURLE_WRITE_ERROR; + } + + writeit = data->set.fwrite_rtp?data->set.fwrite_rtp:data->set.fwrite_func; + wrote = writeit(ptr, 1, len, data->set.rtp_out); + + if(CURL_WRITEFUNC_PAUSE == wrote) { + failf (data, "Cannot pause RTP"); + return CURLE_WRITE_ERROR; + } + + if(wrote != len) { + failf (data, "Failed writing RTP data"); + return CURLE_WRITE_ERROR; + } + + return CURLE_OK; +} + +CURLcode Curl_rtsp_parseheader(struct connectdata *conn, + char *header) +{ + struct SessionHandle *data = conn->data; + long CSeq = 0; + + if(checkprefix("CSeq:", header)) { + /* Store the received CSeq. Match is verified in rtsp_done */ + int nc = sscanf(&header[4], ": %ld", &CSeq); + if(nc == 1) { + data->state.proto.rtsp->CSeq_recv = CSeq; /* mark the request */ + data->state.rtsp_CSeq_recv = CSeq; /* update the handle */ + } + else { + failf(data, "Unable to read the CSeq header: [%s]", header); + return CURLE_RTSP_CSEQ_ERROR; + } + } + else if(checkprefix("Session:", header)) { + char *start; + + /* Find the first non-space letter */ + start = header + 9; + while(*start && ISSPACE(*start)) + start++; + + if(!*start) { + failf(data, "Got a blank Session ID"); + } + else if(data->set.str[STRING_RTSP_SESSION_ID]) { + /* If the Session ID is set, then compare */ + if(strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], + strlen(data->set.str[STRING_RTSP_SESSION_ID])) != 0) { + failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", + start, data->set.str[STRING_RTSP_SESSION_ID]); + return CURLE_RTSP_SESSION_ERROR; + } + } + else { + /* If the Session ID is not set, and we find it in a response, then + set it */ + + /* The session ID can be an alphanumeric or a 'safe' character + * + * RFC 2326 15.1 Base Syntax: + * safe = "\$" | "-" | "_" | "." | "+" + * */ + char *end = start; + while(*end && + (ISALNUM(*end) || *end == '-' || *end == '_' || *end == '.' || + *end == '+' || + (*end == '\\' && *(end + 1) && *(end + 1) == '$' && (++end, 1)))) + end++; + + /* Copy the id substring into a new buffer */ + data->set.str[STRING_RTSP_SESSION_ID] = malloc(end - start + 1); + if(data->set.str[STRING_RTSP_SESSION_ID] == NULL) + return CURLE_OUT_OF_MEMORY; + memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, end - start); + (data->set.str[STRING_RTSP_SESSION_ID])[end - start] = '\0'; + } + } + return CURLE_OK; +} + +#endif /* CURL_DISABLE_RTSP */ diff --git a/lib/curl_security.c b/lib/curl_security.c new file mode 100644 index 000000000..b7544ffbd --- /dev/null +++ b/lib/curl_security.c @@ -0,0 +1,604 @@ +/* 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 and modified a lot 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) 1998, 1999 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * + * Copyright (C) 2001 - 2011, Daniel Stenberg, , et al. + * + * 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 "curl_setup.h" + +#ifndef CURL_DISABLE_FTP +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + +#ifdef HAVE_NETDB_H +#include +#endif + +#ifdef HAVE_LIMITS_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_base64.h" +#include "curl_memory.h" +#include "curl_krb4.h" +#include "curl_ftp.h" +#include "curl_sendf.h" +#include "curl_rawstr.h" +#include "curl_warnless.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +static const struct { + enum protection_level level; + const char *name; +} level_names[] = { + { PROT_CLEAR, "clear" }, + { PROT_SAFE, "safe" }, + { PROT_CONFIDENTIAL, "confidential" }, + { PROT_PRIVATE, "private" } +}; + +static enum protection_level +name_to_level(const char *name) +{ + int i; + for(i = 0; i < (int)sizeof(level_names)/(int)sizeof(level_names[0]); i++) + if(checkprefix(name, level_names[i].name)) + return level_names[i].level; + return PROT_NONE; +} + +/* Convert a protocol |level| to its char representation. + We take an int to catch programming mistakes. */ +static char level_to_char(int level) { + switch(level) { + case PROT_CLEAR: + return 'C'; + case PROT_SAFE: + return 'S'; + case PROT_CONFIDENTIAL: + return 'E'; + case PROT_PRIVATE: + return 'P'; + case PROT_CMD: + /* Fall through */ + default: + /* Those 2 cases should not be reached! */ + break; + } + DEBUGASSERT(0); + /* Default to the most secure alternative. */ + return 'P'; +} + +static const struct Curl_sec_client_mech * const mechs[] = { +#if defined(HAVE_GSSAPI) + &Curl_krb5_client_mech, +#endif +#if defined(HAVE_KRB4) + &Curl_krb4_client_mech, +#endif + NULL +}; + +/* Send an FTP command defined by |message| and the optional arguments. The + function returns the ftp_code. If an error occurs, -1 is returned. */ +static int ftp_send_command(struct connectdata *conn, const char *message, ...) +{ + int ftp_code; + ssize_t nread; + va_list args; + char print_buffer[50]; + + va_start(args, message); + vsnprintf(print_buffer, sizeof(print_buffer), message, args); + va_end(args); + + if(Curl_ftpsendf(conn, print_buffer) != CURLE_OK) { + ftp_code = -1; + } + else { + if(Curl_GetFTPResponse(&nread, conn, &ftp_code) != CURLE_OK) + ftp_code = -1; + } + + (void)nread; /* Unused */ + return ftp_code; +} + +/* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode + saying whether an error occurred or CURLE_OK if |len| was read. */ +static CURLcode +socket_read(curl_socket_t fd, void *to, size_t len) +{ + char *to_p = to; + CURLcode code; + ssize_t nread; + + while(len > 0) { + code = Curl_read_plain(fd, to_p, len, &nread); + if(code == CURLE_OK) { + len -= nread; + to_p += nread; + } + else { + /* FIXME: We are doing a busy wait */ + if(code == CURLE_AGAIN) + continue; + return code; + } + } + return CURLE_OK; +} + + +/* Write |len| bytes from the buffer |to| to the socket |fd|. Return a + CURLcode saying whether an error occurred or CURLE_OK if |len| was + written. */ +static CURLcode +socket_write(struct connectdata *conn, curl_socket_t fd, const void *to, + size_t len) +{ + const char *to_p = to; + CURLcode code; + ssize_t written; + + while(len > 0) { + code = Curl_write_plain(conn, fd, to_p, len, &written); + if(code == CURLE_OK) { + len -= written; + to_p += written; + } + else { + /* FIXME: We are doing a busy wait */ + if(code == CURLE_AGAIN) + continue; + return code; + } + } + return CURLE_OK; +} + +static CURLcode read_data(struct connectdata *conn, + curl_socket_t fd, + struct krb4buffer *buf) +{ + int len; + void* tmp; + CURLcode ret; + + ret = socket_read(fd, &len, sizeof(len)); + if(ret != CURLE_OK) + return ret; + + len = ntohl(len); + tmp = realloc(buf->data, len); + if(tmp == NULL) + return CURLE_OUT_OF_MEMORY; + + buf->data = tmp; + ret = socket_read(fd, buf->data, len); + if(ret != CURLE_OK) + return ret; + buf->size = conn->mech->decode(conn->app_data, buf->data, len, + conn->data_prot, conn); + buf->index = 0; + return CURLE_OK; +} + +static size_t +buffer_read(struct krb4buffer *buf, void *data, size_t len) +{ + if(buf->size - buf->index < len) + len = buf->size - buf->index; + memcpy(data, (char*)buf->data + buf->index, len); + buf->index += len; + return len; +} + +/* Matches Curl_recv signature */ +static ssize_t sec_recv(struct connectdata *conn, int sockindex, + char *buffer, size_t len, CURLcode *err) +{ + size_t bytes_read; + size_t total_read = 0; + curl_socket_t fd = conn->sock[sockindex]; + + *err = CURLE_OK; + + /* Handle clear text response. */ + if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) + return read(fd, buffer, len); + + if(conn->in_buffer.eof_flag) { + conn->in_buffer.eof_flag = 0; + return 0; + } + + bytes_read = buffer_read(&conn->in_buffer, buffer, len); + len -= bytes_read; + total_read += bytes_read; + buffer += bytes_read; + + while(len > 0) { + if(read_data(conn, fd, &conn->in_buffer) != CURLE_OK) + return -1; + if(conn->in_buffer.size == 0) { + if(bytes_read > 0) + conn->in_buffer.eof_flag = 1; + return bytes_read; + } + bytes_read = buffer_read(&conn->in_buffer, buffer, len); + len -= bytes_read; + total_read += bytes_read; + buffer += bytes_read; + } + /* FIXME: Check for overflow */ + return total_read; +} + +/* Send |length| bytes from |from| to the |fd| socket taking care of encoding + and negociating with the server. |from| can be NULL. */ +/* FIXME: We don't check for errors nor report any! */ +static void do_sec_send(struct connectdata *conn, curl_socket_t fd, + const char *from, int length) +{ + int bytes, htonl_bytes; /* 32-bit integers for htonl */ + char *buffer = NULL; + char *cmd_buffer; + size_t cmd_size = 0; + CURLcode error; + enum protection_level prot_level = conn->data_prot; + bool iscmd = (prot_level == PROT_CMD)?TRUE:FALSE; + + DEBUGASSERT(prot_level > PROT_NONE && prot_level < PROT_LAST); + + if(iscmd) { + if(!strncmp(from, "PASS ", 5) || !strncmp(from, "ACCT ", 5)) + prot_level = PROT_PRIVATE; + else + prot_level = conn->command_prot; + } + bytes = conn->mech->encode(conn->app_data, from, length, prot_level, + (void**)&buffer, conn); + if(!buffer || bytes <= 0) + return; /* error */ + + if(iscmd) { + error = Curl_base64_encode(conn->data, buffer, curlx_sitouz(bytes), + &cmd_buffer, &cmd_size); + if(error) { + free(buffer); + return; /* error */ + } + if(cmd_size > 0) { + static const char *enc = "ENC "; + static const char *mic = "MIC "; + if(prot_level == PROT_PRIVATE) + socket_write(conn, fd, enc, 4); + else + socket_write(conn, fd, mic, 4); + + socket_write(conn, fd, cmd_buffer, cmd_size); + socket_write(conn, fd, "\r\n", 2); + infof(conn->data, "Send: %s%s\n", prot_level == PROT_PRIVATE?enc:mic, + cmd_buffer); + free(cmd_buffer); + } + } + else { + htonl_bytes = htonl(bytes); + socket_write(conn, fd, &htonl_bytes, sizeof(htonl_bytes)); + socket_write(conn, fd, buffer, curlx_sitouz(bytes)); + } + free(buffer); +} + +static ssize_t sec_write(struct connectdata *conn, curl_socket_t fd, + const char *buffer, size_t length) +{ + /* FIXME: Check for overflow */ + ssize_t tx = 0, len = conn->buffer_size; + + len -= conn->mech->overhead(conn->app_data, conn->data_prot, + curlx_sztosi(len)); + if(len <= 0) + len = length; + while(length) { + if(len >= 0 || length < (size_t)len) { + /* FIXME: Check for overflow. */ + len = length; + } + do_sec_send(conn, fd, buffer, curlx_sztosi(len)); + length -= len; + buffer += len; + tx += len; + } + return tx; +} + +/* Matches Curl_send signature */ +static ssize_t sec_send(struct connectdata *conn, int sockindex, + const void *buffer, size_t len, CURLcode *err) +{ + curl_socket_t fd = conn->sock[sockindex]; + *err = CURLE_OK; + return sec_write(conn, fd, buffer, len); +} + +int Curl_sec_read_msg(struct connectdata *conn, char *buffer, + enum protection_level level) +{ + /* decoded_len should be size_t or ssize_t but conn->mech->decode returns an + int */ + int decoded_len; + char *buf; + int ret_code; + size_t decoded_sz = 0; + CURLcode error; + + DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); + + error = Curl_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); + if(error || decoded_sz == 0) + return -1; + + if(decoded_sz > (size_t)INT_MAX) { + free(buf); + return -1; + } + decoded_len = curlx_uztosi(decoded_sz); + + decoded_len = conn->mech->decode(conn->app_data, buf, decoded_len, + level, conn); + if(decoded_len <= 0) { + free(buf); + return -1; + } + + if(conn->data->set.verbose) { + buf[decoded_len] = '\n'; + Curl_debug(conn->data, CURLINFO_HEADER_IN, buf, decoded_len + 1, conn); + } + + buf[decoded_len] = '\0'; + DEBUGASSERT(decoded_len > 3); + if(buf[3] == '-') + ret_code = 0; + else { + /* Check for error? */ + sscanf(buf, "%d", &ret_code); + } + + if(buf[decoded_len - 1] == '\n') + buf[decoded_len - 1] = '\0'; + /* FIXME: Is |buffer| length always greater than |decoded_len|? */ + strcpy(buffer, buf); + free(buf); + return ret_code; +} + +/* FIXME: The error code returned here is never checked. */ +static int sec_set_protection_level(struct connectdata *conn) +{ + int code; + char* pbsz; + static unsigned int buffer_size = 1 << 20; /* 1048576 */ + enum protection_level level = conn->request_data_prot; + + DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); + + if(!conn->sec_complete) { + infof(conn->data, "Trying to change the protection level after the" + "completion of the data exchange.\n"); + return -1; + } + + /* Bail out if we try to set up the same level */ + if(conn->data_prot == level) + return 0; + + if(level) { + code = ftp_send_command(conn, "PBSZ %u", buffer_size); + if(code < 0) + return -1; + + if(code/100 != 2) { + failf(conn->data, "Failed to set the protection's buffer size."); + return -1; + } + conn->buffer_size = buffer_size; + + pbsz = strstr(conn->data->state.buffer, "PBSZ="); + if(pbsz) { + /* FIXME: Checks for errors in sscanf? */ + sscanf(pbsz, "PBSZ=%u", &buffer_size); + if(buffer_size < conn->buffer_size) + conn->buffer_size = buffer_size; + } + } + + /* Now try to negiociate the protection level. */ + code = ftp_send_command(conn, "PROT %c", level_to_char(level)); + + if(code < 0) + return -1; + + if(code/100 != 2) { + failf(conn->data, "Failed to set the protection level."); + return -1; + } + + conn->data_prot = level; + if(level == PROT_PRIVATE) + conn->command_prot = level; + + return 0; +} + +int +Curl_sec_request_prot(struct connectdata *conn, const char *level) +{ + enum protection_level l = name_to_level(level); + if(l == PROT_NONE) + return -1; + DEBUGASSERT(l > PROT_NONE && l < PROT_LAST); + conn->request_data_prot = l; + return 0; +} + +static CURLcode choose_mech(struct connectdata *conn) +{ + int ret; + struct SessionHandle *data = conn->data; + const struct Curl_sec_client_mech * const *mech; + void *tmp_allocation; + const char *mech_name; + + for(mech = mechs; (*mech); ++mech) { + mech_name = (*mech)->name; + /* We have no mechanism with a NULL name but keep this check */ + DEBUGASSERT(mech_name != NULL); + if(mech_name == NULL) { + infof(data, "Skipping mechanism with empty name (%p)\n", mech); + continue; + } + tmp_allocation = realloc(conn->app_data, (*mech)->size); + if(tmp_allocation == NULL) { + failf(data, "Failed realloc of size %u", (*mech)->size); + mech = NULL; + return CURLE_OUT_OF_MEMORY; + } + conn->app_data = tmp_allocation; + + if((*mech)->init) { + ret = (*mech)->init(conn->app_data); + if(ret != 0) { + infof(data, "Failed initialization for %s. Skipping it.\n", mech_name); + continue; + } + } + + infof(data, "Trying mechanism %s...\n", mech_name); + ret = ftp_send_command(conn, "AUTH %s", mech_name); + if(ret < 0) + /* FIXME: This error is too generic but it is OK for now. */ + return CURLE_COULDNT_CONNECT; + + if(ret/100 != 3) { + switch(ret) { + case 504: + infof(data, "Mechanism %s is not supported by the server (server " + "returned ftp code: 504).\n", mech_name); + break; + case 534: + infof(data, "Mechanism %s was rejected by the server (server returned " + "ftp code: 534).\n", mech_name); + break; + default: + if(ret/100 == 5) { + infof(data, "server does not support the security extensions\n"); + return CURLE_USE_SSL_FAILED; + } + break; + } + continue; + } + + /* Authenticate */ + ret = (*mech)->auth(conn->app_data, conn); + + if(ret == AUTH_CONTINUE) + continue; + else if(ret != AUTH_OK) { + /* Mechanism has dumped the error to stderr, don't error here. */ + return -1; + } + DEBUGASSERT(ret == AUTH_OK); + + conn->mech = *mech; + conn->sec_complete = 1; + conn->recv[FIRSTSOCKET] = sec_recv; + conn->send[FIRSTSOCKET] = sec_send; + conn->recv[SECONDARYSOCKET] = sec_recv; + conn->send[SECONDARYSOCKET] = sec_send; + conn->command_prot = PROT_SAFE; + /* Set the requested protection level */ + /* BLOCKING */ + (void)sec_set_protection_level(conn); + break; + } + + return mech != NULL ? CURLE_OK : CURLE_FAILED_INIT; +} + +CURLcode +Curl_sec_login(struct connectdata *conn) +{ + return choose_mech(conn); +} + + +void +Curl_sec_end(struct connectdata *conn) +{ + if(conn->mech != NULL && conn->mech->end) + conn->mech->end(conn->app_data); + if(conn->app_data) { + free(conn->app_data); + conn->app_data = NULL; + } + if(conn->in_buffer.data) { + free(conn->in_buffer.data); + conn->in_buffer.data = NULL; + conn->in_buffer.size = 0; + conn->in_buffer.index = 0; + /* FIXME: Is this really needed? */ + conn->in_buffer.eof_flag = 0; + } + conn->sec_complete = 0; + conn->data_prot = PROT_CLEAR; + conn->mech = NULL; +} + +#endif /* HAVE_KRB4 || HAVE_GSSAPI */ + +#endif /* CURL_DISABLE_FTP */ diff --git a/lib/curl_select.c b/lib/curl_select.c new file mode 100644 index 000000000..d4519d39e --- /dev/null +++ b/lib/curl_select.c @@ -0,0 +1,529 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#if !defined(HAVE_SELECT) && !defined(HAVE_POLL_FINE) +#error "We can't compile without select() or poll() support." +#endif + +#if defined(__BEOS__) && !defined(__HAIKU__) +/* BeOS has FD_SET defined in socket.h */ +#include +#endif + +#ifdef MSDOS +#include /* delay() */ +#endif + +#include + +#include "curl_urldata.h" +#include "curl_connect.h" +#include "curl_select.h" +#include "curl_warnless.h" + +/* Convenience local macros */ + +#define elapsed_ms (int)curlx_tvdiff(curlx_tvnow(), initial_tv) + +#ifdef CURL_ACKNOWLEDGE_EINTR +#define error_not_EINTR (1) +#else +#define error_not_EINTR (error != EINTR) +#endif + +/* + * Internal function used for waiting a specific amount of ms + * in Curl_socket_ready() and Curl_poll() when no file descriptor + * is provided to wait on, just being used to delay execution. + * WinSock select() and poll() timeout mechanisms need a valid + * socket descriptor in a not null file descriptor set to work. + * Waiting indefinitely with this function is not allowed, a + * zero or negative timeout value will return immediately. + * Timeout resolution, accuracy, as well as maximum supported + * value is system dependent, neither factor is a citical issue + * for the intended use of this function in the library. + * On non-DOS and non-Winsock platforms, when compiled with + * CURL_ACKNOWLEDGE_EINTR defined, EINTR condition is honored + * and function might exit early without awaiting full timeout, + * otherwise EINTR will be ignored and full timeout will elapse. + * + * Return values: + * -1 = system call error, invalid timeout value, or interrupted + * 0 = specified timeout has elapsed + */ +int Curl_wait_ms(int timeout_ms) +{ +#if !defined(MSDOS) && !defined(USE_WINSOCK) +#ifndef HAVE_POLL_FINE + struct timeval pending_tv; +#endif + struct timeval initial_tv; + int pending_ms; + int error; +#endif + int r = 0; + + if(!timeout_ms) + return 0; + if(timeout_ms < 0) { + SET_SOCKERRNO(EINVAL); + return -1; + } +#if defined(MSDOS) + delay(timeout_ms); +#elif defined(USE_WINSOCK) + Sleep(timeout_ms); +#else + pending_ms = timeout_ms; + initial_tv = curlx_tvnow(); + do { +#if defined(HAVE_POLL_FINE) + r = poll(NULL, 0, pending_ms); +#else + pending_tv.tv_sec = pending_ms / 1000; + pending_tv.tv_usec = (pending_ms % 1000) * 1000; + r = select(0, NULL, NULL, NULL, &pending_tv); +#endif /* HAVE_POLL_FINE */ + if(r != -1) + break; + error = SOCKERRNO; + if(error && error_not_EINTR) + break; + pending_ms = timeout_ms - elapsed_ms; + if(pending_ms <= 0) + break; + } while(r == -1); +#endif /* USE_WINSOCK */ + if(r) + r = -1; + return r; +} + +/* + * Wait for read or write events on a set of file descriptors. It uses poll() + * when a fine poll() is available, in order to avoid limits with FD_SETSIZE, + * otherwise select() is used. An error is returned if select() is being used + * and a file descriptor is too large for FD_SETSIZE. + * + * A negative timeout value makes this function wait indefinitely, + * unles no valid file descriptor is given, when this happens the + * negative timeout is ignored and the function times out immediately. + * When compiled with CURL_ACKNOWLEDGE_EINTR defined, EINTR condition + * is honored and function might exit early without awaiting timeout, + * otherwise EINTR will be ignored. + * + * Return values: + * -1 = system call error or fd >= FD_SETSIZE + * 0 = timeout + * [bitmask] = action as described below + * + * CURL_CSELECT_IN - first socket is readable + * CURL_CSELECT_IN2 - second socket is readable + * CURL_CSELECT_OUT - write socket is writable + * CURL_CSELECT_ERR - an error condition occurred + */ +int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ + curl_socket_t readfd1, + curl_socket_t writefd, /* socket to write to */ + long timeout_ms) /* milliseconds to wait */ +{ +#ifdef HAVE_POLL_FINE + struct pollfd pfd[3]; + int num; +#else + struct timeval pending_tv; + struct timeval *ptimeout; + fd_set fds_read; + fd_set fds_write; + fd_set fds_err; + curl_socket_t maxfd; +#endif + struct timeval initial_tv = {0,0}; + int pending_ms = 0; + int error; + int r; + int ret; + + if((readfd0 == CURL_SOCKET_BAD) && (readfd1 == CURL_SOCKET_BAD) && + (writefd == CURL_SOCKET_BAD)) { + /* no sockets, just wait */ + r = Curl_wait_ms((int)timeout_ms); + return r; + } + + /* Avoid initial timestamp, avoid curlx_tvnow() call, when elapsed + time in this function does not need to be measured. This happens + when function is called with a zero timeout or a negative timeout + value indicating a blocking call should be performed. */ + + if(timeout_ms > 0) { + pending_ms = (int)timeout_ms; + initial_tv = curlx_tvnow(); + } + +#ifdef HAVE_POLL_FINE + + num = 0; + if(readfd0 != CURL_SOCKET_BAD) { + pfd[num].fd = readfd0; + pfd[num].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI; + pfd[num].revents = 0; + num++; + } + if(readfd1 != CURL_SOCKET_BAD) { + pfd[num].fd = readfd1; + pfd[num].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI; + pfd[num].revents = 0; + num++; + } + if(writefd != CURL_SOCKET_BAD) { + pfd[num].fd = writefd; + pfd[num].events = POLLWRNORM|POLLOUT; + pfd[num].revents = 0; + num++; + } + + do { + if(timeout_ms < 0) + pending_ms = -1; + else if(!timeout_ms) + pending_ms = 0; + r = poll(pfd, num, pending_ms); + if(r != -1) + break; + error = SOCKERRNO; + if(error && error_not_EINTR) + break; + if(timeout_ms > 0) { + pending_ms = (int)(timeout_ms - elapsed_ms); + if(pending_ms <= 0) { + r = 0; /* Simulate a "call timed out" case */ + break; + } + } + } while(r == -1); + + if(r < 0) + return -1; + if(r == 0) + return 0; + + ret = 0; + num = 0; + if(readfd0 != CURL_SOCKET_BAD) { + if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP)) + ret |= CURL_CSELECT_IN; + if(pfd[num].revents & (POLLRDBAND|POLLPRI|POLLNVAL)) + ret |= CURL_CSELECT_ERR; + num++; + } + if(readfd1 != CURL_SOCKET_BAD) { + if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP)) + ret |= CURL_CSELECT_IN2; + if(pfd[num].revents & (POLLRDBAND|POLLPRI|POLLNVAL)) + ret |= CURL_CSELECT_ERR; + num++; + } + if(writefd != CURL_SOCKET_BAD) { + if(pfd[num].revents & (POLLWRNORM|POLLOUT)) + ret |= CURL_CSELECT_OUT; + if(pfd[num].revents & (POLLERR|POLLHUP|POLLNVAL)) + ret |= CURL_CSELECT_ERR; + } + + return ret; + +#else /* HAVE_POLL_FINE */ + + FD_ZERO(&fds_err); + maxfd = (curl_socket_t)-1; + + FD_ZERO(&fds_read); + if(readfd0 != CURL_SOCKET_BAD) { + VERIFY_SOCK(readfd0); + FD_SET(readfd0, &fds_read); + FD_SET(readfd0, &fds_err); + maxfd = readfd0; + } + if(readfd1 != CURL_SOCKET_BAD) { + VERIFY_SOCK(readfd1); + FD_SET(readfd1, &fds_read); + FD_SET(readfd1, &fds_err); + if(readfd1 > maxfd) + maxfd = readfd1; + } + + FD_ZERO(&fds_write); + if(writefd != CURL_SOCKET_BAD) { + VERIFY_SOCK(writefd); + FD_SET(writefd, &fds_write); + FD_SET(writefd, &fds_err); + if(writefd > maxfd) + maxfd = writefd; + } + + ptimeout = (timeout_ms < 0) ? NULL : &pending_tv; + + do { + if(timeout_ms > 0) { + pending_tv.tv_sec = pending_ms / 1000; + pending_tv.tv_usec = (pending_ms % 1000) * 1000; + } + else if(!timeout_ms) { + pending_tv.tv_sec = 0; + pending_tv.tv_usec = 0; + } + r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout); + if(r != -1) + break; + error = SOCKERRNO; + if(error && error_not_EINTR) + break; + if(timeout_ms > 0) { + pending_ms = timeout_ms - elapsed_ms; + if(pending_ms <= 0) { + r = 0; /* Simulate a "call timed out" case */ + break; + } + } + } while(r == -1); + + if(r < 0) + return -1; + if(r == 0) + return 0; + + ret = 0; + if(readfd0 != CURL_SOCKET_BAD) { + if(FD_ISSET(readfd0, &fds_read)) + ret |= CURL_CSELECT_IN; + if(FD_ISSET(readfd0, &fds_err)) + ret |= CURL_CSELECT_ERR; + } + if(readfd1 != CURL_SOCKET_BAD) { + if(FD_ISSET(readfd1, &fds_read)) + ret |= CURL_CSELECT_IN2; + if(FD_ISSET(readfd1, &fds_err)) + ret |= CURL_CSELECT_ERR; + } + if(writefd != CURL_SOCKET_BAD) { + if(FD_ISSET(writefd, &fds_write)) + ret |= CURL_CSELECT_OUT; + if(FD_ISSET(writefd, &fds_err)) + ret |= CURL_CSELECT_ERR; + } + + return ret; + +#endif /* HAVE_POLL_FINE */ + +} + +/* + * This is a wrapper around poll(). If poll() does not exist, then + * select() is used instead. An error is returned if select() is + * being used and a file descriptor is too large for FD_SETSIZE. + * A negative timeout value makes this function wait indefinitely, + * unles no valid file descriptor is given, when this happens the + * negative timeout is ignored and the function times out immediately. + * When compiled with CURL_ACKNOWLEDGE_EINTR defined, EINTR condition + * is honored and function might exit early without awaiting timeout, + * otherwise EINTR will be ignored. + * + * Return values: + * -1 = system call error or fd >= FD_SETSIZE + * 0 = timeout + * N = number of structures with non zero revent fields + */ +int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms) +{ +#ifndef HAVE_POLL_FINE + struct timeval pending_tv; + struct timeval *ptimeout; + fd_set fds_read; + fd_set fds_write; + fd_set fds_err; + curl_socket_t maxfd; +#endif + struct timeval initial_tv = {0,0}; + bool fds_none = TRUE; + unsigned int i; + int pending_ms = 0; + int error; + int r; + + if(ufds) { + for(i = 0; i < nfds; i++) { + if(ufds[i].fd != CURL_SOCKET_BAD) { + fds_none = FALSE; + break; + } + } + } + if(fds_none) { + r = Curl_wait_ms(timeout_ms); + return r; + } + + /* Avoid initial timestamp, avoid curlx_tvnow() call, when elapsed + time in this function does not need to be measured. This happens + when function is called with a zero timeout or a negative timeout + value indicating a blocking call should be performed. */ + + if(timeout_ms > 0) { + pending_ms = timeout_ms; + initial_tv = curlx_tvnow(); + } + +#ifdef HAVE_POLL_FINE + + do { + if(timeout_ms < 0) + pending_ms = -1; + else if(!timeout_ms) + pending_ms = 0; + r = poll(ufds, nfds, pending_ms); + if(r != -1) + break; + error = SOCKERRNO; + if(error && error_not_EINTR) + break; + if(timeout_ms > 0) { + pending_ms = timeout_ms - elapsed_ms; + if(pending_ms <= 0) + break; + } + } while(r == -1); + + if(r < 0) + return -1; + if(r == 0) + return 0; + + for(i = 0; i < nfds; i++) { + if(ufds[i].fd == CURL_SOCKET_BAD) + continue; + if(ufds[i].revents & POLLHUP) + ufds[i].revents |= POLLIN; + if(ufds[i].revents & POLLERR) + ufds[i].revents |= (POLLIN|POLLOUT); + } + +#else /* HAVE_POLL_FINE */ + + FD_ZERO(&fds_read); + FD_ZERO(&fds_write); + FD_ZERO(&fds_err); + maxfd = (curl_socket_t)-1; + + for(i = 0; i < nfds; i++) { + ufds[i].revents = 0; + if(ufds[i].fd == CURL_SOCKET_BAD) + continue; + VERIFY_SOCK(ufds[i].fd); + if(ufds[i].events & (POLLIN|POLLOUT|POLLPRI| + POLLRDNORM|POLLWRNORM|POLLRDBAND)) { + if(ufds[i].fd > maxfd) + maxfd = ufds[i].fd; + if(ufds[i].events & (POLLRDNORM|POLLIN)) + FD_SET(ufds[i].fd, &fds_read); + if(ufds[i].events & (POLLWRNORM|POLLOUT)) + FD_SET(ufds[i].fd, &fds_write); + if(ufds[i].events & (POLLRDBAND|POLLPRI)) + FD_SET(ufds[i].fd, &fds_err); + } + } + + ptimeout = (timeout_ms < 0) ? NULL : &pending_tv; + + do { + if(timeout_ms > 0) { + pending_tv.tv_sec = pending_ms / 1000; + pending_tv.tv_usec = (pending_ms % 1000) * 1000; + } + else if(!timeout_ms) { + pending_tv.tv_sec = 0; + pending_tv.tv_usec = 0; + } + r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout); + if(r != -1) + break; + error = SOCKERRNO; + if(error && error_not_EINTR) + break; + if(timeout_ms > 0) { + pending_ms = timeout_ms - elapsed_ms; + if(pending_ms <= 0) + break; + } + } while(r == -1); + + if(r < 0) + return -1; + if(r == 0) + return 0; + + r = 0; + for(i = 0; i < nfds; i++) { + ufds[i].revents = 0; + if(ufds[i].fd == CURL_SOCKET_BAD) + continue; + if(FD_ISSET(ufds[i].fd, &fds_read)) + ufds[i].revents |= POLLIN; + if(FD_ISSET(ufds[i].fd, &fds_write)) + ufds[i].revents |= POLLOUT; + if(FD_ISSET(ufds[i].fd, &fds_err)) + ufds[i].revents |= POLLPRI; + if(ufds[i].revents != 0) + r++; + } + +#endif /* HAVE_POLL_FINE */ + + return r; +} + +#ifdef TPF +/* + * This is a replacement for select() on the TPF platform. + * It is used whenever libcurl calls select(). + * The call below to tpf_process_signals() is required because + * TPF's select calls are not signal interruptible. + * + * Return values are the same as select's. + */ +int tpf_select_libcurl(int maxfds, fd_set* reads, fd_set* writes, + fd_set* excepts, struct timeval* tv) +{ + int rc; + + rc = tpf_select_bsd(maxfds, reads, writes, excepts, tv); + tpf_process_signals(); + return(rc); +} +#endif /* TPF */ diff --git a/lib/curl_sendf.c b/lib/curl_sendf.c new file mode 100644 index 000000000..a1ec50977 --- /dev/null +++ b/lib/curl_sendf.c @@ -0,0 +1,687 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_connect.h" +#include "curl_sslgen.h" +#include "curl_ssh.h" +#include "curl_multiif.h" +#include "curl_non_ascii.h" + +#define _MPRINTF_REPLACE /* use the internal *printf() functions */ +#include + +/* the krb4 functions only exists for FTP and if krb4 or gssapi is defined */ +#if !defined(CURL_DISABLE_FTP) && (defined(HAVE_KRB4) || defined(HAVE_GSSAPI)) +#include "curl_krb4.h" +#else +#define Curl_sec_send(a,b,c,d) -1 +#define Curl_sec_read(a,b,c,d) -1 +#endif + +#include "curl_memory.h" +#include "curl_strerror.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifdef CURL_DO_LINEEND_CONV +/* + * convert_lineends() changes CRLF (\r\n) end-of-line markers to a single LF + * (\n), with special processing for CRLF sequences that are split between two + * blocks of data. Remaining, bare CRs are changed to LFs. The possibly new + * size of the data is returned. + */ +static size_t convert_lineends(struct SessionHandle *data, + char *startPtr, size_t size) +{ + char *inPtr, *outPtr; + + /* sanity check */ + if((startPtr == NULL) || (size < 1)) { + return(size); + } + + if(data->state.prev_block_had_trailing_cr) { + /* The previous block of incoming data + had a trailing CR, which was turned into a LF. */ + if(*startPtr == '\n') { + /* This block of incoming data starts with the + previous block's LF so get rid of it */ + memmove(startPtr, startPtr+1, size-1); + size--; + /* and it wasn't a bare CR but a CRLF conversion instead */ + data->state.crlf_conversions++; + } + data->state.prev_block_had_trailing_cr = FALSE; /* reset the flag */ + } + + /* find 1st CR, if any */ + inPtr = outPtr = memchr(startPtr, '\r', size); + if(inPtr) { + /* at least one CR, now look for CRLF */ + while(inPtr < (startPtr+size-1)) { + /* note that it's size-1, so we'll never look past the last byte */ + if(memcmp(inPtr, "\r\n", 2) == 0) { + /* CRLF found, bump past the CR and copy the NL */ + inPtr++; + *outPtr = *inPtr; + /* keep track of how many CRLFs we converted */ + data->state.crlf_conversions++; + } + else { + if(*inPtr == '\r') { + /* lone CR, move LF instead */ + *outPtr = '\n'; + } + else { + /* not a CRLF nor a CR, just copy whatever it is */ + *outPtr = *inPtr; + } + } + outPtr++; + inPtr++; + } /* end of while loop */ + + if(inPtr < startPtr+size) { + /* handle last byte */ + if(*inPtr == '\r') { + /* deal with a CR at the end of the buffer */ + *outPtr = '\n'; /* copy a NL instead */ + /* note that a CRLF might be split across two blocks */ + data->state.prev_block_had_trailing_cr = TRUE; + } + else { + /* copy last byte */ + *outPtr = *inPtr; + } + outPtr++; + } + if(outPtr < startPtr+size) + /* tidy up by null terminating the now shorter data */ + *outPtr = '\0'; + + return(outPtr - startPtr); + } + return(size); +} +#endif /* CURL_DO_LINEEND_CONV */ + +/* Curl_infof() is for info message along the way */ + +void Curl_infof(struct SessionHandle *data, const char *fmt, ...) +{ + if(data && data->set.verbose) { + va_list ap; + size_t len; + char print_buffer[2048 + 1]; + va_start(ap, fmt); + vsnprintf(print_buffer, sizeof(print_buffer), fmt, ap); + va_end(ap); + len = strlen(print_buffer); + Curl_debug(data, CURLINFO_TEXT, print_buffer, len, NULL); + } +} + +/* Curl_failf() is for messages stating why we failed. + * The message SHALL NOT include any LF or CR. + */ + +void Curl_failf(struct SessionHandle *data, const char *fmt, ...) +{ + va_list ap; + size_t len; + va_start(ap, fmt); + + vsnprintf(data->state.buffer, BUFSIZE, fmt, ap); + + if(data->set.errorbuffer && !data->state.errorbuf) { + snprintf(data->set.errorbuffer, CURL_ERROR_SIZE, "%s", data->state.buffer); + data->state.errorbuf = TRUE; /* wrote error string */ + } + if(data->set.verbose) { + len = strlen(data->state.buffer); + if(len < BUFSIZE - 1) { + data->state.buffer[len] = '\n'; + data->state.buffer[++len] = '\0'; + } + Curl_debug(data, CURLINFO_TEXT, data->state.buffer, len, NULL); + } + + va_end(ap); +} + +/* Curl_sendf() sends formated data to the server */ +CURLcode Curl_sendf(curl_socket_t sockfd, struct connectdata *conn, + const char *fmt, ...) +{ + struct SessionHandle *data = conn->data; + ssize_t bytes_written; + size_t write_len; + CURLcode res = CURLE_OK; + char *s; + char *sptr; + va_list ap; + va_start(ap, fmt); + s = vaprintf(fmt, ap); /* returns an allocated string */ + va_end(ap); + if(!s) + return CURLE_OUT_OF_MEMORY; /* failure */ + + bytes_written=0; + write_len = strlen(s); + sptr = s; + + for(;;) { + /* Write the buffer to the socket */ + res = Curl_write(conn, sockfd, sptr, write_len, &bytes_written); + + if(CURLE_OK != res) + break; + + if(data->set.verbose) + Curl_debug(data, CURLINFO_DATA_OUT, sptr, (size_t)bytes_written, conn); + + if((size_t)bytes_written != write_len) { + /* if not all was written at once, we must advance the pointer, decrease + the size left and try again! */ + write_len -= bytes_written; + sptr += bytes_written; + } + else + break; + } + + free(s); /* free the output string */ + + return res; +} + +/* + * Curl_write() is an internal write function that sends data to the + * server. Works with plain sockets, SCP, SSL or kerberos. + * + * If the write would block (CURLE_AGAIN), we return CURLE_OK and + * (*written == 0). Otherwise we return regular CURLcode value. + */ +CURLcode Curl_write(struct connectdata *conn, + curl_socket_t sockfd, + const void *mem, + size_t len, + ssize_t *written) +{ + ssize_t bytes_written; + CURLcode curlcode = CURLE_OK; + int num = (sockfd == conn->sock[SECONDARYSOCKET]); + + bytes_written = conn->send[num](conn, num, mem, len, &curlcode); + + *written = bytes_written; + if(bytes_written >= 0) + /* we completely ignore the curlcode value when subzero is not returned */ + return CURLE_OK; + + /* handle CURLE_AGAIN or a send failure */ + switch(curlcode) { + case CURLE_AGAIN: + *written = 0; + return CURLE_OK; + + case CURLE_OK: + /* general send failure */ + return CURLE_SEND_ERROR; + + default: + /* we got a specific curlcode, forward it */ + return curlcode; + } +} + +ssize_t Curl_send_plain(struct connectdata *conn, int num, + const void *mem, size_t len, CURLcode *code) +{ + curl_socket_t sockfd = conn->sock[num]; + ssize_t bytes_written = swrite(sockfd, mem, len); + + *code = CURLE_OK; + if(-1 == bytes_written) { + int err = SOCKERRNO; + + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == err) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned + due to its inability to send off data without blocking. We therefor + treat both error codes the same here */ + (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) +#endif + ) { + /* this is just a case of EWOULDBLOCK */ + bytes_written=0; + *code = CURLE_AGAIN; + } + else { + failf(conn->data, "Send failure: %s", + Curl_strerror(conn, err)); + conn->data->state.os_errno = err; + *code = CURLE_SEND_ERROR; + } + } + return bytes_written; +} + +/* + * Curl_write_plain() is an internal write function that sends data to the + * server using plain sockets only. Otherwise meant to have the exact same + * proto as Curl_write() + */ +CURLcode Curl_write_plain(struct connectdata *conn, + curl_socket_t sockfd, + const void *mem, + size_t len, + ssize_t *written) +{ + ssize_t bytes_written; + CURLcode retcode; + int num = (sockfd == conn->sock[SECONDARYSOCKET]); + + bytes_written = Curl_send_plain(conn, num, mem, len, &retcode); + + *written = bytes_written; + + return retcode; +} + +ssize_t Curl_recv_plain(struct connectdata *conn, int num, char *buf, + size_t len, CURLcode *code) +{ + curl_socket_t sockfd = conn->sock[num]; + ssize_t nread = sread(sockfd, buf, len); + + *code = CURLE_OK; + if(-1 == nread) { + int err = SOCKERRNO; + + if( +#ifdef WSAEWOULDBLOCK + /* This is how Windows does it */ + (WSAEWOULDBLOCK == err) +#else + /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned + due to its inability to send off data without blocking. We therefor + treat both error codes the same here */ + (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) +#endif + ) { + /* this is just a case of EWOULDBLOCK */ + *code = CURLE_AGAIN; + } + else { + failf(conn->data, "Recv failure: %s", + Curl_strerror(conn, err)); + conn->data->state.os_errno = err; + *code = CURLE_RECV_ERROR; + } + } + return nread; +} + +static CURLcode pausewrite(struct SessionHandle *data, + int type, /* what type of data */ + const char *ptr, + size_t len) +{ + /* signalled to pause sending on this connection, but since we have data + we want to send we need to dup it to save a copy for when the sending + is again enabled */ + struct SingleRequest *k = &data->req; + char *dupl = malloc(len); + if(!dupl) + return CURLE_OUT_OF_MEMORY; + + memcpy(dupl, ptr, len); + + /* store this information in the state struct for later use */ + data->state.tempwrite = dupl; + data->state.tempwritesize = len; + data->state.tempwritetype = type; + + /* mark the connection as RECV paused */ + k->keepon |= KEEP_RECV_PAUSE; + + DEBUGF(infof(data, "Pausing with %zu bytes in buffer for type %02x\n", + len, type)); + + return CURLE_OK; +} + + +/* Curl_client_write() sends data to the write callback(s) + + The bit pattern defines to what "streams" to write to. Body and/or header. + The defines are in curl_sendf.h of course. + + If CURL_DO_LINEEND_CONV is enabled, data is converted IN PLACE to the + local character encoding. This is a problem and should be changed in + the future to leave the original data alone. + */ +CURLcode Curl_client_write(struct connectdata *conn, + int type, + char *ptr, + size_t len) +{ + struct SessionHandle *data = conn->data; + size_t wrote; + + if(0 == len) + len = strlen(ptr); + + /* If reading is actually paused, we're forced to append this chunk of data + to the already held data, but only if it is the same type as otherwise it + can't work and it'll return error instead. */ + if(data->req.keepon & KEEP_RECV_PAUSE) { + size_t newlen; + char *newptr; + if(type != data->state.tempwritetype) + /* major internal confusion */ + return CURLE_RECV_ERROR; + + DEBUGASSERT(data->state.tempwrite); + + /* figure out the new size of the data to save */ + newlen = len + data->state.tempwritesize; + /* allocate the new memory area */ + newptr = realloc(data->state.tempwrite, newlen); + if(!newptr) + return CURLE_OUT_OF_MEMORY; + /* copy the new data to the end of the new area */ + memcpy(newptr + data->state.tempwritesize, ptr, len); + /* update the pointer and the size */ + data->state.tempwrite = newptr; + data->state.tempwritesize = newlen; + + return CURLE_OK; + } + + if(type & CLIENTWRITE_BODY) { + if((conn->handler->protocol&CURLPROTO_FTP) && + conn->proto.ftpc.transfertype == 'A') { + /* convert from the network encoding */ + CURLcode rc = Curl_convert_from_network(data, ptr, len); + /* Curl_convert_from_network calls failf if unsuccessful */ + if(rc) + return rc; + +#ifdef CURL_DO_LINEEND_CONV + /* convert end-of-line markers */ + len = convert_lineends(data, ptr, len); +#endif /* CURL_DO_LINEEND_CONV */ + } + /* If the previous block of data ended with CR and this block of data is + just a NL, then the length might be zero */ + if(len) { + wrote = data->set.fwrite_func(ptr, 1, len, data->set.out); + } + else { + wrote = len; + } + + if(CURL_WRITEFUNC_PAUSE == wrote) + return pausewrite(data, type, ptr, len); + + if(wrote != len) { + failf(data, "Failed writing body (%zu != %zu)", wrote, len); + return CURLE_WRITE_ERROR; + } + } + + if((type & CLIENTWRITE_HEADER) && + (data->set.fwrite_header || data->set.writeheader) ) { + /* + * Write headers to the same callback or to the especially setup + * header callback function (added after version 7.7.1). + */ + curl_write_callback writeit= + data->set.fwrite_header?data->set.fwrite_header:data->set.fwrite_func; + + /* Note: The header is in the host encoding + regardless of the ftp transfer mode (ASCII/Image) */ + + wrote = writeit(ptr, 1, len, data->set.writeheader); + if(CURL_WRITEFUNC_PAUSE == wrote) + /* here we pass in the HEADER bit only since if this was body as well + then it was passed already and clearly that didn't trigger the pause, + so this is saved for later with the HEADER bit only */ + return pausewrite(data, CLIENTWRITE_HEADER, ptr, len); + + if(wrote != len) { + failf (data, "Failed writing header"); + return CURLE_WRITE_ERROR; + } + } + + return CURLE_OK; +} + +CURLcode Curl_read_plain(curl_socket_t sockfd, + char *buf, + size_t bytesfromsocket, + ssize_t *n) +{ + ssize_t nread = sread(sockfd, buf, bytesfromsocket); + + if(-1 == nread) { + int err = SOCKERRNO; +#ifdef USE_WINSOCK + if(WSAEWOULDBLOCK == err) +#else + if((EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err)) +#endif + return CURLE_AGAIN; + else + return CURLE_RECV_ERROR; + } + + /* we only return number of bytes read when we return OK */ + *n = nread; + return CURLE_OK; +} + +/* + * Internal read-from-socket function. This is meant to deal with plain + * sockets, SSL sockets and kerberos sockets. + * + * Returns a regular CURLcode value. + */ +CURLcode Curl_read(struct connectdata *conn, /* connection data */ + curl_socket_t sockfd, /* read from this socket */ + char *buf, /* store read data here */ + size_t sizerequested, /* max amount to read */ + ssize_t *n) /* amount bytes read */ +{ + CURLcode curlcode = CURLE_RECV_ERROR; + ssize_t nread = 0; + size_t bytesfromsocket = 0; + char *buffertofill = NULL; + bool pipelining = (conn->data->multi && + Curl_multi_canPipeline(conn->data->multi)) ? TRUE : FALSE; + + /* Set 'num' to 0 or 1, depending on which socket that has been sent here. + If it is the second socket, we set num to 1. Otherwise to 0. This lets + us use the correct ssl handle. */ + int num = (sockfd == conn->sock[SECONDARYSOCKET]); + + *n=0; /* reset amount to zero */ + + /* If session can pipeline, check connection buffer */ + if(pipelining) { + size_t bytestocopy = CURLMIN(conn->buf_len - conn->read_pos, + sizerequested); + + /* Copy from our master buffer first if we have some unread data there*/ + if(bytestocopy > 0) { + memcpy(buf, conn->master_buffer + conn->read_pos, bytestocopy); + conn->read_pos += bytestocopy; + conn->bits.stream_was_rewound = FALSE; + + *n = (ssize_t)bytestocopy; + return CURLE_OK; + } + /* If we come here, it means that there is no data to read from the buffer, + * so we read from the socket */ + bytesfromsocket = CURLMIN(sizerequested, BUFSIZE * sizeof (char)); + buffertofill = conn->master_buffer; + } + else { + bytesfromsocket = CURLMIN((long)sizerequested, + conn->data->set.buffer_size ? + conn->data->set.buffer_size : BUFSIZE); + buffertofill = buf; + } + + nread = conn->recv[num](conn, num, buffertofill, bytesfromsocket, &curlcode); + if(nread < 0) + return curlcode; + + if(pipelining) { + memcpy(buf, conn->master_buffer, nread); + conn->buf_len = nread; + conn->read_pos = nread; + } + + *n += nread; + + return CURLE_OK; +} + +/* return 0 on success */ +static int showit(struct SessionHandle *data, curl_infotype type, + char *ptr, size_t size) +{ + static const char s_infotype[CURLINFO_END][3] = { + "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; + +#ifdef CURL_DOES_CONVERSIONS + char buf[BUFSIZE+1]; + size_t conv_size = 0; + + switch(type) { + case CURLINFO_HEADER_OUT: + /* assume output headers are ASCII */ + /* copy the data into my buffer so the original is unchanged */ + if(size > BUFSIZE) { + size = BUFSIZE; /* truncate if necessary */ + buf[BUFSIZE] = '\0'; + } + conv_size = size; + memcpy(buf, ptr, size); + /* Special processing is needed for this block if it + * contains both headers and data (separated by CRLFCRLF). + * We want to convert just the headers, leaving the data as-is. + */ + if(size > 4) { + size_t i; + for(i = 0; i < size-4; i++) { + if(memcmp(&buf[i], "\x0d\x0a\x0d\x0a", 4) == 0) { + /* convert everything through this CRLFCRLF but no further */ + conv_size = i + 4; + break; + } + } + } + + Curl_convert_from_network(data, buf, conv_size); + /* Curl_convert_from_network calls failf if unsuccessful */ + /* we might as well continue even if it fails... */ + ptr = buf; /* switch pointer to use my buffer instead */ + break; + default: + /* leave everything else as-is */ + break; + } +#endif /* CURL_DOES_CONVERSIONS */ + + if(data->set.fdebug) + return (*data->set.fdebug)(data, type, ptr, size, + data->set.debugdata); + + switch(type) { + case CURLINFO_TEXT: + case CURLINFO_HEADER_OUT: + case CURLINFO_HEADER_IN: + fwrite(s_infotype[type], 2, 1, data->set.err); + fwrite(ptr, size, 1, data->set.err); +#ifdef CURL_DOES_CONVERSIONS + if(size != conv_size) { + /* we had untranslated data so we need an explicit newline */ + fwrite("\n", 1, 1, data->set.err); + } +#endif + break; + default: /* nada */ + break; + } + return 0; +} + +int Curl_debug(struct SessionHandle *data, curl_infotype type, + char *ptr, size_t size, + struct connectdata *conn) +{ + int rc; + if(data->set.printhost && conn && conn->host.dispname) { + char buffer[160]; + const char *t=NULL; + const char *w="Data"; + switch (type) { + case CURLINFO_HEADER_IN: + w = "Header"; + case CURLINFO_DATA_IN: + t = "from"; + break; + case CURLINFO_HEADER_OUT: + w = "Header"; + case CURLINFO_DATA_OUT: + t = "to"; + break; + default: + break; + } + + if(t) { + snprintf(buffer, sizeof(buffer), "[%s %s %s]", w, t, + conn->host.dispname); + rc = showit(data, CURLINFO_TEXT, buffer, strlen(buffer)); + if(rc) + return rc; + } + } + rc = showit(data, type, ptr, size); + return rc; +} diff --git a/lib/curl_share.c b/lib/curl_share.c new file mode 100644 index 000000000..182e6e99b --- /dev/null +++ b/lib/curl_share.c @@ -0,0 +1,254 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include +#include "curl_urldata.h" +#include "curl_share.h" +#include "curl_sslgen.h" +#include "curl_memory.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +CURLSH * +curl_share_init(void) +{ + struct Curl_share *share = calloc(1, sizeof(struct Curl_share)); + if(share) + share->specifier |= (1<dirty) + /* don't allow setting options while one or more handles are already + using this share */ + return CURLSHE_IN_USE; + + va_start(param, option); + + switch(option) { + case CURLSHOPT_SHARE: + /* this is a type this share will share */ + type = va_arg(param, int); + share->specifier |= (1<hostcache) { + share->hostcache = Curl_mk_dnscache(); + if(!share->hostcache) + res = CURLSHE_NOMEM; + } + break; + + case CURL_LOCK_DATA_COOKIE: +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(!share->cookies) { + share->cookies = Curl_cookie_init(NULL, NULL, NULL, TRUE ); + if(!share->cookies) + res = CURLSHE_NOMEM; + } +#else /* CURL_DISABLE_HTTP */ + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_SSL_SESSION: +#ifdef USE_SSL + if(!share->sslsession) { + share->max_ssl_sessions = 8; + share->sslsession = calloc(share->max_ssl_sessions, + sizeof(struct curl_ssl_session)); + share->sessionage = 0; + if(!share->sslsession) + res = CURLSHE_NOMEM; + } +#else + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_CONNECT: /* not supported (yet) */ + break; + + default: + res = CURLSHE_BAD_OPTION; + } + break; + + case CURLSHOPT_UNSHARE: + /* this is a type this share will no longer share */ + type = va_arg(param, int); + share->specifier &= ~(1<hostcache) { + Curl_hash_destroy(share->hostcache); + share->hostcache = NULL; + } + break; + + case CURL_LOCK_DATA_COOKIE: +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(share->cookies) { + Curl_cookie_cleanup(share->cookies); + share->cookies = NULL; + } +#else /* CURL_DISABLE_HTTP */ + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_SSL_SESSION: +#ifdef USE_SSL + Curl_safefree(share->sslsession); +#else + res = CURLSHE_NOT_BUILT_IN; +#endif + break; + + case CURL_LOCK_DATA_CONNECT: + break; + + default: + res = CURLSHE_BAD_OPTION; + break; + } + break; + + case CURLSHOPT_LOCKFUNC: + lockfunc = va_arg(param, curl_lock_function); + share->lockfunc = lockfunc; + break; + + case CURLSHOPT_UNLOCKFUNC: + unlockfunc = va_arg(param, curl_unlock_function); + share->unlockfunc = unlockfunc; + break; + + case CURLSHOPT_USERDATA: + ptr = va_arg(param, void *); + share->clientdata = ptr; + break; + + default: + res = CURLSHE_BAD_OPTION; + break; + } + + va_end(param); + + return res; +} + +CURLSHcode +curl_share_cleanup(CURLSH *sh) +{ + struct Curl_share *share = (struct Curl_share *)sh; + + if(share == NULL) + return CURLSHE_INVALID; + + if(share->lockfunc) + share->lockfunc(NULL, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE, + share->clientdata); + + if(share->dirty) { + if(share->unlockfunc) + share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata); + return CURLSHE_IN_USE; + } + + if(share->hostcache) { + Curl_hash_destroy(share->hostcache); + share->hostcache = NULL; + } + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(share->cookies) + Curl_cookie_cleanup(share->cookies); +#endif + +#ifdef USE_SSL + if(share->sslsession) { + size_t i; + for(i = 0; i < share->max_ssl_sessions; i++) + Curl_ssl_kill_session(&(share->sslsession[i])); + free(share->sslsession); + } +#endif + + if(share->unlockfunc) + share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata); + free(share); + + return CURLSHE_OK; +} + + +CURLSHcode +Curl_share_lock(struct SessionHandle *data, curl_lock_data type, + curl_lock_access accesstype) +{ + struct Curl_share *share = data->share; + + if(share == NULL) + return CURLSHE_INVALID; + + if(share->specifier & (1<lockfunc) /* only call this if set! */ + share->lockfunc(data, type, accesstype, share->clientdata); + } + /* else if we don't share this, pretend successful lock */ + + return CURLSHE_OK; +} + +CURLSHcode +Curl_share_unlock(struct SessionHandle *data, curl_lock_data type) +{ + struct Curl_share *share = data->share; + + if(share == NULL) + return CURLSHE_INVALID; + + if(share->specifier & (1<unlockfunc) /* only call this if set! */ + share->unlockfunc (data, type, share->clientdata); + } + + return CURLSHE_OK; +} diff --git a/lib/curl_slist.c b/lib/curl_slist.c new file mode 100644 index 000000000..2a30ea620 --- /dev/null +++ b/lib/curl_slist.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_memory.h" +#include "curl_slist.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* returns last node in linked list */ +static struct curl_slist *slist_get_last(struct curl_slist *list) +{ + struct curl_slist *item; + + /* if caller passed us a NULL, return now */ + if(!list) + return NULL; + + /* loop through to find the last item */ + item = list; + while(item->next) { + item = item->next; + } + return item; +} + +/* + * curl_slist_append() appends a string to the linked list. It always returns + * the address of the first record, so that you can use this function as an + * initialization function as well as an append function. If you find this + * bothersome, then simply create a separate _init function and call it + * appropriately from within the program. + */ +struct curl_slist *curl_slist_append(struct curl_slist *list, + const char *data) +{ + struct curl_slist *last; + struct curl_slist *new_item; + + new_item = malloc(sizeof(struct curl_slist)); + if(new_item) { + char *dupdata = strdup(data); + if(dupdata) { + new_item->next = NULL; + new_item->data = dupdata; + } + else { + free(new_item); + return NULL; + } + } + else + return NULL; + + if(list) { + last = slist_get_last(list); + last->next = new_item; + return list; + } + + /* if this is the first item, then new_item *is* the list */ + return new_item; +} + +/* + * Curl_slist_duplicate() duplicates a linked list. It always returns the + * address of the first record of the cloned list or NULL in case of an + * error (or if the input list was NULL). + */ +struct curl_slist *Curl_slist_duplicate(struct curl_slist *inlist) +{ + struct curl_slist *outlist = NULL; + struct curl_slist *tmp; + + while(inlist) { + tmp = curl_slist_append(outlist, inlist->data); + + if(!tmp) { + curl_slist_free_all(outlist); + return NULL; + } + + outlist = tmp; + inlist = inlist->next; + } + return outlist; +} + +/* be nice and clean up resources */ +void curl_slist_free_all(struct curl_slist *list) +{ + struct curl_slist *next; + struct curl_slist *item; + + if(!list) + return; + + item = list; + do { + next = item->next; + Curl_safefree(item->data); + free(item); + item = next; + } while(next); +} + diff --git a/lib/curl_smtp.c b/lib/curl_smtp.c new file mode 100644 index 000000000..5c4c25c6f --- /dev/null +++ b/lib/curl_smtp.c @@ -0,0 +1,1750 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + * RFC2821 SMTP protocol + * RFC3207 SMTP over TLS + * RFC4954 SMTP Authentication + * RFC2195 CRAM-MD5 authentication + * RFC2831 DIGEST-MD5 authentication + * RFC4616 PLAIN authentication + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_SMTP + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_if2ip.h" +#include "curl_hostip.h" +#include "curl_progress.h" +#include "curl_transfer.h" +#include "curl_escape.h" +#include "curl_http.h" /* for HTTP proxy tunnel stuff */ +#include "curl_socks.h" +#include "curl_smtp.h" + +#include "curl_strtoofft.h" +#include "curl_strequal.h" +#include "curl_sslgen.h" +#include "curl_connect.h" +#include "curl_strerror.h" +#include "curl_select.h" +#include "curl_multiif.h" +#include "curl_url.h" +#include "curl_rawstr.h" +#include "curl_gethostname.h" +#include "curl_sasl.h" +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* Local API functions */ +static CURLcode smtp_regular_transfer(struct connectdata *conn, bool *done); +static CURLcode smtp_do(struct connectdata *conn, bool *done); +static CURLcode smtp_done(struct connectdata *conn, CURLcode status, + bool premature); +static CURLcode smtp_connect(struct connectdata *conn, bool *done); +static CURLcode smtp_disconnect(struct connectdata *conn, bool dead); +static CURLcode smtp_multi_statemach(struct connectdata *conn, bool *done); +static int smtp_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks); +static CURLcode smtp_doing(struct connectdata *conn, bool *dophase_done); +static CURLcode smtp_setup_connection(struct connectdata *conn); +static CURLcode smtp_state_upgrade_tls(struct connectdata *conn); + +/* + * SMTP protocol handler. + */ + +const struct Curl_handler Curl_handler_smtp = { + "SMTP", /* scheme */ + smtp_setup_connection, /* setup_connection */ + smtp_do, /* do_it */ + smtp_done, /* done */ + ZERO_NULL, /* do_more */ + smtp_connect, /* connect_it */ + smtp_multi_statemach, /* connecting */ + smtp_doing, /* doing */ + smtp_getsock, /* proto_getsock */ + smtp_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + smtp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_SMTP, /* defport */ + CURLPROTO_SMTP, /* protocol */ + PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY /* flags */ +}; + +#ifdef USE_SSL +/* + * SMTPS protocol handler. + */ + +const struct Curl_handler Curl_handler_smtps = { + "SMTPS", /* scheme */ + smtp_setup_connection, /* setup_connection */ + smtp_do, /* do_it */ + smtp_done, /* done */ + ZERO_NULL, /* do_more */ + smtp_connect, /* connect_it */ + smtp_multi_statemach, /* connecting */ + smtp_doing, /* doing */ + smtp_getsock, /* proto_getsock */ + smtp_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + smtp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_SMTPS, /* defport */ + CURLPROTO_SMTP | CURLPROTO_SMTPS, /* protocol */ + PROTOPT_CLOSEACTION | PROTOPT_SSL + | PROTOPT_NOURLQUERY /* flags */ +}; +#endif + +#ifndef CURL_DISABLE_HTTP +/* + * HTTP-proxyed SMTP protocol handler. + */ + +static const struct Curl_handler Curl_handler_smtp_proxy = { + "SMTP", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_SMTP, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +#ifdef USE_SSL +/* + * HTTP-proxyed SMTPS protocol handler. + */ + +static const struct Curl_handler Curl_handler_smtps_proxy = { + "SMTPS", /* scheme */ + ZERO_NULL, /* setup_connection */ + Curl_http, /* do_it */ + Curl_http_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_SMTPS, /* defport */ + CURLPROTO_HTTP, /* protocol */ + PROTOPT_NONE /* flags */ +}; +#endif +#endif + +/* Function that checks for an ending smtp status code at the start of the + given string, but also detects the supported authentication mechanisms + from the EHLO AUTH response. */ +static int smtp_endofresp(struct pingpong *pp, int *resp) +{ + char *line = pp->linestart_resp; + size_t len = pp->nread_resp; + struct connectdata *conn = pp->conn; + struct smtp_conn *smtpc = &conn->proto.smtpc; + int result; + size_t wordlen; + + if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2])) + return FALSE; /* Nothing for us */ + + if((result = (line[3] == ' ')) != 0) + *resp = curlx_sltosi(strtol(line, NULL, 10)); + + line += 4; + len -= 4; + + if(smtpc->state == SMTP_EHLO && len >= 4 && !memcmp(line, "SIZE", 4)) { + DEBUGF(infof(conn->data, "Server supports SIZE extension.\n")); + smtpc->size_supported = true; + } + + if(smtpc->state == SMTP_EHLO && len >= 5 && !memcmp(line, "AUTH ", 5)) { + line += 5; + len -= 5; + + for(;;) { + while(len && + (*line == ' ' || *line == '\t' || + *line == '\r' || *line == '\n')) { + line++; + len--; + } + + if(!len) + break; + + for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && + line[wordlen] != '\t' && line[wordlen] != '\r' && + line[wordlen] != '\n';) + wordlen++; + + if(wordlen == 5 && !memcmp(line, "LOGIN", 5)) + smtpc->authmechs |= SASL_MECH_LOGIN; + else if(wordlen == 5 && !memcmp(line, "PLAIN", 5)) + smtpc->authmechs |= SASL_MECH_PLAIN; + else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8)) + smtpc->authmechs |= SASL_MECH_CRAM_MD5; + else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10)) + smtpc->authmechs |= SASL_MECH_DIGEST_MD5; + else if(wordlen == 6 && !memcmp(line, "GSSAPI", 6)) + smtpc->authmechs |= SASL_MECH_GSSAPI; + else if(wordlen == 8 && !memcmp(line, "EXTERNAL", 8)) + smtpc->authmechs |= SASL_MECH_EXTERNAL; + else if(wordlen == 4 && !memcmp(line, "NTLM", 4)) + smtpc->authmechs |= SASL_MECH_NTLM; + + line += wordlen; + len -= wordlen; + } + } + + return result; +} + +/* This is the ONLY way to change SMTP state! */ +static void state(struct connectdata *conn, smtpstate newstate) +{ + struct smtp_conn *smtpc = &conn->proto.smtpc; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[] = { + "STOP", + "SERVERGREET", + "EHLO", + "HELO", + "STARTTLS", + "UPGRADETLS", + "AUTH_PLAIN", + "AUTH_LOGIN", + "AUTH_PASSWD", + "AUTH_CRAMMD5", + "AUTH_DIGESTMD5", + "AUTH_DIGESTMD5_RESP", + "AUTH_NTLM", + "AUTH_NTLM_TYPE2MSG", + "AUTH", + "MAIL", + "RCPT", + "DATA", + "POSTDATA", + "QUIT", + /* LAST */ + }; + + if(smtpc->state != newstate) + infof(conn->data, "SMTP %p state change from %s to %s\n", + smtpc, names[smtpc->state], names[newstate]); +#endif + + smtpc->state = newstate; +} + +static CURLcode smtp_state_ehlo(struct connectdata *conn) +{ + CURLcode result; + struct smtp_conn *smtpc = &conn->proto.smtpc; + + smtpc->authmechs = 0; /* No known authentication mechanisms yet */ + smtpc->authused = 0; /* Clear the authentication mechanism used + for esmtp connections */ + + /* send EHLO */ + result = Curl_pp_sendf(&smtpc->pp, "EHLO %s", smtpc->domain); + + if(result) + return result; + + state(conn, SMTP_EHLO); + + return CURLE_OK; +} + +static CURLcode smtp_state_helo(struct connectdata *conn) +{ + CURLcode result; + struct smtp_conn *smtpc = &conn->proto.smtpc; + + smtpc->authused = 0; /* No authentication mechanism used in smtp + connections */ + + /* send HELO */ + result = Curl_pp_sendf(&smtpc->pp, "HELO %s", smtpc->domain); + + if(result) + return result; + + state(conn, SMTP_HELO); + + return CURLE_OK; +} + +static CURLcode smtp_authenticate(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct smtp_conn *smtpc = &conn->proto.smtpc; + char *initresp = NULL; + const char *mech = NULL; + size_t len = 0; + smtpstate state1 = SMTP_STOP; + smtpstate state2 = SMTP_STOP; + + /* Check we have a username and password to authenticate with and end the + connect phase if we don't */ + if(!conn->bits.user_passwd) { + state(conn, SMTP_STOP); + + return result; + } + + /* Check supported authentication mechanisms by decreasing order of + security */ +#ifndef CURL_DISABLE_CRYPTO_AUTH + if(smtpc->authmechs & SASL_MECH_DIGEST_MD5) { + mech = "DIGEST-MD5"; + state1 = SMTP_AUTH_DIGESTMD5; + smtpc->authused = SASL_MECH_DIGEST_MD5; + } + else if(smtpc->authmechs & SASL_MECH_CRAM_MD5) { + mech = "CRAM-MD5"; + state1 = SMTP_AUTH_CRAMMD5; + smtpc->authused = SASL_MECH_CRAM_MD5; + } + else +#endif +#ifdef USE_NTLM + if(smtpc->authmechs & SASL_MECH_NTLM) { + mech = "NTLM"; + state1 = SMTP_AUTH_NTLM; + state2 = SMTP_AUTH_NTLM_TYPE2MSG; + smtpc->authused = SASL_MECH_NTLM; + result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, + &conn->ntlm, + &initresp, &len); + } + else +#endif + if(smtpc->authmechs & SASL_MECH_LOGIN) { + mech = "LOGIN"; + state1 = SMTP_AUTH_LOGIN; + state2 = SMTP_AUTH_PASSWD; + smtpc->authused = SASL_MECH_LOGIN; + result = Curl_sasl_create_login_message(conn->data, conn->user, + &initresp, &len); + } + else if(smtpc->authmechs & SASL_MECH_PLAIN) { + mech = "PLAIN"; + state1 = SMTP_AUTH_PLAIN; + state2 = SMTP_AUTH; + smtpc->authused = SASL_MECH_PLAIN; + result = Curl_sasl_create_plain_message(conn->data, conn->user, + conn->passwd, &initresp, &len); + } + else { + infof(conn->data, "No known authentication mechanisms supported!\n"); + result = CURLE_LOGIN_DENIED; /* Other mechanisms not supported */ + } + + if(!result) { + if(initresp && + strlen(mech) + len <= 512 - 8) { /* AUTH ... */ + result = Curl_pp_sendf(&smtpc->pp, "AUTH %s %s", mech, initresp); + + if(!result) + state(conn, state2); + } + else { + result = Curl_pp_sendf(&smtpc->pp, "AUTH %s", mech); + + if(!result) + state(conn, state1); + } + Curl_safefree(initresp); + } + + return result; +} + +/* For the SMTP "protocol connect" and "doing" phases only */ +static int smtp_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + return Curl_pp_getsock(&conn->proto.smtpc.pp, socks, numsocks); +} + +#ifdef USE_SSL +static void smtp_to_smtps(struct connectdata *conn) +{ + conn->handler = &Curl_handler_smtps; +} +#else +#define smtp_to_smtps(x) Curl_nop_stmt +#endif + +/* For the initial server greeting */ +static CURLcode smtp_state_servergreet_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2) { + failf(data, "Got unexpected smtp-server response: %d", smtpcode); + return CURLE_FTP_WEIRD_SERVER_REPLY; + } + + result = smtp_state_ehlo(conn); + + return result; +} + +/* For STARTTLS responses */ +static CURLcode smtp_state_starttls_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 220) { + if(data->set.use_ssl != CURLUSESSL_TRY) { + failf(data, "STARTTLS denied. %c", smtpcode); + result = CURLE_USE_SSL_FAILED; + } + else + result = smtp_authenticate(conn); + } + else { + if(data->state.used_interface == Curl_if_multi) { + state(conn, SMTP_UPGRADETLS); + result = smtp_state_upgrade_tls(conn); + } + else { + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(CURLE_OK == result) { + smtp_to_smtps(conn); + result = smtp_state_ehlo(conn); + } + } + } + + return result; +} + +static CURLcode smtp_state_upgrade_tls(struct connectdata *conn) +{ + struct smtp_conn *smtpc = &conn->proto.smtpc; + CURLcode result; + + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &smtpc->ssldone); + + if(smtpc->ssldone) { + smtp_to_smtps(conn); + result = smtp_state_ehlo(conn); + } + + return result; +} + +/* For EHLO responses */ +static CURLcode smtp_state_ehlo_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2) { + if((data->set.use_ssl <= CURLUSESSL_TRY || conn->ssl[FIRSTSOCKET].use) && + !conn->bits.user_passwd) + result = smtp_state_helo(conn); + else { + failf(data, "Remote access denied: %d", smtpcode); + result = CURLE_REMOTE_ACCESS_DENIED; + } + } + else if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { + /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch + to TLS connection now */ + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "STARTTLS"); + state(conn, SMTP_STARTTLS); + } + else + result = smtp_authenticate(conn); + + return result; +} + +/* For HELO responses */ +static CURLcode smtp_state_helo_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2) { + failf(data, "Remote access denied: %d", smtpcode); + result = CURLE_REMOTE_ACCESS_DENIED; + } + else + /* End of connect phase */ + state(conn, SMTP_STOP); + + return result; +} + +/* For AUTH PLAIN (without initial response) responses */ +static CURLcode smtp_state_auth_plain_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *plainauth = NULL; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 334) { + failf(data, "Access denied: %d", smtpcode); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the authorisation message */ + result = Curl_sasl_create_plain_message(conn->data, conn->user, + conn->passwd, &plainauth, &len); + + /* Send the message */ + if(!result) { + if(plainauth) { + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", plainauth); + + if(!result) + state(conn, SMTP_AUTH); + } + + Curl_safefree(plainauth); + } + } + + return result; +} + +/* For AUTH LOGIN (without initial response) responses */ +static CURLcode smtp_state_auth_login_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *authuser = NULL; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 334) { + failf(data, "Access denied: %d", smtpcode); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the user message */ + result = Curl_sasl_create_login_message(conn->data, conn->user, + &authuser, &len); + + /* Send the user */ + if(!result) { + if(authuser) { + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", authuser); + + if(!result) + state(conn, SMTP_AUTH_PASSWD); + } + + Curl_safefree(authuser); + } + } + + return result; +} + +/* For responses to user entry of AUTH LOGIN */ +static CURLcode smtp_state_auth_passwd_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *authpasswd = NULL; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 334) { + failf(data, "Access denied: %d", smtpcode); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the password message */ + result = Curl_sasl_create_login_message(conn->data, conn->passwd, + &authpasswd, &len); + + /* Send the password */ + if(!result) { + if(authpasswd) { + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", authpasswd); + + if(!result) + state(conn, SMTP_AUTH); + } + + Curl_safefree(authpasswd); + } + } + + return result; +} + +#ifndef CURL_DISABLE_CRYPTO_AUTH +/* For AUTH CRAM-MD5 responses */ +static CURLcode smtp_state_auth_cram_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + char *chlg64 = data->state.buffer; + size_t len = 0; + char *rplyb64 = NULL; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 334) { + failf(data, "Access denied: %d", smtpcode); + return CURLE_LOGIN_DENIED; + } + + /* Get the challenge */ + for(chlg64 += 4; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++) + ; + + /* Terminate the challenge */ + if(*chlg64 != '=') { + for(len = strlen(chlg64); len--;) + if(chlg64[len] != '\r' && chlg64[len] != '\n' && chlg64[len] != ' ' && + chlg64[len] != '\t') + break; + + if(++len) { + chlg64[len] = '\0'; + } + } + + /* Create the response message */ + result = Curl_sasl_create_cram_md5_message(data, chlg64, conn->user, + conn->passwd, &rplyb64, &len); + + /* Send the response */ + if(!result) { + if(rplyb64) { + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", rplyb64); + + if(!result) + state(conn, SMTP_AUTH); + } + + Curl_safefree(rplyb64); + } + + return result; +} + +/* For AUTH DIGEST-MD5 challenge responses */ +static CURLcode smtp_state_auth_digest_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + char *chlg64 = data->state.buffer; + size_t len = 0; + char *rplyb64 = NULL; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 334) { + failf(data, "Access denied: %d", smtpcode); + return CURLE_LOGIN_DENIED; + } + + /* Get the challenge */ + for(chlg64 += 4; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++) + ; + + /* Create the response message */ + result = Curl_sasl_create_digest_md5_message(data, chlg64, conn->user, + conn->passwd, "smtp", + &rplyb64, &len); + + /* Send the response */ + if(!result) { + if(rplyb64) { + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", rplyb64); + + if(!result) + state(conn, SMTP_AUTH_DIGESTMD5_RESP); + } + + Curl_safefree(rplyb64); + } + + return result; +} + +/* For AUTH DIGEST-MD5 challenge-response responses */ +static CURLcode smtp_state_auth_digest_resp_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 334) { + failf(data, "Authentication failed: %d", smtpcode); + result = CURLE_LOGIN_DENIED; + } + else { + /* Send an empty response */ + result = Curl_pp_sendf(&conn->proto.smtpc.pp, ""); + + if(!result) + state(conn, SMTP_AUTH); + } + + return result; +} + +#endif + +#ifdef USE_NTLM +/* For AUTH NTLM (without initial response) responses */ +static CURLcode smtp_state_auth_ntlm_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + char *type1msg = NULL; + size_t len = 0; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 334) { + failf(data, "Access denied: %d", smtpcode); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the type-1 message */ + result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, + &conn->ntlm, + &type1msg, &len); + + /* Send the message */ + if(!result) { + if(type1msg) { + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", type1msg); + + if(!result) + state(conn, SMTP_AUTH_NTLM_TYPE2MSG); + } + + Curl_safefree(type1msg); + } + } + + return result; +} + +/* For NTLM type-2 responses (sent in reponse to our type-1 message) */ +static CURLcode smtp_state_auth_ntlm_type2msg_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + char *type3msg = NULL; + size_t len = 0; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 334) { + failf(data, "Access denied: %d", smtpcode); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the type-3 message */ + result = Curl_sasl_create_ntlm_type3_message(data, + data->state.buffer + 4, + conn->user, conn->passwd, + &conn->ntlm, + &type3msg, &len); + + /* Send the message */ + if(!result) { + if(type3msg) { + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", type3msg); + + if(!result) + state(conn, SMTP_AUTH); + } + + Curl_safefree(type3msg); + } + } + + return result; +} +#endif + +/* For the final responses to the AUTH sequence */ +static CURLcode smtp_state_auth_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 235) { + failf(data, "Authentication failed: %d", smtpcode); + result = CURLE_LOGIN_DENIED; + } + else + /* End of connect phase */ + state(conn, SMTP_STOP); + + return result; +} + +/* Start the DO phase */ +static CURLcode smtp_mail(struct connectdata *conn) +{ + char *from = NULL; + char *auth = NULL; + char *size = NULL; + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + /* Calculate the FROM parameter */ + if(!data->set.str[STRING_MAIL_FROM]) + /* Null reverse-path, RFC-2821, sect. 3.7 */ + from = strdup("<>"); + else if(data->set.str[STRING_MAIL_FROM][0] == '<') + from = aprintf("%s", data->set.str[STRING_MAIL_FROM]); + else + from = aprintf("<%s>", data->set.str[STRING_MAIL_FROM]); + + if(!from) + return CURLE_OUT_OF_MEMORY; + + /* Calculate the optional AUTH parameter */ + if(data->set.str[STRING_MAIL_AUTH] && conn->proto.smtpc.authused) { + if(data->set.str[STRING_MAIL_AUTH][0] != '\0') + auth = aprintf("%s", data->set.str[STRING_MAIL_AUTH]); + else + /* Empty AUTH, RFC-2554, sect. 5 */ + auth = strdup("<>"); + + if(!auth) { + Curl_safefree(from); + + return CURLE_OUT_OF_MEMORY; + } + } + + /* calculate the optional SIZE parameter */ + if(conn->proto.smtpc.size_supported && conn->data->set.infilesize > 0) { + size = aprintf("%" FORMAT_OFF_T, data->set.infilesize); + + if(!size) { + Curl_safefree(from); + Curl_safefree(auth); + + return CURLE_OUT_OF_MEMORY; + } + } + + /* Send the MAIL command */ + if(!auth && !size) + result = Curl_pp_sendf(&conn->proto.smtpc.pp, + "MAIL FROM:%s", from); + else if(auth && !size) + result = Curl_pp_sendf(&conn->proto.smtpc.pp, + "MAIL FROM:%s AUTH=%s", from, auth); + else if(auth && size) + result = Curl_pp_sendf(&conn->proto.smtpc.pp, + "MAIL FROM:%s AUTH=%s SIZE=%s", from, auth, size); + else + result = Curl_pp_sendf(&conn->proto.smtpc.pp, + "MAIL FROM:%s SIZE=%s", from, size); + + Curl_safefree(from); + Curl_safefree(auth); + Curl_safefree(size); + + if(result) + return result; + + state(conn, SMTP_MAIL); + + return result; +} + +static CURLcode smtp_rcpt_to(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct smtp_conn *smtpc = &conn->proto.smtpc; + + /* Send the RCPT TO command */ + if(smtpc->rcpt) { + if(smtpc->rcpt->data[0] == '<') + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "RCPT TO:%s", + smtpc->rcpt->data); + else + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "RCPT TO:<%s>", + smtpc->rcpt->data); + if(!result) + state(conn, SMTP_RCPT); + } + + return result; +} + +/* For MAIL responses */ +static CURLcode smtp_state_mail_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2) { + failf(data, "MAIL failed: %d", smtpcode); + result = CURLE_SEND_ERROR; + state(conn, SMTP_STOP); + } + else { + struct smtp_conn *smtpc = &conn->proto.smtpc; + smtpc->rcpt = data->set.mail_rcpt; + + result = smtp_rcpt_to(conn); + } + + return result; +} + +/* For RCPT responses */ +static CURLcode smtp_state_rcpt_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* no use for this yet */ + + if(smtpcode/100 != 2) { + failf(data, "RCPT failed: %d", smtpcode); + result = CURLE_SEND_ERROR; + state(conn, SMTP_STOP); + } + else { + struct smtp_conn *smtpc = &conn->proto.smtpc; + + if(smtpc->rcpt) { + smtpc->rcpt = smtpc->rcpt->next; + result = smtp_rcpt_to(conn); + + /* If we failed or still are sending RCPT data then return */ + if(result || smtpc->rcpt) + return result; + } + + /* Send the DATA command */ + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "DATA"); + + if(result) + return result; + + state(conn, SMTP_DATA); + } + + return result; +} + +/* For DATA response */ +static CURLcode smtp_state_data_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + struct SessionHandle *data = conn->data; + struct FTP *smtp = data->state.proto.smtp; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 354) { + state(conn, SMTP_STOP); + return CURLE_SEND_ERROR; + } + + /* SMTP upload */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ + FIRSTSOCKET, smtp->bytecountp); + + /* End of do phase */ + state(conn, SMTP_STOP); + + return CURLE_OK; +} + +/* For POSTDATA responses, which are received after the entire DATA + part has been sent to the server */ +static CURLcode smtp_state_postdata_resp(struct connectdata *conn, + int smtpcode, + smtpstate instate) +{ + CURLcode result = CURLE_OK; + + (void)instate; /* no use for this yet */ + + if(smtpcode != 250) + result = CURLE_RECV_ERROR; + + /* End of done phase */ + state(conn, SMTP_STOP); + + return result; +} + +static CURLcode smtp_statemach_act(struct connectdata *conn) +{ + CURLcode result; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + struct SessionHandle *data = conn->data; + int smtpcode; + struct smtp_conn *smtpc = &conn->proto.smtpc; + struct pingpong *pp = &smtpc->pp; + size_t nread = 0; + + /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */ + if(smtpc->state == SMTP_UPGRADETLS) + return smtp_state_upgrade_tls(conn); + + /* Flush any data that needs to be sent */ + if(pp->sendleft) + return Curl_pp_flushsend(pp); + + /* Read the response from the server */ + result = Curl_pp_readresp(sock, pp, &smtpcode, &nread); + if(result) + return result; + + /* Store the latest response for later retrieval */ + if(smtpc->state != SMTP_QUIT) + data->info.httpcode = smtpcode; + + if(smtpcode) { + /* We have now received a full SMTP server response */ + switch(smtpc->state) { + case SMTP_SERVERGREET: + result = smtp_state_servergreet_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_EHLO: + result = smtp_state_ehlo_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_HELO: + result = smtp_state_helo_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_STARTTLS: + result = smtp_state_starttls_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_AUTH_PLAIN: + result = smtp_state_auth_plain_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_AUTH_LOGIN: + result = smtp_state_auth_login_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_AUTH_PASSWD: + result = smtp_state_auth_passwd_resp(conn, smtpcode, smtpc->state); + break; + +#ifndef CURL_DISABLE_CRYPTO_AUTH + case SMTP_AUTH_CRAMMD5: + result = smtp_state_auth_cram_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_AUTH_DIGESTMD5: + result = smtp_state_auth_digest_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_AUTH_DIGESTMD5_RESP: + result = smtp_state_auth_digest_resp_resp(conn, smtpcode, smtpc->state); + break; +#endif + +#ifdef USE_NTLM + case SMTP_AUTH_NTLM: + result = smtp_state_auth_ntlm_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_AUTH_NTLM_TYPE2MSG: + result = smtp_state_auth_ntlm_type2msg_resp(conn, smtpcode, + smtpc->state); + break; +#endif + + case SMTP_AUTH: + result = smtp_state_auth_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_MAIL: + result = smtp_state_mail_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_RCPT: + result = smtp_state_rcpt_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_DATA: + result = smtp_state_data_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_POSTDATA: + result = smtp_state_postdata_resp(conn, smtpcode, smtpc->state); + break; + + case SMTP_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + state(conn, SMTP_STOP); + break; + } + } + + return result; +} + +/* Called repeatedly until done from curl_multi.c */ +static CURLcode smtp_multi_statemach(struct connectdata *conn, bool *done) +{ + struct smtp_conn *smtpc = &conn->proto.smtpc; + CURLcode result; + + if((conn->handler->flags & PROTOPT_SSL) && !smtpc->ssldone) + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &smtpc->ssldone); + else + result = Curl_pp_multi_statemach(&smtpc->pp); + + *done = (smtpc->state == SMTP_STOP) ? TRUE : FALSE; + + return result; +} + +static CURLcode smtp_easy_statemach(struct connectdata *conn) +{ + struct smtp_conn *smtpc = &conn->proto.smtpc; + struct pingpong *pp = &smtpc->pp; + CURLcode result = CURLE_OK; + + while(smtpc->state != SMTP_STOP) { + result = Curl_pp_easy_statemach(pp); + if(result) + break; + } + + return result; +} + +/* Allocate and initialize the SMTP struct for the current SessionHandle if + required */ +static CURLcode smtp_init(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct FTP *smtp = data->state.proto.smtp; + + if(!smtp) { + smtp = data->state.proto.smtp = calloc(sizeof(struct FTP), 1); + if(!smtp) + return CURLE_OUT_OF_MEMORY; + } + + /* Get some initial data into the smtp struct */ + smtp->bytecountp = &data->req.bytecount; + + /* No need to duplicate user+password, the connectdata struct won't change + during a session, but we re-init them here since on subsequent inits + since the conn struct may have changed or been replaced. + */ + smtp->user = conn->user; + smtp->passwd = conn->passwd; + + return CURLE_OK; +} + +/*********************************************************************** + * + * smtp_connect() + * + * This function should do everything that is to be considered a part of + * the connection phase. + * + * The variable pointed to by 'done' will be TRUE if the protocol-layer + * connect phase is done when this function returns, or FALSE if not. When + * called as a part of the easy interface, it will always be TRUE. + */ +static CURLcode smtp_connect(struct connectdata *conn, bool *done) +{ + CURLcode result; + struct smtp_conn *smtpc = &conn->proto.smtpc; + struct SessionHandle *data = conn->data; + struct pingpong *pp = &smtpc->pp; + const char *path = conn->data->state.path; + char localhost[HOSTNAME_MAX + 1]; + + *done = FALSE; /* default to not done yet */ + + /* If there already is a protocol-specific struct allocated for this + sessionhandle, deal with it */ + Curl_reset_reqproto(conn); + + result = smtp_init(conn); + if(CURLE_OK != result) + return result; + + /* We always support persistent connections on smtp */ + conn->bits.close = FALSE; + + pp->response_time = RESP_TIMEOUT; /* set default response time-out */ + pp->statemach_act = smtp_statemach_act; + pp->endofresp = smtp_endofresp; + pp->conn = conn; + + if((conn->handler->protocol & CURLPROTO_SMTPS) && + data->state.used_interface != Curl_if_multi) { + /* SMTPS is simply smtp with SSL for the control channel */ + /* so perform the SSL initialization for this socket */ + result = Curl_ssl_connect(conn, FIRSTSOCKET); + if(result) + return result; + } + + /* Initialise the response reader stuff */ + Curl_pp_init(pp); + + /* Set the default response time-out */ + pp->response_time = RESP_TIMEOUT; + pp->statemach_act = smtp_statemach_act; + pp->endofresp = smtp_endofresp; + pp->conn = conn; + + /* Calculate the path if necessary */ + if(!*path) { + if(!Curl_gethostname(localhost, sizeof(localhost))) + path = localhost; + else + path = "localhost"; + } + + /* URL decode the path and use it as the domain in our EHLO */ + result = Curl_urldecode(conn->data, path, 0, &smtpc->domain, NULL, TRUE); + if(result) + return result; + + /* Start off waiting for the server greeting response */ + state(conn, SMTP_SERVERGREET); + + if(data->state.used_interface == Curl_if_multi) + result = smtp_multi_statemach(conn, done); + else { + result = smtp_easy_statemach(conn); + if(!result) + *done = TRUE; + } + + return result; +} + +/*********************************************************************** + * + * smtp_done() + * + * The DONE function. This does what needs to be done after a single DO has + * performed. + * + * Input argument is already checked for validity. + */ +static CURLcode smtp_done(struct connectdata *conn, CURLcode status, + bool premature) +{ + struct SessionHandle *data = conn->data; + struct FTP *smtp = data->state.proto.smtp; + CURLcode result = CURLE_OK; + ssize_t bytes_written; + + (void)premature; + + if(!smtp) + /* When the easy handle is removed from the multi while libcurl is still + * trying to resolve the host name, it seems that the smtp struct is not + * yet initialized, but the removal action calls Curl_done() which calls + * this function. So we simply return success if no smtp pointer is set. + */ + return CURLE_OK; + + if(status) { + conn->bits.close = TRUE; /* marked for closure */ + result = status; /* use the already set error code */ + } + else if(!data->set.connect_only) { + struct smtp_conn *smtpc = &conn->proto.smtpc; + struct pingpong *pp = &smtpc->pp; + + /* Send the end of block data */ + result = Curl_write(conn, + conn->writesockfd, /* socket to send to */ + SMTP_EOB, /* buffer pointer */ + SMTP_EOB_LEN, /* buffer size */ + &bytes_written); /* actually sent away */ + + if(result) + return result; + + if(bytes_written != SMTP_EOB_LEN) { + /* The whole chunk was not sent so keep it around and adjust the + pingpong structure accordingly */ + pp->sendthis = strdup(SMTP_EOB); + pp->sendsize = SMTP_EOB_LEN; + pp->sendleft = SMTP_EOB_LEN - bytes_written; + } + else + /* Successfully sent so adjust the response timeout relative to now */ + pp->response = Curl_tvnow(); + + state(conn, SMTP_POSTDATA); + + /* Run the state-machine + + TODO: when the multi interface is used, this _really_ should be using + the smtp_multi_statemach function but we have no general support for + non-blocking DONE operations, not in the multi state machine and with + Curl_done() invokes on several places in the code! + */ + result = smtp_easy_statemach(conn); + } + + /* Clear the transfer mode for the next connection */ + smtp->transfer = FTPTRANSFER_BODY; + + return result; +} + +/*********************************************************************** + * + * smtp_perform() + * + * This is the actual DO function for SMTP. Get a file/directory according to + * the options previously setup. + */ +static CURLcode smtp_perform(struct connectdata *conn, bool *connected, + bool *dophase_done) +{ + /* This is SMTP and no proxy */ + CURLcode result = CURLE_OK; + + DEBUGF(infof(conn->data, "DO phase starts\n")); + + if(conn->data->set.opt_no_body) { + /* Requested no body means no transfer */ + struct FTP *smtp = conn->data->state.proto.smtp; + smtp->transfer = FTPTRANSFER_INFO; + } + + *dophase_done = FALSE; /* not done yet */ + + /* Start the first command in the DO phase */ + result = smtp_mail(conn); + if(result) + return result; + + /* Run the state-machine */ + if(conn->data->state.used_interface == Curl_if_multi) + result = smtp_multi_statemach(conn, dophase_done); + else { + result = smtp_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect[FIRSTSOCKET]; + + if(*dophase_done) + DEBUGF(infof(conn->data, "DO phase is complete\n")); + + return result; +} + +/*********************************************************************** + * + * smtp_do() + * + * This function is registered as 'curl_do' function. It decodes the path + * parts etc as a wrapper to the actual DO function (smtp_perform). + * + * The input argument is already checked for validity. + */ +static CURLcode smtp_do(struct connectdata *conn, bool *done) +{ + CURLcode retcode = CURLE_OK; + + *done = FALSE; /* default to false */ + + /* + Since connections can be re-used between SessionHandles, this might be a + connection already existing but on a fresh SessionHandle struct so we must + make sure we have a good 'struct SMTP' to play with. For new connections, + the struct SMTP is allocated and setup in the smtp_connect() function. + */ + Curl_reset_reqproto(conn); + retcode = smtp_init(conn); + if(retcode) + return retcode; + + retcode = smtp_regular_transfer(conn, done); + + return retcode; +} + +/*********************************************************************** + * + * smtp_quit() + * + * This should be called before calling sclose(). We should then wait for the + * response from the server before returning. The calling code should then try + * to close the connection. + */ +static CURLcode smtp_quit(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + result = Curl_pp_sendf(&conn->proto.smtpc.pp, "QUIT"); + if(result) + return result; + + state(conn, SMTP_QUIT); + + result = smtp_easy_statemach(conn); + + return result; +} + +/*********************************************************************** + * + * smtp_disconnect() + * + * Disconnect from an SMTP server. Cleanup protocol-specific per-connection + * resources. BLOCKING. + */ +static CURLcode smtp_disconnect(struct connectdata *conn, + bool dead_connection) +{ + struct smtp_conn *smtpc = &conn->proto.smtpc; + + /* We cannot send quit unconditionally. If this connection is stale or + bad in any way, sending quit and waiting around here will make the + disconnect wait in vain and cause more problems than we need to */ + + /* The SMTP session may or may not have been allocated/setup at this + point! */ + if(!dead_connection && smtpc->pp.conn) + (void)smtp_quit(conn); /* ignore errors on the LOGOUT */ + + /* Disconnect from the server */ + Curl_pp_disconnect(&smtpc->pp); + + /* Cleanup the SASL module */ + Curl_sasl_cleanup(conn, smtpc->authused); + + /* Cleanup our connection based variables */ + Curl_safefree(smtpc->domain); + + return CURLE_OK; +} + +/* Call this when the DO phase has completed */ +static CURLcode smtp_dophase_done(struct connectdata *conn, bool connected) +{ + struct FTP *smtp = conn->data->state.proto.smtp; + + (void)connected; + + if(smtp->transfer != FTPTRANSFER_BODY) + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + return CURLE_OK; +} + +/* Called from curl_multi.c while DOing */ +static CURLcode smtp_doing(struct connectdata *conn, bool *dophase_done) +{ + CURLcode result = smtp_multi_statemach(conn, dophase_done); + + if(result) + DEBUGF(infof(conn->data, "DO phase failed\n")); + else { + if(*dophase_done) { + result = smtp_dophase_done(conn, FALSE /* not connected */); + + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + } + + return result; +} + +/*********************************************************************** + * + * smtp_regular_transfer() + * + * The input argument is already checked for validity. + * + * Performs all commands done before a regular transfer between a local and a + * remote host. + */ +static CURLcode smtp_regular_transfer(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + bool connected = FALSE; + struct SessionHandle *data = conn->data; + + /* Make sure size is unknown at this point */ + data->req.size = -1; + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, 0); + Curl_pgrsSetDownloadSize(data, 0); + + result = smtp_perform(conn, &connected, dophase_done); + + if(CURLE_OK == result) { + if(!*dophase_done) + /* The DO phase has not completed yet */ + return CURLE_OK; + + result = smtp_dophase_done(conn, connected); + if(result) + return result; + } + + return result; +} + +static CURLcode smtp_setup_connection(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + + if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { + /* Unless we have asked to tunnel smtp operations through the proxy, we + switch and use HTTP operations only */ +#ifndef CURL_DISABLE_HTTP + if(conn->handler == &Curl_handler_smtp) + conn->handler = &Curl_handler_smtp_proxy; + else { +#ifdef USE_SSL + conn->handler = &Curl_handler_smtps_proxy; +#else + failf(data, "SMTPS not supported!"); + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + + /* We explicitly mark this connection as persistent here as we're doing + SMTP over HTTP and thus we accidentally avoid setting this value + otherwise */ + conn->bits.close = FALSE; +#else + failf(data, "SMTP over http proxy requires HTTP support built-in!"); + return CURLE_UNSUPPORTED_PROTOCOL; +#endif + } + + data->state.path++; /* don't include the initial slash */ + + return CURLE_OK; +} + +CURLcode Curl_smtp_escape_eob(struct connectdata *conn, ssize_t nread) +{ + /* When sending a SMTP payload we must detect CRLF. sequences making sure + they are sent as CRLF.. instead, as a . on the beginning of a line will + be deleted by the server when not part of an EOB terminator and a + genuine CRLF.CRLF which isn't escaped will wrongly be detected as end of + data by the server. + */ + ssize_t i; + ssize_t si; + struct smtp_conn *smtpc = &conn->proto.smtpc; + struct SessionHandle *data = conn->data; + + /* Do we need to allocate the scatch buffer? */ + if(!data->state.scratch) { + data->state.scratch = malloc(2 * BUFSIZE); + + if(!data->state.scratch) { + failf (data, "Failed to alloc scratch buffer!"); + return CURLE_OUT_OF_MEMORY; + } + } + + /* This loop can be improved by some kind of Boyer-Moore style of + approach but that is saved for later... */ + for(i = 0, si = 0; i < nread; i++) { + if(SMTP_EOB[smtpc->eob] == data->req.upload_fromhere[i]) + smtpc->eob++; + else if(smtpc->eob) { + /* A previous substring matched so output that first */ + memcpy(&data->state.scratch[si], SMTP_EOB, smtpc->eob); + si += smtpc->eob; + + /* Then compare the first byte */ + if(SMTP_EOB[0] == data->req.upload_fromhere[i]) + smtpc->eob = 1; + else + smtpc->eob = 0; + } + + /* Do we have a match for CRLF. as per RFC-2821, sect. 4.5.2 */ + if(SMTP_EOB_FIND_LEN == smtpc->eob) { + /* Copy the replacement data to the target buffer */ + memcpy(&data->state.scratch[si], SMTP_EOB_REPL, SMTP_EOB_REPL_LEN); + si += SMTP_EOB_REPL_LEN; + smtpc->eob = 0; + } + else if(!smtpc->eob) + data->state.scratch[si++] = data->req.upload_fromhere[i]; + } + + if(smtpc->eob) { + /* A substring matched before processing ended so output that now */ + memcpy(&data->state.scratch[si], SMTP_EOB, smtpc->eob); + si += smtpc->eob; + smtpc->eob = 0; + } + + if(si != nread) { + /* Only use the new buffer if we replaced something */ + nread = si; + + /* Upload from the new (replaced) buffer instead */ + data->req.upload_fromhere = data->state.scratch; + + /* Set the new amount too */ + data->req.upload_present = nread; + } + + return CURLE_OK; +} + +#endif /* CURL_DISABLE_SMTP */ diff --git a/lib/curl_socks.c b/lib/curl_socks.c new file mode 100644 index 000000000..1b70dd629 --- /dev/null +++ b/lib/curl_socks.c @@ -0,0 +1,744 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_strequal.h" +#include "curl_select.h" +#include "curl_connect.h" +#include "curl_timeval.h" +#include "curl_socks.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + * 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, /* 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 */ +{ + ssize_t nread; + ssize_t allread = 0; + int result; + long timeleft; + *n = 0; + for(;;) { + timeleft = Curl_timeleft(conn->data, NULL, TRUE); + if(timeleft < 0) { + /* we already got the timeout */ + result = CURLE_OPERATION_TIMEDOUT; + break; + } + if(Curl_socket_ready(sockfd, CURL_SOCKET_BAD, timeleft) <= 0) { + result = ~CURLE_OK; + break; + } + result = Curl_read_plain(sockfd, buf, buffersize, &nread); + if(CURLE_AGAIN == result) + continue; + else if(result) + break; + + if(buffersize == nread) { + allread += nread; + *n = allread; + result = CURLE_OK; + break; + } + if(!nread) { + result = ~CURLE_OK; + break; + } + + buffersize -= nread; + buf += nread; + allread += nread; + } + return result; +} + +/* +* This function logs in to a SOCKS4 proxy and sends the specifics to the final +* destination server. +* +* Reference : +* http://socks.permeo.com/protocol/socks4.protocol +* +* Note : +* Set protocol4a=true for "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)" +* Nonsupport "Identification Protocol (RFC1413)" +*/ +CURLcode Curl_SOCKS4(const char *proxy_name, + const char *hostname, + int remote_port, + int sockindex, + struct connectdata *conn, + bool protocol4a) +{ +#define SOCKS4REQLEN 262 + unsigned char socksreq[SOCKS4REQLEN]; /* room for SOCKS4 request incl. user + id */ + int result; + CURLcode code; + curl_socket_t sock = conn->sock[sockindex]; + struct SessionHandle *data = conn->data; + + if(Curl_timeleft(data, NULL, TRUE) < 0) { + /* time-out, bail out, go home */ + failf(data, "Connection time-out"); + return CURLE_OPERATION_TIMEDOUT; + } + + curlx_nonblock(sock, FALSE); + + /* + * Compose socks4 request + * + * Request format + * + * +----+----+----+----+----+----+----+----+----+----+....+----+ + * | VN | CD | DSTPORT | DSTIP | USERID |NULL| + * +----+----+----+----+----+----+----+----+----+----+....+----+ + * # of bytes: 1 1 2 4 variable 1 + */ + + socksreq[0] = 4; /* version (SOCKS4) */ + socksreq[1] = 1; /* connect */ + socksreq[2] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ + socksreq[3] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ + + /* DNS resolve only for SOCKS4, not SOCKS4a */ + if(!protocol4a) { + struct Curl_dns_entry *dns; + Curl_addrinfo *hp=NULL; + int rc; + + rc = Curl_resolv(conn, hostname, remote_port, &dns); + + if(rc == CURLRESOLV_ERROR) + return CURLE_COULDNT_RESOLVE_PROXY; + + if(rc == CURLRESOLV_PENDING) + /* ignores the return code, but 'dns' remains NULL on failure */ + (void)Curl_resolver_wait_resolv(conn, &dns); + + /* + * We cannot use 'hostent' as a struct that Curl_resolv() returns. It + * returns a Curl_addrinfo pointer that may not always look the same. + */ + if(dns) + hp=dns->addr; + if(hp) { + char buf[64]; + unsigned short ip[4]; + Curl_printable_address(hp, buf, sizeof(buf)); + + if(4 == sscanf( buf, "%hu.%hu.%hu.%hu", + &ip[0], &ip[1], &ip[2], &ip[3])) { + /* Set DSTIP */ + socksreq[4] = (unsigned char)ip[0]; + socksreq[5] = (unsigned char)ip[1]; + socksreq[6] = (unsigned char)ip[2]; + socksreq[7] = (unsigned char)ip[3]; + } + else + hp = NULL; /* fail! */ + + Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + + } + if(!hp) { + failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", + hostname); + return CURLE_COULDNT_RESOLVE_HOST; + } + } + + /* + * This is currently not supporting "Identification Protocol (RFC1413)". + */ + socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ + if(proxy_name) + strlcat((char*)socksreq + 8, proxy_name, sizeof(socksreq) - 8); + + /* + * Make connection + */ + { + ssize_t actualread; + ssize_t written; + ssize_t hostnamelen = 0; + int packetsize = 9 + + (int)strlen((char*)socksreq + 8); /* size including NUL */ + + /* If SOCKS4a, set special invalid IP address 0.0.0.x */ + if(protocol4a) { + socksreq[4] = 0; + socksreq[5] = 0; + socksreq[6] = 0; + socksreq[7] = 1; + /* If still enough room in buffer, also append hostname */ + hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */ + if(packetsize + hostnamelen <= SOCKS4REQLEN) + strcpy((char*)socksreq + packetsize, hostname); + else + hostnamelen = 0; /* Flag: hostname did not fit in buffer */ + } + + /* Send request */ + code = Curl_write_plain(conn, sock, (char *)socksreq, + packetsize + hostnamelen, + &written); + if((code != CURLE_OK) || (written != packetsize + hostnamelen)) { + failf(data, "Failed to send SOCKS4 connect request."); + return CURLE_COULDNT_CONNECT; + } + if(protocol4a && hostnamelen == 0) { + /* SOCKS4a with very long hostname - send that name separately */ + hostnamelen = (ssize_t)strlen(hostname) + 1; + code = Curl_write_plain(conn, sock, (char *)hostname, hostnamelen, + &written); + if((code != CURLE_OK) || (written != hostnamelen)) { + failf(data, "Failed to send SOCKS4 connect request."); + return CURLE_COULDNT_CONNECT; + } + } + + packetsize = 8; /* receive data size */ + + /* Receive response */ + result = Curl_blockread_all(conn, sock, (char *)socksreq, packetsize, + &actualread); + if((result != CURLE_OK) || (actualread != packetsize)) { + failf(data, "Failed to receive SOCKS4 connect request ack."); + return CURLE_COULDNT_CONNECT; + } + + /* + * Response format + * + * +----+----+----+----+----+----+----+----+ + * | VN | CD | DSTPORT | DSTIP | + * +----+----+----+----+----+----+----+----+ + * # of bytes: 1 1 2 4 + * + * VN is the version of the reply code and should be 0. CD is the result + * code with one of the following values: + * + * 90: request granted + * 91: request rejected or failed + * 92: request rejected because SOCKS server cannot connect to + * identd on the client + * 93: request rejected because the client program and identd + * report different user-ids + */ + + /* wrong version ? */ + if(socksreq[0] != 0) { + failf(data, + "SOCKS4 reply has wrong version, version should be 4."); + return CURLE_COULDNT_CONNECT; + } + + /* Result */ + switch(socksreq[1]) { + case 90: + infof(data, "SOCKS4%s request granted.\n", protocol4a?"a":""); + break; + case 91: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected or failed.", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + ((socksreq[8] << 8) | socksreq[9]), + socksreq[1]); + return CURLE_COULDNT_CONNECT; + case 92: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected because SOCKS server cannot connect to " + "identd on the client.", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + ((socksreq[8] << 8) | socksreq[9]), + socksreq[1]); + return CURLE_COULDNT_CONNECT; + case 93: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", request rejected because the client program and identd " + "report different user-ids.", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + ((socksreq[8] << 8) | socksreq[9]), + socksreq[1]); + return CURLE_COULDNT_CONNECT; + default: + failf(data, + "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" + ", Unknown.", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + ((socksreq[8] << 8) | socksreq[9]), + socksreq[1]); + return CURLE_COULDNT_CONNECT; + } + } + + curlx_nonblock(sock, TRUE); + + return CURLE_OK; /* Proxy was successful! */ +} + +/* + * This function logs in to a SOCKS5 proxy and sends the specifics to the final + * destination server. + */ +CURLcode Curl_SOCKS5(const char *proxy_name, + const char *proxy_password, + const char *hostname, + int remote_port, + int sockindex, + struct connectdata *conn) +{ + /* + According to the RFC1928, section "6. Replies". This is what a SOCK5 + replies: + + +----+-----+-------+------+----------+----------+ + |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + +----+-----+-------+------+----------+----------+ + | 1 | 1 | X'00' | 1 | Variable | 2 | + +----+-----+-------+------+----------+----------+ + + Where: + + o VER protocol version: X'05' + o REP Reply field: + o X'00' succeeded + */ + + unsigned char socksreq[600]; /* room for large user/pw (255 max each) */ + ssize_t actualread; + ssize_t written; + int result; + CURLcode code; + curl_socket_t sock = conn->sock[sockindex]; + struct SessionHandle *data = conn->data; + long timeout; + bool socks5_resolve_local = (conn->proxytype == CURLPROXY_SOCKS5)?TRUE:FALSE; + const size_t hostname_len = strlen(hostname); + ssize_t len = 0; + + /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ + if(!socks5_resolve_local && hostname_len > 255) { + infof(conn->data,"SOCKS5: server resolving disabled for hostnames of " + "length > 255 [actual len=%zu]\n", hostname_len); + socks5_resolve_local = TRUE; + } + + /* get timeout */ + timeout = Curl_timeleft(data, NULL, TRUE); + + if(timeout < 0) { + /* time-out, bail out, go home */ + failf(data, "Connection time-out"); + return CURLE_OPERATION_TIMEDOUT; + } + + curlx_nonblock(sock, TRUE); + + /* wait until socket gets connected */ + result = Curl_socket_ready(CURL_SOCKET_BAD, sock, timeout); + + if(-1 == result) { + failf(conn->data, "SOCKS5: no connection here"); + return CURLE_COULDNT_CONNECT; + } + else if(0 == result) { + failf(conn->data, "SOCKS5: connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + if(result & CURL_CSELECT_ERR) { + failf(conn->data, "SOCKS5: error occurred during connection"); + return CURLE_COULDNT_CONNECT; + } + + 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 + + curlx_nonblock(sock, FALSE); + + code = Curl_write_plain(conn, sock, (char *)socksreq, (2 + (int)socksreq[1]), + &written); + if((code != CURLE_OK) || (written != (2 + (int)socksreq[1]))) { + failf(data, "Unable to send initial SOCKS5 request."); + return CURLE_COULDNT_CONNECT; + } + + curlx_nonblock(sock, TRUE); + + result = Curl_socket_ready(sock, CURL_SOCKET_BAD, timeout); + + if(-1 == result) { + failf(conn->data, "SOCKS5 nothing to read"); + return CURLE_COULDNT_CONNECT; + } + else if(0 == result) { + failf(conn->data, "SOCKS5 read timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + if(result & CURL_CSELECT_ERR) { + failf(conn->data, "SOCKS5 read error occurred"); + return CURLE_RECV_ERROR; + } + + curlx_nonblock(sock, FALSE); + + result=Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); + if((result != CURLE_OK) || (actualread != 2)) { + failf(data, "Unable to receive initial SOCKS5 response."); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[0] != 5) { + failf(data, "Received invalid version in initial SOCKS5 response."); + return CURLE_COULDNT_CONNECT; + } + if(socksreq[1] == 0) { + /* 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 proxy_name_len, proxy_password_len; + if(proxy_name && proxy_password) { + proxy_name_len = strlen(proxy_name); + proxy_password_len = strlen(proxy_password); + } + else { + proxy_name_len = 0; + proxy_password_len = 0; + } + + /* username/password request looks like + * +----+------+----------+------+----------+ + * |VER | ULEN | UNAME | PLEN | PASSWD | + * +----+------+----------+------+----------+ + * | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + * +----+------+----------+------+----------+ + */ + len = 0; + socksreq[len++] = 1; /* username/pw subnegotiation version */ + socksreq[len++] = (unsigned char) proxy_name_len; + if(proxy_name && proxy_name_len) + memcpy(socksreq + len, proxy_name, proxy_name_len); + len += proxy_name_len; + socksreq[len++] = (unsigned char) proxy_password_len; + if(proxy_password && proxy_password_len) + memcpy(socksreq + len, proxy_password, proxy_password_len); + len += proxy_password_len; + + code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); + if((code != CURLE_OK) || (len != written)) { + failf(data, "Failed to send SOCKS5 sub-negotiation request."); + return CURLE_COULDNT_CONNECT; + } + + result=Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); + if((result != CURLE_OK) || (actualread != 2)) { + failf(data, "Unable to receive SOCKS5 sub-negotiation response."); + return CURLE_COULDNT_CONNECT; + } + + /* ignore the first (VER) byte */ + if(socksreq[1] != 0) { /* status */ + failf(data, "User was rejected by the SOCKS5 server (%d %d).", + socksreq[0], socksreq[1]); + return CURLE_COULDNT_CONNECT; + } + + /* Everything is good so far, user was authenticated! */ + } + 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" + " that the SOCKS5 server wanted a username/password, since none" + " was supplied to the server on this connection.)"); + } + else { + failf(data, "No authentication method was acceptable."); + } + return CURLE_COULDNT_CONNECT; + } + else { + failf(data, + "Undocumented SOCKS5 mode attempted to be used by server."); + return CURLE_COULDNT_CONNECT; + } + } + + /* Authentication is complete, now specify destination to the proxy */ + len = 0; + socksreq[len++] = 5; /* version (SOCKS5) */ + socksreq[len++] = 1; /* connect */ + socksreq[len++] = 0; /* must be zero */ + + if(!socks5_resolve_local) { + socksreq[len++] = 3; /* ATYP: domain name = 3 */ + socksreq[len++] = (char) hostname_len; /* address length */ + memcpy(&socksreq[len], hostname, hostname_len); /* address str w/o NULL */ + len += hostname_len; + } + else { + struct Curl_dns_entry *dns; + Curl_addrinfo *hp = NULL; + int rc = Curl_resolv(conn, hostname, remote_port, &dns); + + if(rc == CURLRESOLV_ERROR) + return CURLE_COULDNT_RESOLVE_HOST; + + if(rc == CURLRESOLV_PENDING) { + /* this requires that we're in "wait for resolve" state */ + code = Curl_resolver_wait_resolv(conn, &dns); + if(code != CURLE_OK) + return code; + } + + /* + * We cannot use 'hostent' as a struct that Curl_resolv() returns. It + * returns a Curl_addrinfo pointer that may not always look the same. + */ + if(dns) + hp=dns->addr; + if(hp) { + struct sockaddr_in *saddr_in; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 *saddr_in6; +#endif + int i; + + if(hp->ai_family == AF_INET) { + socksreq[len++] = 1; /* ATYP: IPv4 = 1 */ + + saddr_in = (struct sockaddr_in*)hp->ai_addr; + for(i = 0; i < 4; i++) { + socksreq[len++] = ((unsigned char*)&saddr_in->sin_addr.s_addr)[i]; + infof(data, "%d\n", socksreq[len-1]); + } + } +#ifdef ENABLE_IPV6 + else if(hp->ai_family == AF_INET6) { + socksreq[len++] = 4; /* ATYP: IPv6 = 4 */ + + saddr_in6 = (struct sockaddr_in6*)hp->ai_addr; + for(i = 0; i < 16; i++) { + socksreq[len++] = ((unsigned char*)&saddr_in6->sin6_addr.s6_addr)[i]; + } + } +#endif + else + hp = NULL; /* fail! */ + + Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + } + if(!hp) { + failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", + hostname); + return CURLE_COULDNT_RESOLVE_HOST; + } + } + + socksreq[len++] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ + socksreq[len++] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ + +#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, len, &written); + + if((code != CURLE_OK) || (len != written)) { + failf(data, "Failed to send SOCKS5 connect request."); + return CURLE_COULDNT_CONNECT; + } + + len = 10; /* minimum packet size is 10 */ + +#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, + len, &actualread); + + if((result != CURLE_OK) || (len != actualread)) { + failf(data, "Failed to receive SOCKS5 connect request ack."); + return CURLE_COULDNT_CONNECT; + } + + if(socksreq[0] != 5) { /* version */ + failf(data, + "SOCKS5 reply has wrong version, version should be 5."); + return CURLE_COULDNT_CONNECT; + } + if(socksreq[1] != 0) { /* Anything besides 0 is an error */ + if(socksreq[3] == 1) { + failf(data, + "Can't complete SOCKS5 connection to %d.%d.%d.%d:%d. (%d)", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + ((socksreq[8] << 8) | socksreq[9]), + socksreq[1]); + } + else if(socksreq[3] == 3) { + failf(data, + "Can't complete SOCKS5 connection to %s:%d. (%d)", + hostname, + ((socksreq[8] << 8) | socksreq[9]), + socksreq[1]); + } + else if(socksreq[3] == 4) { + failf(data, + "Can't complete SOCKS5 connection to %02x%02x:%02x%02x:" + "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%d. (%d)", + (unsigned char)socksreq[4], (unsigned char)socksreq[5], + (unsigned char)socksreq[6], (unsigned char)socksreq[7], + (unsigned char)socksreq[8], (unsigned char)socksreq[9], + (unsigned char)socksreq[10], (unsigned char)socksreq[11], + (unsigned char)socksreq[12], (unsigned char)socksreq[13], + (unsigned char)socksreq[14], (unsigned char)socksreq[15], + (unsigned char)socksreq[16], (unsigned char)socksreq[17], + (unsigned char)socksreq[18], (unsigned char)socksreq[19], + ((socksreq[8] << 8) | socksreq[9]), + socksreq[1]); + } + return CURLE_COULDNT_CONNECT; + } + + /* Fix: in general, returned BND.ADDR is variable length parameter by RFC + 1928, so the reply packet should be read until the end to avoid errors at + subsequent protocol level. + + +----+-----+-------+------+----------+----------+ + |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + +----+-----+-------+------+----------+----------+ + | 1 | 1 | X'00' | 1 | Variable | 2 | + +----+-----+-------+------+----------+----------+ + + ATYP: + o IP v4 address: X'01', BND.ADDR = 4 byte + o domain name: X'03', BND.ADDR = [ 1 byte length, string ] + o IP v6 address: X'04', BND.ADDR = 16 byte + */ + + /* Calculate real packet size */ + if(socksreq[3] == 3) { + /* domain name */ + int addrlen = (int) socksreq[4]; + len = 5 + addrlen + 2; + } + else if(socksreq[3] == 4) { + /* IPv6 */ + len = 4 + 16 + 2; + } + + /* At this point we already read first 10 bytes */ +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + if(!conn->socks5_gssapi_enctype) { + /* decrypt_gssapi_blockread already read the whole packet */ +#endif + if(len > 10) { + len -= 10; + result = Curl_blockread_all(conn, sock, (char *)&socksreq[10], + len, &actualread); + if((result != CURLE_OK) || (len != actualread)) { + failf(data, "Failed to receive SOCKS5 connect request ack."); + return CURLE_COULDNT_CONNECT; + } + } +#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) + } +#endif + + curlx_nonblock(sock, TRUE); + return CURLE_OK; /* Proxy was successful! */ +} + +#endif /* CURL_DISABLE_PROXY */ + diff --git a/lib/curl_socks_gssapi.c b/lib/curl_socks_gssapi.c new file mode 100644 index 000000000..2bd3d4508 --- /dev/null +++ b/lib/curl_socks_gssapi.c @@ -0,0 +1,533 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2009, 2011, Markus Moeller, + * Copyright (C) 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_PROXY + +#ifdef HAVE_GSSAPI +#ifdef HAVE_OLD_GSSMIT +#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +#define NCOMPAT 1 +#endif +#ifndef gss_nt_service_name +#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE +#endif + +#include "curl_gssapi.h" +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_connect.h" +#include "curl_timeval.h" +#include "curl_socks.h" +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; + +/* + * 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); + gss_release_buffer(&min_stat, &status_string); + break; + } + gss_release_buffer(&min_stat, &status_string); + } + failf(data, "GSSAPI error: %s failed:\n%s", 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; + 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; + unsigned 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]; + + /* 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 = Curl_gss_init_sec_context(data, + &gss_minor_status, + &gss_context, + server, + NULL, + gss_token, + &gss_send_token, + &gss_ret_flags); + + 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); + 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); + + 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); + 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); + + 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 + +#endif /* CURL_DISABLE_PROXY */ diff --git a/lib/curl_socks_sspi.c b/lib/curl_socks_sspi.c new file mode 100644 index 000000000..c57610717 --- /dev/null +++ b/lib/curl_socks_sspi.c @@ -0,0 +1,591 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2009, 2011, Markus Moeller, + * Copyright (C) 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_PROXY) + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_connect.h" +#include "curl_strerror.h" +#include "curl_timeval.h" +#include "curl_socks.h" +#include "curl_sspi.h" +#include "curl_multibyte.h" +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use the internal *printf() functions */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* + * Definitions required from ntsecapi.h are directly provided below this point + * to avoid including ntsecapi.h due to a conflict with OpenSSL's safestack.h + */ +#define KERB_WRAP_NO_ENCRYPT 0x80000001 + +/* + * Helper sspi error functions. + */ +static int check_sspi_err(struct connectdata *conn, + SECURITY_STATUS status, + const char* function) +{ + if(status != SEC_E_OK && + status != SEC_I_COMPLETE_AND_CONTINUE && + status != SEC_I_COMPLETE_NEEDED && + status != SEC_I_CONTINUE_NEEDED) { + failf(conn->data, "SSPI error: %s failed: %s", function, + Curl_sspi_strerror(conn, status)); + 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; + /* Needs GSSAPI authentication */ + SECURITY_STATUS status; + unsigned long 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; + unsigned short us_length; + unsigned long qop; + unsigned char socksreq[4]; /* room for gssapi exchange header only */ + char *service = data->set.str[STRING_SOCKS5_GSSAPI_SERVICE]; + + /* 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; + + status = s_pSecFn->AcquireCredentialsHandle(NULL, + (TCHAR *) TEXT("Kerberos"), + SECPKG_CRED_OUTBOUND, + NULL, + NULL, + NULL, + NULL, + &cred_handle, + &expiry); + + if(check_sspi_err(conn, status, "AcquireCredentialsHandle")) { + failf(data, "Failed to acquire credentials."); + Curl_safefree(service_name); + s_pSecFn->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(;;) { + TCHAR *sname; + + sname = Curl_convert_UTF8_to_tchar(service_name); + if(!sname) + return CURLE_OUT_OF_MEMORY; + + status = s_pSecFn->InitializeSecurityContext(&cred_handle, + context_handle, + sname, + 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); + + Curl_unicodefree(sname); + + if(sspi_recv_token.pvBuffer) { + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + sspi_recv_token.pvBuffer = NULL; + sspi_recv_token.cbBuffer = 0; + } + + if(check_sspi_err(conn, status, "InitializeSecurityContext")) { + Curl_safefree(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + s_pSecFn->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."); + Curl_safefree(service_name); + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->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 != (size_t)written)) { + failf(data, "Failed to send SSPI authentication token."); + Curl_safefree(service_name); + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + } + + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + sspi_send_token.pvBuffer = NULL; + sspi_send_token.cbBuffer = 0; + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + sspi_recv_token.pvBuffer = NULL; + sspi_recv_token.cbBuffer = 0; + if(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); + if(result != CURLE_OK || actualread != 4) { + failf(data, "Failed to receive SSPI authentication response."); + Curl_safefree(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->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]); + Curl_safefree(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->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]); + Curl_safefree(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->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) { + Curl_safefree(service_name); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + result = Curl_blockread_all(conn, sock, (char *)sspi_recv_token.pvBuffer, + sspi_recv_token.cbBuffer, &actualread); + + if(result != CURLE_OK || actualread != us_length) { + failf(data, "Failed to receive SSPI authentication token."); + Curl_safefree(service_name); + s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + context_handle = &sspi_context; + } + + Curl_safefree(service_name); + + /* Everything is good so far, user was authenticated! */ + status = s_pSecFn->QueryCredentialsAttributes(&cred_handle, + SECPKG_CRED_ATTR_NAMES, + &names); + s_pSecFn->FreeCredentialsHandle(&cred_handle); + if(check_sspi_err(conn, status, "QueryCredentialAttributes")) { + s_pSecFn->DeleteSecurityContext(&sspi_context); + s_pSecFn->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); + s_pSecFn->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 { + status = s_pSecFn->QueryContextAttributes(&sspi_context, + SECPKG_ATTR_SIZES, + &sspi_sizes); + if(check_sspi_err(conn, status, "QueryContextAttributes")) { + s_pSecFn->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) { + s_pSecFn->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) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->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) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_OUT_OF_MEMORY; + } + status = s_pSecFn->EncryptMessage(&sspi_context, + KERB_WRAP_NO_ENCRYPT, + &wrap_desc, + 0); + if(check_sspi_err(conn, status, "EncryptMessage")) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + s_pSecFn->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) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + s_pSecFn->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); + + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + sspi_w_token[0].pvBuffer = NULL; + sspi_w_token[0].cbBuffer = 0; + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + sspi_w_token[1].pvBuffer = NULL; + sspi_w_token[1].cbBuffer = 0; + s_pSecFn->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."); + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + s_pSecFn->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."); + s_pSecFn->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 != (size_t)written)) { + failf(data, "Failed to send SSPI encryption type."); + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + } + + result = Curl_blockread_all(conn, sock, (char *)socksreq, 4, &actualread); + if(result != CURLE_OK || actualread != 4) { + failf(data, "Failed to receive SSPI encryption response."); + s_pSecFn->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]); + s_pSecFn->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]); + s_pSecFn->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) { + s_pSecFn->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); + + if(result != CURLE_OK || actualread != us_length) { + failf(data, "Failed to receive SSPI encryption type."); + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->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; + + status = s_pSecFn->DecryptMessage(&sspi_context, + &wrap_desc, + 0, + &qop); + + if(check_sspi_err(conn, status, "DecryptMessage")) { + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->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); + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + + memcpy(socksreq,sspi_w_token[1].pvBuffer,sspi_w_token[1].cbBuffer); + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->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); + s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + s_pSecFn->DeleteSecurityContext(&sspi_context); + return CURLE_COULDNT_CONNECT; + } + memcpy(socksreq,sspi_w_token[0].pvBuffer,sspi_w_token[0].cbBuffer); + s_pSecFn->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 { + s_pSecFn->DeleteSecurityContext(&sspi_context); + conn->socks5_sspi_context = sspi_context; + } + */ + return CURLE_OK; +} +#endif diff --git a/lib/curl_speedcheck.c b/lib/curl_speedcheck.c new file mode 100644 index 000000000..b9ce77dbf --- /dev/null +++ b/lib/curl_speedcheck.c @@ -0,0 +1,74 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_multiif.h" +#include "curl_speedcheck.h" + +void Curl_speedinit(struct SessionHandle *data) +{ + memset(&data->state.keeps_speed, 0, sizeof(struct timeval)); +} + +CURLcode Curl_speedcheck(struct SessionHandle *data, + struct timeval now) +{ + if((data->progress.current_speed >= 0) && + data->set.low_speed_time && + (Curl_tvlong(data->state.keeps_speed) != 0) && + (data->progress.current_speed < data->set.low_speed_limit)) { + long howlong = Curl_tvdiff(now, data->state.keeps_speed); + long nextcheck = (data->set.low_speed_time * 1000) - howlong; + + /* We are now below the "low speed limit". If we are below it + for "low speed time" seconds we consider that enough reason + to abort the download. */ + if(nextcheck <= 0) { + /* we have been this slow for long enough, now die */ + failf(data, + "Operation too slow. " + "Less than %ld bytes/sec transferred the last %ld seconds", + data->set.low_speed_limit, + data->set.low_speed_time); + return CURLE_OPERATION_TIMEDOUT; + } + else { + /* wait complete low_speed_time */ + Curl_expire(data, nextcheck); + } + } + else { + /* we keep up the required speed all right */ + data->state.keeps_speed = now; + + if(data->set.low_speed_limit) + /* if there is a low speed limit enabled, we set the expire timer to + make this connection's speed get checked again no later than when + this time is up */ + Curl_expire(data, data->set.low_speed_time*1000); + } + return CURLE_OK; +} diff --git a/lib/curl_splay.c b/lib/curl_splay.c new file mode 100644 index 000000000..21f1d222e --- /dev/null +++ b/lib/curl_splay.c @@ -0,0 +1,288 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1997 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_splay.h" + +/* + * This macro compares two node keys i and j and returns: + * + * negative value: when i is smaller than j + * zero : when i is equal to j + * positive when : when i is larger than j + */ +#define compare(i,j) Curl_splaycomparekeys((i),(j)) + +/* + * Splay using the key i (which may or may not be in the tree.) The starting + * root is t. + */ +struct Curl_tree *Curl_splay(struct timeval i, + struct Curl_tree *t) +{ + struct Curl_tree N, *l, *r, *y; + long comp; + + if(t == NULL) + return t; + N.smaller = N.larger = NULL; + l = r = &N; + + for(;;) { + comp = compare(i, t->key); + if(comp < 0) { + if(t->smaller == NULL) + break; + if(compare(i, t->smaller->key) < 0) { + y = t->smaller; /* rotate smaller */ + t->smaller = y->larger; + y->larger = t; + t = y; + if(t->smaller == NULL) + break; + } + r->smaller = t; /* link smaller */ + r = t; + t = t->smaller; + } + else if(comp > 0) { + if(t->larger == NULL) + break; + if(compare(i, t->larger->key) > 0) { + y = t->larger; /* rotate larger */ + t->larger = y->smaller; + y->smaller = t; + t = y; + if(t->larger == NULL) + break; + } + l->larger = t; /* link larger */ + l = t; + t = t->larger; + } + else + break; + } + + l->larger = t->smaller; /* assemble */ + r->smaller = t->larger; + t->smaller = N.larger; + t->larger = N.smaller; + + return t; +} + +/* Insert key i into the tree t. Return a pointer to the resulting tree or + * NULL if something went wrong. + * + * @unittest: 1309 + */ +struct Curl_tree *Curl_splayinsert(struct timeval i, + struct Curl_tree *t, + struct Curl_tree *node) +{ + static struct timeval KEY_NOTUSED = {-1,-1}; /* will *NEVER* appear */ + + if(node == NULL) + return t; + + if(t != NULL) { + t = Curl_splay(i,t); + if(compare(i, t->key)==0) { + /* There already exists a node in the tree with the very same key. Build + a linked list of nodes. We make the new 'node' struct the new master + node and make the previous node the first one in the 'same' list. */ + + node->same = t; + node->key = i; + node->smaller = t->smaller; + node->larger = t->larger; + + t->smaller = node; /* in the sub node for this same key, we use the + smaller pointer to point back to the master + node */ + + t->key = KEY_NOTUSED; /* and we set the key in the sub node to NOTUSED + to quickly identify this node as a subnode */ + + return node; /* new root node */ + } + } + + if(t == NULL) { + node->smaller = node->larger = NULL; + } + else if(compare(i, t->key) < 0) { + node->smaller = t->smaller; + node->larger = t; + t->smaller = NULL; + + } + else { + node->larger = t->larger; + node->smaller = t; + t->larger = NULL; + } + node->key = i; + + node->same = NULL; /* no identical node (yet) */ + return node; +} + +/* Finds and deletes the best-fit node from the tree. Return a pointer to the + resulting tree. best-fit means the node with the given or lower key */ +struct Curl_tree *Curl_splaygetbest(struct timeval i, + struct Curl_tree *t, + struct Curl_tree **removed) +{ + struct Curl_tree *x; + + if(!t) { + *removed = NULL; /* none removed since there was no root */ + return NULL; + } + + t = Curl_splay(i,t); + if(compare(i, t->key) < 0) { + /* too big node, try the smaller chain */ + if(t->smaller) + t=Curl_splay(t->smaller->key, t); + else { + /* fail */ + *removed = NULL; + return t; + } + } + + if(compare(i, t->key) >= 0) { /* found it */ + /* FIRST! Check if there is a list with identical keys */ + x = t->same; + if(x) { + /* there is, pick one from the list */ + + /* 'x' is the new root node */ + + x->key = t->key; + x->larger = t->larger; + x->smaller = t->smaller; + + *removed = t; + return x; /* new root */ + } + + if(t->smaller == NULL) { + x = t->larger; + } + else { + x = Curl_splay(i, t->smaller); + x->larger = t->larger; + } + *removed = t; + + return x; + } + else { + *removed = NULL; /* no match */ + return t; /* It wasn't there */ + } +} + + +/* Deletes the very node we point out from the tree if it's there. Stores a + * pointer to the new resulting tree in 'newroot'. + * + * Returns zero on success and non-zero on errors! TODO: document error codes. + * When returning error, it does not touch the 'newroot' pointer. + * + * NOTE: when the last node of the tree is removed, there's no tree left so + * 'newroot' will be made to point to NULL. + * + * @unittest: 1309 + */ +int Curl_splayremovebyaddr(struct Curl_tree *t, + struct Curl_tree *removenode, + struct Curl_tree **newroot) +{ + static struct timeval KEY_NOTUSED = {-1,-1}; /* will *NEVER* appear */ + struct Curl_tree *x; + + if(!t || !removenode) + return 1; + + if(compare(KEY_NOTUSED, removenode->key) == 0) { + /* Key set to NOTUSED means it is a subnode within a 'same' linked list + and thus we can unlink it easily. The 'smaller' link of a subnode + links to the parent node. */ + if(removenode->smaller == NULL) + return 3; + + removenode->smaller->same = removenode->same; + if(removenode->same) + removenode->same->smaller = removenode->smaller; + + /* Ensures that double-remove gets caught. */ + removenode->smaller = NULL; + + /* voila, we're done! */ + *newroot = t; /* return the same root */ + return 0; + } + + t = Curl_splay(removenode->key, t); + + /* First make sure that we got the same root node as the one we want + to remove, as otherwise we might be trying to remove a node that + isn't actually in the tree. + + We cannot just compare the keys here as a double remove in quick + succession of a node with key != KEY_NOTUSED && same != NULL + could return the same key but a different node. */ + if(t != removenode) + return 2; + + /* Check if there is a list with identical sizes, as then we're trying to + remove the root node of a list of nodes with identical keys. */ + x = t->same; + if(x) { + /* 'x' is the new root node, we just make it use the root node's + smaller/larger links */ + + x->key = t->key; + x->larger = t->larger; + x->smaller = t->smaller; + } + else { + /* Remove the root node */ + if(t->smaller == NULL) + x = t->larger; + else { + x = Curl_splay(removenode->key, t->smaller); + x->larger = t->larger; + } + } + + *newroot = x; /* store new root pointer */ + + return 0; +} + diff --git a/lib/curl_ssh.c b/lib/curl_ssh.c new file mode 100644 index 000000000..d769a041b --- /dev/null +++ b/lib/curl_ssh.c @@ -0,0 +1,3310 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +/* #define CURL_LIBSSH2_DEBUG */ + +#include "curl_setup.h" + +#ifdef USE_LIBSSH2 + +#ifdef HAVE_LIMITS_H +# include +#endif + +#include +#include + +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_UTSNAME_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) +#undef in_addr_t +#define in_addr_t unsigned long +#endif + +#include +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_hostip.h" +#include "curl_progress.h" +#include "curl_transfer.h" +#include "curl_escape.h" +#include "curl_http.h" /* for HTTP proxy tunnel stuff */ +#include "curl_ssh.h" +#include "curl_url.h" +#include "curl_speedcheck.h" +#include "curl_getinfo.h" + +#include "curl_strequal.h" +#include "curl_sslgen.h" +#include "curl_connect.h" +#include "curl_strerror.h" +#include "curl_inet_ntop.h" +#include "curl_parsedate.h" /* for the week day and month names */ +#include "curl_sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "curl_strtoofft.h" +#include "curl_multiif.h" +#include "curl_select.h" +#include "curl_warnless.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifdef WIN32 +# undef PATH_MAX +# define PATH_MAX MAX_PATH +#endif + +#ifndef PATH_MAX +#define PATH_MAX 1024 /* just an extra precaution since there are systems that + have their definition hidden well */ +#endif + +#define sftp_libssh2_last_error(s) curlx_ultosi(libssh2_sftp_last_error(s)) + +#define sftp_libssh2_realpath(s,p,t,m) \ + libssh2_sftp_symlink_ex((s), (p), curlx_uztoui(strlen(p)), \ + (t), (m), LIBSSH2_SFTP_REALPATH) + +/* Local functions: */ +static const char *sftp_libssh2_strerror(int err); +static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc); +static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc); +static LIBSSH2_FREE_FUNC(my_libssh2_free); + +static CURLcode get_pathname(const char **cpp, char **path); + +static CURLcode ssh_connect(struct connectdata *conn, bool *done); +static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done); +static CURLcode ssh_do(struct connectdata *conn, bool *done); + +static CURLcode ssh_getworkingpath(struct connectdata *conn, + char *homedir, /* when SFTP is used */ + char **path); + +static CURLcode scp_done(struct connectdata *conn, + CURLcode, bool premature); +static CURLcode scp_doing(struct connectdata *conn, + bool *dophase_done); +static CURLcode scp_disconnect(struct connectdata *conn, bool dead_connection); + +static CURLcode sftp_done(struct connectdata *conn, + CURLcode, bool premature); +static CURLcode sftp_doing(struct connectdata *conn, + bool *dophase_done); +static CURLcode sftp_disconnect(struct connectdata *conn, bool dead); +static +CURLcode sftp_perform(struct connectdata *conn, + bool *connected, + bool *dophase_done); + +static int ssh_getsock(struct connectdata *conn, + curl_socket_t *sock, /* points to numsocks number + of sockets */ + int numsocks); + +static int ssh_perform_getsock(const struct connectdata *conn, + curl_socket_t *sock, /* points to numsocks + number of sockets */ + int numsocks); + +/* + * SCP protocol handler. + */ + +const struct Curl_handler Curl_handler_scp = { + "SCP", /* scheme */ + ZERO_NULL, /* setup_connection */ + ssh_do, /* do_it */ + scp_done, /* done */ + ZERO_NULL, /* do_more */ + ssh_connect, /* connect_it */ + ssh_multi_statemach, /* connecting */ + scp_doing, /* doing */ + ssh_getsock, /* proto_getsock */ + ssh_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ssh_perform_getsock, /* perform_getsock */ + scp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_SSH, /* defport */ + CURLPROTO_SCP, /* protocol */ + PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION + | PROTOPT_NOURLQUERY /* flags */ +}; + + +/* + * SFTP protocol handler. + */ + +const struct Curl_handler Curl_handler_sftp = { + "SFTP", /* scheme */ + ZERO_NULL, /* setup_connection */ + ssh_do, /* do_it */ + sftp_done, /* done */ + ZERO_NULL, /* do_more */ + ssh_connect, /* connect_it */ + ssh_multi_statemach, /* connecting */ + sftp_doing, /* doing */ + ssh_getsock, /* proto_getsock */ + ssh_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ssh_perform_getsock, /* perform_getsock */ + sftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_SSH, /* defport */ + CURLPROTO_SFTP, /* protocol */ + PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION + | PROTOPT_NOURLQUERY /* flags */ +}; + + +static void +kbd_callback(const char *name, int name_len, const char *instruction, + int instruction_len, int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **abstract) +{ + struct connectdata *conn = (struct connectdata *)*abstract; + +#ifdef CURL_LIBSSH2_DEBUG + fprintf(stderr, "name=%s\n", name); + fprintf(stderr, "name_len=%d\n", name_len); + fprintf(stderr, "instruction=%s\n", instruction); + fprintf(stderr, "instruction_len=%d\n", instruction_len); + fprintf(stderr, "num_prompts=%d\n", num_prompts); +#else + (void)name; + (void)name_len; + (void)instruction; + (void)instruction_len; +#endif /* CURL_LIBSSH2_DEBUG */ + if(num_prompts == 1) { + responses[0].text = strdup(conn->passwd); + responses[0].length = curlx_uztoui(strlen(conn->passwd)); + } + (void)prompts; + (void)abstract; +} /* kbd_callback */ + +static CURLcode sftp_libssh2_error_to_CURLE(int err) +{ + switch (err) { + case LIBSSH2_FX_OK: + return CURLE_OK; + + case LIBSSH2_FX_NO_SUCH_FILE: + case LIBSSH2_FX_NO_SUCH_PATH: + return CURLE_REMOTE_FILE_NOT_FOUND; + + case LIBSSH2_FX_PERMISSION_DENIED: + case LIBSSH2_FX_WRITE_PROTECT: + case LIBSSH2_FX_LOCK_CONFlICT: + return CURLE_REMOTE_ACCESS_DENIED; + + case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: + case LIBSSH2_FX_QUOTA_EXCEEDED: + return CURLE_REMOTE_DISK_FULL; + + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + return CURLE_REMOTE_FILE_EXISTS; + + case LIBSSH2_FX_DIR_NOT_EMPTY: + return CURLE_QUOTE_ERROR; + + default: + break; + } + + return CURLE_SSH; +} + +static CURLcode libssh2_session_error_to_CURLE(int err) +{ + switch (err) { + /* Ordered by order of appearance in libssh2.h */ + case LIBSSH2_ERROR_NONE: + return CURLE_OK; + + case LIBSSH2_ERROR_SOCKET_NONE: + return CURLE_COULDNT_CONNECT; + + case LIBSSH2_ERROR_ALLOC: + return CURLE_OUT_OF_MEMORY; + + case LIBSSH2_ERROR_SOCKET_SEND: + return CURLE_SEND_ERROR; + + case LIBSSH2_ERROR_HOSTKEY_INIT: + case LIBSSH2_ERROR_HOSTKEY_SIGN: + case LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED: + case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED: + return CURLE_PEER_FAILED_VERIFICATION; + + case LIBSSH2_ERROR_PASSWORD_EXPIRED: + return CURLE_LOGIN_DENIED; + + case LIBSSH2_ERROR_SOCKET_TIMEOUT: + case LIBSSH2_ERROR_TIMEOUT: + return CURLE_OPERATION_TIMEDOUT; + + case LIBSSH2_ERROR_EAGAIN: + return CURLE_AGAIN; + } + + /* TODO: map some more of the libssh2 errors to the more appropriate CURLcode + error code, and possibly add a few new SSH-related one. We must however + not return or even depend on libssh2 errors in the public libcurl API */ + + return CURLE_SSH; +} + +static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc) +{ + (void)abstract; /* arg not used */ + return malloc(count); +} + +static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc) +{ + (void)abstract; /* arg not used */ + return realloc(ptr, count); +} + +static LIBSSH2_FREE_FUNC(my_libssh2_free) +{ + (void)abstract; /* arg not used */ + if(ptr) /* ssh2 agent sometimes call free with null ptr */ + free(ptr); +} + +/* + * SSH State machine related code + */ +/* This is the ONLY way to change SSH state! */ +static void state(struct connectdata *conn, sshstate nowstate) +{ +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + /* for debug purposes */ + static const char * const names[] = { + "SSH_STOP", + "SSH_INIT", + "SSH_S_STARTUP", + "SSH_HOSTKEY", + "SSH_AUTHLIST", + "SSH_AUTH_PKEY_INIT", + "SSH_AUTH_PKEY", + "SSH_AUTH_PASS_INIT", + "SSH_AUTH_PASS", + "SSH_AUTH_AGENT_INIT", + "SSH_AUTH_AGENT_LIST", + "SSH_AUTH_AGENT", + "SSH_AUTH_HOST_INIT", + "SSH_AUTH_HOST", + "SSH_AUTH_KEY_INIT", + "SSH_AUTH_KEY", + "SSH_AUTH_DONE", + "SSH_SFTP_INIT", + "SSH_SFTP_REALPATH", + "SSH_SFTP_QUOTE_INIT", + "SSH_SFTP_POSTQUOTE_INIT", + "SSH_SFTP_QUOTE", + "SSH_SFTP_NEXT_QUOTE", + "SSH_SFTP_QUOTE_STAT", + "SSH_SFTP_QUOTE_SETSTAT", + "SSH_SFTP_QUOTE_SYMLINK", + "SSH_SFTP_QUOTE_MKDIR", + "SSH_SFTP_QUOTE_RENAME", + "SSH_SFTP_QUOTE_RMDIR", + "SSH_SFTP_QUOTE_UNLINK", + "SSH_SFTP_TRANS_INIT", + "SSH_SFTP_UPLOAD_INIT", + "SSH_SFTP_CREATE_DIRS_INIT", + "SSH_SFTP_CREATE_DIRS", + "SSH_SFTP_CREATE_DIRS_MKDIR", + "SSH_SFTP_READDIR_INIT", + "SSH_SFTP_READDIR", + "SSH_SFTP_READDIR_LINK", + "SSH_SFTP_READDIR_BOTTOM", + "SSH_SFTP_READDIR_DONE", + "SSH_SFTP_DOWNLOAD_INIT", + "SSH_SFTP_DOWNLOAD_STAT", + "SSH_SFTP_CLOSE", + "SSH_SFTP_SHUTDOWN", + "SSH_SCP_TRANS_INIT", + "SSH_SCP_UPLOAD_INIT", + "SSH_SCP_DOWNLOAD_INIT", + "SSH_SCP_DONE", + "SSH_SCP_SEND_EOF", + "SSH_SCP_WAIT_EOF", + "SSH_SCP_WAIT_CLOSE", + "SSH_SCP_CHANNEL_FREE", + "SSH_SESSION_DISCONNECT", + "SSH_SESSION_FREE", + "QUIT" + }; +#endif + struct ssh_conn *sshc = &conn->proto.sshc; + +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + if(sshc->state != nowstate) { + infof(conn->data, "SFTP %p state change from %s to %s\n", + sshc, names[sshc->state], names[nowstate]); + } +#endif + + sshc->state = nowstate; +} + +/* figure out the path to work with in this particular request */ +static CURLcode ssh_getworkingpath(struct connectdata *conn, + char *homedir, /* when SFTP is used */ + char **path) /* returns the allocated + real path to work with */ +{ + struct SessionHandle *data = conn->data; + char *real_path = NULL; + char *working_path; + int working_path_len; + + working_path = curl_easy_unescape(data, data->state.path, 0, + &working_path_len); + if(!working_path) + return CURLE_OUT_OF_MEMORY; + + /* Check for /~/ , indicating relative to the user's home directory */ + if(conn->handler->protocol & CURLPROTO_SCP) { + real_path = malloc(working_path_len+1); + if(real_path == NULL) { + free(working_path); + return CURLE_OUT_OF_MEMORY; + } + if((working_path_len > 3) && (!memcmp(working_path, "/~/", 3))) + /* It is referenced to the home directory, so strip the leading '/~/' */ + memcpy(real_path, working_path+3, 4 + working_path_len-3); + else + memcpy(real_path, working_path, 1 + working_path_len); + } + else if(conn->handler->protocol & CURLPROTO_SFTP) { + if((working_path_len > 1) && (working_path[1] == '~')) { + size_t homelen = strlen(homedir); + real_path = malloc(homelen + working_path_len + 1); + if(real_path == NULL) { + free(working_path); + return CURLE_OUT_OF_MEMORY; + } + /* It is referenced to the home directory, so strip the + leading '/' */ + memcpy(real_path, homedir, homelen); + real_path[homelen] = '/'; + real_path[homelen+1] = '\0'; + if(working_path_len > 3) { + memcpy(real_path+homelen+1, working_path + 3, + 1 + working_path_len -3); + } + } + else { + real_path = malloc(working_path_len+1); + if(real_path == NULL) { + free(working_path); + return CURLE_OUT_OF_MEMORY; + } + memcpy(real_path, working_path, 1+working_path_len); + } + } + + free(working_path); + + /* store the pointer for the caller to receive */ + *path = real_path; + + return CURLE_OK; +} + +#ifdef HAVE_LIBSSH2_KNOWNHOST_API +static int sshkeycallback(CURL *easy, + const struct curl_khkey *knownkey, /* known */ + const struct curl_khkey *foundkey, /* found */ + enum curl_khmatch match, + void *clientp) +{ + (void)easy; + (void)knownkey; + (void)foundkey; + (void)clientp; + + /* we only allow perfect matches, and we reject everything else */ + return (match != CURLKHMATCH_OK)?CURLKHSTAT_REJECT:CURLKHSTAT_FINE; +} +#endif + +/* + * Earlier libssh2 versions didn't have the ability to seek to 64bit positions + * with 32bit size_t. + */ +#ifdef HAVE_LIBSSH2_SFTP_SEEK64 +#define SFTP_SEEK(x,y) libssh2_sftp_seek64(x, (libssh2_uint64_t)y) +#else +#define SFTP_SEEK(x,y) libssh2_sftp_seek(x, (size_t)y) +#endif + +/* + * Earlier libssh2 versions didn't do SCP properly beyond 32bit sizes on 32bit + * architectures so we check of the necessary function is present. + */ +#ifndef HAVE_LIBSSH2_SCP_SEND64 +#define SCP_SEND(a,b,c,d) libssh2_scp_send_ex(a, b, (int)(c), (size_t)d, 0, 0) +#else +#define SCP_SEND(a,b,c,d) libssh2_scp_send64(a, b, (int)(c), \ + (libssh2_uint64_t)d, 0, 0) +#endif + +/* + * libssh2 1.2.8 fixed the problem with 32bit ints used for sockets on win64. + */ +#ifdef HAVE_LIBSSH2_SESSION_HANDSHAKE +#define libssh2_session_startup(x,y) libssh2_session_handshake(x,y) +#endif + +static CURLcode ssh_knownhost(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + struct SessionHandle *data = conn->data; + + if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + /* we're asked to verify the host against a file */ + struct ssh_conn *sshc = &conn->proto.sshc; + int rc; + int keytype; + size_t keylen; + const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, + &keylen, &keytype); + int keycheck = LIBSSH2_KNOWNHOST_CHECK_FAILURE; + int keybit = 0; + + if(remotekey) { + /* + * A subject to figure out is what host name we need to pass in here. + * What host name does OpenSSH store in its file if an IDN name is + * used? + */ + struct libssh2_knownhost *host; + enum curl_khmatch keymatch; + curl_sshkeycallback func = + data->set.ssh_keyfunc?data->set.ssh_keyfunc:sshkeycallback; + struct curl_khkey knownkey; + struct curl_khkey *knownkeyp = NULL; + struct curl_khkey foundkey; + + keybit = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? + LIBSSH2_KNOWNHOST_KEY_SSHRSA:LIBSSH2_KNOWNHOST_KEY_SSHDSS; + + keycheck = libssh2_knownhost_check(sshc->kh, + conn->host.name, + remotekey, keylen, + LIBSSH2_KNOWNHOST_TYPE_PLAIN| + LIBSSH2_KNOWNHOST_KEYENC_RAW| + keybit, + &host); + + infof(data, "SSH host check: %d, key: %s\n", keycheck, + (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)? + host->key:""); + + /* setup 'knownkey' */ + if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { + knownkey.key = host->key; + knownkey.len = 0; + knownkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? + CURLKHTYPE_RSA : CURLKHTYPE_DSS; + knownkeyp = &knownkey; + } + + /* setup 'foundkey' */ + foundkey.key = remotekey; + foundkey.len = keylen; + foundkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? + CURLKHTYPE_RSA : CURLKHTYPE_DSS; + + /* + * if any of the LIBSSH2_KNOWNHOST_CHECK_* defines and the + * curl_khmatch enum are ever modified, we need to introduce a + * translation table here! + */ + keymatch = (enum curl_khmatch)keycheck; + + /* Ask the callback how to behave */ + rc = func(data, knownkeyp, /* from the knownhosts file */ + &foundkey, /* from the remote host */ + keymatch, data->set.ssh_keyfunc_userp); + } + else + /* no remotekey means failure! */ + rc = CURLKHSTAT_REJECT; + + switch(rc) { + default: /* unknown return codes will equal reject */ + case CURLKHSTAT_REJECT: + state(conn, SSH_SESSION_FREE); + case CURLKHSTAT_DEFER: + /* DEFER means bail out but keep the SSH_HOSTKEY state */ + result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + break; + case CURLKHSTAT_FINE: + case CURLKHSTAT_FINE_ADD_TO_FILE: + /* proceed */ + if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { + /* the found host+key didn't match but has been told to be fine + anyway so we add it in memory */ + int addrc = libssh2_knownhost_add(sshc->kh, + conn->host.name, NULL, + remotekey, keylen, + LIBSSH2_KNOWNHOST_TYPE_PLAIN| + LIBSSH2_KNOWNHOST_KEYENC_RAW| + keybit, NULL); + if(addrc) + infof(data, "Warning adding the known host %s failed!\n", + conn->host.name); + else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE) { + /* now we write the entire in-memory list of known hosts to the + known_hosts file */ + int wrc = + libssh2_knownhost_writefile(sshc->kh, + data->set.str[STRING_SSH_KNOWNHOSTS], + LIBSSH2_KNOWNHOST_FILE_OPENSSH); + if(wrc) { + infof(data, "Warning, writing %s failed!\n", + data->set.str[STRING_SSH_KNOWNHOSTS]); + } + } + } + break; + } + } +#else /* HAVE_LIBSSH2_KNOWNHOST_API */ + (void)conn; +#endif + return result; +} + +static CURLcode ssh_check_fingerprint(struct connectdata *conn) +{ + struct ssh_conn *sshc = &conn->proto.sshc; + struct SessionHandle *data = conn->data; + const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; + char md5buffer[33]; + int i; + + const char *fingerprint = libssh2_hostkey_hash(sshc->ssh_session, + LIBSSH2_HOSTKEY_HASH_MD5); + + if(fingerprint) { + /* The fingerprint points to static storage (!), don't free() it. */ + for(i = 0; i < 16; i++) + snprintf(&md5buffer[i*2], 3, "%02x", (unsigned char) fingerprint[i]); + infof(data, "SSH MD5 fingerprint: %s\n", md5buffer); + } + + /* Before we authenticate we check the hostkey's MD5 fingerprint + * against a known fingerprint, if available. + */ + if(pubkey_md5 && strlen(pubkey_md5) == 32) { + if(!fingerprint || !strequal(md5buffer, pubkey_md5)) { + if(fingerprint) + failf(data, + "Denied establishing ssh session: mismatch md5 fingerprint. " + "Remote %s is not equal to %s", md5buffer, pubkey_md5); + else + failf(data, + "Denied establishing ssh session: md5 fingerprint not available"); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; + return sshc->actualcode; + } + else { + infof(data, "MD5 checksum match!\n"); + /* as we already matched, we skip the check for known hosts */ + return CURLE_OK; + } + } + else + return ssh_knownhost(conn); +} + +/* + * ssh_statemach_act() runs the SSH state machine as far as it can without + * blocking and without reaching the end. The data the pointer 'block' points + * to will be set to TRUE if the libssh2 function returns LIBSSH2_ERROR_EAGAIN + * meaning it wants to be called again when the socket is ready + */ + +static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct SSHPROTO *sftp_scp = data->state.proto.ssh; + struct ssh_conn *sshc = &conn->proto.sshc; + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + char *new_readdir_line; + int rc = LIBSSH2_ERROR_NONE; + int err; + int seekerr = CURL_SEEKFUNC_OK; + *block = 0; /* we're not blocking by default */ + + do { + + switch(sshc->state) { + case SSH_INIT: + sshc->secondCreateDirs = 0; + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_OK; + + /* Set libssh2 to non-blocking, since everything internally is + non-blocking */ + libssh2_session_set_blocking(sshc->ssh_session, 0); + + state(conn, SSH_S_STARTUP); + /* fall-through */ + + case SSH_S_STARTUP: + rc = libssh2_session_startup(sshc->ssh_session, (int)sock); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + failf(data, "Failure establishing ssh session"); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_FAILED_INIT; + break; + } + + state(conn, SSH_HOSTKEY); + + /* fall-through */ + case SSH_HOSTKEY: + /* + * Before we authenticate we should check the hostkey's fingerprint + * against our known hosts. How that is handled (reading from file, + * whatever) is up to us. + */ + result = ssh_check_fingerprint(conn); + if(result == CURLE_OK) + state(conn, SSH_AUTHLIST); + break; + + case SSH_AUTHLIST: + /* + * Figure out authentication methods + * NB: As soon as we have provided a username to an openssh server we + * must never change it later. Thus, always specify the correct username + * here, even though the libssh2 docs kind of indicate that it should be + * possible to get a 'generic' list (not user-specific) of authentication + * methods, presumably with a blank username. That won't work in my + * experience. + * So always specify it here. + */ + sshc->authlist = libssh2_userauth_list(sshc->ssh_session, + conn->user, + curlx_uztoui(strlen(conn->user))); + + if(!sshc->authlist) { + if((err = libssh2_session_last_errno(sshc->ssh_session)) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + else { + state(conn, SSH_SESSION_FREE); + sshc->actualcode = libssh2_session_error_to_CURLE(err); + break; + } + } + infof(data, "SSH authentication methods available: %s\n", + sshc->authlist); + + state(conn, SSH_AUTH_PKEY_INIT); + break; + + case SSH_AUTH_PKEY_INIT: + /* + * Check the supported auth types in the order I feel is most secure + * with the requested type of authentication + */ + sshc->authed = FALSE; + + if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && + (strstr(sshc->authlist, "publickey") != NULL)) { + char *home = NULL; + bool rsa_pub_empty_but_ok = FALSE; + + sshc->rsa_pub = sshc->rsa = NULL; + + /* To ponder about: should really the lib be messing about with the + HOME environment variable etc? */ + home = curl_getenv("HOME"); + + if(data->set.str[STRING_SSH_PUBLIC_KEY] && + !*data->set.str[STRING_SSH_PUBLIC_KEY]) + rsa_pub_empty_but_ok = true; + else if(data->set.str[STRING_SSH_PUBLIC_KEY]) + sshc->rsa_pub = aprintf("%s", data->set.str[STRING_SSH_PUBLIC_KEY]); + else if(home) + sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home); + else + /* as a final resort, try current dir! */ + sshc->rsa_pub = strdup("id_dsa.pub"); + + if(!rsa_pub_empty_but_ok && (sshc->rsa_pub == NULL)) { + Curl_safefree(home); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + if(data->set.str[STRING_SSH_PRIVATE_KEY]) + sshc->rsa = aprintf("%s", data->set.str[STRING_SSH_PRIVATE_KEY]); + else if(home) + sshc->rsa = aprintf("%s/.ssh/id_dsa", home); + else + /* as a final resort, try current dir! */ + sshc->rsa = strdup("id_dsa"); + + if(sshc->rsa == NULL) { + Curl_safefree(home); + Curl_safefree(sshc->rsa_pub); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + sshc->passphrase = data->set.str[STRING_KEY_PASSWD]; + if(!sshc->passphrase) + sshc->passphrase = ""; + + Curl_safefree(home); + + infof(data, "Using ssh public key file %s\n", sshc->rsa_pub); + infof(data, "Using ssh private key file %s\n", sshc->rsa); + + state(conn, SSH_AUTH_PKEY); + } + else { + state(conn, SSH_AUTH_PASS_INIT); + } + break; + + case SSH_AUTH_PKEY: + /* The function below checks if the files exists, no need to stat() here. + */ + rc = libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, + conn->user, + curlx_uztoui( + strlen(conn->user)), + sshc->rsa_pub, + sshc->rsa, sshc->passphrase); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + + if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized SSH public key authentication\n"); + state(conn, SSH_AUTH_DONE); + } + else { + char *err_msg; + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + infof(data, "SSH public key authentication failed: %s\n", err_msg); + state(conn, SSH_AUTH_PASS_INIT); + } + break; + + case SSH_AUTH_PASS_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && + (strstr(sshc->authlist, "password") != NULL)) { + state(conn, SSH_AUTH_PASS); + } + else { + state(conn, SSH_AUTH_HOST_INIT); + } + break; + + case SSH_AUTH_PASS: + rc = libssh2_userauth_password_ex(sshc->ssh_session, conn->user, + curlx_uztoui(strlen(conn->user)), + conn->passwd, + curlx_uztoui(strlen(conn->passwd)), + NULL); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized password authentication\n"); + state(conn, SSH_AUTH_DONE); + } + else { + state(conn, SSH_AUTH_HOST_INIT); + } + break; + + case SSH_AUTH_HOST_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && + (strstr(sshc->authlist, "hostbased") != NULL)) { + state(conn, SSH_AUTH_HOST); + } + else { + state(conn, SSH_AUTH_AGENT_INIT); + } + break; + + case SSH_AUTH_HOST: + state(conn, SSH_AUTH_AGENT_INIT); + break; + + case SSH_AUTH_AGENT_INIT: +#ifdef HAVE_LIBSSH2_AGENT_API + if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT) + && (strstr(sshc->authlist, "publickey") != NULL)) { + + /* Connect to the ssh-agent */ + /* The agent could be shared by a curl thread i believe + but nothing obvious as keys can be added/removed at any time */ + if(!sshc->ssh_agent) { + sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session); + if(!sshc->ssh_agent) { + infof(data, "Could not create agent object\n"); + + state(conn, SSH_AUTH_KEY_INIT); + } + } + + rc = libssh2_agent_connect(sshc->ssh_agent); + if(rc == LIBSSH2_ERROR_EAGAIN) + break; + if(rc < 0) { + infof(data, "Failure connecting to agent\n"); + state(conn, SSH_AUTH_KEY_INIT); + } + else { + state(conn, SSH_AUTH_AGENT_LIST); + } + } + else +#endif /* HAVE_LIBSSH2_AGENT_API */ + state(conn, SSH_AUTH_KEY_INIT); + break; + + case SSH_AUTH_AGENT_LIST: +#ifdef HAVE_LIBSSH2_AGENT_API + rc = libssh2_agent_list_identities(sshc->ssh_agent); + + if(rc == LIBSSH2_ERROR_EAGAIN) + break; + if(rc < 0) { + infof(data, "Failure requesting identities to agent\n"); + state(conn, SSH_AUTH_KEY_INIT); + } + else { + state(conn, SSH_AUTH_AGENT); + sshc->sshagent_prev_identity = NULL; + } +#endif + break; + + case SSH_AUTH_AGENT: +#ifdef HAVE_LIBSSH2_AGENT_API + /* as prev_identity evolves only after an identity user auth finished we + can safely request it again as long as EAGAIN is returned here or by + libssh2_agent_userauth */ + rc = libssh2_agent_get_identity(sshc->ssh_agent, + &sshc->sshagent_identity, + sshc->sshagent_prev_identity); + if(rc == LIBSSH2_ERROR_EAGAIN) + break; + + if(rc == 0) { + rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user, + sshc->sshagent_identity); + + if(rc < 0) { + if(rc != LIBSSH2_ERROR_EAGAIN) { + /* tried and failed? go to next identity */ + sshc->sshagent_prev_identity = sshc->sshagent_identity; + } + break; + } + } + + if(rc < 0) + infof(data, "Failure requesting identities to agent\n"); + else if(rc == 1) + infof(data, "No identity would match\n"); + + if(rc == LIBSSH2_ERROR_NONE) { + sshc->authed = TRUE; + infof(data, "Agent based authentication successful\n"); + state(conn, SSH_AUTH_DONE); + } + else + state(conn, SSH_AUTH_KEY_INIT); +#endif + break; + + case SSH_AUTH_KEY_INIT: + if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) + && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { + state(conn, SSH_AUTH_KEY); + } + else { + state(conn, SSH_AUTH_DONE); + } + break; + + case SSH_AUTH_KEY: + /* Authentication failed. Continue with keyboard-interactive now. */ + rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, + conn->user, + curlx_uztoui( + strlen(conn->user)), + &kbd_callback); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc == 0) { + sshc->authed = TRUE; + infof(data, "Initialized keyboard interactive authentication\n"); + } + state(conn, SSH_AUTH_DONE); + break; + + case SSH_AUTH_DONE: + if(!sshc->authed) { + failf(data, "Authentication failure"); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_LOGIN_DENIED; + break; + } + + /* + * At this point we have an authenticated ssh session. + */ + infof(data, "Authentication complete\n"); + + Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSH is connected */ + + conn->sockfd = sock; + conn->writesockfd = CURL_SOCKET_BAD; + + if(conn->handler->protocol == CURLPROTO_SFTP) { + state(conn, SSH_SFTP_INIT); + break; + } + infof(data, "SSH CONNECT phase done\n"); + state(conn, SSH_STOP); + break; + + case SSH_SFTP_INIT: + /* + * Start the libssh2 sftp session + */ + sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); + if(!sshc->sftp_session) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + else { + char *err_msg; + + (void)libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0); + failf(data, "Failure initializing sftp session: %s", err_msg); + state(conn, SSH_SESSION_FREE); + sshc->actualcode = CURLE_FAILED_INIT; + break; + } + } + state(conn, SSH_SFTP_REALPATH); + break; + + case SSH_SFTP_REALPATH: + { + char tempHome[PATH_MAX]; + + /* + * Get the "home" directory + */ + rc = sftp_libssh2_realpath(sshc->sftp_session, ".", + tempHome, PATH_MAX-1); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc > 0) { + /* It seems that this string is not always NULL terminated */ + tempHome[rc] = '\0'; + sshc->homedir = strdup(tempHome); + if(!sshc->homedir) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + conn->data->state.most_recent_ftp_entrypath = sshc->homedir; + } + else { + /* Return the error type */ + err = sftp_libssh2_last_error(sshc->sftp_session); + result = sftp_libssh2_error_to_CURLE(err); + sshc->actualcode = result?result:CURLE_SSH; + DEBUGF(infof(data, "error = %d makes libcurl = %d\n", + err, (int)result)); + state(conn, SSH_STOP); + break; + } + } + /* This is the last step in the SFTP connect phase. Do note that while + we get the homedir here, we get the "workingpath" in the DO action + since the homedir will remain the same between request but the + working path will not. */ + DEBUGF(infof(data, "SSH CONNECT phase done\n")); + state(conn, SSH_STOP); + break; + + case SSH_SFTP_QUOTE_INIT: + + result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path); + if(result) { + sshc->actualcode = result; + state(conn, SSH_STOP); + break; + } + + if(data->set.quote) { + infof(data, "Sending quote commands\n"); + sshc->quote_item = data->set.quote; + state(conn, SSH_SFTP_QUOTE); + } + else { + state(conn, SSH_SFTP_TRANS_INIT); + } + break; + + case SSH_SFTP_POSTQUOTE_INIT: + if(data->set.postquote) { + infof(data, "Sending quote commands\n"); + sshc->quote_item = data->set.postquote; + state(conn, SSH_SFTP_QUOTE); + } + else { + state(conn, SSH_STOP); + } + break; + + case SSH_SFTP_QUOTE: + /* Send any quote commands */ + { + const char *cp; + + /* + * Support some of the "FTP" commands + */ + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server reponds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } + + if(curl_strequal("pwd", cmd)) { + /* output debug output if that is requested */ + char *tmp = aprintf("257 \"%s\" is current directory.\n", + sftp_scp->path); + if(!tmp) { + result = CURLE_OUT_OF_MEMORY; + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + break; + } + if(data->set.verbose) { + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4, conn); + Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn); + } + /* this sends an FTP-like "header" to the header callback so that the + current directory can be read very similar to how it is read when + using ordinary FTP. */ + result = Curl_client_write(conn, CLIENTWRITE_HEADER, tmp, strlen(tmp)); + free(tmp); + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + } + else if(cmd) { + /* + * the arguments following the command must be separated from the + * command with a space so we can check for it unconditionally + */ + cp = strchr(cmd, ' '); + if(cp == NULL) { + failf(data, "Syntax error in SFTP command. Supply parameter(s)!"); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + + /* + * also, every command takes at least one argument so we get that + * first argument right now + */ + result = get_pathname(&cp, &sshc->quote_path1); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error: Bad first parameter"); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + break; + } + + /* + * SFTP is a binary protocol, so we don't send text commands to + * the server. Instead, we scan for commands for commands used by + * OpenSSH's sftp program and call the appropriate libssh2 + * functions. + */ + if(curl_strnequal(cmd, "chgrp ", 6) || + curl_strnequal(cmd, "chmod ", 6) || + curl_strnequal(cmd, "chown ", 6) ) { + /* attribute change */ + + /* sshc->quote_path1 contains the mode to set */ + /* get the destination */ + result = get_pathname(&cp, &sshc->quote_path2); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error in chgrp/chmod/chown: " + "Bad second parameter"); + Curl_safefree(sshc->quote_path1); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + break; + } + memset(&sshc->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); + state(conn, SSH_SFTP_QUOTE_STAT); + break; + } + else if(curl_strnequal(cmd, "ln ", 3) || + curl_strnequal(cmd, "symlink ", 8)) { + /* symbolic linking */ + /* sshc->quote_path1 is the source */ + /* get the destination */ + result = get_pathname(&cp, &sshc->quote_path2); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, + "Syntax error in ln/symlink: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + break; + } + state(conn, SSH_SFTP_QUOTE_SYMLINK); + break; + } + else if(curl_strnequal(cmd, "mkdir ", 6)) { + /* create dir */ + state(conn, SSH_SFTP_QUOTE_MKDIR); + break; + } + else if(curl_strnequal(cmd, "rename ", 7)) { + /* rename file */ + /* first param is the source path */ + /* second param is the dest. path */ + result = get_pathname(&cp, &sshc->quote_path2); + if(result) { + if(result == CURLE_OUT_OF_MEMORY) + failf(data, "Out of memory"); + else + failf(data, "Syntax error in rename: Bad second parameter"); + Curl_safefree(sshc->quote_path1); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = result; + break; + } + state(conn, SSH_SFTP_QUOTE_RENAME); + break; + } + else if(curl_strnequal(cmd, "rmdir ", 6)) { + /* delete dir */ + state(conn, SSH_SFTP_QUOTE_RMDIR); + break; + } + else if(curl_strnequal(cmd, "rm ", 3)) { + state(conn, SSH_SFTP_QUOTE_UNLINK); + break; + } + + failf(data, "Unknown SFTP command"); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + if(!sshc->quote_item) { + state(conn, SSH_SFTP_TRANS_INIT); + } + break; + + case SSH_SFTP_NEXT_QUOTE: + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + + sshc->quote_item = sshc->quote_item->next; + + if(sshc->quote_item) { + state(conn, SSH_SFTP_QUOTE); + } + else { + if(sshc->nextstate != SSH_NO_STATE) { + state(conn, sshc->nextstate); + sshc->nextstate = SSH_NO_STATE; + } + else { + state(conn, SSH_SFTP_TRANS_INIT); + } + } + break; + + case SSH_SFTP_QUOTE_STAT: + { + char *cmd = sshc->quote_item->data; + sshc->acceptfail = FALSE; + + /* if a command starts with an asterisk, which a legal SFTP command never + can, the command will be allowed to fail without it causing any + aborts or cancels etc. It will cause libcurl to act as if the command + is successful, whatever the server reponds. */ + + if(cmd[0] == '*') { + cmd++; + sshc->acceptfail = TRUE; + } + + if(!curl_strnequal(cmd, "chmod", 5)) { + /* Since chown and chgrp only set owner OR group but libssh2 wants to + * set them both at once, we need to obtain the current ownership + * first. This takes an extra protocol round trip. + */ + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_STAT, + &sshc->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0 && !sshc->acceptfail) { /* get those attributes */ + err = sftp_libssh2_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Attempt to get SFTP stats failed: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + + /* Now set the new attributes... */ + if(curl_strnequal(cmd, "chgrp", 5)) { + sshc->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10); + sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; + if(sshc->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chgrp gid not a number"); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + else if(curl_strnequal(cmd, "chmod", 5)) { + sshc->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8); + sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; + /* permissions are octal */ + if(sshc->quote_attrs.permissions == 0 && + !ISDIGIT(sshc->quote_path1[0])) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chmod permissions not a number"); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + else if(curl_strnequal(cmd, "chown", 5)) { + sshc->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10); + sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; + if(sshc->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && + !sshc->acceptfail) { + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Syntax error: chown uid not a number"); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + } + + /* Now send the completed structure... */ + state(conn, SSH_SFTP_QUOTE_SETSTAT); + break; + } + + case SSH_SFTP_QUOTE_SETSTAT: + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_SETSTAT, + &sshc->quote_attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0 && !sshc->acceptfail) { + err = sftp_libssh2_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "Attempt to set SFTP stats failed: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_SYMLINK: + rc = libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_SYMLINK); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0 && !sshc->acceptfail) { + err = sftp_libssh2_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "symlink command failed: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_MKDIR: + rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0 && !sshc->acceptfail) { + err = sftp_libssh2_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RENAME: + rc = libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1)), + sshc->quote_path2, + curlx_uztoui(strlen(sshc->quote_path2)), + LIBSSH2_SFTP_RENAME_OVERWRITE | + LIBSSH2_SFTP_RENAME_ATOMIC | + LIBSSH2_SFTP_RENAME_NATIVE); + + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0 && !sshc->acceptfail) { + err = sftp_libssh2_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + failf(data, "rename command failed: %s", sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_RMDIR: + rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1))); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0 && !sshc->acceptfail) { + err = sftp_libssh2_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_QUOTE_UNLINK: + rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1, + curlx_uztoui(strlen(sshc->quote_path1))); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc != 0 && !sshc->acceptfail) { + err = sftp_libssh2_last_error(sshc->sftp_session); + Curl_safefree(sshc->quote_path1); + failf(data, "rm command failed: %s", sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + sshc->nextstate = SSH_NO_STATE; + sshc->actualcode = CURLE_QUOTE_ERROR; + break; + } + state(conn, SSH_SFTP_NEXT_QUOTE); + break; + + case SSH_SFTP_TRANS_INIT: + if(data->set.upload) + state(conn, SSH_SFTP_UPLOAD_INIT); + else { + if(data->set.opt_no_body) + state(conn, SSH_STOP); + else if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') + state(conn, SSH_SFTP_READDIR_INIT); + else + state(conn, SSH_SFTP_DOWNLOAD_INIT); + } + break; + + case SSH_SFTP_UPLOAD_INIT: + { + unsigned long flags; + /* + * NOTE!!! libssh2 requires that the destination path is a full path + * that includes the destination file and name OR ends in a "/" + * If this is not done the destination file will be named the + * same name as the last directory in the path. + */ + + if(data->state.resume_from != 0) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + if(data->state.resume_from < 0) { + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path, + curlx_uztoui(strlen(sftp_scp->path)), + LIBSSH2_SFTP_STAT, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + data->state.resume_from = 0; + } + else { + curl_off_t size = attrs.filesize; + if(size < 0) { + failf(data, "Bad file size (%" FORMAT_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + data->state.resume_from = attrs.filesize; + } + } + } + + if(data->set.ftp_append) + /* Try to open for append, but create if nonexisting */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND; + else if(data->state.resume_from > 0) + /* If we have restart position then open for append */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND; + else + /* Clear file before writing (normal behaviour) */ + flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC; + + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sftp_scp->path, + curlx_uztoui(strlen(sftp_scp->path)), + flags, data->set.new_file_perms, + LIBSSH2_SFTP_OPENFILE); + + if(!sshc->sftp_handle) { + rc = libssh2_session_last_errno(sshc->ssh_session); + + if(LIBSSH2_ERROR_EAGAIN == rc) + break; + else { + if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc) + /* only when there was an SFTP protocol error can we extract + the sftp error! */ + err = sftp_libssh2_last_error(sshc->sftp_session); + else + err = -1; /* not an sftp error at all */ + + if(sshc->secondCreateDirs) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = err>= LIBSSH2_FX_OK? + sftp_libssh2_error_to_CURLE(err):CURLE_SSH; + failf(data, "Creating the dir/file failed: %s", + sftp_libssh2_strerror(err)); + break; + } + else if(((err == LIBSSH2_FX_NO_SUCH_FILE) || + (err == LIBSSH2_FX_FAILURE) || + (err == LIBSSH2_FX_NO_SUCH_PATH)) && + (data->set.ftp_create_missing_dirs && + (strlen(sftp_scp->path) > 1))) { + /* try to create the path remotely */ + sshc->secondCreateDirs = 1; + state(conn, SSH_SFTP_CREATE_DIRS_INIT); + break; + } + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = err>= LIBSSH2_FX_OK? + sftp_libssh2_error_to_CURLE(err):CURLE_SSH; + if(!sshc->actualcode) { + /* Sometimes, for some reason libssh2_sftp_last_error() returns + zero even though libssh2_sftp_open() failed previously! We need + to work around that! */ + sshc->actualcode = CURLE_SSH; + err=-1; + } + failf(data, "Upload failed: %s (%d/%d)", + err>= LIBSSH2_FX_OK?sftp_libssh2_strerror(err):"ssh error", + err, rc); + break; + } + } + + /* If we have restart point then we need to seek to the correct + position. */ + if(data->state.resume_from > 0) { + /* Let's read off the proper amount of bytes from the input. */ + if(conn->seek_func) { + seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, + SEEK_SET); + } + + if(seekerr != CURL_SEEKFUNC_OK) { + + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_FTP_COULDNT_USE_REST; + } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ + else { + curl_off_t passed=0; + do { + size_t readthisamountnow = + (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? + BUFSIZE : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + conn->fread_func(data->state.buffer, 1, readthisamountnow, + conn->fread_in); + + passed += actuallyread; + if((actuallyread == 0) || (actuallyread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Failed to read data"); + return CURLE_FTP_COULDNT_USE_REST; + } + } while(passed < data->state.resume_from); + } + } + + /* now, decrease the size of the read */ + if(data->set.infilesize > 0) { + data->set.infilesize -= data->state.resume_from; + data->req.size = data->set.infilesize; + Curl_pgrsSetUploadSize(data, data->set.infilesize); + } + + SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); + } + if(data->set.infilesize > 0) { + data->req.size = data->set.infilesize; + Curl_pgrsSetUploadSize(data, data->set.infilesize); + } + /* upload data */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + if(result) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + /* store this original bitmask setup to use later on if we can't + figure out a "real" bitmask */ + sshc->orig_waitfor = data->req.keepon; + + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh2 sftp send function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_OUT; + + /* since we don't really wait for anything at this point, we want the + state machine to move on as soon as possible so we set a very short + timeout here */ + Curl_expire(data, 1); + + state(conn, SSH_STOP); + } + break; + } + + case SSH_SFTP_CREATE_DIRS_INIT: + if(strlen(sftp_scp->path) > 1) { + sshc->slash_pos = sftp_scp->path + 1; /* ignore the leading '/' */ + state(conn, SSH_SFTP_CREATE_DIRS); + } + else { + state(conn, SSH_SFTP_UPLOAD_INIT); + } + break; + + case SSH_SFTP_CREATE_DIRS: + if((sshc->slash_pos = strchr(sshc->slash_pos, '/')) != NULL) { + *sshc->slash_pos = 0; + + infof(data, "Creating directory '%s'\n", sftp_scp->path); + state(conn, SSH_SFTP_CREATE_DIRS_MKDIR); + break; + } + else { + state(conn, SSH_SFTP_UPLOAD_INIT); + } + break; + + case SSH_SFTP_CREATE_DIRS_MKDIR: + /* 'mode' - parameter is preliminary - default to 0644 */ + rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sftp_scp->path, + curlx_uztoui(strlen(sftp_scp->path)), + data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + *sshc->slash_pos = '/'; + ++sshc->slash_pos; + if(rc == -1) { + /* + * Abort if failure wasn't that the dir already exists or the + * permission was denied (creation might succeed further down the + * path) - retry on unspecific FAILURE also + */ + err = sftp_libssh2_last_error(sshc->sftp_session); + if((err != LIBSSH2_FX_FILE_ALREADY_EXISTS) && + (err != LIBSSH2_FX_FAILURE) && + (err != LIBSSH2_FX_PERMISSION_DENIED)) { + result = sftp_libssh2_error_to_CURLE(err); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = result?result:CURLE_SSH; + break; + } + } + state(conn, SSH_SFTP_CREATE_DIRS); + break; + + case SSH_SFTP_READDIR_INIT: + /* + * This is a directory that we are trying to get, so produce a directory + * listing + */ + sshc->sftp_handle = libssh2_sftp_open_ex(sshc->sftp_session, + sftp_scp->path, + curlx_uztoui( + strlen(sftp_scp->path)), + 0, 0, LIBSSH2_SFTP_OPENDIR); + if(!sshc->sftp_handle) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + else { + err = sftp_libssh2_last_error(sshc->sftp_session); + failf(data, "Could not open directory for reading: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + result = sftp_libssh2_error_to_CURLE(err); + sshc->actualcode = result?result:CURLE_SSH; + break; + } + } + if((sshc->readdir_filename = malloc(PATH_MAX+1)) == NULL) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + if((sshc->readdir_longentry = malloc(PATH_MAX+1)) == NULL) { + Curl_safefree(sshc->readdir_filename); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + state(conn, SSH_SFTP_READDIR); + break; + + case SSH_SFTP_READDIR: + sshc->readdir_len = libssh2_sftp_readdir_ex(sshc->sftp_handle, + sshc->readdir_filename, + PATH_MAX, + sshc->readdir_longentry, + PATH_MAX, + &sshc->readdir_attrs); + if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + if(sshc->readdir_len > 0) { + sshc->readdir_filename[sshc->readdir_len] = '\0'; + + if(data->set.ftp_list_only) { + char *tmpLine; + + tmpLine = aprintf("%s\n", sshc->readdir_filename); + if(tmpLine == NULL) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + result = Curl_client_write(conn, CLIENTWRITE_BODY, + tmpLine, sshc->readdir_len+1); + Curl_safefree(tmpLine); + + if(result) { + state(conn, SSH_STOP); + break; + } + /* since this counts what we send to the client, we include the + newline in this counter */ + data->req.bytecount += sshc->readdir_len+1; + + /* output debug output if that is requested */ + if(data->set.verbose) { + Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_filename, + sshc->readdir_len, conn); + } + } + else { + sshc->readdir_currLen = (int)strlen(sshc->readdir_longentry); + sshc->readdir_totalLen = 80 + sshc->readdir_currLen; + sshc->readdir_line = calloc(sshc->readdir_totalLen, 1); + if(!sshc->readdir_line) { + Curl_safefree(sshc->readdir_filename); + Curl_safefree(sshc->readdir_longentry); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + memcpy(sshc->readdir_line, sshc->readdir_longentry, + sshc->readdir_currLen); + if((sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && + ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == + LIBSSH2_SFTP_S_IFLNK)) { + sshc->readdir_linkPath = malloc(PATH_MAX + 1); + if(sshc->readdir_linkPath == NULL) { + Curl_safefree(sshc->readdir_filename); + Curl_safefree(sshc->readdir_longentry); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + + snprintf(sshc->readdir_linkPath, PATH_MAX, "%s%s", sftp_scp->path, + sshc->readdir_filename); + state(conn, SSH_SFTP_READDIR_LINK); + break; + } + state(conn, SSH_SFTP_READDIR_BOTTOM); + break; + } + } + else if(sshc->readdir_len == 0) { + Curl_safefree(sshc->readdir_filename); + Curl_safefree(sshc->readdir_longentry); + state(conn, SSH_SFTP_READDIR_DONE); + break; + } + else if(sshc->readdir_len <= 0) { + err = sftp_libssh2_last_error(sshc->sftp_session); + result = sftp_libssh2_error_to_CURLE(err); + sshc->actualcode = result?result:CURLE_SSH; + failf(data, "Could not open remote file for reading: %s :: %d", + sftp_libssh2_strerror(err), + libssh2_session_last_errno(sshc->ssh_session)); + Curl_safefree(sshc->readdir_filename); + Curl_safefree(sshc->readdir_longentry); + state(conn, SSH_SFTP_CLOSE); + break; + } + break; + + case SSH_SFTP_READDIR_LINK: + sshc->readdir_len = + libssh2_sftp_symlink_ex(sshc->sftp_session, + sshc->readdir_linkPath, + curlx_uztoui(strlen(sshc->readdir_linkPath)), + sshc->readdir_filename, + PATH_MAX, LIBSSH2_SFTP_READLINK); + if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + Curl_safefree(sshc->readdir_linkPath); + + /* get room for the filename and extra output */ + sshc->readdir_totalLen += 4 + sshc->readdir_len; + new_readdir_line = realloc(sshc->readdir_line, sshc->readdir_totalLen); + if(!new_readdir_line) { + Curl_safefree(sshc->readdir_line); + Curl_safefree(sshc->readdir_filename); + Curl_safefree(sshc->readdir_longentry); + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = CURLE_OUT_OF_MEMORY; + break; + } + sshc->readdir_line = new_readdir_line; + + sshc->readdir_currLen += snprintf(sshc->readdir_line + + sshc->readdir_currLen, + sshc->readdir_totalLen - + sshc->readdir_currLen, + " -> %s", + sshc->readdir_filename); + + state(conn, SSH_SFTP_READDIR_BOTTOM); + break; + + case SSH_SFTP_READDIR_BOTTOM: + sshc->readdir_currLen += snprintf(sshc->readdir_line + + sshc->readdir_currLen, + sshc->readdir_totalLen - + sshc->readdir_currLen, "\n"); + result = Curl_client_write(conn, CLIENTWRITE_BODY, + sshc->readdir_line, + sshc->readdir_currLen); + + if(result == CURLE_OK) { + + /* output debug output if that is requested */ + if(data->set.verbose) { + Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_line, + sshc->readdir_currLen, conn); + } + data->req.bytecount += sshc->readdir_currLen; + } + Curl_safefree(sshc->readdir_line); + if(result) { + state(conn, SSH_STOP); + } + else + state(conn, SSH_SFTP_READDIR); + break; + + case SSH_SFTP_READDIR_DONE: + if(libssh2_sftp_closedir(sshc->sftp_handle) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + sshc->sftp_handle = NULL; + Curl_safefree(sshc->readdir_filename); + Curl_safefree(sshc->readdir_longentry); + + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + state(conn, SSH_STOP); + break; + + case SSH_SFTP_DOWNLOAD_INIT: + /* + * Work on getting the specified file + */ + sshc->sftp_handle = + libssh2_sftp_open_ex(sshc->sftp_session, sftp_scp->path, + curlx_uztoui(strlen(sftp_scp->path)), + LIBSSH2_FXF_READ, data->set.new_file_perms, + LIBSSH2_SFTP_OPENFILE); + if(!sshc->sftp_handle) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + else { + err = sftp_libssh2_last_error(sshc->sftp_session); + failf(data, "Could not open remote file for reading: %s", + sftp_libssh2_strerror(err)); + state(conn, SSH_SFTP_CLOSE); + result = sftp_libssh2_error_to_CURLE(err); + sshc->actualcode = result?result:CURLE_SSH; + break; + } + } + state(conn, SSH_SFTP_DOWNLOAD_STAT); + break; + + case SSH_SFTP_DOWNLOAD_STAT: + { + LIBSSH2_SFTP_ATTRIBUTES attrs; + + rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path, + curlx_uztoui(strlen(sftp_scp->path)), + LIBSSH2_SFTP_STAT, &attrs); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + /* + * libssh2_sftp_open() didn't return an error, so maybe the server + * just doesn't support stat() + */ + data->req.size = -1; + data->req.maxdownload = -1; + } + else { + curl_off_t size = attrs.filesize; + + if(size < 0) { + failf(data, "Bad file size (%" FORMAT_OFF_T ")", size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(conn->data->state.use_range) { + curl_off_t from, to; + char *ptr; + char *ptr2; + + from=curlx_strtoofft(conn->data->state.range, &ptr, 0); + while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) + ptr++; + to=curlx_strtoofft(ptr, &ptr2, 0); + if((ptr == ptr2) /* no "to" value given */ + || (to >= size)) { + to = size - 1; + } + if(from < 0) { + /* from is relative to end of file */ + from += size; + } + if(from >= size) { + failf(data, "Offset (%" + FORMAT_OFF_T ") was beyond file size (%" FORMAT_OFF_T ")", + from, attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(from > to) { + from = to; + size = 0; + } + else { + size = to - from + 1; + } + + SFTP_SEEK(conn->proto.sshc.sftp_handle, from); + } + data->req.size = size; + data->req.maxdownload = size; + Curl_pgrsSetDownloadSize(data, size); + } + + /* We can resume if we can seek to the resume position */ + if(data->state.resume_from) { + if(data->state.resume_from < 0) { + /* We're supposed to download the last abs(from) bytes */ + if((curl_off_t)attrs.filesize < -data->state.resume_from) { + failf(data, "Offset (%" + FORMAT_OFF_T ") was beyond file size (%" FORMAT_OFF_T ")", + data->state.resume_from, attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + /* download from where? */ + data->state.resume_from += attrs.filesize; + } + else { + if((curl_off_t)attrs.filesize < data->state.resume_from) { + failf(data, "Offset (%" FORMAT_OFF_T + ") was beyond file size (%" FORMAT_OFF_T ")", + data->state.resume_from, attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + } + /* Does a completed file need to be seeked and started or closed ? */ + /* Now store the number of bytes we are expected to download */ + data->req.size = attrs.filesize - data->state.resume_from; + data->req.maxdownload = attrs.filesize - data->state.resume_from; + Curl_pgrsSetDownloadSize(data, + attrs.filesize - data->state.resume_from); + SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); + } + } + /* Setup the actual download */ + if(data->req.size == 0) { + /* no data to transfer */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + infof(data, "File already completely downloaded\n"); + state(conn, SSH_STOP); + break; + } + else { + Curl_setup_transfer(conn, FIRSTSOCKET, data->req.size, + FALSE, NULL, -1, NULL); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->writesockfd = conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh2 recv function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_IN; + } + if(result) { + state(conn, SSH_SFTP_CLOSE); + sshc->actualcode = result; + } + else { + state(conn, SSH_STOP); + } + break; + + case SSH_SFTP_CLOSE: + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to close libssh2 file\n"); + } + sshc->sftp_handle = NULL; + } + if(sftp_scp) + Curl_safefree(sftp_scp->path); + + DEBUGF(infof(data, "SFTP DONE done\n")); + + /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT + After nextstate is executed,the control should come back to + SSH_SFTP_CLOSE to pass the correct result back */ + if(sshc->nextstate != SSH_NO_STATE) { + state(conn, sshc->nextstate); + sshc->nextstate = SSH_SFTP_CLOSE; + } + else { + state(conn, SSH_STOP); + result = sshc->actualcode; + } + break; + + case SSH_SFTP_SHUTDOWN: + /* during times we get here due to a broken transfer and then the + sftp_handle might not have been taken down so make sure that is done + before we proceed */ + + if(sshc->sftp_handle) { + rc = libssh2_sftp_close(sshc->sftp_handle); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to close libssh2 file\n"); + } + sshc->sftp_handle = NULL; + } + if(sshc->sftp_session) { + rc = libssh2_sftp_shutdown(sshc->sftp_session); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to stop libssh2 sftp subsystem\n"); + } + sshc->sftp_session = NULL; + } + + Curl_safefree(sshc->homedir); + conn->data->state.most_recent_ftp_entrypath = NULL; + + state(conn, SSH_SESSION_DISCONNECT); + break; + + case SSH_SCP_TRANS_INIT: + result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path); + if(result) { + sshc->actualcode = result; + state(conn, SSH_STOP); + break; + } + + if(data->set.upload) { + if(data->set.infilesize < 0) { + failf(data, "SCP requires a known file size for upload"); + sshc->actualcode = CURLE_UPLOAD_FAILED; + state(conn, SSH_SCP_CHANNEL_FREE); + break; + } + state(conn, SSH_SCP_UPLOAD_INIT); + } + else { + state(conn, SSH_SCP_DOWNLOAD_INIT); + } + break; + + case SSH_SCP_UPLOAD_INIT: + /* + * libssh2 requires that the destination path is a full path that + * includes the destination file and name OR ends in a "/" . If this is + * not done the destination file will be named the same name as the last + * directory in the path. + */ + sshc->ssh_channel = + SCP_SEND(sshc->ssh_session, sftp_scp->path, data->set.new_file_perms, + data->set.infilesize); + if(!sshc->ssh_channel) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + else { + int ssh_err; + char *err_msg; + + ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0)); + failf(conn->data, "%s", err_msg); + state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); + break; + } + } + + /* upload data */ + Curl_setup_transfer(conn, -1, data->req.size, FALSE, NULL, + FIRSTSOCKET, NULL); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + if(result) { + state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = result; + } + else { + /* we want to use the _sending_ function even when the socket turns + out readable as the underlying libssh2 scp send function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_OUT; + + state(conn, SSH_STOP); + } + break; + + case SSH_SCP_DOWNLOAD_INIT: + { + /* + * We must check the remote file; if it is a directory no values will + * be set in sb + */ + struct stat sb; + curl_off_t bytecount; + + /* clear the struct scp recv will fill in */ + memset(&sb, 0, sizeof(struct stat)); + + /* get a fresh new channel from the ssh layer */ + sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, + sftp_scp->path, &sb); + if(!sshc->ssh_channel) { + if(libssh2_session_last_errno(sshc->ssh_session) == + LIBSSH2_ERROR_EAGAIN) { + rc = LIBSSH2_ERROR_EAGAIN; + break; + } + else { + int ssh_err; + char *err_msg; + + ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, + &err_msg, NULL, 0)); + failf(conn->data, "%s", err_msg); + state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); + break; + } + } + + /* download data */ + bytecount = (curl_off_t)sb.st_size; + data->req.maxdownload = (curl_off_t)sb.st_size; + Curl_setup_transfer(conn, FIRSTSOCKET, bytecount, FALSE, NULL, -1, NULL); + + /* not set by Curl_setup_transfer to preserve keepon bits */ + conn->writesockfd = conn->sockfd; + + /* we want to use the _receiving_ function even when the socket turns + out writableable as the underlying libssh2 recv function will deal + with both accordingly */ + conn->cselect_bits = CURL_CSELECT_IN; + + if(result) { + state(conn, SSH_SCP_CHANNEL_FREE); + sshc->actualcode = result; + } + else + state(conn, SSH_STOP); + } + break; + + case SSH_SCP_DONE: + if(data->set.upload) + state(conn, SSH_SCP_SEND_EOF); + else + state(conn, SSH_SCP_CHANNEL_FREE); + break; + + case SSH_SCP_SEND_EOF: + if(sshc->ssh_channel) { + rc = libssh2_channel_send_eof(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + infof(data, "Failed to send libssh2 channel EOF\n"); + } + } + state(conn, SSH_SCP_WAIT_EOF); + break; + + case SSH_SCP_WAIT_EOF: + if(sshc->ssh_channel) { + rc = libssh2_channel_wait_eof(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + infof(data, "Failed to get channel EOF: %d\n", rc); + } + } + state(conn, SSH_SCP_WAIT_CLOSE); + break; + + case SSH_SCP_WAIT_CLOSE: + if(sshc->ssh_channel) { + rc = libssh2_channel_wait_closed(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc) { + infof(data, "Channel failed to close: %d\n", rc); + } + } + state(conn, SSH_SCP_CHANNEL_FREE); + break; + + case SSH_SCP_CHANNEL_FREE: + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to free libssh2 scp subsystem\n"); + } + sshc->ssh_channel = NULL; + } + DEBUGF(infof(data, "SCP DONE phase complete\n")); +#if 0 /* PREV */ + state(conn, SSH_SESSION_DISCONNECT); +#endif + state(conn, SSH_STOP); + result = sshc->actualcode; + break; + + case SSH_SESSION_DISCONNECT: + /* during weird times when we've been prematurely aborted, the channel + is still alive when we reach this state and we MUST kill the channel + properly first */ + if(sshc->ssh_channel) { + rc = libssh2_channel_free(sshc->ssh_channel); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to free libssh2 scp subsystem\n"); + } + sshc->ssh_channel = NULL; + } + + if(sshc->ssh_session) { + rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to disconnect libssh2 session\n"); + } + } + + Curl_safefree(sshc->homedir); + conn->data->state.most_recent_ftp_entrypath = NULL; + + state(conn, SSH_SESSION_FREE); + break; + + case SSH_SESSION_FREE: +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + if(sshc->kh) { + libssh2_knownhost_free(sshc->kh); + sshc->kh = NULL; + } +#endif + +#ifdef HAVE_LIBSSH2_AGENT_API + if(sshc->ssh_agent) { + rc = libssh2_agent_disconnect(sshc->ssh_agent); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to disconnect from libssh2 agent\n"); + } + libssh2_agent_free (sshc->ssh_agent); + sshc->ssh_agent = NULL; + + /* NB: there is no need to free identities, they are part of internal + agent stuff */ + sshc->sshagent_identity = NULL; + sshc->sshagent_prev_identity = NULL; + } +#endif + + if(sshc->ssh_session) { + rc = libssh2_session_free(sshc->ssh_session); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + else if(rc < 0) { + infof(data, "Failed to free libssh2 session\n"); + } + sshc->ssh_session = NULL; + } + + /* worst-case scenario cleanup */ + + DEBUGASSERT(sshc->ssh_session == NULL); + DEBUGASSERT(sshc->ssh_channel == NULL); + DEBUGASSERT(sshc->sftp_session == NULL); + DEBUGASSERT(sshc->sftp_handle == NULL); +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + DEBUGASSERT(sshc->kh == NULL); +#endif +#ifdef HAVE_LIBSSH2_AGENT_API + DEBUGASSERT(sshc->ssh_agent == NULL); +#endif + + Curl_safefree(sshc->rsa_pub); + Curl_safefree(sshc->rsa); + + Curl_safefree(sshc->quote_path1); + Curl_safefree(sshc->quote_path2); + + Curl_safefree(sshc->homedir); + + Curl_safefree(sshc->readdir_filename); + Curl_safefree(sshc->readdir_longentry); + Curl_safefree(sshc->readdir_line); + Curl_safefree(sshc->readdir_linkPath); + + /* the code we are about to return */ + result = sshc->actualcode; + + memset(sshc, 0, sizeof(struct ssh_conn)); + + conn->bits.close = TRUE; + sshc->state = SSH_SESSION_FREE; /* current */ + sshc->nextstate = SSH_NO_STATE; + state(conn, SSH_STOP); + break; + + case SSH_QUIT: + /* fallthrough, just stop! */ + default: + /* internal error */ + sshc->nextstate = SSH_NO_STATE; + state(conn, SSH_STOP); + break; + } + + } while(!rc && (sshc->state != SSH_STOP)); + + if(rc == LIBSSH2_ERROR_EAGAIN) { + /* we would block, we need to wait for the socket to be ready (in the + right direction too)! */ + *block = TRUE; + } + + return result; +} + +/* called by the multi interface to figure out what socket(s) to wait for and + for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ +static int ssh_perform_getsock(const struct connectdata *conn, + curl_socket_t *sock, /* points to numsocks + number of sockets */ + int numsocks) +{ +#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION + int bitmap = GETSOCK_BLANK; + (void)numsocks; + + sock[0] = conn->sock[FIRSTSOCKET]; + + if(conn->waitfor & KEEP_RECV) + bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); + + if(conn->waitfor & KEEP_SEND) + bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); + + return bitmap; +#else + /* if we don't know the direction we can use the generic *_getsock() + function even for the protocol_connect and doing states */ + return Curl_single_getsock(conn, sock, numsocks); +#endif +} + +/* Generic function called by the multi interface to figure out what socket(s) + to wait for and for what actions during the DOING and PROTOCONNECT states*/ +static int ssh_getsock(struct connectdata *conn, + curl_socket_t *sock, /* points to numsocks number + of sockets */ + int numsocks) +{ +#ifndef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION + (void)conn; + (void)sock; + (void)numsocks; + /* if we don't know any direction we can just play along as we used to and + not provide any sensible info */ + return GETSOCK_BLANK; +#else + /* if we know the direction we can use the generic *_getsock() function even + for the protocol_connect and doing states */ + return ssh_perform_getsock(conn, sock, numsocks); +#endif +} + +#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION +/* + * When one of the libssh2 functions has returned LIBSSH2_ERROR_EAGAIN this + * function is used to figure out in what direction and stores this info so + * that the multi interface can take advantage of it. Make sure to call this + * function in all cases so that when it _doesn't_ return EAGAIN we can + * restore the default wait bits. + */ +static void ssh_block2waitfor(struct connectdata *conn, bool block) +{ + struct ssh_conn *sshc = &conn->proto.sshc; + int dir; + if(!block) + conn->waitfor = 0; + else if((dir = libssh2_session_block_directions(sshc->ssh_session))) { + /* translate the libssh2 define bits into our own bit defines */ + conn->waitfor = ((dir&LIBSSH2_SESSION_BLOCK_INBOUND)?KEEP_RECV:0) | + ((dir&LIBSSH2_SESSION_BLOCK_OUTBOUND)?KEEP_SEND:0); + } + else + /* It didn't block or libssh2 didn't reveal in which direction, put back + the original set */ + conn->waitfor = sshc->orig_waitfor; +} +#else + /* no libssh2 directional support so we simply don't know */ +#define ssh_block2waitfor(x,y) Curl_nop_stmt +#endif + +/* called repeatedly until done from curl_multi.c */ +static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done) +{ + struct ssh_conn *sshc = &conn->proto.sshc; + CURLcode result = CURLE_OK; + bool block; /* we store the status and use that to provide a ssh_getsock() + implementation */ + + result = ssh_statemach_act(conn, &block); + *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; + ssh_block2waitfor(conn, block); + + return result; +} + +static CURLcode ssh_easy_statemach(struct connectdata *conn, + bool duringconnect) +{ + struct ssh_conn *sshc = &conn->proto.sshc; + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + while((sshc->state != SSH_STOP) && !result) { + bool block; + long left; + + result = ssh_statemach_act(conn, &block); + if(result) + break; + + if(Curl_pgrsUpdate(conn)) + return CURLE_ABORTED_BY_CALLBACK; + else { + struct timeval now = Curl_tvnow(); + result = Curl_speedcheck(data, now); + if(result) + break; + } + + left = Curl_timeleft(data, NULL, duringconnect); + if(left < 0) { + failf(data, "Operation timed out"); + return CURLE_OPERATION_TIMEDOUT; + } + +#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION + if((CURLE_OK == result) && block) { + int dir = libssh2_session_block_directions(sshc->ssh_session); + curl_socket_t sock = conn->sock[FIRSTSOCKET]; + curl_socket_t fd_read = CURL_SOCKET_BAD; + curl_socket_t fd_write = CURL_SOCKET_BAD; + if(LIBSSH2_SESSION_BLOCK_INBOUND & dir) + fd_read = sock; + if(LIBSSH2_SESSION_BLOCK_OUTBOUND & dir) + fd_write = sock; + /* wait for the socket to become ready */ + Curl_socket_ready(fd_read, fd_write, + left>1000?1000:left); /* ignore result */ + } +#endif + + } + + return result; +} + +/* + * SSH setup and connection + */ +static CURLcode ssh_init(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct SSHPROTO *ssh; + struct ssh_conn *sshc = &conn->proto.sshc; + + sshc->actualcode = CURLE_OK; /* reset error code */ + sshc->secondCreateDirs =0; /* reset the create dir attempt state + variable */ + + if(data->state.proto.ssh) + return CURLE_OK; + + ssh = calloc(1, sizeof(struct SSHPROTO)); + if(!ssh) + return CURLE_OUT_OF_MEMORY; + + data->state.proto.ssh = ssh; + + return CURLE_OK; +} + +static Curl_recv scp_recv, sftp_recv; +static Curl_send scp_send, sftp_send; + +/* + * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to + * do protocol-specific actions at connect-time. + */ +static CURLcode ssh_connect(struct connectdata *conn, bool *done) +{ +#ifdef CURL_LIBSSH2_DEBUG + curl_socket_t sock; +#endif + struct ssh_conn *ssh; + CURLcode result; + struct SessionHandle *data = conn->data; + + /* We default to persistent connections. We set this already in this connect + function to make the re-use checks properly be able to check this bit. */ + conn->bits.close = FALSE; + + /* If there already is a protocol-specific struct allocated for this + sessionhandle, deal with it */ + Curl_reset_reqproto(conn); + + result = ssh_init(conn); + if(result) + return result; + + if(conn->handler->protocol & CURLPROTO_SCP) { + conn->recv[FIRSTSOCKET] = scp_recv; + conn->send[FIRSTSOCKET] = scp_send; + } + else { + conn->recv[FIRSTSOCKET] = sftp_recv; + conn->send[FIRSTSOCKET] = sftp_send; + } + ssh = &conn->proto.sshc; + +#ifdef CURL_LIBSSH2_DEBUG + if(conn->user) { + infof(data, "User: %s\n", conn->user); + } + if(conn->passwd) { + infof(data, "Password: %s\n", conn->passwd); + } + sock = conn->sock[FIRSTSOCKET]; +#endif /* CURL_LIBSSH2_DEBUG */ + + ssh->ssh_session = libssh2_session_init_ex(my_libssh2_malloc, + my_libssh2_free, + my_libssh2_realloc, conn); + if(ssh->ssh_session == NULL) { + failf(data, "Failure initialising ssh session"); + return CURLE_FAILED_INIT; + } + +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + if(data->set.str[STRING_SSH_KNOWNHOSTS]) { + int rc; + ssh->kh = libssh2_knownhost_init(ssh->ssh_session); + if(!ssh->kh) { + /* eeek. TODO: free the ssh_session! */ + return CURLE_FAILED_INIT; + } + + /* read all known hosts from there */ + rc = libssh2_knownhost_readfile(ssh->kh, + data->set.str[STRING_SSH_KNOWNHOSTS], + LIBSSH2_KNOWNHOST_FILE_OPENSSH); + if(rc < 0) + infof(data, "Failed to read known hosts from %s\n", + data->set.str[STRING_SSH_KNOWNHOSTS]); + } +#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ + +#ifdef CURL_LIBSSH2_DEBUG + libssh2_trace(ssh->ssh_session, ~0); + infof(data, "SSH socket: %d\n", (int)sock); +#endif /* CURL_LIBSSH2_DEBUG */ + + state(conn, SSH_INIT); + + if(data->state.used_interface == Curl_if_multi) + result = ssh_multi_statemach(conn, done); + else { + result = ssh_easy_statemach(conn, TRUE); + if(!result) + *done = TRUE; + } + + return result; +} + +/* + *********************************************************************** + * + * scp_perform() + * + * This is the actual DO function for SCP. Get a file according to + * the options previously setup. + */ + +static +CURLcode scp_perform(struct connectdata *conn, + bool *connected, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + + DEBUGF(infof(conn->data, "DO phase starts\n")); + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + state(conn, SSH_SCP_TRANS_INIT); + + /* run the state-machine */ + if(conn->data->state.used_interface == Curl_if_multi) { + result = ssh_multi_statemach(conn, dophase_done); + } + else { + result = ssh_easy_statemach(conn, FALSE); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect[FIRSTSOCKET]; + + if(*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + + return result; +} + +/* called from curl_multi.c while DOing */ +static CURLcode scp_doing(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result; + result = ssh_multi_statemach(conn, dophase_done); + + if(*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + return result; +} + +/* + * The DO function is generic for both protocols. There was previously two + * separate ones but this way means less duplicated code. + */ + +static CURLcode ssh_do(struct connectdata *conn, bool *done) +{ + CURLcode res; + bool connected = 0; + struct SessionHandle *data = conn->data; + + *done = FALSE; /* default to false */ + + /* + Since connections can be re-used between SessionHandles, this might be a + connection already existing but on a fresh SessionHandle struct so we must + make sure we have a good 'struct SSHPROTO' to play with. For new + connections, the struct SSHPROTO is allocated and setup in the + ssh_connect() function. + */ + Curl_reset_reqproto(conn); + res = ssh_init(conn); + if(res) + return res; + + data->req.size = -1; /* make sure this is unknown at this point */ + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + Curl_pgrsSetUploadSize(data, 0); + Curl_pgrsSetDownloadSize(data, 0); + + if(conn->handler->protocol & CURLPROTO_SCP) + res = scp_perform(conn, &connected, done); + else + res = sftp_perform(conn, &connected, done); + + return res; +} + +/* BLOCKING, but the function is using the state machine so the only reason + this is still blocking is that the multi interface code has no support for + disconnecting operations that takes a while */ +static CURLcode scp_disconnect(struct connectdata *conn, bool dead_connection) +{ + CURLcode result = CURLE_OK; + struct ssh_conn *ssh = &conn->proto.sshc; + (void) dead_connection; + + Curl_safefree(conn->data->state.proto.ssh); + + if(ssh->ssh_session) { + /* only if there's a session still around to use! */ + + state(conn, SSH_SESSION_DISCONNECT); + + result = ssh_easy_statemach(conn, FALSE); + } + + return result; +} + +/* generic done function for both SCP and SFTP called from their specific + done functions */ +static CURLcode ssh_done(struct connectdata *conn, CURLcode status) +{ + CURLcode result = CURLE_OK; + struct SSHPROTO *sftp_scp = conn->data->state.proto.ssh; + + if(status == CURLE_OK) { + /* run the state-machine + + TODO: when the multi interface is used, this _really_ should be using + the ssh_multi_statemach function but we have no general support for + non-blocking DONE operations, not in the multi state machine and with + Curl_done() invokes on several places in the code! + */ + result = ssh_easy_statemach(conn, FALSE); + } + else + result = status; + + if(sftp_scp) + Curl_safefree(sftp_scp->path); + if(Curl_pgrsDone(conn)) + return CURLE_ABORTED_BY_CALLBACK; + + conn->data->req.keepon = 0; /* clear all bits */ + return result; +} + + +static CURLcode scp_done(struct connectdata *conn, CURLcode status, + bool premature) +{ + (void)premature; /* not used */ + + if(status == CURLE_OK) + state(conn, SSH_SCP_DONE); + + return ssh_done(conn, status); + +} + +/* return number of received (decrypted) bytes */ +static ssize_t scp_send(struct connectdata *conn, int sockindex, + const void *mem, size_t len, CURLcode *err) +{ + ssize_t nwrite; + (void)sockindex; /* we only support SCP on the fixed known primary socket */ + + /* libssh2_channel_write() returns int! */ + nwrite = (ssize_t) + libssh2_channel_write(conn->proto.sshc.ssh_channel, mem, len); + + ssh_block2waitfor(conn, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + + if(nwrite == LIBSSH2_ERROR_EAGAIN) { + *err = CURLE_AGAIN; + nwrite = 0; + } + else if(nwrite < LIBSSH2_ERROR_NONE) { + *err = libssh2_session_error_to_CURLE((int)nwrite); + nwrite = -1; + } + + return nwrite; +} + +/* + * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return + * a regular CURLcode value. + */ +static ssize_t scp_recv(struct connectdata *conn, int sockindex, + char *mem, size_t len, CURLcode *err) +{ + ssize_t nread; + (void)sockindex; /* we only support SCP on the fixed known primary socket */ + + /* libssh2_channel_read() returns int */ + nread = (ssize_t) + libssh2_channel_read(conn->proto.sshc.ssh_channel, mem, len); + + ssh_block2waitfor(conn, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + if(nread == LIBSSH2_ERROR_EAGAIN) { + *err = CURLE_AGAIN; + nread = -1; + } + + return nread; +} + +/* + * =============== SFTP =============== + */ + +/* + *********************************************************************** + * + * sftp_perform() + * + * This is the actual DO function for SFTP. Get a file/directory according to + * the options previously setup. + */ + +static +CURLcode sftp_perform(struct connectdata *conn, + bool *connected, + bool *dophase_done) +{ + CURLcode result = CURLE_OK; + + DEBUGF(infof(conn->data, "DO phase starts\n")); + + *dophase_done = FALSE; /* not done yet */ + + /* start the first command in the DO phase */ + state(conn, SSH_SFTP_QUOTE_INIT); + + /* run the state-machine */ + if(conn->data->state.used_interface == Curl_if_multi) { + result = ssh_multi_statemach(conn, dophase_done); + } + else { + result = ssh_easy_statemach(conn, FALSE); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + *connected = conn->bits.tcpconnect[FIRSTSOCKET]; + + if(*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + + return result; +} + +/* called from curl_multi.c while DOing */ +static CURLcode sftp_doing(struct connectdata *conn, + bool *dophase_done) +{ + CURLcode result; + result = ssh_multi_statemach(conn, dophase_done); + + if(*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + return result; +} + +/* BLOCKING, but the function is using the state machine so the only reason + this is still blocking is that the multi interface code has no support for + disconnecting operations that takes a while */ +static CURLcode sftp_disconnect(struct connectdata *conn, bool dead_connection) +{ + CURLcode result = CURLE_OK; + (void) dead_connection; + + DEBUGF(infof(conn->data, "SSH DISCONNECT starts now\n")); + + Curl_safefree(conn->data->state.proto.ssh); + + if(conn->proto.sshc.ssh_session) { + /* only if there's a session still around to use! */ + state(conn, SSH_SFTP_SHUTDOWN); + result = ssh_easy_statemach(conn, FALSE); + } + + DEBUGF(infof(conn->data, "SSH DISCONNECT is done\n")); + + return result; + +} + +static CURLcode sftp_done(struct connectdata *conn, CURLcode status, + bool premature) +{ + struct ssh_conn *sshc = &conn->proto.sshc; + + if(status == CURLE_OK) { + /* Post quote commands are executed after the SFTP_CLOSE state to avoid + errors that could happen due to open file handles during POSTQUOTE + operation */ + if(!status && !premature && conn->data->set.postquote) { + sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; + state(conn, SSH_SFTP_CLOSE); + } + else + state(conn, SSH_SFTP_CLOSE); + } + return ssh_done(conn, status); +} + +/* return number of sent bytes */ +static ssize_t sftp_send(struct connectdata *conn, int sockindex, + const void *mem, size_t len, CURLcode *err) +{ + ssize_t nwrite; /* libssh2_sftp_write() used to return size_t in 0.14 + but is changed to ssize_t in 0.15. These days we don't + support libssh2 0.15*/ + (void)sockindex; + + nwrite = libssh2_sftp_write(conn->proto.sshc.sftp_handle, mem, len); + + ssh_block2waitfor(conn, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + + if(nwrite == LIBSSH2_ERROR_EAGAIN) { + *err = CURLE_AGAIN; + nwrite = 0; + } + else if(nwrite < LIBSSH2_ERROR_NONE) { + *err = libssh2_session_error_to_CURLE((int)nwrite); + nwrite = -1; + } + + return nwrite; +} + +/* + * Return number of received (decrypted) bytes + */ +static ssize_t sftp_recv(struct connectdata *conn, int sockindex, + char *mem, size_t len, CURLcode *err) +{ + ssize_t nread; + (void)sockindex; + + nread = libssh2_sftp_read(conn->proto.sshc.sftp_handle, mem, len); + + ssh_block2waitfor(conn, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); + + if(nread == LIBSSH2_ERROR_EAGAIN) { + *err = CURLE_AGAIN; + nread = -1; + } + return nread; +} + +/* The get_pathname() function is being borrowed from OpenSSH-sftp.c + version 4.6p1. */ +/* + * Copyright (c) 2001-2004 Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +static CURLcode +get_pathname(const char **cpp, char **path) +{ + const char *cp = *cpp, *end; + char quot; + unsigned int i, j; + static const char WHITESPACE[] = " \t\r\n"; + + cp += strspn(cp, WHITESPACE); + if(!*cp) { + *cpp = cp; + *path = NULL; + return CURLE_QUOTE_ERROR; + } + + *path = malloc(strlen(cp) + 1); + if(*path == NULL) + return CURLE_OUT_OF_MEMORY; + + /* Check for quoted filenames */ + if(*cp == '\"' || *cp == '\'') { + quot = *cp++; + + /* Search for terminating quote, unescape some chars */ + for(i = j = 0; i <= strlen(cp); i++) { + if(cp[i] == quot) { /* Found quote */ + i++; + (*path)[j] = '\0'; + break; + } + if(cp[i] == '\0') { /* End of string */ + /*error("Unterminated quote");*/ + goto fail; + } + if(cp[i] == '\\') { /* Escaped characters */ + i++; + if(cp[i] != '\'' && cp[i] != '\"' && + cp[i] != '\\') { + /*error("Bad escaped character '\\%c'", + cp[i]);*/ + goto fail; + } + } + (*path)[j++] = cp[i]; + } + + if(j == 0) { + /*error("Empty quotes");*/ + goto fail; + } + *cpp = cp + i + strspn(cp + i, WHITESPACE); + } + else { + /* Read to end of filename */ + end = strpbrk(cp, WHITESPACE); + if(end == NULL) + end = strchr(cp, '\0'); + *cpp = end + strspn(end, WHITESPACE); + + memcpy(*path, cp, end - cp); + (*path)[end - cp] = '\0'; + } + return CURLE_OK; + + fail: + Curl_safefree(*path); + return CURLE_QUOTE_ERROR; +} + + +static const char *sftp_libssh2_strerror(int err) +{ + switch (err) { + case LIBSSH2_FX_NO_SUCH_FILE: + return "No such file or directory"; + + case LIBSSH2_FX_PERMISSION_DENIED: + return "Permission denied"; + + case LIBSSH2_FX_FAILURE: + return "Operation failed"; + + case LIBSSH2_FX_BAD_MESSAGE: + return "Bad message from SFTP server"; + + case LIBSSH2_FX_NO_CONNECTION: + return "Not connected to SFTP server"; + + case LIBSSH2_FX_CONNECTION_LOST: + return "Connection to SFTP server lost"; + + case LIBSSH2_FX_OP_UNSUPPORTED: + return "Operation not supported by SFTP server"; + + case LIBSSH2_FX_INVALID_HANDLE: + return "Invalid handle"; + + case LIBSSH2_FX_NO_SUCH_PATH: + return "No such file or directory"; + + case LIBSSH2_FX_FILE_ALREADY_EXISTS: + return "File already exists"; + + case LIBSSH2_FX_WRITE_PROTECT: + return "File is write protected"; + + case LIBSSH2_FX_NO_MEDIA: + return "No media"; + + case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: + return "Disk full"; + + case LIBSSH2_FX_QUOTA_EXCEEDED: + return "User quota exceeded"; + + case LIBSSH2_FX_UNKNOWN_PRINCIPLE: + return "Unknown principle"; + + case LIBSSH2_FX_LOCK_CONFlICT: + return "File lock conflict"; + + case LIBSSH2_FX_DIR_NOT_EMPTY: + return "Directory not empty"; + + case LIBSSH2_FX_NOT_A_DIRECTORY: + return "Not a directory"; + + case LIBSSH2_FX_INVALID_FILENAME: + return "Invalid filename"; + + case LIBSSH2_FX_LINK_LOOP: + return "Link points to itself"; + } + return "Unknown error in libssh2"; +} + +#endif /* USE_LIBSSH2 */ diff --git a/lib/curl_sslgen.c b/lib/curl_sslgen.c new file mode 100644 index 000000000..d85ba8ae6 --- /dev/null +++ b/lib/curl_sslgen.c @@ -0,0 +1,541 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +/* This file is for implementing all "generic" SSL functions that all libcurl + internals should use. It is then responsible for calling the proper + "backend" function. + + SSL-functions in libcurl should call functions in this source file, and not + to any specific SSL-layer. + + Curl_ssl_ - prefix for generic ones + Curl_ossl_ - prefix for OpenSSL ones + Curl_gtls_ - prefix for GnuTLS ones + Curl_nss_ - prefix for NSS ones + Curl_polarssl_ - prefix for PolarSSL ones + Curl_cyassl_ - prefix for CyaSSL ones + Curl_schannel_ - prefix for Schannel SSPI ones + Curl_darwinssl_ - prefix for SecureTransport (Darwin) ones + + Note that this source code uses curlssl_* functions, and they are all + defines/macros #defined by the lib-specific header files. + + "SSL/TLS Strong Encryption: An Introduction" + http://httpd.apache.org/docs-2.0/ssl/ssl_intro.html +*/ + +#include "curl_setup.h" + +#include "curl_urldata.h" +#define SSLGEN_C +#include "curl_sslgen.h" /* generic SSL protos etc */ +#include "curl_ssluse.h" /* OpenSSL versions */ +#include "curl_gtls.h" /* GnuTLS versions */ +#include "curl_nssg.h" /* NSS versions */ +#include "curl_qssl.h" /* QSOSSL versions */ +#include "curl_polarssl.h" /* PolarSSL versions */ +#include "curl_axtls.h" /* axTLS versions */ +#include "curl_cyassl.h" /* CyaSSL versions */ +#include "curl_schannel.h" /* Schannel SSPI version */ +#include "curl_darwinssl.h" /* SecureTransport (Darwin) version */ +#include "curl_sendf.h" +#include "curl_rawstr.h" +#include "curl_url.h" +#include "curl_memory.h" +#include "curl_progress.h" +#include "curl_share.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* convenience macro to check if this handle is using a shared SSL session */ +#define SSLSESSION_SHARED(data) (data->share && \ + (data->share->specifier & \ + (1<version == needle->version) && + (data->verifypeer == needle->verifypeer) && + (data->verifyhost == needle->verifyhost) && + safe_strequal(data->CApath, needle->CApath) && + safe_strequal(data->CAfile, needle->CAfile) && + safe_strequal(data->random_file, needle->random_file) && + safe_strequal(data->egdsocket, needle->egdsocket) && + safe_strequal(data->cipher_list, needle->cipher_list)) + return TRUE; + + return FALSE; +} + +bool +Curl_clone_ssl_config(struct ssl_config_data *source, + struct ssl_config_data *dest) +{ + dest->sessionid = source->sessionid; + dest->verifyhost = source->verifyhost; + dest->verifypeer = source->verifypeer; + dest->version = source->version; + + if(source->CAfile) { + dest->CAfile = strdup(source->CAfile); + if(!dest->CAfile) + return FALSE; + } + else + dest->CAfile = NULL; + + if(source->CApath) { + dest->CApath = strdup(source->CApath); + if(!dest->CApath) + return FALSE; + } + else + dest->CApath = NULL; + + if(source->cipher_list) { + dest->cipher_list = strdup(source->cipher_list); + if(!dest->cipher_list) + return FALSE; + } + else + dest->cipher_list = NULL; + + if(source->egdsocket) { + dest->egdsocket = strdup(source->egdsocket); + if(!dest->egdsocket) + return FALSE; + } + else + dest->egdsocket = NULL; + + if(source->random_file) { + dest->random_file = strdup(source->random_file); + if(!dest->random_file) + return FALSE; + } + else + dest->random_file = NULL; + + return TRUE; +} + +void Curl_free_ssl_config(struct ssl_config_data* sslc) +{ + Curl_safefree(sslc->CAfile); + Curl_safefree(sslc->CApath); + Curl_safefree(sslc->cipher_list); + Curl_safefree(sslc->egdsocket); + Curl_safefree(sslc->random_file); +} + +#ifdef USE_SSL + +/* "global" init done? */ +static bool init_ssl=FALSE; + +/** + * Global SSL init + * + * @retval 0 error initializing SSL + * @retval 1 SSL initialized successfully + */ +int Curl_ssl_init(void) +{ + /* make sure this is only done once */ + if(init_ssl) + return 1; + init_ssl = TRUE; /* never again */ + + return curlssl_init(); +} + + +/* Global cleanup */ +void Curl_ssl_cleanup(void) +{ + if(init_ssl) { + /* only cleanup if we did a previous init */ + curlssl_cleanup(); + init_ssl = FALSE; + } +} + +CURLcode +Curl_ssl_connect(struct connectdata *conn, int sockindex) +{ + CURLcode res; + /* mark this is being ssl-enabled from here on. */ + conn->ssl[sockindex].use = TRUE; + conn->ssl[sockindex].state = ssl_connection_negotiating; + + res = curlssl_connect(conn, sockindex); + + if(!res) + Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSL is connected */ + + return res; +} + +CURLcode +Curl_ssl_connect_nonblocking(struct connectdata *conn, int sockindex, + bool *done) +{ + CURLcode res; + /* mark this is being ssl requested from here on. */ + conn->ssl[sockindex].use = TRUE; +#ifdef curlssl_connect_nonblocking + res = curlssl_connect_nonblocking(conn, sockindex, done); +#else + *done = TRUE; /* fallback to BLOCKING */ + res = curlssl_connect(conn, sockindex); +#endif /* non-blocking connect support */ + if(!res && *done) + Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSL is connected */ + return res; +} + +/* + * Check if there's a session ID for the given connection in the cache, and if + * there's one suitable, it is provided. Returns TRUE when no entry matched. + */ +int Curl_ssl_getsessionid(struct connectdata *conn, + void **ssl_sessionid, + size_t *idsize) /* set 0 if unknown */ +{ + struct curl_ssl_session *check; + struct SessionHandle *data = conn->data; + size_t i; + long *general_age; + bool no_match = TRUE; + + *ssl_sessionid = NULL; + + if(!conn->ssl_config.sessionid) + /* session ID re-use is disabled */ + return TRUE; + + /* Lock if shared */ + if(SSLSESSION_SHARED(data)) { + Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); + general_age = &data->share->sessionage; + } + else + general_age = &data->state.sessionage; + + for(i = 0; i < data->set.ssl.max_ssl_sessions; i++) { + check = &data->state.session[i]; + if(!check->sessionid) + /* not session ID means blank entry */ + continue; + if(Curl_raw_equal(conn->host.name, check->name) && + (conn->remote_port == check->remote_port) && + Curl_ssl_config_matches(&conn->ssl_config, &check->ssl_config)) { + /* yes, we have a session ID! */ + (*general_age)++; /* increase general age */ + check->age = *general_age; /* set this as used in this age */ + *ssl_sessionid = check->sessionid; + if(idsize) + *idsize = check->idsize; + no_match = FALSE; + break; + } + } + + /* Unlock */ + if(SSLSESSION_SHARED(data)) + Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); + + return no_match; +} + +/* + * Kill a single session ID entry in the cache. + */ +void Curl_ssl_kill_session(struct curl_ssl_session *session) +{ + if(session->sessionid) { + /* defensive check */ + + /* free the ID the SSL-layer specific way */ + curlssl_session_free(session->sessionid); + + session->sessionid = NULL; + session->age = 0; /* fresh */ + + Curl_free_ssl_config(&session->ssl_config); + + Curl_safefree(session->name); + } +} + +/* + * Delete the given session ID from the cache. + */ +void Curl_ssl_delsessionid(struct connectdata *conn, void *ssl_sessionid) +{ + size_t i; + struct SessionHandle *data=conn->data; + + if(SSLSESSION_SHARED(data)) + Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); + + for(i = 0; i < data->set.ssl.max_ssl_sessions; i++) { + struct curl_ssl_session *check = &data->state.session[i]; + + if(check->sessionid == ssl_sessionid) { + Curl_ssl_kill_session(check); + break; + } + } + + if(SSLSESSION_SHARED(data)) + Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); +} + +/* + * Store session id in the session cache. The ID passed on to this function + * must already have been extracted and allocated the proper way for the SSL + * layer. Curl_XXXX_session_free() will be called to free/kill the session ID + * later on. + */ +CURLcode Curl_ssl_addsessionid(struct connectdata *conn, + void *ssl_sessionid, + size_t idsize) +{ + size_t i; + struct SessionHandle *data=conn->data; /* the mother of all structs */ + struct curl_ssl_session *store = &data->state.session[0]; + long oldest_age=data->state.session[0].age; /* zero if unused */ + char *clone_host; + long *general_age; + + /* Even though session ID re-use might be disabled, that only disables USING + IT. We still store it here in case the re-using is again enabled for an + upcoming transfer */ + + clone_host = strdup(conn->host.name); + if(!clone_host) + return CURLE_OUT_OF_MEMORY; /* bail out */ + + /* Now we should add the session ID and the host name to the cache, (remove + the oldest if necessary) */ + + /* If using shared SSL session, lock! */ + if(SSLSESSION_SHARED(data)) { + Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); + general_age = &data->share->sessionage; + } + else { + general_age = &data->state.sessionage; + } + + /* find an empty slot for us, or find the oldest */ + for(i = 1; (i < data->set.ssl.max_ssl_sessions) && + data->state.session[i].sessionid; i++) { + if(data->state.session[i].age < oldest_age) { + oldest_age = data->state.session[i].age; + store = &data->state.session[i]; + } + } + if(i == data->set.ssl.max_ssl_sessions) + /* cache is full, we must "kill" the oldest entry! */ + Curl_ssl_kill_session(store); + else + store = &data->state.session[i]; /* use this slot */ + + /* now init the session struct wisely */ + store->sessionid = ssl_sessionid; + store->idsize = idsize; + store->age = *general_age; /* set current age */ + if(store->name) + /* free it if there's one already present */ + free(store->name); + store->name = clone_host; /* clone host name */ + store->remote_port = conn->remote_port; /* port number */ + + + /* Unlock */ + if(SSLSESSION_SHARED(data)) + Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); + + if(!Curl_clone_ssl_config(&conn->ssl_config, &store->ssl_config)) { + store->sessionid = NULL; /* let caller free sessionid */ + free(clone_host); + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + + +void Curl_ssl_close_all(struct SessionHandle *data) +{ + size_t i; + /* kill the session ID cache if not shared */ + if(data->state.session && !SSLSESSION_SHARED(data)) { + for(i = 0; i < data->set.ssl.max_ssl_sessions; i++) + /* the single-killer function handles empty table slots */ + Curl_ssl_kill_session(&data->state.session[i]); + + /* free the cache data */ + Curl_safefree(data->state.session); + } + + curlssl_close_all(data); +} + +void Curl_ssl_close(struct connectdata *conn, int sockindex) +{ + DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); + curlssl_close(conn, sockindex); +} + +CURLcode Curl_ssl_shutdown(struct connectdata *conn, int sockindex) +{ + if(curlssl_shutdown(conn, sockindex)) + return CURLE_SSL_SHUTDOWN_FAILED; + + conn->ssl[sockindex].use = FALSE; /* get back to ordinary socket usage */ + conn->ssl[sockindex].state = ssl_connection_none; + + conn->recv[sockindex] = Curl_recv_plain; + conn->send[sockindex] = Curl_send_plain; + + return CURLE_OK; +} + +/* Selects an SSL crypto engine + */ +CURLcode Curl_ssl_set_engine(struct SessionHandle *data, const char *engine) +{ + return curlssl_set_engine(data, engine); +} + +/* Selects the default SSL crypto engine + */ +CURLcode Curl_ssl_set_engine_default(struct SessionHandle *data) +{ + return curlssl_set_engine_default(data); +} + +/* Return list of OpenSSL crypto engine names. */ +struct curl_slist *Curl_ssl_engines_list(struct SessionHandle *data) +{ + return curlssl_engines_list(data); +} + +/* + * This sets up a session ID cache to the specified size. Make sure this code + * is agnostic to what underlying SSL technology we use. + */ +CURLcode Curl_ssl_initsessions(struct SessionHandle *data, size_t amount) +{ + struct curl_ssl_session *session; + + if(data->state.session) + /* this is just a precaution to prevent multiple inits */ + return CURLE_OK; + + session = calloc(amount, sizeof(struct curl_ssl_session)); + if(!session) + return CURLE_OUT_OF_MEMORY; + + /* store the info in the SSL section */ + data->set.ssl.max_ssl_sessions = amount; + data->state.session = session; + data->state.sessionage = 1; /* this is brand new */ + return CURLE_OK; +} + +size_t Curl_ssl_version(char *buffer, size_t size) +{ + return curlssl_version(buffer, size); +} + +/* + * This function tries to determine connection status. + * + * Return codes: + * 1 means the connection is still in place + * 0 means the connection has been closed + * -1 means the connection status is unknown + */ +int Curl_ssl_check_cxn(struct connectdata *conn) +{ + return curlssl_check_cxn(conn); +} + +bool Curl_ssl_data_pending(const struct connectdata *conn, + int connindex) +{ + return curlssl_data_pending(conn, connindex); +} + +void Curl_ssl_free_certinfo(struct SessionHandle *data) +{ + int i; + struct curl_certinfo *ci = &data->info.certs; + if(ci->num_of_certs) { + /* free all individual lists used */ + for(i=0; inum_of_certs; i++) { + curl_slist_free_all(ci->certinfo[i]); + ci->certinfo[i] = NULL; + } + free(ci->certinfo); /* free the actual array too */ + ci->certinfo = NULL; + ci->num_of_certs = 0; + } +} + +#if defined(USE_SSLEAY) || defined(USE_GNUTLS) || defined(USE_NSS) || \ + defined(USE_DARWINSSL) +/* these functions are only used by some SSL backends */ + +void Curl_ssl_random(struct SessionHandle *data, + unsigned char *entropy, + size_t length) +{ + curlssl_random(data, entropy, length); +} + +void Curl_ssl_md5sum(unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *md5sum, /* output */ + size_t md5len) +{ + curlssl_md5sum(tmp, tmplen, md5sum, md5len); +} +#endif /* USE_SSLEAY || USE_GNUTLS || USE_NSS || USE_DARWINSSL */ + +#endif /* USE_SSL */ diff --git a/lib/curl_ssluse.c b/lib/curl_ssluse.c new file mode 100644 index 000000000..0809d4614 --- /dev/null +++ b/lib/curl_ssluse.c @@ -0,0 +1,2736 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +/* + * Source file for all OpenSSL-specific code for the TLS/SSL layer. No code + * but curl_sslgen.c should ever call or use these functions. + */ + +/* + * The original SSLeay-using code for curl was written by Linas Vepstas and + * Sampo Kellomaki 1998. + */ + +#include "curl_setup.h" + +#ifdef HAVE_LIMITS_H +#include +#endif + +#include "curl_urldata.h" +#include "curl_sendf.h" +#include "curl_formdata.h" /* for the boundary function */ +#include "curl_url.h" /* for the ssl config check function */ +#include "curl_inet_pton.h" +#include "curl_ssluse.h" +#include "curl_connect.h" +#include "curl_strequal.h" +#include "curl_select.h" +#include "curl_sslgen.h" +#include "curl_rawstr.h" +#include "curl_hostcheck.h" + +#define _MPRINTF_REPLACE /* use the internal *printf() functions */ +#include + +#ifdef USE_SSLEAY + +#ifdef USE_OPENSSL +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#endif + +#include "curl_warnless.h" +#include "curl_memory.h" +#include "curl_non_ascii.h" /* for Curl_convert_from_utf8 prototype */ + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#ifndef OPENSSL_VERSION_NUMBER +#error "OPENSSL_VERSION_NUMBER not defined" +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x0090581fL +#define HAVE_SSL_GET1_SESSION 1 +#else +#undef HAVE_SSL_GET1_SESSION +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x00904100L +#define HAVE_USERDATA_IN_PWD_CALLBACK 1 +#else +#undef HAVE_USERDATA_IN_PWD_CALLBACK +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x00907001L +/* ENGINE_load_private_key() takes four arguments */ +#define HAVE_ENGINE_LOAD_FOUR_ARGS +#include +#else +/* ENGINE_load_private_key() takes three arguments */ +#undef HAVE_ENGINE_LOAD_FOUR_ARGS +#endif + +#if (OPENSSL_VERSION_NUMBER >= 0x00903001L) && defined(HAVE_OPENSSL_PKCS12_H) +/* OpenSSL has PKCS 12 support */ +#define HAVE_PKCS12_SUPPORT +#else +/* OpenSSL/SSLEay does not have PKCS12 support */ +#undef HAVE_PKCS12_SUPPORT +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x00906001L +#define HAVE_ERR_ERROR_STRING_N 1 +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x00909000L +#define SSL_METHOD_QUAL const +#else +#define SSL_METHOD_QUAL +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x00907000L +/* 0.9.6 didn't have X509_STORE_set_flags() */ +#define HAVE_X509_STORE_SET_FLAGS 1 +#else +#define X509_STORE_set_flags(x,y) Curl_nop_stmt +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +#define HAVE_ERR_REMOVE_THREAD_STATE 1 +#endif + +#ifndef HAVE_SSLV2_CLIENT_METHOD +#undef OPENSSL_NO_SSL2 /* undef first to avoid compiler warnings */ +#define OPENSSL_NO_SSL2 +#endif + +/* + * Number of bytes to read from the random number seed file. This must be + * a finite value (because some entropy "files" like /dev/urandom have + * an infinite length), but must be large enough to provide enough + * entopy to properly seed OpenSSL's PRNG. + */ +#define RAND_LOAD_LENGTH 1024 + +#ifndef HAVE_USERDATA_IN_PWD_CALLBACK +static char global_passwd[64]; +#endif + +static int passwd_callback(char *buf, int num, int encrypting +#ifdef HAVE_USERDATA_IN_PWD_CALLBACK + /* This was introduced in 0.9.4, we can set this + using SSL_CTX_set_default_passwd_cb_userdata() + */ + , void *global_passwd +#endif + ) +{ + DEBUGASSERT(0 == encrypting); + + if(!encrypting) { + int klen = curlx_uztosi(strlen((char *)global_passwd)); + if(num > klen) { + memcpy(buf, global_passwd, klen+1); + return klen; + } + } + return 0; +} + +/* + * rand_enough() is a function that returns TRUE if we have seeded the random + * engine properly. We use some preprocessor magic to provide a seed_enough() + * macro to use, just to prevent a compiler warning on this function if we + * pass in an argument that is never used. + */ + +#ifdef HAVE_RAND_STATUS +#define seed_enough(x) rand_enough() +static bool rand_enough(void) +{ + return (0 != RAND_status()) ? TRUE : FALSE; +} +#else +#define seed_enough(x) rand_enough(x) +static bool rand_enough(int nread) +{ + /* this is a very silly decision to make */ + return (nread > 500) ? TRUE : FALSE; +} +#endif + +static int ossl_seed(struct SessionHandle *data) +{ + char *buf = data->state.buffer; /* point to the big buffer */ + int nread=0; + + /* Q: should we add support for a random file name as a libcurl option? + A: Yes, it is here */ + +#ifndef RANDOM_FILE + /* if RANDOM_FILE isn't defined, we only perform this if an option tells + us to! */ + if(data->set.ssl.random_file) +#define RANDOM_FILE "" /* doesn't matter won't be used */ +#endif + { + /* let the option override the define */ + nread += RAND_load_file((data->set.str[STRING_SSL_RANDOM_FILE]? + data->set.str[STRING_SSL_RANDOM_FILE]: + RANDOM_FILE), + RAND_LOAD_LENGTH); + if(seed_enough(nread)) + return nread; + } + +#if defined(HAVE_RAND_EGD) + /* only available in OpenSSL 0.9.5 and later */ + /* EGD_SOCKET is set at configure time or not at all */ +#ifndef EGD_SOCKET + /* If we don't have the define set, we only do this if the egd-option + is set */ + if(data->set.str[STRING_SSL_EGDSOCKET]) +#define EGD_SOCKET "" /* doesn't matter won't be used */ +#endif + { + /* If there's an option and a define, the option overrides the + define */ + int ret = RAND_egd(data->set.str[STRING_SSL_EGDSOCKET]? + data->set.str[STRING_SSL_EGDSOCKET]:EGD_SOCKET); + if(-1 != ret) { + nread += ret; + if(seed_enough(nread)) + return nread; + } + } +#endif + + /* If we get here, it means we need to seed the PRNG using a "silly" + approach! */ + { + int len; + char *area; + + /* Changed call to RAND_seed to use the underlying RAND_add implementation + * directly. Do this in a loop, with the amount of additional entropy + * being dependent upon the algorithm used by Curl_FormBoundary(): N bytes + * of a 7-bit ascii set. -- Richard Gorton, March 11 2003. + */ + + do { + area = Curl_FormBoundary(); + if(!area) + return 3; /* out of memory */ + + len = curlx_uztosi(strlen(area)); + RAND_add(area, len, (len >> 1)); + + free(area); /* now remove the random junk */ + } while(!RAND_status()); + } + + /* generates a default path for the random seed file */ + buf[0]=0; /* blank it first */ + RAND_file_name(buf, BUFSIZE); + if(buf[0]) { + /* we got a file name to try */ + nread += RAND_load_file(buf, RAND_LOAD_LENGTH); + if(seed_enough(nread)) + return nread; + } + + infof(data, "libcurl is now using a weak random seed!\n"); + return nread; +} + +int Curl_ossl_seed(struct SessionHandle *data) +{ + /* we have the "SSL is seeded" boolean static to prevent multiple + time-consuming seedings in vain */ + static bool ssl_seeded = FALSE; + + if(!ssl_seeded || data->set.str[STRING_SSL_RANDOM_FILE] || + data->set.str[STRING_SSL_EGDSOCKET]) { + ossl_seed(data); + ssl_seeded = TRUE; + } + return 0; +} + + +#ifndef SSL_FILETYPE_ENGINE +#define SSL_FILETYPE_ENGINE 42 +#endif +#ifndef SSL_FILETYPE_PKCS12 +#define SSL_FILETYPE_PKCS12 43 +#endif +static int do_file_type(const char *type) +{ + if(!type || !type[0]) + return SSL_FILETYPE_PEM; + if(Curl_raw_equal(type, "PEM")) + return SSL_FILETYPE_PEM; + if(Curl_raw_equal(type, "DER")) + return SSL_FILETYPE_ASN1; + if(Curl_raw_equal(type, "ENG")) + return SSL_FILETYPE_ENGINE; + if(Curl_raw_equal(type, "P12")) + return SSL_FILETYPE_PKCS12; + return -1; +} + +static +int cert_stuff(struct connectdata *conn, + SSL_CTX* ctx, + char *cert_file, + const char *cert_type, + char *key_file, + const char *key_type) +{ + struct SessionHandle *data = conn->data; + + int file_type = do_file_type(cert_type); + + if(cert_file != NULL || file_type == SSL_FILETYPE_ENGINE) { + SSL *ssl; + X509 *x509; + int cert_done = 0; + + if(data->set.str[STRING_KEY_PASSWD]) { +#ifndef HAVE_USERDATA_IN_PWD_CALLBACK + /* + * If password has been given, we store that in the global + * area (*shudder*) for a while: + */ + size_t len = strlen(data->set.str[STRING_KEY_PASSWD]); + if(len < sizeof(global_passwd)) + memcpy(global_passwd, data->set.str[STRING_KEY_PASSWD], len+1); + else + global_passwd[0] = '\0'; +#else + /* + * We set the password in the callback userdata + */ + SSL_CTX_set_default_passwd_cb_userdata(ctx, + data->set.str[STRING_KEY_PASSWD]); +#endif + /* Set passwd callback: */ + SSL_CTX_set_default_passwd_cb(ctx, passwd_callback); + } + + +#define SSL_CLIENT_CERT_ERR \ + "unable to use client certificate (no key found or wrong pass phrase?)" + + switch(file_type) { + case SSL_FILETYPE_PEM: + /* SSL_CTX_use_certificate_chain_file() only works on PEM files */ + if(SSL_CTX_use_certificate_chain_file(ctx, + cert_file) != 1) { + failf(data, SSL_CLIENT_CERT_ERR); + return 0; + } + break; + + case SSL_FILETYPE_ASN1: + /* SSL_CTX_use_certificate_file() works with either PEM or ASN1, but + we use the case above for PEM so this can only be performed with + ASN1 files. */ + if(SSL_CTX_use_certificate_file(ctx, + cert_file, + file_type) != 1) { + failf(data, SSL_CLIENT_CERT_ERR); + return 0; + } + break; + case SSL_FILETYPE_ENGINE: +#if defined(HAVE_OPENSSL_ENGINE_H) && defined(ENGINE_CTRL_GET_CMD_FROM_NAME) + { + if(data->state.engine) { + const char *cmd_name = "LOAD_CERT_CTRL"; + struct { + const char *cert_id; + X509 *cert; + } params; + + params.cert_id = cert_file; + params.cert = NULL; + + /* Does the engine supports LOAD_CERT_CTRL ? */ + if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME, + 0, (void *)cmd_name, NULL)) { + failf(data, "ssl engine does not support loading certificates"); + return 0; + } + + /* Load the certificate from the engine */ + if(!ENGINE_ctrl_cmd(data->state.engine, cmd_name, + 0, ¶ms, NULL, 1)) { + failf(data, "ssl engine cannot load client cert with id" + " '%s' [%s]", cert_file, + ERR_error_string(ERR_get_error(), NULL)); + return 0; + } + + if(!params.cert) { + failf(data, "ssl engine didn't initialized the certificate " + "properly."); + return 0; + } + + if(SSL_CTX_use_certificate(ctx, params.cert) != 1) { + failf(data, "unable to set client certificate"); + X509_free(params.cert); + return 0; + } + X509_free(params.cert); /* we don't need the handle any more... */ + } + else { + failf(data, "crypto engine not set, can't load certificate"); + return 0; + } + } + break; +#else + failf(data, "file type ENG for certificate not implemented"); + return 0; +#endif + + case SSL_FILETYPE_PKCS12: + { +#ifdef HAVE_PKCS12_SUPPORT + FILE *f; + PKCS12 *p12; + EVP_PKEY *pri; + STACK_OF(X509) *ca = NULL; + int i; + + f = fopen(cert_file,"rb"); + if(!f) { + failf(data, "could not open PKCS12 file '%s'", cert_file); + return 0; + } + p12 = d2i_PKCS12_fp(f, NULL); + fclose(f); + + if(!p12) { + failf(data, "error reading PKCS12 file '%s'", cert_file ); + return 0; + } + + PKCS12_PBE_add(); + + if(!PKCS12_parse(p12, data->set.str[STRING_KEY_PASSWD], &pri, &x509, + &ca)) { + failf(data, + "could not parse PKCS12 file, check password, OpenSSL error %s", + ERR_error_string(ERR_get_error(), NULL) ); + PKCS12_free(p12); + return 0; + } + + PKCS12_free(p12); + + if(SSL_CTX_use_certificate(ctx, x509) != 1) { + failf(data, SSL_CLIENT_CERT_ERR); + EVP_PKEY_free(pri); + X509_free(x509); + sk_X509_pop_free(ca, X509_free); + return 0; + } + + if(SSL_CTX_use_PrivateKey(ctx, pri) != 1) { + failf(data, "unable to use private key from PKCS12 file '%s'", + cert_file); + EVP_PKEY_free(pri); + X509_free(x509); + sk_X509_pop_free(ca, X509_free); + return 0; + } + + if(!SSL_CTX_check_private_key (ctx)) { + failf(data, "private key from PKCS12 file '%s' " + "does not match certificate in same file", cert_file); + EVP_PKEY_free(pri); + X509_free(x509); + sk_X509_pop_free(ca, X509_free); + return 0; + } + /* Set Certificate Verification chain */ + if(ca && sk_X509_num(ca)) { + for(i = 0; i < sk_X509_num(ca); i++) { + if(!SSL_CTX_add_extra_chain_cert(ctx,sk_X509_value(ca, i))) { + failf(data, "cannot add certificate to certificate chain"); + EVP_PKEY_free(pri); + X509_free(x509); + sk_X509_pop_free(ca, X509_free); + return 0; + } + if(!SSL_CTX_add_client_CA(ctx, sk_X509_value(ca, i))) { + failf(data, "cannot add certificate to client CA list"); + EVP_PKEY_free(pri); + X509_free(x509); + sk_X509_pop_free(ca, X509_free); + return 0; + } + } + } + + EVP_PKEY_free(pri); + X509_free(x509); + sk_X509_pop_free(ca, X509_free); + cert_done = 1; + break; +#else + failf(data, "file type P12 for certificate not supported"); + return 0; +#endif + } + default: + failf(data, "not supported file type '%s' for certificate", cert_type); + return 0; + } + + file_type = do_file_type(key_type); + + switch(file_type) { + case SSL_FILETYPE_PEM: + if(cert_done) + break; + if(key_file == NULL) + /* cert & key can only be in PEM case in the same file */ + key_file=cert_file; + case SSL_FILETYPE_ASN1: + if(SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type) != 1) { + failf(data, "unable to set private key file: '%s' type %s", + key_file, key_type?key_type:"PEM"); + return 0; + } + break; + case SSL_FILETYPE_ENGINE: +#ifdef HAVE_OPENSSL_ENGINE_H + { /* XXXX still needs some work */ + EVP_PKEY *priv_key = NULL; + if(data->state.engine) { +#ifdef HAVE_ENGINE_LOAD_FOUR_ARGS + UI_METHOD *ui_method = UI_OpenSSL(); +#endif + /* the typecast below was added to please mingw32 */ + priv_key = (EVP_PKEY *) + ENGINE_load_private_key(data->state.engine,key_file, +#ifdef HAVE_ENGINE_LOAD_FOUR_ARGS + ui_method, +#endif + data->set.str[STRING_KEY_PASSWD]); + if(!priv_key) { + failf(data, "failed to load private key from crypto engine"); + return 0; + } + if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) { + failf(data, "unable to set private key"); + EVP_PKEY_free(priv_key); + return 0; + } + EVP_PKEY_free(priv_key); /* we don't need the handle any more... */ + } + else { + failf(data, "crypto engine not set, can't load private key"); + return 0; + } + } + break; +#else + failf(data, "file type ENG for private key not supported"); + return 0; +#endif + case SSL_FILETYPE_PKCS12: + if(!cert_done) { + failf(data, "file type P12 for private key not supported"); + return 0; + } + break; + default: + failf(data, "not supported file type for private key"); + return 0; + } + + ssl=SSL_new(ctx); + if(NULL == ssl) { + failf(data,"unable to create an SSL structure"); + return 0; + } + + x509=SSL_get_certificate(ssl); + + /* This version was provided by Evan Jordan and is supposed to not + leak memory as the previous version: */ + if(x509 != NULL) { + EVP_PKEY *pktmp = X509_get_pubkey(x509); + EVP_PKEY_copy_parameters(pktmp,SSL_get_privatekey(ssl)); + EVP_PKEY_free(pktmp); + } + + SSL_free(ssl); + + /* If we are using DSA, we can copy the parameters from + * the private key */ + + + /* Now we know that a key and cert have been set against + * the SSL context */ + if(!SSL_CTX_check_private_key(ctx)) { + failf(data, "Private key does not match the certificate public key"); + return 0; + } +#ifndef HAVE_USERDATA_IN_PWD_CALLBACK + /* erase it now */ + memset(global_passwd, 0, sizeof(global_passwd)); +#endif + } + return 1; +} + +/* returns non-zero on failure */ +static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) +{ +#if 0 + return X509_NAME_oneline(a, buf, size); +#else + BIO *bio_out = BIO_new(BIO_s_mem()); + BUF_MEM *biomem; + int rc; + + if(!bio_out) + return 1; /* alloc failed! */ + + rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); + BIO_get_mem_ptr(bio_out, &biomem); + + if((size_t)biomem->length < size) + size = biomem->length; + else + size--; /* don't overwrite the buffer end */ + + memcpy(buf, biomem->data, size); + buf[size]=0; + + BIO_free(bio_out); + + return !rc; +#endif +} + +static +int cert_verify_callback(int ok, X509_STORE_CTX *ctx) +{ + X509 *err_cert; + char buf[256]; + + err_cert=X509_STORE_CTX_get_current_cert(ctx); + (void)x509_name_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf)); + return ok; +} + +/* Return error string for last OpenSSL error + */ +static char *SSL_strerror(unsigned long error, char *buf, size_t size) +{ +#ifdef HAVE_ERR_ERROR_STRING_N + /* OpenSSL 0.9.6 and later has a function named + ERRO_error_string_n() that takes the size of the buffer as a + third argument */ + ERR_error_string_n(error, buf, size); +#else + (void) size; + ERR_error_string(error, buf); +#endif + return buf; +} + +#endif /* USE_SSLEAY */ + +#ifdef USE_SSLEAY +/** + * Global SSL init + * + * @retval 0 error initializing SSL + * @retval 1 SSL initialized successfully + */ +int Curl_ossl_init(void) +{ +#ifdef HAVE_ENGINE_LOAD_BUILTIN_ENGINES + ENGINE_load_builtin_engines(); +#endif + + /* Lets get nice error messages */ + SSL_load_error_strings(); + + /* Init the global ciphers and digests */ + if(!SSLeay_add_ssl_algorithms()) + return 0; + + OpenSSL_add_all_algorithms(); + + return 1; +} + +#endif /* USE_SSLEAY */ + +#ifdef USE_SSLEAY + +/* Global cleanup */ +void Curl_ossl_cleanup(void) +{ + /* Free ciphers and digests lists */ + EVP_cleanup(); + +#ifdef HAVE_ENGINE_CLEANUP + /* Free engine list */ + ENGINE_cleanup(); +#endif + +#ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA + /* Free OpenSSL ex_data table */ + CRYPTO_cleanup_all_ex_data(); +#endif + + /* Free OpenSSL error strings */ + ERR_free_strings(); + + /* Free thread local error state, destroying hash upon zero refcount */ +#ifdef HAVE_ERR_REMOVE_THREAD_STATE + ERR_remove_thread_state(NULL); +#else + ERR_remove_state(0); +#endif +} + +/* + * This function uses SSL_peek to determine connection status. + * + * Return codes: + * 1 means the connection is still in place + * 0 means the connection has been closed + * -1 means the connection status is unknown + */ +int Curl_ossl_check_cxn(struct connectdata *conn) +{ + int rc; + char buf; + + rc = SSL_peek(conn->ssl[FIRSTSOCKET].handle, (void*)&buf, 1); + if(rc > 0) + return 1; /* connection still in place */ + + if(rc == 0) + return 0; /* connection has been closed */ + + return -1; /* connection status unknown */ +} + +/* Selects an OpenSSL crypto engine + */ +CURLcode Curl_ossl_set_engine(struct SessionHandle *data, const char *engine) +{ +#if defined(USE_SSLEAY) && defined(HAVE_OPENSSL_ENGINE_H) + ENGINE *e; + +#if OPENSSL_VERSION_NUMBER >= 0x00909000L + e = ENGINE_by_id(engine); +#else + /* avoid memory leak */ + for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { + const char *e_id = ENGINE_get_id(e); + if(!strcmp(engine, e_id)) + break; + } +#endif + + if(!e) { + failf(data, "SSL Engine '%s' not found", engine); + return CURLE_SSL_ENGINE_NOTFOUND; + } + + if(data->state.engine) { + ENGINE_finish(data->state.engine); + ENGINE_free(data->state.engine); + data->state.engine = NULL; + } + if(!ENGINE_init(e)) { + char buf[256]; + + ENGINE_free(e); + failf(data, "Failed to initialise SSL Engine '%s':\n%s", + engine, SSL_strerror(ERR_get_error(), buf, sizeof(buf))); + return CURLE_SSL_ENGINE_INITFAILED; + } + data->state.engine = e; + return CURLE_OK; +#else + (void)engine; + failf(data, "SSL Engine not supported"); + return CURLE_SSL_ENGINE_NOTFOUND; +#endif +} + +/* Sets engine as default for all SSL operations + */ +CURLcode Curl_ossl_set_engine_default(struct SessionHandle *data) +{ +#ifdef HAVE_OPENSSL_ENGINE_H + if(data->state.engine) { + if(ENGINE_set_default(data->state.engine, ENGINE_METHOD_ALL) > 0) { + infof(data,"set default crypto engine '%s'\n", + ENGINE_get_id(data->state.engine)); + } + else { + failf(data, "set default crypto engine '%s' failed", + ENGINE_get_id(data->state.engine)); + return CURLE_SSL_ENGINE_SETFAILED; + } + } +#else + (void) data; +#endif + return CURLE_OK; +} + +/* Return list of OpenSSL crypto engine names. + */ +struct curl_slist *Curl_ossl_engines_list(struct SessionHandle *data) +{ + struct curl_slist *list = NULL; +#if defined(USE_SSLEAY) && defined(HAVE_OPENSSL_ENGINE_H) + struct curl_slist *beg; + ENGINE *e; + + for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { + beg = curl_slist_append(list, ENGINE_get_id(e)); + if(!beg) { + curl_slist_free_all(list); + return NULL; + } + list = beg; + } +#endif + (void) data; + return list; +} + + +/* + * This function is called when an SSL connection is closed. + */ +void Curl_ossl_close(struct connectdata *conn, int sockindex) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + if(connssl->handle) { + (void)SSL_shutdown(connssl->handle); + SSL_set_connect_state(connssl->handle); + + SSL_free (connssl->handle); + connssl->handle = NULL; + } + if(connssl->ctx) { + SSL_CTX_free (connssl->ctx); + connssl->ctx = NULL; + } +} + +/* + * This function is called to shut down the SSL layer but keep the + * socket open (CCC - Clear Command Channel) + */ +int Curl_ossl_shutdown(struct connectdata *conn, int sockindex) +{ + int retval = 0; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct SessionHandle *data = conn->data; + char buf[120]; /* We will use this for the OpenSSL error buffer, so it has + to be at least 120 bytes long. */ + unsigned long sslerror; + ssize_t nread; + int buffsize; + int err; + int done = 0; + + /* This has only been tested on the proftpd server, and the mod_tls code + sends a close notify alert without waiting for a close notify alert in + response. Thus we wait for a close notify alert from the server, but + we do not send one. Let's hope other servers do the same... */ + + if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) + (void)SSL_shutdown(connssl->handle); + + if(connssl->handle) { + buffsize = (int)sizeof(buf); + while(!done) { + int what = Curl_socket_ready(conn->sock[sockindex], + CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); + if(what > 0) { + ERR_clear_error(); + + /* Something to read, let's do it and hope that it is the close + notify alert from the server */ + nread = (ssize_t)SSL_read(conn->ssl[sockindex].handle, buf, + buffsize); + err = SSL_get_error(conn->ssl[sockindex].handle, (int)nread); + + switch(err) { + case SSL_ERROR_NONE: /* this is not an error */ + case SSL_ERROR_ZERO_RETURN: /* no more data */ + /* This is the expected response. There was no data but only + the close notify alert */ + done = 1; + break; + case SSL_ERROR_WANT_READ: + /* there's data pending, re-invoke SSL_read() */ + infof(data, "SSL_ERROR_WANT_READ\n"); + break; + case SSL_ERROR_WANT_WRITE: + /* SSL wants a write. Really odd. Let's bail out. */ + infof(data, "SSL_ERROR_WANT_WRITE\n"); + done = 1; + break; + default: + /* openssl/ssl.h says "look at error stack/return value/errno" */ + sslerror = ERR_get_error(); + failf(conn->data, "SSL read: %s, errno %d", + ERR_error_string(sslerror, buf), + SOCKERRNO); + done = 1; + break; + } + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + done = 1; + } + else { + /* anything that gets here is fatally bad */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + retval = -1; + done = 1; + } + } /* while()-loop for the select() */ + + if(data->set.verbose) { +#ifdef HAVE_SSL_GET_SHUTDOWN + switch(SSL_get_shutdown(connssl->handle)) { + case SSL_SENT_SHUTDOWN: + infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN\n"); + break; + case SSL_RECEIVED_SHUTDOWN: + infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN\n"); + break; + case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN: + infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|" + "SSL_RECEIVED__SHUTDOWN\n"); + break; + } +#endif + } + + SSL_free (connssl->handle); + connssl->handle = NULL; + } + return retval; +} + +void Curl_ossl_session_free(void *ptr) +{ + /* free the ID */ + SSL_SESSION_free(ptr); +} + +/* + * This function is called when the 'data' struct is going away. Close + * down everything and free all resources! + */ +int Curl_ossl_close_all(struct SessionHandle *data) +{ +#ifdef HAVE_OPENSSL_ENGINE_H + if(data->state.engine) { + ENGINE_finish(data->state.engine); + ENGINE_free(data->state.engine); + data->state.engine = NULL; + } +#else + (void)data; +#endif + return 0; +} + +static int asn1_output(const ASN1_UTCTIME *tm, + char *buf, + size_t sizeofbuf) +{ + const char *asn1_string; + int gmt=FALSE; + int i; + int year=0,month=0,day=0,hour=0,minute=0,second=0; + + i=tm->length; + asn1_string=(const char *)tm->data; + + if(i < 10) + return 1; + if(asn1_string[i-1] == 'Z') + gmt=TRUE; + for(i=0; i<10; i++) + if((asn1_string[i] > '9') || (asn1_string[i] < '0')) + return 2; + + year= (asn1_string[0]-'0')*10+(asn1_string[1]-'0'); + if(year < 50) + year+=100; + + month= (asn1_string[2]-'0')*10+(asn1_string[3]-'0'); + if((month > 12) || (month < 1)) + return 3; + + day= (asn1_string[4]-'0')*10+(asn1_string[5]-'0'); + hour= (asn1_string[6]-'0')*10+(asn1_string[7]-'0'); + minute= (asn1_string[8]-'0')*10+(asn1_string[9]-'0'); + + if((asn1_string[10] >= '0') && (asn1_string[10] <= '9') && + (asn1_string[11] >= '0') && (asn1_string[11] <= '9')) + second= (asn1_string[10]-'0')*10+(asn1_string[11]-'0'); + + snprintf(buf, sizeofbuf, + "%04d-%02d-%02d %02d:%02d:%02d %s", + year+1900, month, day, hour, minute, second, (gmt?"GMT":"")); + + return 0; +} + +/* ====================================================== */ + + +/* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + +*/ +static CURLcode verifyhost(struct connectdata *conn, + X509 *server_cert) +{ + int matched = -1; /* -1 is no alternative match yet, 1 means match and 0 + means mismatch */ + int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */ + size_t addrlen = 0; + struct SessionHandle *data = conn->data; + STACK_OF(GENERAL_NAME) *altnames; +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + CURLcode res = CURLE_OK; + +#ifdef ENABLE_IPV6 + if(conn->bits.ipv6_ip && + Curl_inet_pton(AF_INET6, conn->host.name, &addr)) { + target = GEN_IPADD; + addrlen = sizeof(struct in6_addr); + } + else +#endif + if(Curl_inet_pton(AF_INET, conn->host.name, &addr)) { + target = GEN_IPADD; + addrlen = sizeof(struct in_addr); + } + + /* get a "list" of alternative names */ + altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL); + + if(altnames) { + int numalts; + int i; + + /* get amount of alternatives, RFC2459 claims there MUST be at least + one, but we don't depend on it... */ + numalts = sk_GENERAL_NAME_num(altnames); + + /* loop through all alternatives while none has matched */ + for(i=0; (itype == target) { + /* get data and length */ + const char *altptr = (char *)ASN1_STRING_data(check->d.ia5); + size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5); + + switch(target) { + case GEN_DNS: /* name/pattern comparison */ + /* The OpenSSL man page explicitly says: "In general it cannot be + assumed that the data returned by ASN1_STRING_data() is null + terminated or does not contain embedded nulls." But also that + "The actual format of the data will depend on the actual string + type itself: for example for and IA5String the data will be ASCII" + + Gisle researched the OpenSSL sources: + "I checked the 0.9.6 and 0.9.8 sources before my patch and + it always 0-terminates an IA5String." + */ + if((altlen == strlen(altptr)) && + /* if this isn't true, there was an embedded zero in the name + string and we cannot match it. */ + Curl_cert_hostcheck(altptr, conn->host.name)) + matched = 1; + else + matched = 0; + break; + + case GEN_IPADD: /* IP address comparison */ + /* compare alternative IP address if the data chunk is the same size + our server IP address is */ + if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) + matched = 1; + else + matched = 0; + break; + } + } + } + GENERAL_NAMES_free(altnames); + } + + if(matched == 1) + /* an alternative name matched the server hostname */ + infof(data, "\t subjectAltName: %s matched\n", conn->host.dispname); + else if(matched == 0) { + /* an alternative name field existed, but didn't match and then + we MUST fail */ + infof(data, "\t subjectAltName does not match %s\n", conn->host.dispname); + res = CURLE_PEER_FAILED_VERIFICATION; + } + else { + /* we have to look to the last occurrence of a commonName in the + distinguished one to get the most significant one. */ + int j,i=-1 ; + +/* The following is done because of a bug in 0.9.6b */ + + unsigned char *nulstr = (unsigned char *)""; + unsigned char *peer_CN = nulstr; + + X509_NAME *name = X509_get_subject_name(server_cert) ; + if(name) + while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i))>=0) + i=j; + + /* we have the name entry and we will now convert this to a string + that we can use for comparison. Doing this we support BMPstring, + UTF8 etc. */ + + if(i>=0) { + ASN1_STRING *tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name,i)); + + /* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input + is already UTF-8 encoded. We check for this case and copy the raw + string manually to avoid the problem. This code can be made + conditional in the future when OpenSSL has been fixed. Work-around + brought by Alexis S. L. Carvalho. */ + if(tmp) { + if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { + j = ASN1_STRING_length(tmp); + if(j >= 0) { + peer_CN = OPENSSL_malloc(j+1); + if(peer_CN) { + memcpy(peer_CN, ASN1_STRING_data(tmp), j); + peer_CN[j] = '\0'; + } + } + } + else /* not a UTF8 name */ + j = ASN1_STRING_to_UTF8(&peer_CN, tmp); + + if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != j)) { + /* there was a terminating zero before the end of string, this + cannot match and we return failure! */ + failf(data, "SSL: illegal cert name field"); + res = CURLE_PEER_FAILED_VERIFICATION; + } + } + } + + if(peer_CN == nulstr) + peer_CN = NULL; + else { + /* convert peer_CN from UTF8 */ + CURLcode rc = Curl_convert_from_utf8(data, peer_CN, strlen(peer_CN)); + /* Curl_convert_from_utf8 calls failf if unsuccessful */ + if(rc) { + OPENSSL_free(peer_CN); + return rc; + } + } + + if(res) + /* error already detected, pass through */ + ; + else if(!peer_CN) { + failf(data, + "SSL: unable to obtain common name from peer certificate"); + res = CURLE_PEER_FAILED_VERIFICATION; + } + else if(!Curl_cert_hostcheck((const char *)peer_CN, conn->host.name)) { + failf(data, "SSL: certificate subject name '%s' does not match " + "target host name '%s'", peer_CN, conn->host.dispname); + res = CURLE_PEER_FAILED_VERIFICATION; + } + else { + infof(data, "\t common name: %s (matched)\n", peer_CN); + } + if(peer_CN) + OPENSSL_free(peer_CN); + } + return res; +} +#endif /* USE_SSLEAY */ + +/* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions + and thus this cannot be done there. */ +#ifdef SSL_CTRL_SET_MSG_CALLBACK + +static const char *ssl_msg_type(int ssl_ver, int msg) +{ + if(ssl_ver == SSL2_VERSION_MAJOR) { + switch (msg) { + case SSL2_MT_ERROR: + return "Error"; + case SSL2_MT_CLIENT_HELLO: + return "Client hello"; + case SSL2_MT_CLIENT_MASTER_KEY: + return "Client key"; + case SSL2_MT_CLIENT_FINISHED: + return "Client finished"; + case SSL2_MT_SERVER_HELLO: + return "Server hello"; + case SSL2_MT_SERVER_VERIFY: + return "Server verify"; + case SSL2_MT_SERVER_FINISHED: + return "Server finished"; + case SSL2_MT_REQUEST_CERTIFICATE: + return "Request CERT"; + case SSL2_MT_CLIENT_CERTIFICATE: + return "Client CERT"; + } + } + else if(ssl_ver == SSL3_VERSION_MAJOR) { + switch (msg) { + case SSL3_MT_HELLO_REQUEST: + return "Hello request"; + case SSL3_MT_CLIENT_HELLO: + return "Client hello"; + case SSL3_MT_SERVER_HELLO: + return "Server hello"; + case SSL3_MT_CERTIFICATE: + return "CERT"; + case SSL3_MT_SERVER_KEY_EXCHANGE: + return "Server key exchange"; + case SSL3_MT_CLIENT_KEY_EXCHANGE: + return "Client key exchange"; + case SSL3_MT_CERTIFICATE_REQUEST: + return "Request CERT"; + case SSL3_MT_SERVER_DONE: + return "Server finished"; + case SSL3_MT_CERTIFICATE_VERIFY: + return "CERT verify"; + case SSL3_MT_FINISHED: + return "Finished"; + } + } + return "Unknown"; +} + +static const char *tls_rt_type(int type) +{ + return ( + type == SSL3_RT_CHANGE_CIPHER_SPEC ? "TLS change cipher, " : + type == SSL3_RT_ALERT ? "TLS alert, " : + type == SSL3_RT_HANDSHAKE ? "TLS handshake, " : + type == SSL3_RT_APPLICATION_DATA ? "TLS app data, " : + "TLS Unknown, "); +} + + +/* + * Our callback from the SSL/TLS layers. + */ +static void ssl_tls_trace(int direction, int ssl_ver, int content_type, + const void *buf, size_t len, const SSL *ssl, + struct connectdata *conn) +{ + struct SessionHandle *data; + const char *msg_name, *tls_rt_name; + char ssl_buf[1024]; + int ver, msg_type, txt_len; + + if(!conn || !conn->data || !conn->data->set.fdebug || + (direction != 0 && direction != 1)) + return; + + data = conn->data; + ssl_ver >>= 8; + ver = (ssl_ver == SSL2_VERSION_MAJOR ? '2' : + ssl_ver == SSL3_VERSION_MAJOR ? '3' : '?'); + + /* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL + * always pass-up content-type as 0. But the interesting message-type + * is at 'buf[0]'. + */ + if(ssl_ver == SSL3_VERSION_MAJOR && content_type != 0) + tls_rt_name = tls_rt_type(content_type); + else + tls_rt_name = ""; + + msg_type = *(char*)buf; + msg_name = ssl_msg_type(ssl_ver, msg_type); + + txt_len = snprintf(ssl_buf, sizeof(ssl_buf), "SSLv%c, %s%s (%d):\n", + ver, tls_rt_name, msg_name, msg_type); + Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len, NULL); + + Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : + CURLINFO_SSL_DATA_IN, (char *)buf, len, NULL); + (void) ssl; +} +#endif + +#ifdef USE_SSLEAY +/* ====================================================== */ + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME +# define use_sni(x) sni = (x) +#else +# define use_sni(x) Curl_nop_stmt +#endif + +static CURLcode +ossl_connect_step1(struct connectdata *conn, + int sockindex) +{ + CURLcode retcode = CURLE_OK; + + struct SessionHandle *data = conn->data; + SSL_METHOD_QUAL SSL_METHOD *req_method=NULL; + void *ssl_sessionid=NULL; + X509_LOOKUP *lookup=NULL; + curl_socket_t sockfd = conn->sock[sockindex]; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + long ctx_options; +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + bool sni; +#ifdef ENABLE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif +#endif + + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + + /* Make funny stuff to get random input */ + Curl_ossl_seed(data); + + /* check to see if we've been told to use an explicit SSL/TLS version */ + + switch(data->set.ssl.version) { + default: + case CURL_SSLVERSION_DEFAULT: +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { + infof(data, "Set version TLSv1 for SRP authorisation\n"); + req_method = TLSv1_client_method() ; + } + else +#endif + /* we try to figure out version */ + req_method = SSLv23_client_method(); + use_sni(TRUE); + break; + case CURL_SSLVERSION_TLSv1: + req_method = TLSv1_client_method(); + use_sni(TRUE); + break; + case CURL_SSLVERSION_SSLv2: +#ifdef OPENSSL_NO_SSL2 + failf(data, "OpenSSL was built without SSLv2 support"); + return CURLE_NOT_BUILT_IN; +#else +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) + return CURLE_SSL_CONNECT_ERROR; +#endif + req_method = SSLv2_client_method(); + use_sni(FALSE); + break; +#endif + case CURL_SSLVERSION_SSLv3: +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) + return CURLE_SSL_CONNECT_ERROR; +#endif + req_method = SSLv3_client_method(); + use_sni(FALSE); + break; + } + + if(connssl->ctx) + SSL_CTX_free(connssl->ctx); + connssl->ctx = SSL_CTX_new(req_method); + + if(!connssl->ctx) { + failf(data, "SSL: couldn't create a context: %s", + ERR_error_string(ERR_peek_error(), NULL)); + return CURLE_OUT_OF_MEMORY; + } + +#ifdef SSL_MODE_RELEASE_BUFFERS + SSL_CTX_set_mode(connssl->ctx, SSL_MODE_RELEASE_BUFFERS); +#endif + +#ifdef SSL_CTRL_SET_MSG_CALLBACK + if(data->set.fdebug && data->set.verbose) { + /* the SSL trace callback is only used for verbose logging so we only + inform about failures of setting it */ + if(!SSL_CTX_callback_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK, + (void (*)(void))ssl_tls_trace)) { + infof(data, "SSL: couldn't set callback!\n"); + } + else if(!SSL_CTX_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, + conn)) { + infof(data, "SSL: couldn't set callback argument!\n"); + } + } +#endif + + /* OpenSSL contains code to work-around lots of bugs and flaws in various + SSL-implementations. SSL_CTX_set_options() is used to enabled those + work-arounds. The man page for this option states that SSL_OP_ALL enables + all the work-arounds and that "It is usually safe to use SSL_OP_ALL to + enable the bug workaround options if compatibility with somewhat broken + implementations is desired." + + The "-no_ticket" option was introduced in Openssl0.9.8j. It's a flag to + disable "rfc4507bis session ticket support". rfc4507bis was later turned + into the proper RFC5077 it seems: http://tools.ietf.org/html/rfc5077 + + The enabled extension concerns the session management. I wonder how often + libcurl stops a connection and then resumes a TLS session. also, sending + the session data is some overhead. .I suggest that you just use your + proposed patch (which explicitly disables TICKET). + + If someone writes an application with libcurl and openssl who wants to + enable the feature, one can do this in the SSL callback. + + SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper + interoperability with web server Netscape Enterprise Server 2.0.1 which + was released back in 1996. + + Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has + become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate + CVE-2010-4180 when using previous OpenSSL versions we no longer enable + this option regardless of OpenSSL version and SSL_OP_ALL definition. + + OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability + (http://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to + SSL_OP_ALL that _disables_ that work-around despite the fact that + SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to + keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit + must not be set. + */ + + ctx_options = SSL_OP_ALL; + +#ifdef SSL_OP_NO_TICKET + ctx_options |= SSL_OP_NO_TICKET; +#endif + +#ifdef SSL_OP_NO_COMPRESSION + ctx_options |= SSL_OP_NO_COMPRESSION; +#endif + +#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG + /* mitigate CVE-2010-4180 */ + ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; +#endif + +#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS + /* unless the user explicitly ask to allow the protocol vulnerability we + use the work-around */ + if(!conn->data->set.ssl_enable_beast) + ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; +#endif + + /* disable SSLv2 in the default case (i.e. allow SSLv3 and TLSv1) */ + if(data->set.ssl.version == CURL_SSLVERSION_DEFAULT) + ctx_options |= SSL_OP_NO_SSLv2; + + SSL_CTX_set_options(connssl->ctx, ctx_options); + +#if 0 + /* + * Not sure it's needed to tell SSL_connect() that socket is + * non-blocking. It doesn't seem to care, but just return with + * SSL_ERROR_WANT_x. + */ + if(data->state.used_interface == Curl_if_multi) + SSL_CTX_ctrl(connssl->ctx, BIO_C_SET_NBIO, 1, NULL); +#endif + + if(data->set.str[STRING_CERT] || data->set.str[STRING_CERT_TYPE]) { + if(!cert_stuff(conn, + connssl->ctx, + data->set.str[STRING_CERT], + data->set.str[STRING_CERT_TYPE], + data->set.str[STRING_KEY], + data->set.str[STRING_KEY_TYPE])) { + /* failf() is already done in cert_stuff() */ + return CURLE_SSL_CERTPROBLEM; + } + } + + if(data->set.str[STRING_SSL_CIPHER_LIST]) { + if(!SSL_CTX_set_cipher_list(connssl->ctx, + data->set.str[STRING_SSL_CIPHER_LIST])) { + failf(data, "failed setting cipher list"); + return CURLE_SSL_CIPHER; + } + } + +#ifdef USE_TLS_SRP + if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { + infof(data, "Using TLS-SRP username: %s\n", data->set.ssl.username); + + if(!SSL_CTX_set_srp_username(connssl->ctx, data->set.ssl.username)) { + failf(data, "Unable to set SRP user name"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!SSL_CTX_set_srp_password(connssl->ctx,data->set.ssl.password)) { + failf(data, "failed setting SRP password"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!data->set.str[STRING_SSL_CIPHER_LIST]) { + infof(data, "Setting cipher list SRP\n"); + + if(!SSL_CTX_set_cipher_list(connssl->ctx, "SRP")) { + failf(data, "failed setting SRP cipher list"); + return CURLE_SSL_CIPHER; + } + } + } +#endif + if(data->set.str[STRING_SSL_CAFILE] || data->set.str[STRING_SSL_CAPATH]) { + /* tell SSL where to find CA certificates that are used to verify + the servers certificate. */ + if(!SSL_CTX_load_verify_locations(connssl->ctx, + data->set.str[STRING_SSL_CAFILE], + data->set.str[STRING_SSL_CAPATH])) { + if(data->set.ssl.verifypeer) { + /* Fail if we insist on successfully verifying the server. */ + failf(data,"error setting certificate verify locations:\n" + " CAfile: %s\n CApath: %s", + data->set.str[STRING_SSL_CAFILE]? + data->set.str[STRING_SSL_CAFILE]: "none", + data->set.str[STRING_SSL_CAPATH]? + data->set.str[STRING_SSL_CAPATH] : "none"); + return CURLE_SSL_CACERT_BADFILE; + } + else { + /* Just continue with a warning if no strict certificate verification + is required. */ + infof(data, "error setting certificate verify locations," + " continuing anyway:\n"); + } + } + else { + /* Everything is fine. */ + infof(data, "successfully set certificate verify locations:\n"); + } + infof(data, + " CAfile: %s\n" + " CApath: %s\n", + data->set.str[STRING_SSL_CAFILE] ? data->set.str[STRING_SSL_CAFILE]: + "none", + data->set.str[STRING_SSL_CAPATH] ? data->set.str[STRING_SSL_CAPATH]: + "none"); + } + + if(data->set.str[STRING_SSL_CRLFILE]) { + /* tell SSL where to find CRL file that is used to check certificate + * revocation */ + lookup=X509_STORE_add_lookup(SSL_CTX_get_cert_store(connssl->ctx), + X509_LOOKUP_file()); + if(!lookup || + (!X509_load_crl_file(lookup,data->set.str[STRING_SSL_CRLFILE], + X509_FILETYPE_PEM)) ) { + failf(data,"error loading CRL file: %s", + data->set.str[STRING_SSL_CRLFILE]); + return CURLE_SSL_CRL_BADFILE; + } + else { + /* Everything is fine. */ + infof(data, "successfully load CRL file:\n"); + X509_STORE_set_flags(SSL_CTX_get_cert_store(connssl->ctx), + X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); + } + infof(data, + " CRLfile: %s\n", data->set.str[STRING_SSL_CRLFILE] ? + data->set.str[STRING_SSL_CRLFILE]: "none"); + } + + /* SSL always tries to verify the peer, this only says whether it should + * fail to connect if the verification fails, or if it should continue + * anyway. In the latter case the result of the verification is checked with + * SSL_get_verify_result() below. */ + SSL_CTX_set_verify(connssl->ctx, + data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE, + cert_verify_callback); + + /* give application a chance to interfere with SSL set up. */ + if(data->set.ssl.fsslctx) { + retcode = (*data->set.ssl.fsslctx)(data, connssl->ctx, + data->set.ssl.fsslctxp); + if(retcode) { + failf(data,"error signaled by ssl ctx callback"); + return retcode; + } + } + + /* Lets make an SSL structure */ + if(connssl->handle) + SSL_free(connssl->handle); + connssl->handle = SSL_new(connssl->ctx); + if(!connssl->handle) { + failf(data, "SSL: couldn't create a context (handle)!"); + return CURLE_OUT_OF_MEMORY; + } + SSL_set_connect_state(connssl->handle); + + connssl->server_cert = 0x0; + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) && +#ifdef ENABLE_IPV6 + (0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) && +#endif + sni && + !SSL_set_tlsext_host_name(connssl->handle, conn->host.name)) + infof(data, "WARNING: failed to configure server name indication (SNI) " + "TLS extension\n"); +#endif + + /* Check if there's a cached ID we can/should use here! */ + if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) { + /* we got a session id, use it! */ + if(!SSL_set_session(connssl->handle, ssl_sessionid)) { + failf(data, "SSL: SSL_set_session failed: %s", + ERR_error_string(ERR_get_error(),NULL)); + return CURLE_SSL_CONNECT_ERROR; + } + /* Informational message */ + infof (data, "SSL re-using session ID\n"); + } + + /* pass the raw socket into the SSL layers */ + if(!SSL_set_fd(connssl->handle, (int)sockfd)) { + failf(data, "SSL: SSL_set_fd failed: %s", + ERR_error_string(ERR_get_error(),NULL)); + return CURLE_SSL_CONNECT_ERROR; + } + + connssl->connecting_state = ssl_connect_2; + return CURLE_OK; +} + +static CURLcode +ossl_connect_step2(struct connectdata *conn, int sockindex) +{ + struct SessionHandle *data = conn->data; + int err; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + DEBUGASSERT(ssl_connect_2 == connssl->connecting_state + || ssl_connect_2_reading == connssl->connecting_state + || ssl_connect_2_writing == connssl->connecting_state); + + ERR_clear_error(); + + err = SSL_connect(connssl->handle); + + /* 1 is fine + 0 is "not successful but was shut down controlled" + <0 is "handshake was not successful, because a fatal error occurred" */ + if(1 != err) { + int detail = SSL_get_error(connssl->handle, err); + + if(SSL_ERROR_WANT_READ == detail) { + connssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + else if(SSL_ERROR_WANT_WRITE == detail) { + connssl->connecting_state = ssl_connect_2_writing; + return CURLE_OK; + } + else { + /* untreated error */ + unsigned long errdetail; + char error_buffer[256]; /* OpenSSL documents that this must be at least + 256 bytes long. */ + CURLcode rc; + const char *cert_problem = NULL; + long lerr; + + connssl->connecting_state = ssl_connect_2; /* the connection failed, + we're not waiting for + anything else. */ + + errdetail = ERR_get_error(); /* Gets the earliest error code from the + thread's error queue and removes the + entry. */ + + switch(errdetail) { + case 0x1407E086: + /* 1407E086: + SSL routines: + SSL2_SET_CERTIFICATE: + certificate verify failed */ + /* fall-through */ + case 0x14090086: + /* 14090086: + SSL routines: + SSL3_GET_SERVER_CERTIFICATE: + certificate verify failed */ + rc = CURLE_SSL_CACERT; + + lerr = SSL_get_verify_result(connssl->handle); + if(lerr != X509_V_OK) { + snprintf(error_buffer, sizeof(error_buffer), + "SSL certificate problem: %s", + X509_verify_cert_error_string(lerr)); + } + else + cert_problem = "SSL certificate problem, verify that the CA cert is" + " OK."; + + break; + default: + rc = CURLE_SSL_CONNECT_ERROR; + SSL_strerror(errdetail, error_buffer, sizeof(error_buffer)); + break; + } + + /* detail is already set to the SSL error above */ + + /* If we e.g. use SSLv2 request-method and the server doesn't like us + * (RST connection etc.), OpenSSL gives no explanation whatsoever and + * the SO_ERROR is also lost. + */ + if(CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) { + failf(data, "Unknown SSL protocol error in connection to %s:%ld ", + conn->host.name, conn->port); + return rc; + } + /* Could be a CERT problem */ + + failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer); + return rc; + } + } + else { + /* we have been connected fine, we're not waiting for anything else. */ + connssl->connecting_state = ssl_connect_3; + + /* Informational message */ + infof (data, "SSL connection using %s\n", + SSL_get_cipher(connssl->handle)); + + return CURLE_OK; + } +} + +static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len) +{ + int i, ilen; + + if((ilen = (int)len) < 0) + return 1; /* buffer too big */ + + i = i2t_ASN1_OBJECT(buf, ilen, a); + + if(i >= ilen) + return 1; /* buffer too small */ + + return 0; +} + +static CURLcode push_certinfo_len(struct SessionHandle *data, + int certnum, + const char *label, + const char *value, + size_t valuelen) +{ + struct curl_certinfo *ci = &data->info.certs; + char *output; + struct curl_slist *nl; + CURLcode res = CURLE_OK; + size_t labellen = strlen(label); + size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */ + + output = malloc(outlen); + if(!output) + return CURLE_OUT_OF_MEMORY; + + /* sprintf the label and colon */ + snprintf(output, outlen, "%s:", label); + + /* memcpy the value (it might not be zero terminated) */ + memcpy(&output[labellen+1], value, valuelen); + + /* zero terminate the output */ + output[labellen + 1 + valuelen] = 0; + + /* TODO: we should rather introduce an internal API that can do the + equivalent of curl_slist_append but doesn't strdup() the given data as + like in this place the extra malloc/free is totally pointless */ + nl = curl_slist_append(ci->certinfo[certnum], output); + free(output); + if(!nl) { + curl_slist_free_all(ci->certinfo[certnum]); + ci->certinfo[certnum] = NULL; + res = CURLE_OUT_OF_MEMORY; + } + else + ci->certinfo[certnum] = nl; + + return res; +} + +/* this is a convenience function for push_certinfo_len that takes a zero + terminated value */ +static CURLcode push_certinfo(struct SessionHandle *data, + int certnum, + const char *label, + const char *value) +{ + size_t valuelen = strlen(value); + + return push_certinfo_len(data, certnum, label, value, valuelen); +} + +static void pubkey_show(struct SessionHandle *data, + int num, + const char *type, + const char *name, + unsigned char *raw, + int len) +{ + size_t left; + int i; + char namebuf[32]; + char *buffer; + + left = len*3 + 1; + buffer = malloc(left); + if(buffer) { + char *ptr=buffer; + snprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name); + for(i=0; i< len; i++) { + snprintf(ptr, left, "%02x:", raw[i]); + ptr += 3; + left -= 3; + } + infof(data, " %s: %s\n", namebuf, buffer); + push_certinfo(data, num, namebuf, buffer); + free(buffer); + } +} + +#define print_pubkey_BN(_type, _name, _num) \ +do { \ + if(pubkey->pkey._type->_name != NULL) { \ + int len = BN_num_bytes(pubkey->pkey._type->_name); \ + if(len < CERTBUFFERSIZE) { \ + BN_bn2bin(pubkey->pkey._type->_name, (unsigned char*)bufp); \ + bufp[len] = 0; \ + pubkey_show(data, _num, #_type, #_name, (unsigned char*)bufp, len); \ + } \ + } \ +} WHILE_FALSE + +static int X509V3_ext(struct SessionHandle *data, + int certnum, + STACK_OF(X509_EXTENSION) *exts) +{ + int i; + size_t j; + + if(sk_X509_EXTENSION_num(exts) <= 0) + /* no extensions, bail out */ + return 1; + + for(i=0; ivalue); + + BIO_get_mem_ptr(bio_out, &biomem); + + /* biomem->length bytes at biomem->data, this little loop here is only + done for the infof() call, we send the "raw" data to the certinfo + function */ + for(j=0; j<(size_t)biomem->length; j++) { + const char *sep=""; + if(biomem->data[j] == '\n') { + sep=", "; + j++; /* skip the newline */ + }; + while((biomem->data[j] == ' ') && (j<(size_t)biomem->length)) + j++; + if(j<(size_t)biomem->length) + ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%s%c", sep, + biomem->data[j]); + } + infof(data, " %s\n", buf); + + push_certinfo(data, certnum, namebuf, buf); + + BIO_free(bio_out); + + } + return 0; /* all is fine */ +} + + +static void X509_signature(struct SessionHandle *data, + int numcert, + ASN1_STRING *sig) +{ + char buf[1024]; + char *ptr = buf; + int i; + for(i=0; ilength; i++) + ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%02x:", sig->data[i]); + + infof(data, " Signature: %s\n", buf); + push_certinfo(data, numcert, "Signature", buf); +} + +static void dumpcert(struct SessionHandle *data, X509 *x, int numcert) +{ + BIO *bio_out = BIO_new(BIO_s_mem()); + BUF_MEM *biomem; + + /* this outputs the cert in this 64 column wide style with newlines and + -----BEGIN CERTIFICATE----- texts and more */ + PEM_write_bio_X509(bio_out, x); + + BIO_get_mem_ptr(bio_out, &biomem); + + infof(data, "%s\n", biomem->data); + + push_certinfo_len(data, numcert, "Cert", biomem->data, biomem->length); + + BIO_free(bio_out); + +} + + +static int init_certinfo(struct SessionHandle *data, + int num) +{ + struct curl_certinfo *ci = &data->info.certs; + struct curl_slist **table; + + Curl_ssl_free_certinfo(data); + + ci->num_of_certs = num; + table = calloc((size_t)num, sizeof(struct curl_slist *)); + if(!table) + return 1; + + ci->certinfo = table; + return 0; +} + +/* + * This size was previously 512 which has been reported "too small" without + * any specifics, so it was enlarged to allow more data to get shown uncut. + * The "perfect" size is yet to figure out. + */ +#define CERTBUFFERSIZE 8192 + +static CURLcode get_cert_chain(struct connectdata *conn, + struct ssl_connect_data *connssl) + +{ + STACK_OF(X509) *sk; + int i; + char *bufp; + struct SessionHandle *data = conn->data; + int numcerts; + + bufp = malloc(CERTBUFFERSIZE); + if(!bufp) + return CURLE_OUT_OF_MEMORY; + + sk = SSL_get_peer_cert_chain(connssl->handle); + if(!sk) { + free(bufp); + return CURLE_OUT_OF_MEMORY; + } + + numcerts = sk_X509_num(sk); + if(init_certinfo(data, numcerts)) { + free(bufp); + return CURLE_OUT_OF_MEMORY; + } + + infof(data, "--- Certificate chain\n"); + for(i=0; ilength <= 4) { + value = ASN1_INTEGER_get(num); + infof(data," Serial Number: %ld (0x%lx)\n", value, value); + snprintf(bufp, CERTBUFFERSIZE, "%lx", value); + } + else { + int left = CERTBUFFERSIZE; + + ptr = bufp; + *ptr++ = 0; + if(num->type == V_ASN1_NEG_INTEGER) + *ptr++='-'; + + for(j=0; (jlength) && (left>=4); j++) { + /* TODO: length restrictions */ + snprintf(ptr, 3, "%02x%c",num->data[j], + ((j+1 == num->length)?'\n':':')); + ptr += 3; + left-=4; + } + if(num->length) + infof(data," Serial Number: %s\n", bufp); + else + bufp[0]=0; + } + if(bufp[0]) + push_certinfo(data, i, "Serial Number", bufp); /* hex */ + + cinf = x->cert_info; + + j = asn1_object_dump(cinf->signature->algorithm, bufp, CERTBUFFERSIZE); + if(!j) { + infof(data, " Signature Algorithm: %s\n", bufp); + push_certinfo(data, i, "Signature Algorithm", bufp); + } + + certdate = X509_get_notBefore(x); + asn1_output(certdate, bufp, CERTBUFFERSIZE); + infof(data, " Start date: %s\n", bufp); + push_certinfo(data, i, "Start date", bufp); + + certdate = X509_get_notAfter(x); + asn1_output(certdate, bufp, CERTBUFFERSIZE); + infof(data, " Expire date: %s\n", bufp); + push_certinfo(data, i, "Expire date", bufp); + + j = asn1_object_dump(cinf->key->algor->algorithm, bufp, CERTBUFFERSIZE); + if(!j) { + infof(data, " Public Key Algorithm: %s\n", bufp); + push_certinfo(data, i, "Public Key Algorithm", bufp); + } + + pubkey = X509_get_pubkey(x); + if(!pubkey) + infof(data, " Unable to load public key\n"); + else { + switch(pubkey->type) { + case EVP_PKEY_RSA: + infof(data, " RSA Public Key (%d bits)\n", + BN_num_bits(pubkey->pkey.rsa->n)); + snprintf(bufp, CERTBUFFERSIZE, "%d", BN_num_bits(pubkey->pkey.rsa->n)); + push_certinfo(data, i, "RSA Public Key", bufp); + + print_pubkey_BN(rsa, n, i); + print_pubkey_BN(rsa, e, i); + print_pubkey_BN(rsa, d, i); + print_pubkey_BN(rsa, p, i); + print_pubkey_BN(rsa, q, i); + print_pubkey_BN(rsa, dmp1, i); + print_pubkey_BN(rsa, dmq1, i); + print_pubkey_BN(rsa, iqmp, i); + break; + case EVP_PKEY_DSA: + print_pubkey_BN(dsa, p, i); + print_pubkey_BN(dsa, q, i); + print_pubkey_BN(dsa, g, i); + print_pubkey_BN(dsa, priv_key, i); + print_pubkey_BN(dsa, pub_key, i); + break; + case EVP_PKEY_DH: + print_pubkey_BN(dh, p, i); + print_pubkey_BN(dh, g, i); + print_pubkey_BN(dh, priv_key, i); + print_pubkey_BN(dh, pub_key, i); + break; +#if 0 + case EVP_PKEY_EC: /* symbol not present in OpenSSL 0.9.6 */ + /* left TODO */ + break; +#endif + } + EVP_PKEY_free(pubkey); + } + + X509V3_ext(data, i, cinf->extensions); + + X509_signature(data, i, x->signature); + + dumpcert(data, x, i); + } + + free(bufp); + + return CURLE_OK; +} + +/* + * Get the server cert, verify it and show it etc, only call failf() if the + * 'strict' argument is TRUE as otherwise all this is for informational + * purposes only! + * + * We check certificates to authenticate the server; otherwise we risk + * man-in-the-middle attack. + */ +static CURLcode servercert(struct connectdata *conn, + struct ssl_connect_data *connssl, + bool strict) +{ + CURLcode retcode = CURLE_OK; + int rc; + long lerr; + ASN1_TIME *certdate; + struct SessionHandle *data = conn->data; + X509 *issuer; + FILE *fp; + char *buffer = data->state.buffer; + + if(data->set.ssl.certinfo) + /* we've been asked to gather certificate info! */ + (void)get_cert_chain(conn, connssl); + + data->set.ssl.certverifyresult = !X509_V_OK; + + connssl->server_cert = SSL_get_peer_certificate(connssl->handle); + if(!connssl->server_cert) { + if(strict) + failf(data, "SSL: couldn't get peer certificate!"); + return CURLE_PEER_FAILED_VERIFICATION; + } + infof (data, "Server certificate:\n"); + + rc = x509_name_oneline(X509_get_subject_name(connssl->server_cert), + buffer, BUFSIZE); + if(rc) { + if(strict) + failf(data, "SSL: couldn't get X509-subject!"); + X509_free(connssl->server_cert); + connssl->server_cert = NULL; + return CURLE_SSL_CONNECT_ERROR; + } + infof(data, "\t subject: %s\n", buffer); + + certdate = X509_get_notBefore(connssl->server_cert); + asn1_output(certdate, buffer, BUFSIZE); + infof(data, "\t start date: %s\n", buffer); + + certdate = X509_get_notAfter(connssl->server_cert); + asn1_output(certdate, buffer, BUFSIZE); + infof(data, "\t expire date: %s\n", buffer); + + if(data->set.ssl.verifyhost) { + retcode = verifyhost(conn, connssl->server_cert); + if(retcode) { + X509_free(connssl->server_cert); + connssl->server_cert = NULL; + return retcode; + } + } + + rc = x509_name_oneline(X509_get_issuer_name(connssl->server_cert), + buffer, BUFSIZE); + if(rc) { + if(strict) + failf(data, "SSL: couldn't get X509-issuer name!"); + retcode = CURLE_SSL_CONNECT_ERROR; + } + else { + infof(data, "\t issuer: %s\n", buffer); + + /* We could do all sorts of certificate verification stuff here before + deallocating the certificate. */ + + /* e.g. match issuer name with provided issuer certificate */ + if(data->set.str[STRING_SSL_ISSUERCERT]) { + fp=fopen(data->set.str[STRING_SSL_ISSUERCERT],"r"); + if(!fp) { + if(strict) + failf(data, "SSL: Unable to open issuer cert (%s)", + data->set.str[STRING_SSL_ISSUERCERT]); + X509_free(connssl->server_cert); + connssl->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } + issuer = PEM_read_X509(fp,NULL,ZERO_NULL,NULL); + if(!issuer) { + if(strict) + failf(data, "SSL: Unable to read issuer cert (%s)", + data->set.str[STRING_SSL_ISSUERCERT]); + X509_free(connssl->server_cert); + X509_free(issuer); + fclose(fp); + return CURLE_SSL_ISSUER_ERROR; + } + fclose(fp); + if(X509_check_issued(issuer,connssl->server_cert) != X509_V_OK) { + if(strict) + failf(data, "SSL: Certificate issuer check failed (%s)", + data->set.str[STRING_SSL_ISSUERCERT]); + X509_free(connssl->server_cert); + X509_free(issuer); + connssl->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } + infof(data, "\t SSL certificate issuer check ok (%s)\n", + data->set.str[STRING_SSL_ISSUERCERT]); + X509_free(issuer); + } + + lerr = data->set.ssl.certverifyresult= + SSL_get_verify_result(connssl->handle); + if(data->set.ssl.certverifyresult != X509_V_OK) { + if(data->set.ssl.verifypeer) { + /* We probably never reach this, because SSL_connect() will fail + and we return earlier if verifypeer is set? */ + if(strict) + failf(data, "SSL certificate verify result: %s (%ld)", + X509_verify_cert_error_string(lerr), lerr); + retcode = CURLE_PEER_FAILED_VERIFICATION; + } + else + infof(data, "\t SSL certificate verify result: %s (%ld)," + " continuing anyway.\n", + X509_verify_cert_error_string(lerr), lerr); + } + else + infof(data, "\t SSL certificate verify ok.\n"); + } + + X509_free(connssl->server_cert); + connssl->server_cert = NULL; + connssl->connecting_state = ssl_connect_done; + + return retcode; +} + + +static CURLcode +ossl_connect_step3(struct connectdata *conn, + int sockindex) +{ + CURLcode retcode = CURLE_OK; + void *old_ssl_sessionid=NULL; + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + int incache; + SSL_SESSION *our_ssl_sessionid; + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + +#ifdef HAVE_SSL_GET1_SESSION + our_ssl_sessionid = SSL_get1_session(connssl->handle); + + /* SSL_get1_session() will increment the reference + count and the session will stay in memory until explicitly freed with + SSL_SESSION_free(3), regardless of its state. + This function was introduced in openssl 0.9.5a. */ +#else + our_ssl_sessionid = SSL_get_session(connssl->handle); + + /* if SSL_get1_session() is unavailable, use SSL_get_session(). + This is an inferior option because the session can be flushed + at any time by openssl. It is included only so curl compiles + under versions of openssl < 0.9.5a. + + WARNING: How curl behaves if it's session is flushed is + untested. + */ +#endif + + incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL)); + if(incache) { + if(old_ssl_sessionid != our_ssl_sessionid) { + infof(data, "old SSL session ID is stale, removing\n"); + Curl_ssl_delsessionid(conn, old_ssl_sessionid); + incache = FALSE; + } + } + if(!incache) { + retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid, + 0 /* unknown size */); + if(retcode) { + failf(data, "failed to store ssl session"); + return retcode; + } + } +#ifdef HAVE_SSL_GET1_SESSION + else { + /* Session was incache, so refcount already incremented earlier. + * Avoid further increments with each SSL_get1_session() call. + * This does not free the session as refcount remains > 0 + */ + SSL_SESSION_free(our_ssl_sessionid); + } +#endif + + /* + * We check certificates to authenticate the server; otherwise we risk + * man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to + * verify the peer ignore faults and failures from the server cert + * operations. + */ + + if(!data->set.ssl.verifypeer) + (void)servercert(conn, connssl, FALSE); + else + retcode = servercert(conn, connssl, TRUE); + + if(CURLE_OK == retcode) + connssl->connecting_state = ssl_connect_done; + return retcode; +} + +static Curl_recv ossl_recv; +static Curl_send ossl_send; + +static CURLcode +ossl_connect_common(struct connectdata *conn, + int sockindex, + bool nonblocking, + bool *done) +{ + CURLcode retcode; + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + curl_socket_t sockfd = conn->sock[sockindex]; + long timeout_ms; + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1==connssl->connecting_state) { + /* Find out how much more time we're allowed */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + retcode = ossl_connect_step1(conn, sockindex); + if(retcode) + return retcode; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check allowed time left */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + if(connssl->connecting_state == ssl_connect_2_reading + || connssl->connecting_state == ssl_connect_2_writing) { + + curl_socket_t writefd = ssl_connect_2_writing== + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + curl_socket_t readfd = ssl_connect_2_reading== + connssl->connecting_state?sockfd:CURL_SOCKET_BAD; + + what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if this + * connection is done nonblocking and this loop would execute again. This + * permits the owner of a multi handle to abort a connection attempt + * before step2 has completed while ensuring that a client using select() + * or epoll() will always have a valid fdset to wait on. + */ + retcode = ossl_connect_step2(conn, sockindex); + if(retcode || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return retcode; + + } /* repeat step2 until all transactions are done. */ + + + if(ssl_connect_3==connssl->connecting_state) { + retcode = ossl_connect_step3(conn, sockindex); + if(retcode) + return retcode; + } + + if(ssl_connect_done==connssl->connecting_state) { + connssl->state = ssl_connection_complete; + conn->recv[sockindex] = ossl_recv; + conn->send[sockindex] = ossl_send; + *done = TRUE; + } + else + *done = FALSE; + + /* Reset our connect state machine */ + connssl->connecting_state = ssl_connect_1; + + return CURLE_OK; +} + +CURLcode +Curl_ossl_connect_nonblocking(struct connectdata *conn, + int sockindex, + bool *done) +{ + return ossl_connect_common(conn, sockindex, TRUE, done); +} + +CURLcode +Curl_ossl_connect(struct connectdata *conn, + int sockindex) +{ + CURLcode retcode; + bool done = FALSE; + + retcode = ossl_connect_common(conn, sockindex, FALSE, &done); + if(retcode) + return retcode; + + DEBUGASSERT(done); + + return CURLE_OK; +} + +bool Curl_ossl_data_pending(const struct connectdata *conn, + int connindex) +{ + if(conn->ssl[connindex].handle) + /* SSL is in use */ + return (0 != SSL_pending(conn->ssl[connindex].handle)) ? TRUE : FALSE; + else + return FALSE; +} + +static ssize_t ossl_send(struct connectdata *conn, + int sockindex, + const void *mem, + size_t len, + CURLcode *curlcode) +{ + /* SSL_write() is said to return 'int' while write() and send() returns + 'size_t' */ + int err; + char error_buffer[120]; /* OpenSSL documents that this must be at least 120 + bytes long. */ + unsigned long sslerror; + int memlen; + int rc; + + ERR_clear_error(); + + memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; + rc = SSL_write(conn->ssl[sockindex].handle, mem, memlen); + + if(rc < 0) { + err = SSL_get_error(conn->ssl[sockindex].handle, rc); + + switch(err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* The operation did not complete; the same TLS/SSL I/O function + should be called again later. This is basically an EWOULDBLOCK + equivalent. */ + *curlcode = CURLE_AGAIN; + return -1; + case SSL_ERROR_SYSCALL: + failf(conn->data, "SSL_write() returned SYSCALL, errno = %d", + SOCKERRNO); + *curlcode = CURLE_SEND_ERROR; + return -1; + case SSL_ERROR_SSL: + /* A failure in the SSL library occurred, usually a protocol error. + The OpenSSL error queue contains more information on the error. */ + sslerror = ERR_get_error(); + failf(conn->data, "SSL_write() error: %s", + ERR_error_string(sslerror, error_buffer)); + *curlcode = CURLE_SEND_ERROR; + return -1; + } + /* a true error */ + failf(conn->data, "SSL_write() return error %d", err); + *curlcode = CURLE_SEND_ERROR; + return -1; + } + return (ssize_t)rc; /* number of bytes */ +} + +static ssize_t ossl_recv(struct connectdata *conn, /* connection data */ + int num, /* socketindex */ + char *buf, /* store read data here */ + size_t buffersize, /* max amount to read */ + CURLcode *curlcode) +{ + char error_buffer[120]; /* OpenSSL documents that this must be at + least 120 bytes long. */ + unsigned long sslerror; + ssize_t nread; + int buffsize; + + ERR_clear_error(); + + buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; + nread = (ssize_t)SSL_read(conn->ssl[num].handle, buf, buffsize); + if(nread < 0) { + /* failed SSL_read */ + int err = SSL_get_error(conn->ssl[num].handle, (int)nread); + + switch(err) { + case SSL_ERROR_NONE: /* this is not an error */ + case SSL_ERROR_ZERO_RETURN: /* no more data */ + break; + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* there's data pending, re-invoke SSL_read() */ + *curlcode = CURLE_AGAIN; + return -1; + default: + /* openssl/ssl.h says "look at error stack/return value/errno" */ + sslerror = ERR_get_error(); + failf(conn->data, "SSL read: %s, errno %d", + ERR_error_string(sslerror, error_buffer), + SOCKERRNO); + *curlcode = CURLE_RECV_ERROR; + return -1; + } + } + return nread; +} + +size_t Curl_ossl_version(char *buffer, size_t size) +{ +#ifdef YASSL_VERSION + /* yassl provides an OpenSSL API compatibility layer so it looks identical + to OpenSSL in all other aspects */ + return snprintf(buffer, size, "yassl/%s", YASSL_VERSION); +#else /* YASSL_VERSION */ + +#if(SSLEAY_VERSION_NUMBER >= 0x905000) + { + char sub[2]; + unsigned long ssleay_value; + sub[1]='\0'; + ssleay_value=SSLeay(); + if(ssleay_value < 0x906000) { + ssleay_value=SSLEAY_VERSION_NUMBER; + sub[0]='\0'; + } + else { + if(ssleay_value&0xff0) { + sub[0]=(char)(((ssleay_value>>4)&0xff) + 'a' -1); + } + else + sub[0]='\0'; + } + + return snprintf(buffer, size, "OpenSSL/%lx.%lx.%lx%s", + (ssleay_value>>28)&0xf, + (ssleay_value>>20)&0xff, + (ssleay_value>>12)&0xff, + sub); + } + +#else /* SSLEAY_VERSION_NUMBER is less than 0.9.5 */ + +#if(SSLEAY_VERSION_NUMBER >= 0x900000) + return snprintf(buffer, size, "OpenSSL/%lx.%lx.%lx", + (SSLEAY_VERSION_NUMBER>>28)&0xff, + (SSLEAY_VERSION_NUMBER>>20)&0xff, + (SSLEAY_VERSION_NUMBER>>12)&0xf); + +#else /* (SSLEAY_VERSION_NUMBER >= 0x900000) */ + { + char sub[2]; + sub[1]='\0'; + if(SSLEAY_VERSION_NUMBER&0x0f) { + sub[0]=(SSLEAY_VERSION_NUMBER&0x0f) + 'a' -1; + } + else + sub[0]='\0'; + + return snprintf(buffer, size, "SSL/%x.%x.%x%s", + (SSLEAY_VERSION_NUMBER>>12)&0xff, + (SSLEAY_VERSION_NUMBER>>8)&0xf, + (SSLEAY_VERSION_NUMBER>>4)&0xf, sub); + } +#endif /* (SSLEAY_VERSION_NUMBER >= 0x900000) */ +#endif /* SSLEAY_VERSION_NUMBER is less than 0.9.5 */ + +#endif /* YASSL_VERSION */ +} + +void Curl_ossl_random(struct SessionHandle *data, unsigned char *entropy, + size_t length) +{ + Curl_ossl_seed(data); /* Initiate the seed if not already done */ + RAND_bytes(entropy, curlx_uztosi(length)); +} + +void Curl_ossl_md5sum(unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *md5sum /* output */, + size_t unused) +{ + MD5_CTX MD5pw; + (void)unused; + MD5_Init(&MD5pw); + MD5_Update(&MD5pw, tmp, tmplen); + MD5_Final(md5sum, &MD5pw); +} +#endif /* USE_SSLEAY */ diff --git a/lib/curl_strdup.c b/lib/curl_strdup.c new file mode 100644 index 000000000..8dcaa67f0 --- /dev/null +++ b/lib/curl_strdup.c @@ -0,0 +1,52 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ +/* + * This file is 'mem-include-scan' clean. See test 1132. + */ +#include "curl_setup.h" + +#include "curl_strdup.h" + +#ifndef HAVE_STRDUP +char *curlx_strdup(const char *str) +{ + size_t len; + char *newstr; + + if(!str) + return (char *)NULL; + + len = strlen(str); + + if(len >= ((size_t)-1) / sizeof(char)) + return (char *)NULL; + + newstr = malloc((len+1)*sizeof(char)); + if(!newstr) + return (char *)NULL; + + memcpy(newstr,str,(len+1)*sizeof(char)); + + return newstr; + +} +#endif diff --git a/lib/curl_strequal.c b/lib/curl_strequal.c new file mode 100644 index 000000000..5d370c854 --- /dev/null +++ b/lib/curl_strequal.c @@ -0,0 +1,124 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_STRINGS_H +#include +#endif + +#include "curl_strequal.h" + +/* + * @unittest: 1301 + */ +int curl_strequal(const char *first, const char *second) +{ +#if defined(HAVE_STRCASECMP) + return !(strcasecmp)(first, second); +#elif defined(HAVE_STRCMPI) + return !(strcmpi)(first, second); +#elif defined(HAVE_STRICMP) + return !(stricmp)(first, second); +#else + while(*first && *second) { + if(toupper(*first) != toupper(*second)) { + break; + } + first++; + second++; + } + return toupper(*first) == toupper(*second); +#endif +} + +/* + * @unittest: 1301 + */ +int curl_strnequal(const char *first, const char *second, size_t max) +{ +#if defined(HAVE_STRNCASECMP) + return !strncasecmp(first, second, max); +#elif defined(HAVE_STRNCMPI) + return !strncmpi(first, second, max); +#elif defined(HAVE_STRNICMP) + return !strnicmp(first, second, max); +#else + while(*first && *second && max) { + if(toupper(*first) != toupper(*second)) { + break; + } + max--; + first++; + second++; + } + if(0 == max) + return 1; /* they are equal this far */ + + return toupper(*first) == toupper(*second); +#endif +} + +#ifndef HAVE_STRLCAT +/* + * The strlcat() function appends the NUL-terminated string src to the end + * of dst. It will append at most size - strlen(dst) - 1 bytes, NUL-termi- + * nating the result. + * + * The strlcpy() and strlcat() functions return the total length of the + * string they tried to create. For strlcpy() that means the length of src. + * For strlcat() that means the initial length of dst plus the length of + * src. While this may seem somewhat confusing it was done to make trunca- + * tion detection simple. + * + * + */ +size_t Curl_strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + union { + ssize_t sig; + size_t uns; + } dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while(n-- != 0 && *d != '\0') + d++; + dlen.sig = d - dst; + n = siz - dlen.uns; + + if(n == 0) + return(dlen.uns + strlen(s)); + while(*s != '\0') { + if(n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen.uns + (s - src)); /* count does not include NUL */ +} +#endif diff --git a/lib/curl_strerror.c b/lib/curl_strerror.c new file mode 100644 index 000000000..27567a1ac --- /dev/null +++ b/lib/curl_strerror.c @@ -0,0 +1,1119 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2004 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_STRERROR_R +# if (!defined(HAVE_POSIX_STRERROR_R) && \ + !defined(HAVE_GLIBC_STRERROR_R) && \ + !defined(HAVE_VXWORKS_STRERROR_R)) || \ + (defined(HAVE_POSIX_STRERROR_R) && defined(HAVE_VXWORKS_STRERROR_R)) || \ + (defined(HAVE_GLIBC_STRERROR_R) && defined(HAVE_VXWORKS_STRERROR_R)) || \ + (defined(HAVE_POSIX_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R)) +# error "strerror_r MUST be either POSIX, glibc or vxworks-style" +# endif +#endif + +#include + +#ifdef USE_LIBIDN +#include +#endif + +#include "curl_strerror.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +const char * +curl_easy_strerror(CURLcode error) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch (error) { + case CURLE_OK: + return "No error"; + + case CURLE_UNSUPPORTED_PROTOCOL: + return "Unsupported protocol"; + + case CURLE_FAILED_INIT: + return "Failed initialization"; + + case CURLE_URL_MALFORMAT: + return "URL using bad/illegal format or missing URL"; + + case CURLE_NOT_BUILT_IN: + return "A requested feature, protocol or option was not found built-in in" + " this libcurl due to a build-time decision."; + + case CURLE_COULDNT_RESOLVE_PROXY: + return "Couldn't resolve proxy name"; + + case CURLE_COULDNT_RESOLVE_HOST: + return "Couldn't resolve host name"; + + case CURLE_COULDNT_CONNECT: + return "Couldn't connect to server"; + + case CURLE_FTP_WEIRD_SERVER_REPLY: + return "FTP: weird server reply"; + + case CURLE_REMOTE_ACCESS_DENIED: + return "Access denied to remote resource"; + + case CURLE_FTP_ACCEPT_FAILED: + return "FTP: The server failed to connect to data port"; + + case CURLE_FTP_ACCEPT_TIMEOUT: + return "FTP: Accepting server connect has timed out"; + + case CURLE_FTP_PRET_FAILED: + return "FTP: The server did not accept the PRET command."; + + case CURLE_FTP_WEIRD_PASS_REPLY: + return "FTP: unknown PASS reply"; + + case CURLE_FTP_WEIRD_PASV_REPLY: + return "FTP: unknown PASV reply"; + + case CURLE_FTP_WEIRD_227_FORMAT: + return "FTP: unknown 227 response format"; + + case CURLE_FTP_CANT_GET_HOST: + return "FTP: can't figure out the host in the PASV response"; + + case CURLE_FTP_COULDNT_SET_TYPE: + return "FTP: couldn't set file type"; + + case CURLE_PARTIAL_FILE: + return "Transferred a partial file"; + + case CURLE_FTP_COULDNT_RETR_FILE: + return "FTP: couldn't retrieve (RETR failed) the specified file"; + + case CURLE_QUOTE_ERROR: + return "Quote command returned error"; + + case CURLE_HTTP_RETURNED_ERROR: + return "HTTP response code said error"; + + case CURLE_WRITE_ERROR: + return "Failed writing received data to disk/application"; + + case CURLE_UPLOAD_FAILED: + return "Upload failed (at start/before it took off)"; + + case CURLE_READ_ERROR: + return "Failed to open/read local data from file/application"; + + case CURLE_OUT_OF_MEMORY: + return "Out of memory"; + + case CURLE_OPERATION_TIMEDOUT: + return "Timeout was reached"; + + case CURLE_FTP_PORT_FAILED: + return "FTP: command PORT failed"; + + case CURLE_FTP_COULDNT_USE_REST: + return "FTP: command REST failed"; + + case CURLE_RANGE_ERROR: + return "Requested range was not delivered by the server"; + + case CURLE_HTTP_POST_ERROR: + return "Internal problem setting up the POST"; + + case CURLE_SSL_CONNECT_ERROR: + return "SSL connect error"; + + case CURLE_BAD_DOWNLOAD_RESUME: + return "Couldn't resume download"; + + case CURLE_FILE_COULDNT_READ_FILE: + return "Couldn't read a file:// file"; + + case CURLE_LDAP_CANNOT_BIND: + return "LDAP: cannot bind"; + + case CURLE_LDAP_SEARCH_FAILED: + return "LDAP: search failed"; + + case CURLE_FUNCTION_NOT_FOUND: + return "A required function in the library was not found"; + + case CURLE_ABORTED_BY_CALLBACK: + return "Operation was aborted by an application callback"; + + case CURLE_BAD_FUNCTION_ARGUMENT: + return "A libcurl function was given a bad argument"; + + case CURLE_INTERFACE_FAILED: + return "Failed binding local connection end"; + + case CURLE_TOO_MANY_REDIRECTS : + return "Number of redirects hit maximum amount"; + + case CURLE_UNKNOWN_OPTION: + return "An unknown option was passed in to libcurl"; + + case CURLE_TELNET_OPTION_SYNTAX : + return "Malformed telnet option"; + + case CURLE_PEER_FAILED_VERIFICATION: + return "SSL peer certificate or SSH remote key was not OK"; + + case CURLE_GOT_NOTHING: + return "Server returned nothing (no headers, no data)"; + + case CURLE_SSL_ENGINE_NOTFOUND: + return "SSL crypto engine not found"; + + case CURLE_SSL_ENGINE_SETFAILED: + return "Can not set SSL crypto engine as default"; + + case CURLE_SSL_ENGINE_INITFAILED: + return "Failed to initialise SSL crypto engine"; + + case CURLE_SEND_ERROR: + return "Failed sending data to the peer"; + + case CURLE_RECV_ERROR: + return "Failure when receiving data from the peer"; + + case CURLE_SSL_CERTPROBLEM: + return "Problem with the local SSL certificate"; + + case CURLE_SSL_CIPHER: + return "Couldn't use specified SSL cipher"; + + case CURLE_SSL_CACERT: + return "Peer certificate cannot be authenticated with given CA " + "certificates"; + + case CURLE_SSL_CACERT_BADFILE: + return "Problem with the SSL CA cert (path? access rights?)"; + + case CURLE_BAD_CONTENT_ENCODING: + return "Unrecognized or bad HTTP Content or Transfer-Encoding"; + + case CURLE_LDAP_INVALID_URL: + return "Invalid LDAP URL"; + + case CURLE_FILESIZE_EXCEEDED: + return "Maximum file size exceeded"; + + case CURLE_USE_SSL_FAILED: + return "Requested SSL level failed"; + + case CURLE_SSL_SHUTDOWN_FAILED: + return "Failed to shut down the SSL connection"; + + case CURLE_SSL_CRL_BADFILE: + return "Failed to load CRL file (path? access rights?, format?)"; + + case CURLE_SSL_ISSUER_ERROR: + return "Issuer check against peer certificate failed"; + + case CURLE_SEND_FAIL_REWIND: + return "Send failed since rewinding of the data stream failed"; + + case CURLE_LOGIN_DENIED: + return "Login denied"; + + case CURLE_TFTP_NOTFOUND: + return "TFTP: File Not Found"; + + case CURLE_TFTP_PERM: + return "TFTP: Access Violation"; + + case CURLE_REMOTE_DISK_FULL: + return "Disk full or allocation exceeded"; + + case CURLE_TFTP_ILLEGAL: + return "TFTP: Illegal operation"; + + case CURLE_TFTP_UNKNOWNID: + return "TFTP: Unknown transfer ID"; + + case CURLE_REMOTE_FILE_EXISTS: + return "Remote file already exists"; + + case CURLE_TFTP_NOSUCHUSER: + return "TFTP: No such user"; + + case CURLE_CONV_FAILED: + return "Conversion failed"; + + case CURLE_CONV_REQD: + return "Caller must register CURLOPT_CONV_ callback options"; + + case CURLE_REMOTE_FILE_NOT_FOUND: + return "Remote file not found"; + + case CURLE_SSH: + return "Error in the SSH layer"; + + case CURLE_AGAIN: + return "Socket not ready for send/recv"; + + case CURLE_RTSP_CSEQ_ERROR: + return "RTSP CSeq mismatch or invalid CSeq"; + + case CURLE_RTSP_SESSION_ERROR: + return "RTSP session error"; + + case CURLE_FTP_BAD_FILE_LIST: + return "Unable to parse FTP file list"; + + case CURLE_CHUNK_FAILED: + return "Chunk callback failed"; + + /* error codes not used by current libcurl */ + case CURLE_OBSOLETE16: + case CURLE_OBSOLETE20: + case CURLE_OBSOLETE24: + case CURLE_OBSOLETE29: + case CURLE_OBSOLETE32: + case CURLE_OBSOLETE40: + case CURLE_OBSOLETE44: + case CURLE_OBSOLETE46: + case CURLE_OBSOLETE50: + case CURLE_OBSOLETE57: + case CURL_LAST: + break; + } + /* + * By using a switch, gcc -Wall will complain about enum values + * which do not appear, helping keep this function up-to-date. + * By using gcc -Wall -Werror, you can't forget. + * + * A table would not have the same benefit. Most compilers will + * generate code very similar to a table in any case, so there + * is little performance gain from a table. And something is broken + * for the user's application, anyways, so does it matter how fast + * it _doesn't_ work? + * + * The line number for the error will be near this comment, which + * is why it is here, and not at the start of the switch. + */ + return "Unknown error"; +#else + if(error == CURLE_OK) + return "No error"; + else + return "Error"; +#endif +} + +const char * +curl_multi_strerror(CURLMcode error) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch (error) { + case CURLM_CALL_MULTI_PERFORM: + return "Please call curl_multi_perform() soon"; + + case CURLM_OK: + return "No error"; + + case CURLM_BAD_HANDLE: + return "Invalid multi handle"; + + case CURLM_BAD_EASY_HANDLE: + return "Invalid easy handle"; + + case CURLM_OUT_OF_MEMORY: + return "Out of memory"; + + case CURLM_INTERNAL_ERROR: + return "Internal error"; + + case CURLM_BAD_SOCKET: + return "Invalid socket argument"; + + case CURLM_UNKNOWN_OPTION: + return "Unknown option"; + + case CURLM_LAST: + break; + } + + return "Unknown error"; +#else + if(error == CURLM_OK) + return "No error"; + else + return "Error"; +#endif +} + +const char * +curl_share_strerror(CURLSHcode error) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch (error) { + case CURLSHE_OK: + return "No error"; + + case CURLSHE_BAD_OPTION: + return "Unknown share option"; + + case CURLSHE_IN_USE: + return "Share currently in use"; + + case CURLSHE_INVALID: + return "Invalid share handle"; + + case CURLSHE_NOMEM: + return "Out of memory"; + + case CURLSHE_NOT_BUILT_IN: + return "Feature not enabled in this library"; + + case CURLSHE_LAST: + break; + } + + return "CURLSHcode unknown"; +#else + if(error == CURLSHE_OK) + return "No error"; + else + return "Error"; +#endif +} + +#ifdef USE_WINSOCK + +/* This function handles most / all (?) Winsock errors cURL is able to produce. + */ +static const char * +get_winsock_error (int err, char *buf, size_t len) +{ + const char *p; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch (err) { + case WSAEINTR: + p = "Call interrupted"; + break; + case WSAEBADF: + p = "Bad file"; + break; + case WSAEACCES: + p = "Bad access"; + break; + case WSAEFAULT: + p = "Bad argument"; + break; + case WSAEINVAL: + p = "Invalid arguments"; + break; + case WSAEMFILE: + p = "Out of file descriptors"; + break; + case WSAEWOULDBLOCK: + p = "Call would block"; + break; + case WSAEINPROGRESS: + case WSAEALREADY: + p = "Blocking call in progress"; + break; + case WSAENOTSOCK: + p = "Descriptor is not a socket"; + break; + case WSAEDESTADDRREQ: + p = "Need destination address"; + break; + case WSAEMSGSIZE: + p = "Bad message size"; + break; + case WSAEPROTOTYPE: + p = "Bad protocol"; + break; + case WSAENOPROTOOPT: + p = "Protocol option is unsupported"; + break; + case WSAEPROTONOSUPPORT: + p = "Protocol is unsupported"; + break; + case WSAESOCKTNOSUPPORT: + p = "Socket is unsupported"; + break; + case WSAEOPNOTSUPP: + p = "Operation not supported"; + break; + case WSAEAFNOSUPPORT: + p = "Address family not supported"; + break; + case WSAEPFNOSUPPORT: + p = "Protocol family not supported"; + break; + case WSAEADDRINUSE: + p = "Address already in use"; + break; + case WSAEADDRNOTAVAIL: + p = "Address not available"; + break; + case WSAENETDOWN: + p = "Network down"; + break; + case WSAENETUNREACH: + p = "Network unreachable"; + break; + case WSAENETRESET: + p = "Network has been reset"; + break; + case WSAECONNABORTED: + p = "Connection was aborted"; + break; + case WSAECONNRESET: + p = "Connection was reset"; + break; + case WSAENOBUFS: + p = "No buffer space"; + break; + case WSAEISCONN: + p = "Socket is already connected"; + break; + case WSAENOTCONN: + p = "Socket is not connected"; + break; + case WSAESHUTDOWN: + p = "Socket has been shut down"; + break; + case WSAETOOMANYREFS: + p = "Too many references"; + break; + case WSAETIMEDOUT: + p = "Timed out"; + break; + case WSAECONNREFUSED: + p = "Connection refused"; + break; + case WSAELOOP: + p = "Loop??"; + break; + case WSAENAMETOOLONG: + p = "Name too long"; + break; + case WSAEHOSTDOWN: + p = "Host down"; + break; + case WSAEHOSTUNREACH: + p = "Host unreachable"; + break; + case WSAENOTEMPTY: + p = "Not empty"; + break; + case WSAEPROCLIM: + p = "Process limit reached"; + break; + case WSAEUSERS: + p = "Too many users"; + break; + case WSAEDQUOT: + p = "Bad quota"; + break; + case WSAESTALE: + p = "Something is stale"; + break; + case WSAEREMOTE: + p = "Remote error"; + break; +#ifdef WSAEDISCON /* missing in SalfordC! */ + case WSAEDISCON: + p = "Disconnected"; + break; +#endif + /* Extended Winsock errors */ + case WSASYSNOTREADY: + p = "Winsock library is not ready"; + break; + case WSANOTINITIALISED: + p = "Winsock library not initialised"; + break; + case WSAVERNOTSUPPORTED: + p = "Winsock version not supported"; + break; + + /* getXbyY() errors (already handled in herrmsg): + * Authoritative Answer: Host not found */ + case WSAHOST_NOT_FOUND: + p = "Host not found"; + break; + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + case WSATRY_AGAIN: + p = "Host not found, try again"; + break; + + /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */ + case WSANO_RECOVERY: + p = "Unrecoverable error in call to nameserver"; + break; + + /* Valid name, no data record of requested type */ + case WSANO_DATA: + p = "No data record of requested type"; + break; + + default: + return NULL; + } +#else + if(err == CURLE_OK) + return NULL; + else + p = "error"; +#endif + strncpy (buf, p, len); + buf [len-1] = '\0'; + return buf; +} +#endif /* USE_WINSOCK */ + +/* + * Our thread-safe and smart strerror() replacement. + * + * The 'err' argument passed in to this function MUST be a true errno number + * as reported on this system. We do no range checking on the number before + * we pass it to the "number-to-message" conversion function and there might + * be systems that don't do proper range checking in there themselves. + * + * We don't do range checking (on systems other than Windows) since there is + * no good reliable and portable way to do it. + */ +const char *Curl_strerror(struct connectdata *conn, int err) +{ + char *buf, *p; + size_t max; + int old_errno = ERRNO; + + DEBUGASSERT(conn); + DEBUGASSERT(err >= 0); + + buf = conn->syserr_buf; + max = sizeof(conn->syserr_buf)-1; + *buf = '\0'; + +#ifdef USE_WINSOCK + +#ifdef _WIN32_WCE + { + wchar_t wbuf[256]; + wbuf[0] = L'\0'; + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, + LANG_NEUTRAL, wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL); + wcstombs(buf,wbuf,max); + } +#else + /* 'sys_nerr' is the maximum errno number, it is not widely portable */ + if(err >= 0 && err < sys_nerr) + strncpy(buf, strerror(err), max); + else { + if(!get_winsock_error(err, buf, max) && + !FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, + LANG_NEUTRAL, buf, (DWORD)max, NULL)) + snprintf(buf, max, "Unknown error %d (%#x)", err, err); + } +#endif + +#else /* not USE_WINSOCK coming up */ + +#if defined(HAVE_STRERROR_R) && defined(HAVE_POSIX_STRERROR_R) + /* + * The POSIX-style strerror_r() may set errno to ERANGE if insufficient + * storage is supplied via 'strerrbuf' and 'buflen' to hold the generated + * message string, or EINVAL if 'errnum' is not a valid error number. + */ + if(0 != strerror_r(err, buf, max)) { + if('\0' == buf[0]) + snprintf(buf, max, "Unknown error %d", err); + } +#elif defined(HAVE_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R) + /* + * The glibc-style strerror_r() only *might* use the buffer we pass to + * the function, but it always returns the error message as a pointer, + * so we must copy that string unconditionally (if non-NULL). + */ + { + char buffer[256]; + char *msg = strerror_r(err, buffer, sizeof(buffer)); + if(msg) + strncpy(buf, msg, max); + else + snprintf(buf, max, "Unknown error %d", err); + } +#elif defined(HAVE_STRERROR_R) && defined(HAVE_VXWORKS_STRERROR_R) + /* + * The vxworks-style strerror_r() does use the buffer we pass to the function. + * The buffer size should be at least MAXERRSTR_SIZE (150) defined in rtsold.h + */ + { + char buffer[256]; + if(OK == strerror_r(err, buffer)) + strncpy(buf, buffer, max); + else + snprintf(buf, max, "Unknown error %d", err); + } +#else + { + char *msg = strerror(err); + if(msg) + strncpy(buf, msg, max); + else + snprintf(buf, max, "Unknown error %d", err); + } +#endif + +#endif /* end of ! USE_WINSOCK */ + + buf[max] = '\0'; /* make sure the string is zero terminated */ + + /* strip trailing '\r\n' or '\n'. */ + if((p = strrchr(buf,'\n')) != NULL && (p - buf) >= 2) + *p = '\0'; + if((p = strrchr(buf,'\r')) != NULL && (p - buf) >= 1) + *p = '\0'; + + if(old_errno != ERRNO) + SET_ERRNO(old_errno); + + return buf; +} + +#ifdef USE_LIBIDN +/* + * Return error-string for libidn status as returned from idna_to_ascii_lz(). + */ +const char *Curl_idn_strerror (struct connectdata *conn, int err) +{ +#ifdef HAVE_IDNA_STRERROR + (void)conn; + return idna_strerror((Idna_rc) err); +#else + const char *str; + char *buf; + size_t max; + + DEBUGASSERT(conn); + + buf = conn->syserr_buf; + max = sizeof(conn->syserr_buf)-1; + *buf = '\0'; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + switch ((Idna_rc)err) { + case IDNA_SUCCESS: + str = "No error"; + break; + case IDNA_STRINGPREP_ERROR: + str = "Error in string preparation"; + break; + case IDNA_PUNYCODE_ERROR: + str = "Error in Punycode operation"; + break; + case IDNA_CONTAINS_NON_LDH: + str = "Illegal ASCII characters"; + break; + case IDNA_CONTAINS_MINUS: + str = "Contains minus"; + break; + case IDNA_INVALID_LENGTH: + str = "Invalid output length"; + break; + case IDNA_NO_ACE_PREFIX: + str = "No ACE prefix (\"xn--\")"; + break; + case IDNA_ROUNDTRIP_VERIFY_ERROR: + str = "Round trip verify error"; + break; + case IDNA_CONTAINS_ACE_PREFIX: + str = "Already have ACE prefix (\"xn--\")"; + break; + case IDNA_ICONV_ERROR: + str = "Locale conversion failed"; + break; + case IDNA_MALLOC_ERROR: + str = "Allocation failed"; + break; + case IDNA_DLOPEN_ERROR: + str = "dlopen() error"; + break; + default: + snprintf(buf, max, "error %d", err); + str = NULL; + break; + } +#else + if((Idna_rc)err == IDNA_SUCCESS) + str = "No error"; + else + str = "Error"; +#endif + if(str) + strncpy(buf, str, max); + buf[max] = '\0'; + return (buf); +#endif +} +#endif /* USE_LIBIDN */ + +#ifdef USE_WINDOWS_SSPI +const char *Curl_sspi_strerror (struct connectdata *conn, int err) +{ +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char txtbuf[80]; + char msgbuf[sizeof(conn->syserr_buf)]; + char *p, *str, *msg = NULL; + bool msg_formatted = FALSE; + int old_errno; +#endif + const char *txt; + char *outbuf; + size_t outmax; + + DEBUGASSERT(conn); + + outbuf = conn->syserr_buf; + outmax = sizeof(conn->syserr_buf)-1; + *outbuf = '\0'; + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + + old_errno = ERRNO; + + switch (err) { + case SEC_E_OK: + txt = "No error"; + break; + case SEC_E_ALGORITHM_MISMATCH: + txt = "SEC_E_ALGORITHM_MISMATCH"; + break; + case SEC_E_BAD_BINDINGS: + txt = "SEC_E_BAD_BINDINGS"; + break; + case SEC_E_BAD_PKGID: + txt = "SEC_E_BAD_PKGID"; + break; + case SEC_E_BUFFER_TOO_SMALL: + txt = "SEC_E_BUFFER_TOO_SMALL"; + break; + case SEC_E_CANNOT_INSTALL: + txt = "SEC_E_CANNOT_INSTALL"; + break; + case SEC_E_CANNOT_PACK: + txt = "SEC_E_CANNOT_PACK"; + break; + case SEC_E_CERT_EXPIRED: + txt = "SEC_E_CERT_EXPIRED"; + break; + case SEC_E_CERT_UNKNOWN: + txt = "SEC_E_CERT_UNKNOWN"; + break; + case SEC_E_CERT_WRONG_USAGE: + txt = "SEC_E_CERT_WRONG_USAGE"; + break; + case SEC_E_CONTEXT_EXPIRED: + txt = "SEC_E_CONTEXT_EXPIRED"; + break; + case SEC_E_CROSSREALM_DELEGATION_FAILURE: + txt = "SEC_E_CROSSREALM_DELEGATION_FAILURE"; + break; + case SEC_E_CRYPTO_SYSTEM_INVALID: + txt = "SEC_E_CRYPTO_SYSTEM_INVALID"; + break; + case SEC_E_DECRYPT_FAILURE: + txt = "SEC_E_DECRYPT_FAILURE"; + break; + case SEC_E_DELEGATION_POLICY: + txt = "SEC_E_DELEGATION_POLICY"; + break; + case SEC_E_DELEGATION_REQUIRED: + txt = "SEC_E_DELEGATION_REQUIRED"; + break; + case SEC_E_DOWNGRADE_DETECTED: + txt = "SEC_E_DOWNGRADE_DETECTED"; + break; + case SEC_E_ENCRYPT_FAILURE: + txt = "SEC_E_ENCRYPT_FAILURE"; + break; + case SEC_E_ILLEGAL_MESSAGE: + txt = "SEC_E_ILLEGAL_MESSAGE"; + break; + case SEC_E_INCOMPLETE_CREDENTIALS: + txt = "SEC_E_INCOMPLETE_CREDENTIALS"; + break; + case SEC_E_INCOMPLETE_MESSAGE: + txt = "SEC_E_INCOMPLETE_MESSAGE"; + 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_PARAMETER: + txt = "SEC_E_INVALID_PARAMETER"; + break; + case SEC_E_INVALID_TOKEN: + txt = "SEC_E_INVALID_TOKEN"; + break; + case SEC_E_ISSUING_CA_UNTRUSTED: + txt = "SEC_E_ISSUING_CA_UNTRUSTED"; + break; + case SEC_E_ISSUING_CA_UNTRUSTED_KDC: + txt = "SEC_E_ISSUING_CA_UNTRUSTED_KDC"; + break; + case SEC_E_KDC_CERT_EXPIRED: + txt = "SEC_E_KDC_CERT_EXPIRED"; + break; + case SEC_E_KDC_CERT_REVOKED: + txt = "SEC_E_KDC_CERT_REVOKED"; + break; + case SEC_E_KDC_INVALID_REQUEST: + txt = "SEC_E_KDC_INVALID_REQUEST"; + break; + case SEC_E_KDC_UNABLE_TO_REFER: + txt = "SEC_E_KDC_UNABLE_TO_REFER"; + break; + case SEC_E_KDC_UNKNOWN_ETYPE: + txt = "SEC_E_KDC_UNKNOWN_ETYPE"; + break; + case SEC_E_LOGON_DENIED: + txt = "SEC_E_LOGON_DENIED"; + break; + case SEC_E_MAX_REFERRALS_EXCEEDED: + txt = "SEC_E_MAX_REFERRALS_EXCEEDED"; + break; + case SEC_E_MESSAGE_ALTERED: + txt = "SEC_E_MESSAGE_ALTERED"; + break; + case SEC_E_MULTIPLE_ACCOUNTS: + txt = "SEC_E_MULTIPLE_ACCOUNTS"; + break; + case SEC_E_MUST_BE_KDC: + txt = "SEC_E_MUST_BE_KDC"; + break; + case SEC_E_NOT_OWNER: + txt = "SEC_E_NOT_OWNER"; + 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_NO_IMPERSONATION: + txt = "SEC_E_NO_IMPERSONATION"; + break; + case SEC_E_NO_IP_ADDRESSES: + txt = "SEC_E_NO_IP_ADDRESSES"; + break; + case SEC_E_NO_KERB_KEY: + txt = "SEC_E_NO_KERB_KEY"; + break; + case SEC_E_NO_PA_DATA: + txt = "SEC_E_NO_PA_DATA"; + break; + case SEC_E_NO_S4U_PROT_SUPPORT: + txt = "SEC_E_NO_S4U_PROT_SUPPORT"; + break; + case SEC_E_NO_TGT_REPLY: + txt = "SEC_E_NO_TGT_REPLY"; + break; + case SEC_E_OUT_OF_SEQUENCE: + txt = "SEC_E_OUT_OF_SEQUENCE"; + break; + case SEC_E_PKINIT_CLIENT_FAILURE: + txt = "SEC_E_PKINIT_CLIENT_FAILURE"; + break; + case SEC_E_PKINIT_NAME_MISMATCH: + txt = "SEC_E_PKINIT_NAME_MISMATCH"; + break; + case SEC_E_POLICY_NLTM_ONLY: + txt = "SEC_E_POLICY_NLTM_ONLY"; + break; + case SEC_E_QOP_NOT_SUPPORTED: + txt = "SEC_E_QOP_NOT_SUPPORTED"; + break; + case SEC_E_REVOCATION_OFFLINE_C: + txt = "SEC_E_REVOCATION_OFFLINE_C"; + break; + case SEC_E_REVOCATION_OFFLINE_KDC: + txt = "SEC_E_REVOCATION_OFFLINE_KDC"; + break; + case SEC_E_SECPKG_NOT_FOUND: + txt = "SEC_E_SECPKG_NOT_FOUND"; + break; + case SEC_E_SECURITY_QOS_FAILED: + txt = "SEC_E_SECURITY_QOS_FAILED"; + break; + case SEC_E_SHUTDOWN_IN_PROGRESS: + txt = "SEC_E_SHUTDOWN_IN_PROGRESS"; + break; + case SEC_E_SMARTCARD_CERT_EXPIRED: + txt = "SEC_E_SMARTCARD_CERT_EXPIRED"; + break; + case SEC_E_SMARTCARD_CERT_REVOKED: + txt = "SEC_E_SMARTCARD_CERT_REVOKED"; + break; + case SEC_E_SMARTCARD_LOGON_REQUIRED: + txt = "SEC_E_SMARTCARD_LOGON_REQUIRED"; + break; + case SEC_E_STRONG_CRYPTO_NOT_SUPPORTED: + txt = "SEC_E_STRONG_CRYPTO_NOT_SUPPORTED"; + break; + case SEC_E_TARGET_UNKNOWN: + txt = "SEC_E_TARGET_UNKNOWN"; + break; + case SEC_E_TIME_SKEW: + txt = "SEC_E_TIME_SKEW"; + break; + case SEC_E_TOO_MANY_PRINCIPALS: + txt = "SEC_E_TOO_MANY_PRINCIPALS"; + break; + case SEC_E_UNFINISHED_CONTEXT_DELETED: + txt = "SEC_E_UNFINISHED_CONTEXT_DELETED"; + break; + case SEC_E_UNKNOWN_CREDENTIALS: + txt = "SEC_E_UNKNOWN_CREDENTIALS"; + break; + case SEC_E_UNSUPPORTED_FUNCTION: + txt = "SEC_E_UNSUPPORTED_FUNCTION"; + break; + case SEC_E_UNSUPPORTED_PREAUTH: + txt = "SEC_E_UNSUPPORTED_PREAUTH"; + break; + case SEC_E_UNTRUSTED_ROOT: + txt = "SEC_E_UNTRUSTED_ROOT"; + break; + case SEC_E_WRONG_CREDENTIAL_HANDLE: + txt = "SEC_E_WRONG_CREDENTIAL_HANDLE"; + break; + case SEC_E_WRONG_PRINCIPAL: + txt = "SEC_E_WRONG_PRINCIPAL"; + break; + 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_CONTEXT_EXPIRED: + txt = "SEC_I_CONTEXT_EXPIRED"; + 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_I_LOCAL_LOGON: + txt = "SEC_I_LOCAL_LOGON"; + break; + case SEC_I_NO_LSA_CONTEXT: + txt = "SEC_I_NO_LSA_CONTEXT"; + break; + case SEC_I_RENEGOTIATE: + txt = "SEC_I_RENEGOTIATE"; + break; + case SEC_I_SIGNATURE_NEEDED: + txt = "SEC_I_SIGNATURE_NEEDED"; + break; + default: + txt = "Unknown error"; + } + + if(err == SEC_E_OK) + strncpy(outbuf, txt, outmax); + else { + str = txtbuf; + snprintf(txtbuf, sizeof(txtbuf), "%s (0x%04X%04X)", + txt, (err >> 16) & 0xffff, err & 0xffff); + txtbuf[sizeof(txtbuf)-1] = '\0'; + +#ifdef _WIN32_WCE + { + wchar_t wbuf[256]; + wbuf[0] = L'\0'; + + if(FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, LANG_NEUTRAL, + wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL)) { + wcstombs(msgbuf,wbuf,sizeof(msgbuf)-1); + msg_formatted = TRUE; + } + } +#else + if(FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, LANG_NEUTRAL, + msgbuf, sizeof(msgbuf)-1, NULL)) { + msg_formatted = TRUE; + } +#endif + if(msg_formatted) { + msgbuf[sizeof(msgbuf)-1] = '\0'; + /* strip trailing '\r\n' or '\n' */ + if((p = strrchr(msgbuf,'\n')) != NULL && (p - msgbuf) >= 2) + *p = '\0'; + if((p = strrchr(msgbuf,'\r')) != NULL && (p - msgbuf) >= 1) + *p = '\0'; + msg = msgbuf; + } + if(msg) + snprintf(outbuf, outmax, "%s - %s", str, msg); + else + strncpy(outbuf, str, outmax); + } + + if(old_errno != ERRNO) + SET_ERRNO(old_errno); + +#else + + if(err == SEC_E_OK) + txt = "No error"; + else + txt = "Error"; + + strncpy(outbuf, txt, outmax); + +#endif + + outbuf[outmax] = '\0'; + + return outbuf; +} +#endif /* USE_WINDOWS_SSPI */ diff --git a/lib/curl_strtok.c b/lib/curl_strtok.c new file mode 100644 index 000000000..33bdd96af --- /dev/null +++ b/lib/curl_strtok.c @@ -0,0 +1,66 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2007, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef HAVE_STRTOK_R +#include + +#include "curl_strtok.h" + +char * +Curl_strtok_r(char *ptr, const char *sep, char **end) +{ + if(!ptr) + /* we got NULL input so then we get our last position instead */ + ptr = *end; + + /* pass all letters that are including in the separator string */ + while(*ptr && strchr(sep, *ptr)) + ++ptr; + + if(*ptr) { + /* so this is where the next piece of string starts */ + char *start = ptr; + + /* set the end pointer to the first byte after the start */ + *end = start + 1; + + /* scan through the string to find where it ends, it ends on a + null byte or a character that exists in the separator string */ + while(**end && !strchr(sep, **end)) + ++*end; + + if(**end) { + /* the end is not a null byte */ + **end = '\0'; /* zero terminate it! */ + ++*end; /* advance the last pointer to beyond the null byte */ + } + + return start; /* return the position where the string starts */ + } + + /* we ended up on a null byte, there are no more strings to find! */ + return NULL; +} + +#endif /* this was only compiled if strtok_r wasn't present */ diff --git a/lib/curl_strtoofft.c b/lib/curl_strtoofft.c new file mode 100644 index 000000000..d203d9cc7 --- /dev/null +++ b/lib/curl_strtoofft.c @@ -0,0 +1,188 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_strtoofft.h" + +/* + * NOTE: + * + * In the ISO C standard (IEEE Std 1003.1), there is a strtoimax() function we + * could use in case strtoll() doesn't exist... See + * http://www.opengroup.org/onlinepubs/009695399/functions/strtoimax.html + */ + +#ifdef NEED_CURL_STRTOLL + +/* Range tests can be used for alphanum decoding if characters are consecutive, + like in ASCII. Else an array is scanned. Determine this condition now. */ + +#if('9' - '0') != 9 || ('Z' - 'A') != 25 || ('z' - 'a') != 25 + +#define NO_RANGE_TEST + +static const char valchars[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +#endif + +static int get_char(char c, int base); + +/** + * Emulated version of the strtoll function. This extracts a long long + * value from the given input string and returns it. + */ +curl_off_t +curlx_strtoll(const char *nptr, char **endptr, int base) +{ + char *end; + int is_negative = 0; + int overflow; + int i; + curl_off_t value = 0; + curl_off_t newval; + + /* Skip leading whitespace. */ + end = (char *)nptr; + while(ISSPACE(end[0])) { + end++; + } + + /* Handle the sign, if any. */ + if(end[0] == '-') { + is_negative = 1; + end++; + } + else if(end[0] == '+') { + end++; + } + else if(end[0] == '\0') { + /* We had nothing but perhaps some whitespace -- there was no number. */ + if(endptr) { + *endptr = end; + } + return 0; + } + + /* Handle special beginnings, if present and allowed. */ + if(end[0] == '0' && end[1] == 'x') { + if(base == 16 || base == 0) { + end += 2; + base = 16; + } + } + else if(end[0] == '0') { + if(base == 8 || base == 0) { + end++; + base = 8; + } + } + + /* Matching strtol, if the base is 0 and it doesn't look like + * the number is octal or hex, we assume it's base 10. + */ + if(base == 0) { + base = 10; + } + + /* Loop handling digits. */ + value = 0; + overflow = 0; + for(i = get_char(end[0], base); + i != -1; + end++, i = get_char(end[0], base)) { + newval = base * value + i; + if(newval < value) { + /* We've overflowed. */ + overflow = 1; + break; + } + else + value = newval; + } + + if(!overflow) { + if(is_negative) { + /* Fix the sign. */ + value *= -1; + } + } + else { + if(is_negative) + value = CURL_OFF_T_MIN; + else + value = CURL_OFF_T_MAX; + + SET_ERRNO(ERANGE); + } + + if(endptr) + *endptr = end; + + return value; +} + +/** + * Returns the value of c in the given base, or -1 if c cannot + * be interpreted properly in that base (i.e., is out of range, + * is a null, etc.). + * + * @param c the character to interpret according to base + * @param base the base in which to interpret c + * + * @return the value of c in base, or -1 if c isn't in range + */ +static int get_char(char c, int base) +{ +#ifndef NO_RANGE_TEST + int value = -1; + if(c <= '9' && c >= '0') { + value = c - '0'; + } + else if(c <= 'Z' && c >= 'A') { + value = c - 'A' + 10; + } + else if(c <= 'z' && c >= 'a') { + value = c - 'a' + 10; + } +#else + const char * cp; + int value; + + cp = memchr(valchars, c, 10 + 26 + 26); + + if(!cp) + return -1; + + value = cp - valchars; + + if(value >= 10 + 26) + value -= 26; /* Lowercase. */ +#endif + + if(value >= base) { + value = -1; + } + + return value; +} +#endif /* Only present if we need strtoll, but don't have it. */ diff --git a/lib/curl_telnet.c b/lib/curl_telnet.c new file mode 100644 index 000000000..54eab1c92 --- /dev/null +++ b/lib/curl_telnet.c @@ -0,0 +1,1678 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_TELNET + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include "curl_urldata.h" +#include +#include "curl_transfer.h" +#include "curl_sendf.h" +#include "curl_telnet.h" +#include "curl_connect.h" +#include "curl_progress.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#define TELOPTS +#define TELCMDS + +#include "curl_arpa_telnet.h" +#include "curl_memory.h" +#include "curl_select.h" +#include "curl_strequal.h" +#include "curl_rawstr.h" +#include "curl_warnless.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#define SUBBUFSIZE 512 + +#define CURL_SB_CLEAR(x) x->subpointer = x->subbuffer +#define CURL_SB_TERM(x) \ + do { \ + x->subend = x->subpointer; \ + CURL_SB_CLEAR(x); \ + } WHILE_FALSE +#define CURL_SB_ACCUM(x,c) \ + do { \ + if(x->subpointer < (x->subbuffer+sizeof x->subbuffer)) \ + *x->subpointer++ = (c); \ + } WHILE_FALSE + +#define CURL_SB_GET(x) ((*x->subpointer++)&0xff) +#define CURL_SB_PEEK(x) ((*x->subpointer)&0xff) +#define CURL_SB_EOF(x) (x->subpointer >= x->subend) +#define CURL_SB_LEN(x) (x->subend - x->subpointer) + +#ifdef CURL_DISABLE_VERBOSE_STRINGS +#define printoption(a,b,c,d) Curl_nop_stmt +#endif + +#ifdef USE_WINSOCK +typedef FARPROC WSOCK2_FUNC; +static CURLcode check_wsock2 ( struct SessionHandle *data ); +#endif + +static +CURLcode telrcv(struct connectdata *, + const unsigned char *inbuf, /* Data received from socket */ + ssize_t count); /* Number of bytes received */ + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void printoption(struct SessionHandle *data, + const char *direction, + int cmd, int option); +#endif + +static void negotiate(struct connectdata *); +static void send_negotiation(struct connectdata *, int cmd, int option); +static void set_local_option(struct connectdata *, int cmd, int option); +static void set_remote_option(struct connectdata *, int cmd, int option); + +static void printsub(struct SessionHandle *data, + int direction, unsigned char *pointer, + size_t length); +static void suboption(struct connectdata *); +static void sendsuboption(struct connectdata *conn, int option); + +static CURLcode telnet_do(struct connectdata *conn, bool *done); +static CURLcode telnet_done(struct connectdata *conn, + CURLcode, bool premature); +static CURLcode send_telnet_data(struct connectdata *conn, + char *buffer, ssize_t nread); + +/* For negotiation compliant to RFC 1143 */ +#define CURL_NO 0 +#define CURL_YES 1 +#define CURL_WANTYES 2 +#define CURL_WANTNO 3 + +#define CURL_EMPTY 0 +#define CURL_OPPOSITE 1 + +/* + * Telnet receiver states for fsm + */ +typedef enum +{ + CURL_TS_DATA = 0, + CURL_TS_IAC, + CURL_TS_WILL, + CURL_TS_WONT, + CURL_TS_DO, + CURL_TS_DONT, + CURL_TS_CR, + CURL_TS_SB, /* sub-option collection */ + CURL_TS_SE /* looking for sub-option end */ +} TelnetReceive; + +struct TELNET { + int please_negotiate; + int already_negotiated; + int us[256]; + int usq[256]; + int us_preferred[256]; + int him[256]; + int himq[256]; + int him_preferred[256]; + int subnegotiation[256]; + char subopt_ttype[32]; /* Set with suboption TTYPE */ + char subopt_xdisploc[128]; /* Set with suboption XDISPLOC */ + unsigned short subopt_wsx; /* Set with suboption NAWS */ + unsigned short subopt_wsy; /* Set with suboption NAWS */ + struct curl_slist *telnet_vars; /* Environment variables */ + + /* suboptions */ + unsigned char subbuffer[SUBBUFSIZE]; + unsigned char *subpointer, *subend; /* buffer for sub-options */ + + TelnetReceive telrcv_state; +}; + + +/* + * TELNET protocol handler. + */ + +const struct Curl_handler Curl_handler_telnet = { + "TELNET", /* scheme */ + ZERO_NULL, /* setup_connection */ + telnet_do, /* do_it */ + telnet_done, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_TELNET, /* defport */ + CURLPROTO_TELNET, /* protocol */ + PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ +}; + + +#ifdef USE_WINSOCK +static CURLcode +check_wsock2 ( struct SessionHandle *data ) +{ + int err; + WORD wVersionRequested; + WSADATA wsaData; + + DEBUGASSERT(data); + + /* telnet requires at least WinSock 2.0 so ask for it. */ + wVersionRequested = MAKEWORD(2, 0); + + err = WSAStartup(wVersionRequested, &wsaData); + + /* We must've called this once already, so this call */ + /* should always succeed. But, just in case... */ + if(err != 0) { + failf(data,"WSAStartup failed (%d)",err); + return CURLE_FAILED_INIT; + } + + /* We have to have a WSACleanup call for every successful */ + /* WSAStartup call. */ + WSACleanup(); + + /* Check that our version is supported */ + if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || + HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested)) { + /* Our version isn't supported */ + failf(data,"insufficient winsock version to support " + "telnet"); + return CURLE_FAILED_INIT; + } + + /* Our version is supported */ + return CURLE_OK; +} +#endif + +static +CURLcode init_telnet(struct connectdata *conn) +{ + struct TELNET *tn; + + tn = calloc(1, sizeof(struct TELNET)); + if(!tn) + return CURLE_OUT_OF_MEMORY; + + conn->data->state.proto.telnet = (void *)tn; /* make us known */ + + tn->telrcv_state = CURL_TS_DATA; + + /* Init suboptions */ + CURL_SB_CLEAR(tn); + + /* Set the options we want by default */ + tn->us_preferred[CURL_TELOPT_SGA] = CURL_YES; + tn->him_preferred[CURL_TELOPT_SGA] = CURL_YES; + + /* To be compliant with previous releases of libcurl + we enable this option by default. This behaviour + can be changed thanks to the "BINARY" option in + CURLOPT_TELNETOPTIONS + */ + tn->us_preferred[CURL_TELOPT_BINARY] = CURL_YES; + tn->him_preferred[CURL_TELOPT_BINARY] = CURL_YES; + + /* We must allow the server to echo what we sent + but it is not necessary to request the server + to do so (it might forces the server to close + the connection). Hence, we ignore ECHO in the + negotiate function + */ + tn->him_preferred[CURL_TELOPT_ECHO] = CURL_YES; + + /* Set the subnegotiation fields to send information + just after negotiation passed (do/will) + + Default values are (0,0) initialized by calloc. + According to the RFC1013 it is valid: + A value equal to zero is acceptable for the width (or height), + and means that no character width (or height) is being sent. + In this case, the width (or height) that will be assumed by the + Telnet server is operating system specific (it will probably be + based upon the terminal type information that may have been sent + using the TERMINAL TYPE Telnet option). */ + tn->subnegotiation[CURL_TELOPT_NAWS] = CURL_YES; + return CURLE_OK; +} + +static void negotiate(struct connectdata *conn) +{ + int i; + struct TELNET *tn = (struct TELNET *) conn->data->state.proto.telnet; + + for(i = 0;i < CURL_NTELOPTS;i++) { + if(i==CURL_TELOPT_ECHO) + continue; + + if(tn->us_preferred[i] == CURL_YES) + set_local_option(conn, i, CURL_YES); + + if(tn->him_preferred[i] == CURL_YES) + set_remote_option(conn, i, CURL_YES); + } +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void printoption(struct SessionHandle *data, + const char *direction, int cmd, int option) +{ + const char *fmt; + const char *opt; + + if(data->set.verbose) { + if(cmd == CURL_IAC) { + if(CURL_TELCMD_OK(option)) + infof(data, "%s IAC %s\n", direction, CURL_TELCMD(option)); + else + infof(data, "%s IAC %d\n", direction, option); + } + else { + fmt = (cmd == CURL_WILL) ? "WILL" : (cmd == CURL_WONT) ? "WONT" : + (cmd == CURL_DO) ? "DO" : (cmd == CURL_DONT) ? "DONT" : 0; + if(fmt) { + if(CURL_TELOPT_OK(option)) + opt = CURL_TELOPT(option); + else if(option == CURL_TELOPT_EXOPL) + opt = "EXOPL"; + else + opt = NULL; + + if(opt) + infof(data, "%s %s %s\n", direction, fmt, opt); + else + infof(data, "%s %s %d\n", direction, fmt, option); + } + else + infof(data, "%s %d %d\n", direction, cmd, option); + } + } +} +#endif + +static void send_negotiation(struct connectdata *conn, int cmd, int option) +{ + unsigned char buf[3]; + ssize_t bytes_written; + int err; + struct SessionHandle *data = conn->data; + + buf[0] = CURL_IAC; + buf[1] = (unsigned char)cmd; + buf[2] = (unsigned char)option; + + bytes_written = swrite(conn->sock[FIRSTSOCKET], buf, 3); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data,"Sending data failed (%d)",err); + } + + printoption(conn->data, "SENT", cmd, option); +} + +static +void set_remote_option(struct connectdata *conn, int option, int newstate) +{ + struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; + if(newstate == CURL_YES) { + switch(tn->him[option]) { + case CURL_NO: + tn->him[option] = CURL_WANTYES; + send_negotiation(conn, CURL_DO, option); + break; + + case CURL_YES: + /* Already enabled */ + break; + + case CURL_WANTNO: + switch(tn->himq[option]) { + case CURL_EMPTY: + /* Already negotiating for CURL_YES, queue the request */ + tn->himq[option] = CURL_OPPOSITE; + break; + case CURL_OPPOSITE: + /* Error: already queued an enable request */ + break; + } + break; + + case CURL_WANTYES: + switch(tn->himq[option]) { + case CURL_EMPTY: + /* Error: already negotiating for enable */ + break; + case CURL_OPPOSITE: + tn->himq[option] = CURL_EMPTY; + break; + } + break; + } + } + else { /* NO */ + switch(tn->him[option]) { + case CURL_NO: + /* Already disabled */ + break; + + case CURL_YES: + tn->him[option] = CURL_WANTNO; + send_negotiation(conn, CURL_DONT, option); + break; + + case CURL_WANTNO: + switch(tn->himq[option]) { + case CURL_EMPTY: + /* Already negotiating for NO */ + break; + case CURL_OPPOSITE: + tn->himq[option] = CURL_EMPTY; + break; + } + break; + + case CURL_WANTYES: + switch(tn->himq[option]) { + case CURL_EMPTY: + tn->himq[option] = CURL_OPPOSITE; + break; + case CURL_OPPOSITE: + break; + } + break; + } + } +} + +static +void rec_will(struct connectdata *conn, int option) +{ + struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; + switch(tn->him[option]) { + case CURL_NO: + if(tn->him_preferred[option] == CURL_YES) { + tn->him[option] = CURL_YES; + send_negotiation(conn, CURL_DO, option); + } + else + send_negotiation(conn, CURL_DONT, option); + + break; + + case CURL_YES: + /* Already enabled */ + break; + + case CURL_WANTNO: + switch(tn->himq[option]) { + case CURL_EMPTY: + /* Error: DONT answered by WILL */ + tn->him[option] = CURL_NO; + break; + case CURL_OPPOSITE: + /* Error: DONT answered by WILL */ + tn->him[option] = CURL_YES; + tn->himq[option] = CURL_EMPTY; + break; + } + break; + + case CURL_WANTYES: + switch(tn->himq[option]) { + case CURL_EMPTY: + tn->him[option] = CURL_YES; + break; + case CURL_OPPOSITE: + tn->him[option] = CURL_WANTNO; + tn->himq[option] = CURL_EMPTY; + send_negotiation(conn, CURL_DONT, option); + break; + } + break; + } +} + +static +void rec_wont(struct connectdata *conn, int option) +{ + struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; + switch(tn->him[option]) { + case CURL_NO: + /* Already disabled */ + break; + + case CURL_YES: + tn->him[option] = CURL_NO; + send_negotiation(conn, CURL_DONT, option); + break; + + case CURL_WANTNO: + switch(tn->himq[option]) { + case CURL_EMPTY: + tn->him[option] = CURL_NO; + break; + + case CURL_OPPOSITE: + tn->him[option] = CURL_WANTYES; + tn->himq[option] = CURL_EMPTY; + send_negotiation(conn, CURL_DO, option); + break; + } + break; + + case CURL_WANTYES: + switch(tn->himq[option]) { + case CURL_EMPTY: + tn->him[option] = CURL_NO; + break; + case CURL_OPPOSITE: + tn->him[option] = CURL_NO; + tn->himq[option] = CURL_EMPTY; + break; + } + break; + } +} + +static void +set_local_option(struct connectdata *conn, int option, int newstate) +{ + struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; + if(newstate == CURL_YES) { + switch(tn->us[option]) { + case CURL_NO: + tn->us[option] = CURL_WANTYES; + send_negotiation(conn, CURL_WILL, option); + break; + + case CURL_YES: + /* Already enabled */ + break; + + case CURL_WANTNO: + switch(tn->usq[option]) { + case CURL_EMPTY: + /* Already negotiating for CURL_YES, queue the request */ + tn->usq[option] = CURL_OPPOSITE; + break; + case CURL_OPPOSITE: + /* Error: already queued an enable request */ + break; + } + break; + + case CURL_WANTYES: + switch(tn->usq[option]) { + case CURL_EMPTY: + /* Error: already negotiating for enable */ + break; + case CURL_OPPOSITE: + tn->usq[option] = CURL_EMPTY; + break; + } + break; + } + } + else { /* NO */ + switch(tn->us[option]) { + case CURL_NO: + /* Already disabled */ + break; + + case CURL_YES: + tn->us[option] = CURL_WANTNO; + send_negotiation(conn, CURL_WONT, option); + break; + + case CURL_WANTNO: + switch(tn->usq[option]) { + case CURL_EMPTY: + /* Already negotiating for NO */ + break; + case CURL_OPPOSITE: + tn->usq[option] = CURL_EMPTY; + break; + } + break; + + case CURL_WANTYES: + switch(tn->usq[option]) { + case CURL_EMPTY: + tn->usq[option] = CURL_OPPOSITE; + break; + case CURL_OPPOSITE: + break; + } + break; + } + } +} + +static +void rec_do(struct connectdata *conn, int option) +{ + struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; + switch(tn->us[option]) { + case CURL_NO: + if(tn->us_preferred[option] == CURL_YES) { + tn->us[option] = CURL_YES; + send_negotiation(conn, CURL_WILL, option); + if(tn->subnegotiation[option] == CURL_YES) + /* transmission of data option */ + sendsuboption(conn, option); + } + else if(tn->subnegotiation[option] == CURL_YES) { + /* send information to achieve this option*/ + tn->us[option] = CURL_YES; + send_negotiation(conn, CURL_WILL, option); + sendsuboption(conn, option); + } + else + send_negotiation(conn, CURL_WONT, option); + break; + + case CURL_YES: + /* Already enabled */ + break; + + case CURL_WANTNO: + switch(tn->usq[option]) { + case CURL_EMPTY: + /* Error: DONT answered by WILL */ + tn->us[option] = CURL_NO; + break; + case CURL_OPPOSITE: + /* Error: DONT answered by WILL */ + tn->us[option] = CURL_YES; + tn->usq[option] = CURL_EMPTY; + break; + } + break; + + case CURL_WANTYES: + switch(tn->usq[option]) { + case CURL_EMPTY: + tn->us[option] = CURL_YES; + if(tn->subnegotiation[option] == CURL_YES) { + /* transmission of data option */ + sendsuboption(conn, option); + } + break; + case CURL_OPPOSITE: + tn->us[option] = CURL_WANTNO; + tn->himq[option] = CURL_EMPTY; + send_negotiation(conn, CURL_WONT, option); + break; + } + break; + } +} + +static +void rec_dont(struct connectdata *conn, int option) +{ + struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; + switch(tn->us[option]) { + case CURL_NO: + /* Already disabled */ + break; + + case CURL_YES: + tn->us[option] = CURL_NO; + send_negotiation(conn, CURL_WONT, option); + break; + + case CURL_WANTNO: + switch(tn->usq[option]) { + case CURL_EMPTY: + tn->us[option] = CURL_NO; + break; + + case CURL_OPPOSITE: + tn->us[option] = CURL_WANTYES; + tn->usq[option] = CURL_EMPTY; + send_negotiation(conn, CURL_WILL, option); + break; + } + break; + + case CURL_WANTYES: + switch(tn->usq[option]) { + case CURL_EMPTY: + tn->us[option] = CURL_NO; + break; + case CURL_OPPOSITE: + tn->us[option] = CURL_NO; + tn->usq[option] = CURL_EMPTY; + break; + } + break; + } +} + + +static void printsub(struct SessionHandle *data, + int direction, /* '<' or '>' */ + unsigned char *pointer, /* where suboption data is */ + size_t length) /* length of suboption data */ +{ + unsigned int i = 0; + unsigned short *pval; + + if(data->set.verbose) { + if(direction) { + infof(data, "%s IAC SB ", (direction == '<')? "RCVD":"SENT"); + if(length >= 3) { + int j; + + i = pointer[length-2]; + j = pointer[length-1]; + + if(i != CURL_IAC || j != CURL_SE) { + infof(data, "(terminated by "); + if(CURL_TELOPT_OK(i)) + infof(data, "%s ", CURL_TELOPT(i)); + else if(CURL_TELCMD_OK(i)) + infof(data, "%s ", CURL_TELCMD(i)); + else + infof(data, "%u ", i); + if(CURL_TELOPT_OK(j)) + infof(data, "%s", CURL_TELOPT(j)); + else if(CURL_TELCMD_OK(j)) + infof(data, "%s", CURL_TELCMD(j)); + else + infof(data, "%d", j); + infof(data, ", not IAC SE!) "); + } + } + length -= 2; + } + if(length < 1) { + infof(data, "(Empty suboption?)"); + return; + } + + if(CURL_TELOPT_OK(pointer[0])) { + switch(pointer[0]) { + case CURL_TELOPT_TTYPE: + case CURL_TELOPT_XDISPLOC: + case CURL_TELOPT_NEW_ENVIRON: + case CURL_TELOPT_NAWS: + infof(data, "%s", CURL_TELOPT(pointer[0])); + break; + default: + infof(data, "%s (unsupported)", CURL_TELOPT(pointer[0])); + break; + } + } + else + infof(data, "%d (unknown)", pointer[i]); + + switch(pointer[0]) { + case CURL_TELOPT_NAWS: + pval = (unsigned short*)(pointer+1); + infof(data, "Width: %hu ; Height: %hu", + ntohs(pval[0]), ntohs(pval[1])); + break; + default: + switch(pointer[1]) { + case CURL_TELQUAL_IS: + infof(data, " IS"); + break; + case CURL_TELQUAL_SEND: + infof(data, " SEND"); + break; + case CURL_TELQUAL_INFO: + infof(data, " INFO/REPLY"); + break; + case CURL_TELQUAL_NAME: + infof(data, " NAME"); + break; + } + + switch(pointer[0]) { + case CURL_TELOPT_TTYPE: + case CURL_TELOPT_XDISPLOC: + pointer[length] = 0; + infof(data, " \"%s\"", &pointer[2]); + break; + case CURL_TELOPT_NEW_ENVIRON: + if(pointer[1] == CURL_TELQUAL_IS) { + infof(data, " "); + for(i = 3;i < length;i++) { + switch(pointer[i]) { + case CURL_NEW_ENV_VAR: + infof(data, ", "); + break; + case CURL_NEW_ENV_VALUE: + infof(data, " = "); + break; + default: + infof(data, "%c", pointer[i]); + break; + } + } + } + break; + default: + for(i = 2; i < length; i++) + infof(data, " %.2x", pointer[i]); + break; + } + } + if(direction) + infof(data, "\n"); + } +} + +static CURLcode check_telnet_options(struct connectdata *conn) +{ + struct curl_slist *head; + struct curl_slist *beg; + char option_keyword[128]; + char option_arg[256]; + struct SessionHandle *data = conn->data; + struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; + CURLcode result = CURLE_OK; + int binary_option; + + /* Add the user name as an environment variable if it + was given on the command line */ + if(conn->bits.user_passwd) { + snprintf(option_arg, sizeof(option_arg), "USER,%s", conn->user); + beg = curl_slist_append(tn->telnet_vars, option_arg); + if(!beg) { + curl_slist_free_all(tn->telnet_vars); + tn->telnet_vars = NULL; + return CURLE_OUT_OF_MEMORY; + } + tn->telnet_vars = beg; + tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; + } + + for(head = data->set.telnet_options; head; head=head->next) { + if(sscanf(head->data, "%127[^= ]%*[ =]%255s", + option_keyword, option_arg) == 2) { + + /* Terminal type */ + if(Curl_raw_equal(option_keyword, "TTYPE")) { + strncpy(tn->subopt_ttype, option_arg, 31); + tn->subopt_ttype[31] = 0; /* String termination */ + tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; + continue; + } + + /* Display variable */ + if(Curl_raw_equal(option_keyword, "XDISPLOC")) { + strncpy(tn->subopt_xdisploc, option_arg, 127); + tn->subopt_xdisploc[127] = 0; /* String termination */ + tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; + continue; + } + + /* Environment variable */ + if(Curl_raw_equal(option_keyword, "NEW_ENV")) { + beg = curl_slist_append(tn->telnet_vars, option_arg); + if(!beg) { + result = CURLE_OUT_OF_MEMORY; + break; + } + tn->telnet_vars = beg; + tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; + continue; + } + + /* Window Size */ + if(Curl_raw_equal(option_keyword, "WS")) { + if(sscanf(option_arg, "%hu%*[xX]%hu", + &tn->subopt_wsx, &tn->subopt_wsy) == 2) + tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES; + else { + failf(data, "Syntax error in telnet option: %s", head->data); + result = CURLE_TELNET_OPTION_SYNTAX; + break; + } + continue; + } + + /* To take care or not of the 8th bit in data exchange */ + if(Curl_raw_equal(option_keyword, "BINARY")) { + binary_option=atoi(option_arg); + if(binary_option!=1) { + tn->us_preferred[CURL_TELOPT_BINARY] = CURL_NO; + tn->him_preferred[CURL_TELOPT_BINARY] = CURL_NO; + } + continue; + } + + failf(data, "Unknown telnet option %s", head->data); + result = CURLE_UNKNOWN_TELNET_OPTION; + break; + } + else { + failf(data, "Syntax error in telnet option: %s", head->data); + result = CURLE_TELNET_OPTION_SYNTAX; + break; + } + } + + if(result) { + curl_slist_free_all(tn->telnet_vars); + tn->telnet_vars = NULL; + } + + return result; +} + +/* + * suboption() + * + * Look at the sub-option buffer, and try to be helpful to the other + * side. + */ + +static void suboption(struct connectdata *conn) +{ + struct curl_slist *v; + unsigned char temp[2048]; + ssize_t bytes_written; + size_t len; + size_t tmplen; + int err; + char varname[128]; + char varval[128]; + struct SessionHandle *data = conn->data; + struct TELNET *tn = (struct TELNET *)data->state.proto.telnet; + + printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn)+2); + switch (CURL_SB_GET(tn)) { + case CURL_TELOPT_TTYPE: + len = strlen(tn->subopt_ttype) + 4 + 2; + snprintf((char *)temp, sizeof(temp), + "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE, + CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE); + bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data,"Sending data failed (%d)",err); + } + printsub(data, '>', &temp[2], len-2); + break; + case CURL_TELOPT_XDISPLOC: + len = strlen(tn->subopt_xdisploc) + 4 + 2; + snprintf((char *)temp, sizeof(temp), + "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC, + CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE); + bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data,"Sending data failed (%d)",err); + } + printsub(data, '>', &temp[2], len-2); + break; + case CURL_TELOPT_NEW_ENVIRON: + snprintf((char *)temp, sizeof(temp), + "%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON, + CURL_TELQUAL_IS); + len = 4; + + for(v = tn->telnet_vars;v;v = v->next) { + tmplen = (strlen(v->data) + 1); + /* Add the variable only if it fits */ + if(len + tmplen < (int)sizeof(temp)-6) { + sscanf(v->data, "%127[^,],%127s", varname, varval); + snprintf((char *)&temp[len], sizeof(temp) - len, + "%c%s%c%s", CURL_NEW_ENV_VAR, varname, + CURL_NEW_ENV_VALUE, varval); + len += tmplen; + } + } + snprintf((char *)&temp[len], sizeof(temp) - len, + "%c%c", CURL_IAC, CURL_SE); + len += 2; + bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data,"Sending data failed (%d)",err); + } + printsub(data, '>', &temp[2], len-2); + break; + } + return; +} + + +/* + * sendsuboption() + * + * Send suboption information to the server side. + */ + +static void sendsuboption(struct connectdata *conn, int option) +{ + ssize_t bytes_written; + int err; + unsigned short x, y; + unsigned char*uc1, *uc2; + + struct SessionHandle *data = conn->data; + struct TELNET *tn = (struct TELNET *)data->state.proto.telnet; + + switch (option) { + case CURL_TELOPT_NAWS: + /* We prepare data to be sent */ + CURL_SB_CLEAR(tn); + CURL_SB_ACCUM(tn, CURL_IAC); + CURL_SB_ACCUM(tn, CURL_SB); + CURL_SB_ACCUM(tn, CURL_TELOPT_NAWS); + /* We must deal either with litte or big endien processors */ + /* Window size must be sent according to the 'network order' */ + x=htons(tn->subopt_wsx); + y=htons(tn->subopt_wsy); + uc1 = (unsigned char*)&x; + uc2 = (unsigned char*)&y; + CURL_SB_ACCUM(tn, uc1[0]); + CURL_SB_ACCUM(tn, uc1[1]); + CURL_SB_ACCUM(tn, uc2[0]); + CURL_SB_ACCUM(tn, uc2[1]); + + CURL_SB_ACCUM(tn, CURL_IAC); + CURL_SB_ACCUM(tn, CURL_SE); + CURL_SB_TERM(tn); + /* data suboption is now ready */ + + printsub(data, '>', (unsigned char *)tn->subbuffer+2, + CURL_SB_LEN(tn)-2); + + /* we send the header of the suboption... */ + bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer, 3); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data, "Sending data failed (%d)", err); + } + /* ... then the window size with the send_telnet_data() function + to deal with 0xFF cases ... */ + send_telnet_data(conn, (char *)tn->subbuffer+3, 4); + /* ... and the footer */ + bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer+7, 2); + if(bytes_written < 0) { + err = SOCKERRNO; + failf(data, "Sending data failed (%d)", err); + } + break; + } +} + + +static +CURLcode telrcv(struct connectdata *conn, + const unsigned char *inbuf, /* Data received from socket */ + ssize_t count) /* Number of bytes received */ +{ + unsigned char c; + CURLcode result; + int in = 0; + int startwrite=-1; + struct SessionHandle *data = conn->data; + struct TELNET *tn = (struct TELNET *)data->state.proto.telnet; + +#define startskipping() \ + if(startwrite >= 0) { \ + result = Curl_client_write(conn, \ + CLIENTWRITE_BODY, \ + (char *)&inbuf[startwrite], \ + in-startwrite); \ + if(result != CURLE_OK) \ + return result; \ + } \ + startwrite = -1 + +#define writebyte() \ + if(startwrite < 0) \ + startwrite = in + +#define bufferflush() startskipping() + + while(count--) { + c = inbuf[in]; + + switch (tn->telrcv_state) { + case CURL_TS_CR: + tn->telrcv_state = CURL_TS_DATA; + if(c == '\0') { + startskipping(); + break; /* Ignore \0 after CR */ + } + writebyte(); + break; + + case CURL_TS_DATA: + if(c == CURL_IAC) { + tn->telrcv_state = CURL_TS_IAC; + startskipping(); + break; + } + else if(c == '\r') + tn->telrcv_state = CURL_TS_CR; + writebyte(); + break; + + case CURL_TS_IAC: + process_iac: + DEBUGASSERT(startwrite < 0); + switch (c) { + case CURL_WILL: + tn->telrcv_state = CURL_TS_WILL; + break; + case CURL_WONT: + tn->telrcv_state = CURL_TS_WONT; + break; + case CURL_DO: + tn->telrcv_state = CURL_TS_DO; + break; + case CURL_DONT: + tn->telrcv_state = CURL_TS_DONT; + break; + case CURL_SB: + CURL_SB_CLEAR(tn); + tn->telrcv_state = CURL_TS_SB; + break; + case CURL_IAC: + tn->telrcv_state = CURL_TS_DATA; + writebyte(); + break; + case CURL_DM: + case CURL_NOP: + case CURL_GA: + default: + tn->telrcv_state = CURL_TS_DATA; + printoption(data, "RCVD", CURL_IAC, c); + break; + } + break; + + case CURL_TS_WILL: + printoption(data, "RCVD", CURL_WILL, c); + tn->please_negotiate = 1; + rec_will(conn, c); + tn->telrcv_state = CURL_TS_DATA; + break; + + case CURL_TS_WONT: + printoption(data, "RCVD", CURL_WONT, c); + tn->please_negotiate = 1; + rec_wont(conn, c); + tn->telrcv_state = CURL_TS_DATA; + break; + + case CURL_TS_DO: + printoption(data, "RCVD", CURL_DO, c); + tn->please_negotiate = 1; + rec_do(conn, c); + tn->telrcv_state = CURL_TS_DATA; + break; + + case CURL_TS_DONT: + printoption(data, "RCVD", CURL_DONT, c); + tn->please_negotiate = 1; + rec_dont(conn, c); + tn->telrcv_state = CURL_TS_DATA; + break; + + case CURL_TS_SB: + if(c == CURL_IAC) + tn->telrcv_state = CURL_TS_SE; + else + CURL_SB_ACCUM(tn,c); + break; + + case CURL_TS_SE: + if(c != CURL_SE) { + if(c != CURL_IAC) { + /* + * This is an error. We only expect to get "IAC IAC" or "IAC SE". + * Several things may have happened. An IAC was not doubled, the + * IAC SE was left off, or another option got inserted into the + * suboption are all possibilities. If we assume that the IAC was + * not doubled, and really the IAC SE was left off, we could get + * into an infinate loop here. So, instead, we terminate the + * suboption, and process the partial suboption if we can. + */ + CURL_SB_ACCUM(tn, CURL_IAC); + CURL_SB_ACCUM(tn, c); + tn->subpointer -= 2; + CURL_SB_TERM(tn); + + printoption(data, "In SUBOPTION processing, RCVD", CURL_IAC, c); + suboption(conn); /* handle sub-option */ + tn->telrcv_state = CURL_TS_IAC; + goto process_iac; + } + CURL_SB_ACCUM(tn,c); + tn->telrcv_state = CURL_TS_SB; + } + else + { + CURL_SB_ACCUM(tn, CURL_IAC); + CURL_SB_ACCUM(tn, CURL_SE); + tn->subpointer -= 2; + CURL_SB_TERM(tn); + suboption(conn); /* handle sub-option */ + tn->telrcv_state = CURL_TS_DATA; + } + break; + } + ++in; + } + bufferflush(); + return CURLE_OK; +} + +/* Escape and send a telnet data block */ +/* TODO: write large chunks of data instead of one byte at a time */ +static CURLcode send_telnet_data(struct connectdata *conn, + char *buffer, ssize_t nread) +{ + unsigned char outbuf[2]; + ssize_t bytes_written, total_written; + int out_count; + CURLcode rc = CURLE_OK; + + while(rc == CURLE_OK && nread--) { + outbuf[0] = *buffer++; + out_count = 1; + if(outbuf[0] == CURL_IAC) + outbuf[out_count++] = CURL_IAC; + + total_written = 0; + do { + /* Make sure socket is writable to avoid EWOULDBLOCK condition */ + struct pollfd pfd[1]; + pfd[0].fd = conn->sock[FIRSTSOCKET]; + pfd[0].events = POLLOUT; + switch (Curl_poll(pfd, 1, -1)) { + case -1: /* error, abort writing */ + case 0: /* timeout (will never happen) */ + rc = CURLE_SEND_ERROR; + break; + default: /* write! */ + bytes_written = 0; + rc = Curl_write(conn, conn->sock[FIRSTSOCKET], outbuf+total_written, + out_count-total_written, &bytes_written); + total_written += bytes_written; + break; + } + /* handle partial write */ + } while(rc == CURLE_OK && total_written < out_count); + } + return rc; +} + +static CURLcode telnet_done(struct connectdata *conn, + CURLcode status, bool premature) +{ + struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; + (void)status; /* unused */ + (void)premature; /* not used */ + + if(!tn) + return CURLE_OK; + + curl_slist_free_all(tn->telnet_vars); + tn->telnet_vars = NULL; + + Curl_safefree(conn->data->state.proto.telnet); + + return CURLE_OK; +} + +static CURLcode telnet_do(struct connectdata *conn, bool *done) +{ + CURLcode code; + struct SessionHandle *data = conn->data; + curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; +#ifdef USE_WINSOCK + HMODULE wsock2; + WSOCK2_FUNC close_event_func; + WSOCK2_FUNC create_event_func; + WSOCK2_FUNC event_select_func; + WSOCK2_FUNC enum_netevents_func; + WSAEVENT event_handle; + WSANETWORKEVENTS events; + HANDLE stdin_handle; + HANDLE objs[2]; + DWORD obj_count; + DWORD wait_timeout; + DWORD waitret; + DWORD readfile_read; + int err; +#else + int interval_ms; + struct pollfd pfd[2]; + int poll_cnt; + curl_off_t total_dl = 0; + curl_off_t total_ul = 0; +#endif + ssize_t nread; + struct timeval now; + bool keepon = TRUE; + char *buf = data->state.buffer; + struct TELNET *tn; + + *done = TRUE; /* unconditionally */ + + code = init_telnet(conn); + if(code) + return code; + + tn = (struct TELNET *)data->state.proto.telnet; + + code = check_telnet_options(conn); + if(code) + return code; + +#ifdef USE_WINSOCK + /* + ** This functionality only works with WinSock >= 2.0. So, + ** make sure have it. + */ + code = check_wsock2(data); + if(code) + return code; + + /* OK, so we have WinSock 2.0. We need to dynamically */ + /* load ws2_32.dll and get the function pointers we need. */ + wsock2 = LoadLibrary(TEXT("WS2_32.DLL")); + if(wsock2 == NULL) { + failf(data,"failed to load WS2_32.DLL (%d)", ERRNO); + return CURLE_FAILED_INIT; + } + + /* Grab a pointer to WSACreateEvent */ + create_event_func = GetProcAddress(wsock2,"WSACreateEvent"); + if(create_event_func == NULL) { + failf(data,"failed to find WSACreateEvent function (%d)", + ERRNO); + FreeLibrary(wsock2); + return CURLE_FAILED_INIT; + } + + /* And WSACloseEvent */ + close_event_func = GetProcAddress(wsock2,"WSACloseEvent"); + if(close_event_func == NULL) { + failf(data,"failed to find WSACloseEvent function (%d)", + ERRNO); + FreeLibrary(wsock2); + return CURLE_FAILED_INIT; + } + + /* And WSAEventSelect */ + event_select_func = GetProcAddress(wsock2,"WSAEventSelect"); + if(event_select_func == NULL) { + failf(data,"failed to find WSAEventSelect function (%d)", + ERRNO); + FreeLibrary(wsock2); + return CURLE_FAILED_INIT; + } + + /* And WSAEnumNetworkEvents */ + enum_netevents_func = GetProcAddress(wsock2,"WSAEnumNetworkEvents"); + if(enum_netevents_func == NULL) { + failf(data,"failed to find WSAEnumNetworkEvents function (%d)", + ERRNO); + FreeLibrary(wsock2); + return CURLE_FAILED_INIT; + } + + /* We want to wait for both stdin and the socket. Since + ** the select() function in winsock only works on sockets + ** we have to use the WaitForMultipleObjects() call. + */ + + /* First, create a sockets event object */ + event_handle = (WSAEVENT)create_event_func(); + if(event_handle == WSA_INVALID_EVENT) { + failf(data,"WSACreateEvent failed (%d)", SOCKERRNO); + FreeLibrary(wsock2); + return CURLE_FAILED_INIT; + } + + /* Tell winsock what events we want to listen to */ + if(event_select_func(sockfd, event_handle, FD_READ|FD_CLOSE) == + SOCKET_ERROR) { + close_event_func(event_handle); + FreeLibrary(wsock2); + return CURLE_OK; + } + + /* The get the Windows file handle for stdin */ + stdin_handle = GetStdHandle(STD_INPUT_HANDLE); + + /* Create the list of objects to wait for */ + objs[0] = event_handle; + objs[1] = stdin_handle; + + /* If stdin_handle is a pipe, use PeekNamedPipe() method to check it, + else use the old WaitForMultipleObjects() way */ + if(GetFileType(stdin_handle) == FILE_TYPE_PIPE || + data->set.is_fread_set) { + /* Don't wait for stdin_handle, just wait for event_handle */ + obj_count = 1; + /* Check stdin_handle per 100 milliseconds */ + wait_timeout = 100; + } + else { + obj_count = 2; + wait_timeout = 1000; + } + + /* Keep on listening and act on events */ + while(keepon) { + waitret = WaitForMultipleObjects(obj_count, objs, FALSE, wait_timeout); + switch(waitret) { + case WAIT_TIMEOUT: + { + for(;;) { + if(obj_count == 1) { + /* read from user-supplied method */ + code = (int)conn->fread_func(buf, 1, BUFSIZE - 1, conn->fread_in); + if(code == CURL_READFUNC_ABORT) { + keepon = FALSE; + code = CURLE_READ_ERROR; + break; + } + + if(code == CURL_READFUNC_PAUSE) + break; + + if(code == 0) /* no bytes */ + break; + + readfile_read = code; /* fall thru with number of bytes read */ + } + else { + /* read from stdin */ + if(!PeekNamedPipe(stdin_handle, NULL, 0, NULL, + &readfile_read, NULL)) { + keepon = FALSE; + code = CURLE_READ_ERROR; + break; + } + + if(!readfile_read) + break; + + if(!ReadFile(stdin_handle, buf, sizeof(data->state.buffer), + &readfile_read, NULL)) { + keepon = FALSE; + code = CURLE_READ_ERROR; + break; + } + } + + code = send_telnet_data(conn, buf, readfile_read); + if(code) { + keepon = FALSE; + break; + } + } + } + break; + + case WAIT_OBJECT_0 + 1: + { + if(!ReadFile(stdin_handle, buf, sizeof(data->state.buffer), + &readfile_read, NULL)) { + keepon = FALSE; + code = CURLE_READ_ERROR; + break; + } + + code = send_telnet_data(conn, buf, readfile_read); + if(code) { + keepon = FALSE; + break; + } + } + break; + + case WAIT_OBJECT_0: + + if(SOCKET_ERROR == enum_netevents_func(sockfd, event_handle, &events)) { + if((err = SOCKERRNO) != EINPROGRESS) { + infof(data,"WSAEnumNetworkEvents failed (%d)", err); + keepon = FALSE; + code = CURLE_READ_ERROR; + } + break; + } + if(events.lNetworkEvents & FD_READ) { + /* read data from network */ + code = Curl_read(conn, sockfd, buf, BUFSIZE - 1, &nread); + /* read would've blocked. Loop again */ + if(code == CURLE_AGAIN) + break; + /* returned not-zero, this an error */ + else if(code) { + keepon = FALSE; + break; + } + /* returned zero but actually received 0 or less here, + the server closed the connection and we bail out */ + else if(nread <= 0) { + keepon = FALSE; + break; + } + + code = telrcv(conn, (unsigned char *)buf, nread); + if(code) { + keepon = FALSE; + break; + } + + /* Negotiate if the peer has started negotiating, + otherwise don't. We don't want to speak telnet with + non-telnet servers, like POP or SMTP. */ + if(tn->please_negotiate && !tn->already_negotiated) { + negotiate(conn); + tn->already_negotiated = 1; + } + } + if(events.lNetworkEvents & FD_CLOSE) { + keepon = FALSE; + } + break; + + } + + if(data->set.timeout) { + now = Curl_tvnow(); + if(Curl_tvdiff(now, conn->created) >= data->set.timeout) { + failf(data, "Time-out"); + code = CURLE_OPERATION_TIMEDOUT; + keepon = FALSE; + } + } + } + + /* We called WSACreateEvent, so call WSACloseEvent */ + if(!close_event_func(event_handle)) { + infof(data,"WSACloseEvent failed (%d)", SOCKERRNO); + } + + /* "Forget" pointers into the library we're about to free */ + create_event_func = NULL; + close_event_func = NULL; + event_select_func = NULL; + enum_netevents_func = NULL; + + /* We called LoadLibrary, so call FreeLibrary */ + if(!FreeLibrary(wsock2)) + infof(data,"FreeLibrary(wsock2) failed (%d)", ERRNO); +#else + pfd[0].fd = sockfd; + pfd[0].events = POLLIN; + + if(conn->fread_func != (curl_read_callback)fread) { + poll_cnt = 1; + interval_ms = 100; /* poll user-supplied read function */ + } + else { + /* really using fread, so infile is a FILE* */ + pfd[1].fd = fileno((FILE *)conn->fread_in); + pfd[1].events = POLLIN; + poll_cnt = 2; + interval_ms = 1 * 1000; + } + + while(keepon) { + switch (Curl_poll(pfd, poll_cnt, interval_ms)) { + case -1: /* error, stop reading */ + keepon = FALSE; + continue; + case 0: /* timeout */ + pfd[0].revents = 0; + pfd[1].revents = 0; + /* fall through */ + default: /* read! */ + if(pfd[0].revents & POLLIN) { + /* read data from network */ + code = Curl_read(conn, sockfd, buf, BUFSIZE - 1, &nread); + /* read would've blocked. Loop again */ + if(code == CURLE_AGAIN) + break; + /* returned not-zero, this an error */ + else if(code) { + keepon = FALSE; + break; + } + /* returned zero but actually received 0 or less here, + the server closed the connection and we bail out */ + else if(nread <= 0) { + keepon = FALSE; + break; + } + + total_dl += nread; + Curl_pgrsSetDownloadCounter(data, total_dl); + code = telrcv(conn, (unsigned char *)buf, nread); + if(code) { + keepon = FALSE; + break; + } + + /* Negotiate if the peer has started negotiating, + otherwise don't. We don't want to speak telnet with + non-telnet servers, like POP or SMTP. */ + if(tn->please_negotiate && !tn->already_negotiated) { + negotiate(conn); + tn->already_negotiated = 1; + } + } + + nread = 0; + if(poll_cnt == 2) { + if(pfd[1].revents & POLLIN) { /* read from in file */ + nread = read(pfd[1].fd, buf, BUFSIZE - 1); + } + } + else { + /* read from user-supplied method */ + nread = (int)conn->fread_func(buf, 1, BUFSIZE - 1, conn->fread_in); + if(nread == CURL_READFUNC_ABORT) { + keepon = FALSE; + break; + } + if(nread == CURL_READFUNC_PAUSE) + break; + } + + if(nread > 0) { + code = send_telnet_data(conn, buf, nread); + if(code) { + keepon = FALSE; + break; + } + total_ul += nread; + Curl_pgrsSetUploadCounter(data, total_ul); + } + else if(nread < 0) + keepon = FALSE; + + break; + } /* poll switch statement */ + + if(data->set.timeout) { + now = Curl_tvnow(); + if(Curl_tvdiff(now, conn->created) >= data->set.timeout) { + failf(data, "Time-out"); + code = CURLE_OPERATION_TIMEDOUT; + keepon = FALSE; + } + } + + if(Curl_pgrsUpdate(conn)) { + code = CURLE_ABORTED_BY_CALLBACK; + break; + } + } +#endif + /* mark this as "no further transfer wanted" */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + return code; +} +#endif diff --git a/lib/curl_tftp.c b/lib/curl_tftp.c new file mode 100644 index 000000000..1af246ec0 --- /dev/null +++ b/lib/curl_tftp.c @@ -0,0 +1,1500 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_TFTP + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include "curl_urldata.h" +#include +#include "curl_transfer.h" +#include "curl_sendf.h" +#include "curl_tftp.h" +#include "curl_progress.h" +#include "curl_connect.h" +#include "curl_strerror.h" +#include "curl_sockaddr.h" /* required for Curl_sockaddr_storage */ +#include "curl_multiif.h" +#include "curl_url.h" +#include "curl_rawstr.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +#include "curl_select.h" + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* RFC2348 allows the block size to be negotiated */ +#define TFTP_BLKSIZE_DEFAULT 512 +#define TFTP_BLKSIZE_MIN 8 +#define TFTP_BLKSIZE_MAX 65464 +#define TFTP_OPTION_BLKSIZE "blksize" + +/* from RFC2349: */ +#define TFTP_OPTION_TSIZE "tsize" +#define TFTP_OPTION_INTERVAL "timeout" + +typedef enum { + TFTP_MODE_NETASCII=0, + TFTP_MODE_OCTET +} tftp_mode_t; + +typedef enum { + TFTP_STATE_START=0, + TFTP_STATE_RX, + TFTP_STATE_TX, + TFTP_STATE_FIN +} tftp_state_t; + +typedef enum { + TFTP_EVENT_NONE = -1, + TFTP_EVENT_INIT = 0, + TFTP_EVENT_RRQ = 1, + TFTP_EVENT_WRQ = 2, + TFTP_EVENT_DATA = 3, + TFTP_EVENT_ACK = 4, + TFTP_EVENT_ERROR = 5, + TFTP_EVENT_OACK = 6, + TFTP_EVENT_TIMEOUT +} tftp_event_t; + +typedef enum { + TFTP_ERR_UNDEF=0, + TFTP_ERR_NOTFOUND, + TFTP_ERR_PERM, + TFTP_ERR_DISKFULL, + TFTP_ERR_ILLEGAL, + TFTP_ERR_UNKNOWNID, + TFTP_ERR_EXISTS, + TFTP_ERR_NOSUCHUSER, /* This will never be triggered by this code */ + + /* The remaining error codes are internal to curl */ + TFTP_ERR_NONE = -100, + TFTP_ERR_TIMEOUT, + TFTP_ERR_NORESPONSE +} tftp_error_t; + +typedef struct tftp_packet { + unsigned char *data; +} tftp_packet_t; + +typedef struct tftp_state_data { + tftp_state_t state; + tftp_mode_t mode; + tftp_error_t error; + tftp_event_t event; + struct connectdata *conn; + curl_socket_t sockfd; + int retries; + int retry_time; + int retry_max; + time_t start_time; + time_t max_time; + time_t rx_time; + unsigned short block; + struct Curl_sockaddr_storage local_addr; + struct Curl_sockaddr_storage remote_addr; + curl_socklen_t remote_addrlen; + int rbytes; + int sbytes; + int blksize; + int requested_blksize; + tftp_packet_t rpacket; + tftp_packet_t spacket; +} tftp_state_data_t; + + +/* Forward declarations */ +static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) ; +static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) ; +static CURLcode tftp_connect(struct connectdata *conn, bool *done); +static CURLcode tftp_disconnect(struct connectdata *conn, + bool dead_connection); +static CURLcode tftp_do(struct connectdata *conn, bool *done); +static CURLcode tftp_done(struct connectdata *conn, + CURLcode, bool premature); +static CURLcode tftp_setup_connection(struct connectdata * conn); +static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done); +static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done); +static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks); +static CURLcode tftp_translate_code(tftp_error_t error); + + +/* + * TFTP protocol handler. + */ + +const struct Curl_handler Curl_handler_tftp = { + "TFTP", /* scheme */ + tftp_setup_connection, /* setup_connection */ + tftp_do, /* do_it */ + tftp_done, /* done */ + ZERO_NULL, /* do_more */ + tftp_connect, /* connect_it */ + tftp_multi_statemach, /* connecting */ + tftp_doing, /* doing */ + tftp_getsock, /* proto_getsock */ + tftp_getsock, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + tftp_disconnect, /* disconnect */ + ZERO_NULL, /* readwrite */ + PORT_TFTP, /* defport */ + CURLPROTO_TFTP, /* protocol */ + PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ +}; + +/********************************************************** + * + * tftp_set_timeouts - + * + * Set timeouts based on state machine state. + * Use user provided connect timeouts until DATA or ACK + * packet is received, then use user-provided transfer timeouts + * + * + **********************************************************/ +static CURLcode tftp_set_timeouts(tftp_state_data_t *state) +{ + time_t maxtime, timeout; + long timeout_ms; + bool start = (state->state == TFTP_STATE_START) ? TRUE : FALSE; + + time(&state->start_time); + + /* Compute drop-dead time */ + timeout_ms = Curl_timeleft(state->conn->data, NULL, start); + + if(timeout_ms < 0) { + /* time-out, bail out, go home */ + failf(state->conn->data, "Connection time-out"); + return CURLE_OPERATION_TIMEDOUT; + } + + if(start) { + + maxtime = (time_t)(timeout_ms + 500) / 1000; + state->max_time = state->start_time+maxtime; + + /* Set per-block timeout to total */ + timeout = maxtime ; + + /* Average restart after 5 seconds */ + state->retry_max = (int)timeout/5; + + if(state->retry_max < 1) + /* avoid division by zero below */ + state->retry_max = 1; + + /* Compute the re-start interval to suit the timeout */ + state->retry_time = (int)timeout/state->retry_max; + if(state->retry_time<1) + state->retry_time=1; + + } + else { + if(timeout_ms > 0) + maxtime = (time_t)(timeout_ms + 500) / 1000; + else + maxtime = 3600; + + state->max_time = state->start_time+maxtime; + + /* Set per-block timeout to total */ + timeout = maxtime; + + /* Average reposting an ACK after 5 seconds */ + state->retry_max = (int)timeout/5; + } + /* But bound the total number */ + if(state->retry_max<3) + state->retry_max=3; + + if(state->retry_max>50) + state->retry_max=50; + + /* Compute the re-ACK interval to suit the timeout */ + state->retry_time = (int)(timeout/state->retry_max); + if(state->retry_time<1) + state->retry_time=1; + + infof(state->conn->data, + "set timeouts for state %d; Total %ld, retry %d maxtry %d\n", + (int)state->state, (long)(state->max_time-state->start_time), + state->retry_time, state->retry_max); + + /* init RX time */ + time(&state->rx_time); + + return CURLE_OK; +} + +/********************************************************** + * + * tftp_set_send_first + * + * Event handler for the START state + * + **********************************************************/ + +static void setpacketevent(tftp_packet_t *packet, unsigned short num) +{ + packet->data[0] = (unsigned char)(num >> 8); + packet->data[1] = (unsigned char)(num & 0xff); +} + + +static void setpacketblock(tftp_packet_t *packet, unsigned short num) +{ + packet->data[2] = (unsigned char)(num >> 8); + packet->data[3] = (unsigned char)(num & 0xff); +} + +static unsigned short getrpacketevent(const tftp_packet_t *packet) +{ + return (unsigned short)((packet->data[0] << 8) | packet->data[1]); +} + +static unsigned short getrpacketblock(const tftp_packet_t *packet) +{ + return (unsigned short)((packet->data[2] << 8) | packet->data[3]); +} + +static size_t Curl_strnlen(const char *string, size_t maxlen) +{ + const char *end = memchr (string, '\0', maxlen); + return end ? (size_t) (end - string) : maxlen; +} + +static const char *tftp_option_get(const char *buf, size_t len, + const char **option, const char **value) +{ + size_t loc; + + loc = Curl_strnlen( buf, len ); + loc++; /* NULL term */ + + if(loc >= len) + return NULL; + *option = buf; + + loc += Curl_strnlen( buf+loc, len-loc ); + loc++; /* NULL term */ + + if(loc > len) + return NULL; + *value = &buf[strlen(*option) + 1]; + + return &buf[loc]; +} + +static CURLcode tftp_parse_option_ack(tftp_state_data_t *state, + const char *ptr, int len) +{ + const char *tmp = ptr; + struct SessionHandle *data = state->conn->data; + + /* if OACK doesn't contain blksize option, the default (512) must be used */ + state->blksize = TFTP_BLKSIZE_DEFAULT; + + while(tmp < ptr + len) { + const char *option, *value; + + tmp = tftp_option_get(tmp, ptr + len - tmp, &option, &value); + if(tmp == NULL) { + failf(data, "Malformed ACK packet, rejecting"); + return CURLE_TFTP_ILLEGAL; + } + + infof(data, "got option=(%s) value=(%s)\n", option, value); + + if(checkprefix(option, TFTP_OPTION_BLKSIZE)) { + long blksize; + + blksize = strtol( value, NULL, 10 ); + + if(!blksize) { + failf(data, "invalid blocksize value in OACK packet"); + return CURLE_TFTP_ILLEGAL; + } + else if(blksize > TFTP_BLKSIZE_MAX) { + failf(data, "%s (%d)", "blksize is larger than max supported", + TFTP_BLKSIZE_MAX); + return CURLE_TFTP_ILLEGAL; + } + else if(blksize < TFTP_BLKSIZE_MIN) { + failf(data, "%s (%d)", "blksize is smaller than min supported", + TFTP_BLKSIZE_MIN); + return CURLE_TFTP_ILLEGAL; + } + else if(blksize > state->requested_blksize) { + /* could realloc pkt buffers here, but the spec doesn't call out + * support for the server requesting a bigger blksize than the client + * requests */ + failf(data, "%s (%ld)", + "server requested blksize larger than allocated", blksize); + return CURLE_TFTP_ILLEGAL; + } + + state->blksize = (int)blksize; + infof(data, "%s (%d) %s (%d)\n", "blksize parsed from OACK", + state->blksize, "requested", state->requested_blksize); + } + else if(checkprefix(option, TFTP_OPTION_TSIZE)) { + long tsize = 0; + + tsize = strtol( value, NULL, 10 ); + infof(data, "%s (%ld)\n", "tsize parsed from OACK", tsize); + + /* tsize should be ignored on upload: Who cares about the size of the + remote file? */ + if(!data->set.upload) { + if(!tsize) { + failf(data, "invalid tsize -:%s:- value in OACK packet", value); + return CURLE_TFTP_ILLEGAL; + } + Curl_pgrsSetDownloadSize(data, tsize); + } + } + } + + return CURLE_OK; +} + +static size_t tftp_option_add(tftp_state_data_t *state, size_t csize, + char *buf, const char *option) +{ + if(( strlen(option) + csize + 1 ) > (size_t)state->blksize) + return 0; + strcpy(buf, option); + return( strlen(option) + 1 ); +} + +static CURLcode tftp_connect_for_tx(tftp_state_data_t *state, + tftp_event_t event) +{ + CURLcode res; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + struct SessionHandle *data = state->conn->data; + + infof(data, "%s\n", "Connected for transmit"); +#endif + state->state = TFTP_STATE_TX; + res = tftp_set_timeouts(state); + if(res != CURLE_OK) + return(res); + return tftp_tx(state, event); +} + +static CURLcode tftp_connect_for_rx(tftp_state_data_t *state, + tftp_event_t event) +{ + CURLcode res; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + struct SessionHandle *data = state->conn->data; + + infof(data, "%s\n", "Connected for receive"); +#endif + state->state = TFTP_STATE_RX; + res = tftp_set_timeouts(state); + if(res != CURLE_OK) + return(res); + return tftp_rx(state, event); +} + +static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event) +{ + size_t sbytes; + ssize_t senddata; + const char *mode = "octet"; + char *filename; + char buf[64]; + struct SessionHandle *data = state->conn->data; + CURLcode res = CURLE_OK; + + /* Set ascii mode if -B flag was used */ + if(data->set.prefer_ascii) + mode = "netascii"; + + switch(event) { + + case TFTP_EVENT_INIT: /* Send the first packet out */ + case TFTP_EVENT_TIMEOUT: /* Resend the first packet out */ + /* Increment the retry counter, quit if over the limit */ + state->retries++; + if(state->retries>state->retry_max) { + state->error = TFTP_ERR_NORESPONSE; + state->state = TFTP_STATE_FIN; + return res; + } + + if(data->set.upload) { + /* If we are uploading, send an WRQ */ + setpacketevent(&state->spacket, TFTP_EVENT_WRQ); + state->conn->data->req.upload_fromhere = + (char *)state->spacket.data+4; + if(data->set.infilesize != -1) + Curl_pgrsSetUploadSize(data, data->set.infilesize); + } + else { + /* If we are downloading, send an RRQ */ + setpacketevent(&state->spacket, TFTP_EVENT_RRQ); + } + /* As RFC3617 describes the separator slash is not actually part of the + file name so we skip the always-present first letter of the path + string. */ + filename = curl_easy_unescape(data, &state->conn->data->state.path[1], 0, + NULL); + if(!filename) + return CURLE_OUT_OF_MEMORY; + + snprintf((char *)state->spacket.data+2, + state->blksize, + "%s%c%s%c", filename, '\0', mode, '\0'); + sbytes = 4 + strlen(filename) + strlen(mode); + + /* add tsize option */ + if(data->set.upload && (data->set.infilesize != -1)) + snprintf( buf, sizeof(buf), "%" FORMAT_OFF_T, data->set.infilesize ); + else + strcpy(buf, "0"); /* the destination is large enough */ + + sbytes += tftp_option_add(state, sbytes, + (char *)state->spacket.data+sbytes, + TFTP_OPTION_TSIZE); + sbytes += tftp_option_add(state, sbytes, + (char *)state->spacket.data+sbytes, buf); + /* add blksize option */ + snprintf( buf, sizeof(buf), "%d", state->requested_blksize ); + sbytes += tftp_option_add(state, sbytes, + (char *)state->spacket.data+sbytes, + TFTP_OPTION_BLKSIZE); + sbytes += tftp_option_add(state, sbytes, + (char *)state->spacket.data+sbytes, buf ); + + /* add timeout option */ + snprintf( buf, sizeof(buf), "%d", state->retry_time); + sbytes += tftp_option_add(state, sbytes, + (char *)state->spacket.data+sbytes, + TFTP_OPTION_INTERVAL); + sbytes += tftp_option_add(state, sbytes, + (char *)state->spacket.data+sbytes, buf ); + + /* the typecase for the 3rd argument is mostly for systems that do + not have a size_t argument, like older unixes that want an 'int' */ + senddata = sendto(state->sockfd, (void *)state->spacket.data, + (SEND_TYPE_ARG3)sbytes, 0, + state->conn->ip_addr->ai_addr, + state->conn->ip_addr->ai_addrlen); + if(senddata != (ssize_t)sbytes) { + failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + } + Curl_safefree(filename); + break; + + case TFTP_EVENT_OACK: + if(data->set.upload) { + res = tftp_connect_for_tx(state, event); + } + else { + res = tftp_connect_for_rx(state, event); + } + break; + + case TFTP_EVENT_ACK: /* Connected for transmit */ + res = tftp_connect_for_tx(state, event); + break; + + case TFTP_EVENT_DATA: /* Connected for receive */ + res = tftp_connect_for_rx(state, event); + break; + + case TFTP_EVENT_ERROR: + state->state = TFTP_STATE_FIN; + break; + + default: + failf(state->conn->data, "tftp_send_first: internal error"); + break; + } + return res; +} + +/* the next blocknum is x + 1 but it needs to wrap at an unsigned 16bit + boundary */ +#define NEXT_BLOCKNUM(x) (((x)+1)&0xffff) + +/********************************************************** + * + * tftp_rx + * + * Event handler for the RX state + * + **********************************************************/ +static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) +{ + ssize_t sbytes; + int rblock; + struct SessionHandle *data = state->conn->data; + + switch(event) { + + case TFTP_EVENT_DATA: + /* Is this the block we expect? */ + rblock = getrpacketblock(&state->rpacket); + if(NEXT_BLOCKNUM(state->block) == rblock) { + /* This is the expected block. Reset counters and ACK it. */ + state->retries = 0; + } + else if(state->block == rblock) { + /* This is the last recently received block again. Log it and ACK it + again. */ + infof(data, "Received last DATA packet block %d again.\n", rblock); + } + else { + /* totally unexpected, just log it */ + infof(data, + "Received unexpected DATA packet block %d, expecting block %d\n", + rblock, NEXT_BLOCKNUM(state->block)); + break; + } + + /* ACK this block. */ + state->block = (unsigned short)rblock; + setpacketevent(&state->spacket, TFTP_EVENT_ACK); + setpacketblock(&state->spacket, state->block); + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + if(sbytes < 0) { + failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + return CURLE_SEND_ERROR; + } + + /* Check if completed (That is, a less than full packet is received) */ + if(state->rbytes < (ssize_t)state->blksize+4) { + state->state = TFTP_STATE_FIN; + } + else { + state->state = TFTP_STATE_RX; + } + time(&state->rx_time); + break; + + case TFTP_EVENT_OACK: + /* ACK option acknowledgement so we can move on to data */ + state->block = 0; + state->retries = 0; + setpacketevent(&state->spacket, TFTP_EVENT_ACK); + setpacketblock(&state->spacket, state->block); + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + if(sbytes < 0) { + failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + return CURLE_SEND_ERROR; + } + + /* we're ready to RX data */ + state->state = TFTP_STATE_RX; + time(&state->rx_time); + break; + + case TFTP_EVENT_TIMEOUT: + /* Increment the retry count and fail if over the limit */ + state->retries++; + infof(data, + "Timeout waiting for block %d ACK. Retries = %d\n", + NEXT_BLOCKNUM(state->block), state->retries); + if(state->retries > state->retry_max) { + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + } + else { + /* Resend the previous ACK */ + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + if(sbytes<0) { + failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + return CURLE_SEND_ERROR; + } + } + break; + + case TFTP_EVENT_ERROR: + setpacketevent(&state->spacket, TFTP_EVENT_ERROR); + setpacketblock(&state->spacket, state->block); + (void)sendto(state->sockfd, (void *)state->spacket.data, + 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* don't bother with the return code, but if the socket is still up we + * should be a good TFTP client and let the server know we're done */ + state->state = TFTP_STATE_FIN; + break; + + default: + failf(data, "%s", "tftp_rx: internal error"); + return CURLE_TFTP_ILLEGAL; /* not really the perfect return code for + this */ + } + return CURLE_OK; +} + +/********************************************************** + * + * tftp_tx + * + * Event handler for the TX state + * + **********************************************************/ +static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) +{ + struct SessionHandle *data = state->conn->data; + ssize_t sbytes; + int rblock; + CURLcode res = CURLE_OK; + struct SingleRequest *k = &data->req; + + switch(event) { + + case TFTP_EVENT_ACK: + case TFTP_EVENT_OACK: + if(event == TFTP_EVENT_ACK) { + /* Ack the packet */ + rblock = getrpacketblock(&state->rpacket); + + if(rblock != state->block && + /* There's a bug in tftpd-hpa that causes it to send us an ack for + * 65535 when the block number wraps to 0. So when we're expecting + * 0, also accept 65535. See + * http://syslinux.zytor.com/archives/2010-September/015253.html + * */ + !(state->block == 0 && rblock == 65535)) { + /* This isn't the expected block. Log it and up the retry counter */ + infof(data, "Received ACK for block %d, expecting %d\n", + rblock, state->block); + state->retries++; + /* Bail out if over the maximum */ + if(state->retries>state->retry_max) { + failf(data, "tftp_tx: giving up waiting for block %d ack", + state->block); + res = CURLE_SEND_ERROR; + } + else { + /* Re-send the data packet */ + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4+state->sbytes, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + res = CURLE_SEND_ERROR; + } + } + return res; + } + /* This is the expected packet. Reset the counters and send the next + block */ + time(&state->rx_time); + state->block++; + } + else + state->block = 1; /* first data block is 1 when using OACK */ + + state->retries = 0; + setpacketevent(&state->spacket, TFTP_EVENT_DATA); + setpacketblock(&state->spacket, state->block); + if(state->block > 1 && state->sbytes < (int)state->blksize) { + state->state = TFTP_STATE_FIN; + return CURLE_OK; + } + res = Curl_fillreadbuffer(state->conn, state->blksize, &state->sbytes); + if(res) + return res; + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4+state->sbytes, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + return CURLE_SEND_ERROR; + } + /* Update the progress meter */ + k->writebytecount += state->sbytes; + Curl_pgrsSetUploadCounter(data, k->writebytecount); + break; + + case TFTP_EVENT_TIMEOUT: + /* Increment the retry counter and log the timeout */ + state->retries++; + infof(data, "Timeout waiting for block %d ACK. " + " Retries = %d\n", NEXT_BLOCKNUM(state->block), state->retries); + /* Decide if we've had enough */ + if(state->retries > state->retry_max) { + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + } + else { + /* Re-send the data packet */ + sbytes = sendto(state->sockfd, (void *)state->spacket.data, + 4+state->sbytes, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + return CURLE_SEND_ERROR; + } + /* since this was a re-send, we remain at the still byte position */ + Curl_pgrsSetUploadCounter(data, k->writebytecount); + } + break; + + case TFTP_EVENT_ERROR: + state->state = TFTP_STATE_FIN; + setpacketevent(&state->spacket, TFTP_EVENT_ERROR); + setpacketblock(&state->spacket, state->block); + (void)sendto(state->sockfd, (void *)state->spacket.data, 4, SEND_4TH_ARG, + (struct sockaddr *)&state->remote_addr, + state->remote_addrlen); + /* don't bother with the return code, but if the socket is still up we + * should be a good TFTP client and let the server know we're done */ + state->state = TFTP_STATE_FIN; + break; + + default: + failf(data, "tftp_tx: internal error, event: %i", (int)(event)); + break; + } + + return res; +} + +/********************************************************** + * + * tftp_translate_code + * + * Translate internal error codes to CURL error codes + * + **********************************************************/ +static CURLcode tftp_translate_code(tftp_error_t error) +{ + CURLcode code = CURLE_OK; + + if(error != TFTP_ERR_NONE) { + switch(error) { + case TFTP_ERR_NOTFOUND: + code = CURLE_TFTP_NOTFOUND; + break; + case TFTP_ERR_PERM: + code = CURLE_TFTP_PERM; + break; + case TFTP_ERR_DISKFULL: + code = CURLE_REMOTE_DISK_FULL; + break; + case TFTP_ERR_UNDEF: + case TFTP_ERR_ILLEGAL: + code = CURLE_TFTP_ILLEGAL; + break; + case TFTP_ERR_UNKNOWNID: + code = CURLE_TFTP_UNKNOWNID; + break; + case TFTP_ERR_EXISTS: + code = CURLE_REMOTE_FILE_EXISTS; + break; + case TFTP_ERR_NOSUCHUSER: + code = CURLE_TFTP_NOSUCHUSER; + break; + case TFTP_ERR_TIMEOUT: + code = CURLE_OPERATION_TIMEDOUT; + break; + case TFTP_ERR_NORESPONSE: + code = CURLE_COULDNT_CONNECT; + break; + default: + code= CURLE_ABORTED_BY_CALLBACK; + break; + } + } + else { + code = CURLE_OK; + } + + return(code); +} + +/********************************************************** + * + * tftp_state_machine + * + * The tftp state machine event dispatcher + * + **********************************************************/ +static CURLcode tftp_state_machine(tftp_state_data_t *state, + tftp_event_t event) +{ + CURLcode res = CURLE_OK; + struct SessionHandle *data = state->conn->data; + switch(state->state) { + case TFTP_STATE_START: + DEBUGF(infof(data, "TFTP_STATE_START\n")); + res = tftp_send_first(state, event); + break; + case TFTP_STATE_RX: + DEBUGF(infof(data, "TFTP_STATE_RX\n")); + res = tftp_rx(state, event); + break; + case TFTP_STATE_TX: + DEBUGF(infof(data, "TFTP_STATE_TX\n")); + res = tftp_tx(state, event); + break; + case TFTP_STATE_FIN: + infof(data, "%s\n", "TFTP finished"); + break; + default: + DEBUGF(infof(data, "STATE: %d\n", state->state)); + failf(data, "%s", "Internal state machine error"); + res = CURLE_TFTP_ILLEGAL; + break; + } + return res; +} + +/********************************************************** + * + * tftp_disconnect + * + * The disconnect callback + * + **********************************************************/ +static CURLcode tftp_disconnect(struct connectdata *conn, bool dead_connection) +{ + tftp_state_data_t *state = conn->proto.tftpc; + (void) dead_connection; + + /* done, free dynamically allocated pkt buffers */ + if(state) { + Curl_safefree(state->rpacket.data); + Curl_safefree(state->spacket.data); + free(state); + } + + return CURLE_OK; +} + +/********************************************************** + * + * tftp_connect + * + * The connect callback + * + **********************************************************/ +static CURLcode tftp_connect(struct connectdata *conn, bool *done) +{ + CURLcode code; + tftp_state_data_t *state; + int blksize, rc; + + blksize = TFTP_BLKSIZE_DEFAULT; + + /* If there already is a protocol-specific struct allocated for this + sessionhandle, deal with it */ + Curl_reset_reqproto(conn); + + state = conn->proto.tftpc = calloc(1, sizeof(tftp_state_data_t)); + if(!state) + return CURLE_OUT_OF_MEMORY; + + /* alloc pkt buffers based on specified blksize */ + if(conn->data->set.tftp_blksize) { + blksize = (int)conn->data->set.tftp_blksize; + if(blksize > TFTP_BLKSIZE_MAX || blksize < TFTP_BLKSIZE_MIN ) + return CURLE_TFTP_ILLEGAL; + } + + if(!state->rpacket.data) { + state->rpacket.data = calloc(1, blksize + 2 + 2); + + if(!state->rpacket.data) + return CURLE_OUT_OF_MEMORY; + } + + if(!state->spacket.data) { + state->spacket.data = calloc(1, blksize + 2 + 2); + + if(!state->spacket.data) + return CURLE_OUT_OF_MEMORY; + } + + conn->bits.close = TRUE; /* we don't keep TFTP connections up bascially + because there's none or very little gain for UDP + */ + + state->conn = conn; + state->sockfd = state->conn->sock[FIRSTSOCKET]; + state->state = TFTP_STATE_START; + state->error = TFTP_ERR_NONE; + state->blksize = TFTP_BLKSIZE_DEFAULT; + state->requested_blksize = blksize; + + ((struct sockaddr *)&state->local_addr)->sa_family = + (unsigned short)(conn->ip_addr->ai_family); + + tftp_set_timeouts(state); + + if(!conn->bits.bound) { + /* If not already bound, bind to any interface, random UDP port. If it is + * reused or a custom local port was desired, this has already been done! + * + * We once used the size of the local_addr struct as the third argument + * for bind() to better work with IPv6 or whatever size the struct could + * have, but we learned that at least Tru64, AIX and IRIX *requires* the + * size of that argument to match the exact size of a 'sockaddr_in' struct + * when running IPv4-only. + * + * Therefore we use the size from the address we connected to, which we + * assume uses the same IP version and thus hopefully this works for both + * IPv4 and IPv6... + */ + rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr, + conn->ip_addr->ai_addrlen); + if(rc) { + failf(conn->data, "bind() failed; %s", + Curl_strerror(conn, SOCKERRNO)); + return CURLE_COULDNT_CONNECT; + } + conn->bits.bound = TRUE; + } + + Curl_pgrsStartNow(conn->data); + + *done = TRUE; + code = CURLE_OK; + return(code); +} + +/********************************************************** + * + * tftp_done + * + * The done callback + * + **********************************************************/ +static CURLcode tftp_done(struct connectdata *conn, CURLcode status, + bool premature) +{ + CURLcode code = CURLE_OK; + tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + + (void)status; /* unused */ + (void)premature; /* not used */ + + if(Curl_pgrsDone(conn)) + return CURLE_ABORTED_BY_CALLBACK; + + /* If we have encountered an error */ + code = tftp_translate_code(state->error); + + return code; +} + +/********************************************************** + * + * tftp_getsock + * + * The getsock callback + * + **********************************************************/ +static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks) +{ + if(!numsocks) + return GETSOCK_BLANK; + + socks[0] = conn->sock[FIRSTSOCKET]; + + return GETSOCK_READSOCK(0); +} + +/********************************************************** + * + * tftp_receive_packet + * + * Called once select fires and data is ready on the socket + * + **********************************************************/ +static CURLcode tftp_receive_packet(struct connectdata *conn) +{ + struct Curl_sockaddr_storage fromaddr; + curl_socklen_t fromlen; + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + struct SingleRequest *k = &data->req; + + /* Receive the packet */ + fromlen = sizeof(fromaddr); + state->rbytes = (int)recvfrom(state->sockfd, + (void *)state->rpacket.data, + state->blksize+4, + 0, + (struct sockaddr *)&fromaddr, + &fromlen); + if(state->remote_addrlen==0) { + memcpy(&state->remote_addr, &fromaddr, fromlen); + state->remote_addrlen = fromlen; + } + + /* Sanity check packet length */ + if(state->rbytes < 4) { + failf(data, "Received too short packet"); + /* Not a timeout, but how best to handle it? */ + state->event = TFTP_EVENT_TIMEOUT; + } + else { + /* The event is given by the TFTP packet time */ + state->event = (tftp_event_t)getrpacketevent(&state->rpacket); + + switch(state->event) { + case TFTP_EVENT_DATA: + /* Don't pass to the client empty or retransmitted packets */ + if(state->rbytes > 4 && + (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { + result = Curl_client_write(conn, CLIENTWRITE_BODY, + (char *)state->rpacket.data+4, + state->rbytes-4); + if(result) { + tftp_state_machine(state, TFTP_EVENT_ERROR); + return result; + } + k->bytecount += state->rbytes-4; + Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount); + } + break; + case TFTP_EVENT_ERROR: + state->error = (tftp_error_t)getrpacketblock(&state->rpacket); + infof(data, "%s\n", (const char *)state->rpacket.data+4); + break; + case TFTP_EVENT_ACK: + break; + case TFTP_EVENT_OACK: + result = tftp_parse_option_ack(state, + (const char *)state->rpacket.data+2, + state->rbytes-2); + if(result) + return result; + break; + case TFTP_EVENT_RRQ: + case TFTP_EVENT_WRQ: + default: + failf(data, "%s", "Internal error: Unexpected packet"); + break; + } + + /* Update the progress meter */ + if(Curl_pgrsUpdate(conn)) { + tftp_state_machine(state, TFTP_EVENT_ERROR); + return CURLE_ABORTED_BY_CALLBACK; + } + } + return result; +} + +/********************************************************** + * + * tftp_state_timeout + * + * Check if timeouts have been reached + * + **********************************************************/ +static long tftp_state_timeout(struct connectdata *conn, tftp_event_t *event) +{ + time_t current; + tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + + if(event) + *event = TFTP_EVENT_NONE; + + time(¤t); + if(current > state->max_time) { + DEBUGF(infof(conn->data, "timeout: %ld > %ld\n", + (long)current, (long)state->max_time)); + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + return 0; + } + else if(current > state->rx_time+state->retry_time) { + if(event) + *event = TFTP_EVENT_TIMEOUT; + time(&state->rx_time); /* update even though we received nothing */ + } + + /* there's a typecast below here since 'time_t' may in fact be larger than + 'long', but we estimate that a 'long' will still be able to hold number + of seconds even if "only" 32 bit */ + return (long)(state->max_time - current); +} + + +/********************************************************** + * + * tftp_easy_statemach + * + * Handle easy request until completion + * + **********************************************************/ +static CURLcode tftp_easy_statemach(struct connectdata *conn) +{ + int rc; + int check_time = 0; + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + curl_socket_t fd_read; + long timeout_ms; + struct SingleRequest *k = &data->req; + struct timeval transaction_start = Curl_tvnow(); + + k->start = transaction_start; + k->now = transaction_start; + + /* Run the TFTP State Machine */ + for(; (state->state != TFTP_STATE_FIN) && (result == CURLE_OK); ) { + + timeout_ms = state->retry_time * 1000; + + if(data->set.upload) { + if(data->set.max_send_speed && + (data->progress.ulspeed > data->set.max_send_speed)) { + fd_read = CURL_SOCKET_BAD; + timeout_ms = Curl_sleep_time(data->set.max_send_speed, + data->progress.ulspeed, state->blksize); + } + else { + fd_read = state->sockfd; + } + } + else { + if(data->set.max_recv_speed && + (data->progress.dlspeed > data->set.max_recv_speed)) { + fd_read = CURL_SOCKET_BAD; + timeout_ms = Curl_sleep_time(data->set.max_recv_speed, + data->progress.dlspeed, state->blksize); + } + else + fd_read = state->sockfd; + } + + if(data->set.timeout) { + timeout_ms = data->set.timeout - Curl_tvdiff(k->now, k->start); + if(timeout_ms > state->retry_time * 1000) + timeout_ms = state->retry_time * 1000; + else if(timeout_ms < 0) + timeout_ms = 0; + } + + + /* Wait until ready to read or timeout occurs */ + rc = Curl_socket_ready(fd_read, CURL_SOCKET_BAD, timeout_ms); + + k->now = Curl_tvnow(); + + /* Force a progress callback if it's been too long */ + if(Curl_tvdiff(k->now, k->start) >= data->set.timeout) { + if(Curl_pgrsUpdate(conn)) { + tftp_state_machine(state, TFTP_EVENT_ERROR); + return CURLE_ABORTED_BY_CALLBACK; + } + k->start = k->now; + } + + if(rc == -1) { + /* bail out */ + int error = SOCKERRNO; + failf(data, "%s", Curl_strerror(conn, error)); + state->event = TFTP_EVENT_ERROR; + } + else { + + if(rc==0) { + /* A timeout occurred, but our timeout is variable, so maybe + just continue? */ + long rtms = state->retry_time * 1000; + if(Curl_tvdiff(k->now, transaction_start) > rtms) { + state->event = TFTP_EVENT_TIMEOUT; + /* Force a look at transfer timeouts */ + check_time = 1; + } + else { + continue; /* skip state machine */ + } + } + else { + result = tftp_receive_packet(conn); + if(result == CURLE_OK) + transaction_start = Curl_tvnow(); + + if(k->bytecountp) + *k->bytecountp = k->bytecount; /* read count */ + if(k->writebytecountp) + *k->writebytecountp = k->writebytecount; /* write count */ + } + } + + if(check_time) { + tftp_state_timeout(conn, NULL); + check_time = 0; + } + + if(result) + return(result); + + result = tftp_state_machine(state, state->event); + } + + /* Tell curl we're done */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + return(result); +} + +/********************************************************** + * + * tftp_multi_statemach + * + * Handle single RX socket event and return + * + **********************************************************/ +static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done) +{ + int rc; + tftp_event_t event; + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + long timeout_ms = tftp_state_timeout(conn, &event); + + *done = FALSE; + + if(timeout_ms <= 0) { + failf(data, "TFTP response timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + else if(event != TFTP_EVENT_NONE) { + result = tftp_state_machine(state, event); + if(result != CURLE_OK) + return(result); + *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; + if(*done) + /* Tell curl we're done */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + } + else { + /* no timeouts to handle, check our socket */ + rc = Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD, 0); + + if(rc == -1) { + /* bail out */ + int error = SOCKERRNO; + failf(data, "%s", Curl_strerror(conn, error)); + state->event = TFTP_EVENT_ERROR; + } + else if(rc != 0) { + result = tftp_receive_packet(conn); + if(result != CURLE_OK) + return(result); + result = tftp_state_machine(state, state->event); + if(result != CURLE_OK) + return(result); + *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; + if(*done) + /* Tell curl we're done */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + } + /* if rc == 0, then select() timed out */ + } + + return result; +} + +/********************************************************** + * + * tftp_doing + * + * Called from curl_multi.c while DOing + * + **********************************************************/ +static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done) +{ + CURLcode result; + result = tftp_multi_statemach(conn, dophase_done); + + if(*dophase_done) { + DEBUGF(infof(conn->data, "DO phase is complete\n")); + } + return result; +} + +/********************************************************** + * + * tftp_peform + * + * Entry point for transfer from tftp_do, sarts state mach + * + **********************************************************/ +static CURLcode tftp_perform(struct connectdata *conn, bool *dophase_done) +{ + CURLcode result = CURLE_OK; + tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + + *dophase_done = FALSE; + + result = tftp_state_machine(state, TFTP_EVENT_INIT); + + if(state->state == TFTP_STATE_FIN || result != CURLE_OK) + return(result); + + if(conn->data->state.used_interface == Curl_if_multi) + tftp_multi_statemach(conn, dophase_done); + else { + result = tftp_easy_statemach(conn); + *dophase_done = TRUE; /* with the easy interface we are done here */ + } + + if(*dophase_done) + DEBUGF(infof(conn->data, "DO phase is complete\n")); + + return result; +} + + +/********************************************************** + * + * tftp_do + * + * The do callback + * + * This callback initiates the TFTP transfer + * + **********************************************************/ + +static CURLcode tftp_do(struct connectdata *conn, bool *done) +{ + tftp_state_data_t *state; + CURLcode code; + + *done = FALSE; + + /* + Since connections can be re-used between SessionHandles, this might be a + connection already existing but on a fresh SessionHandle struct so we must + make sure we have a good 'struct TFTP' to play with. For new connections, + the struct TFTP is allocated and setup in the tftp_connect() function. + */ + Curl_reset_reqproto(conn); + + if(!conn->proto.tftpc) { + code = tftp_connect(conn, done); + if(code) + return code; + } + state = (tftp_state_data_t *)conn->proto.tftpc; + + code = tftp_perform(conn, done); + + /* If tftp_perform() returned an error, use that for return code. If it + was OK, see if tftp_translate_code() has an error. */ + if(code == CURLE_OK) + /* If we have encountered an internal tftp error, translate it. */ + code = tftp_translate_code(state->error); + + return code; +} + +static CURLcode tftp_setup_connection(struct connectdata * conn) +{ + struct SessionHandle *data = conn->data; + char * type; + char command; + + conn->socktype = SOCK_DGRAM; /* UDP datagram based */ + + /* TFTP URLs support an extension like ";mode=" that + * we'll try to get now! */ + type = strstr(data->state.path, ";mode="); + + if(!type) + type = strstr(conn->host.rawalloc, ";mode="); + + if(type) { + *type = 0; /* it was in the middle of the hostname */ + command = Curl_raw_toupper(type[6]); + + switch (command) { + case 'A': /* ASCII mode */ + case 'N': /* NETASCII mode */ + data->set.prefer_ascii = TRUE; + break; + + case 'O': /* octet mode */ + case 'I': /* binary mode */ + default: + /* switch off ASCII */ + data->set.prefer_ascii = FALSE; + break; + } + } + + return CURLE_OK; +} +#endif diff --git a/lib/curl_timeval.c b/lib/curl_timeval.c new file mode 100644 index 000000000..8e4c7bd76 --- /dev/null +++ b/lib/curl_timeval.c @@ -0,0 +1,134 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2008, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_timeval.h" + +#if defined(WIN32) && !defined(MSDOS) + +struct timeval curlx_tvnow(void) +{ + /* + ** GetTickCount() is available on _all_ Windows versions from W95 up + ** to nowadays. Returns milliseconds elapsed since last system boot, + ** increases monotonically and wraps once 49.7 days have elapsed. + */ + struct timeval now; + DWORD milliseconds = GetTickCount(); + now.tv_sec = milliseconds / 1000; + now.tv_usec = (milliseconds % 1000) * 1000; + return now; +} + +#elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) + +struct timeval curlx_tvnow(void) +{ + /* + ** clock_gettime() is granted to be increased monotonically when the + ** monotonic clock is queried. Time starting point is unspecified, it + ** could be the system start-up time, the Epoch, or something else, + ** in any case the time starting point does not change once that the + ** system has started up. + */ + struct timeval now; + struct timespec tsnow; + if(0 == clock_gettime(CLOCK_MONOTONIC, &tsnow)) { + now.tv_sec = tsnow.tv_sec; + now.tv_usec = tsnow.tv_nsec / 1000; + } + /* + ** Even when the configure process has truly detected monotonic clock + ** availability, it might happen that it is not actually available at + ** run-time. When this occurs simply fallback to other time source. + */ +#ifdef HAVE_GETTIMEOFDAY + else + (void)gettimeofday(&now, NULL); +#else + else { + now.tv_sec = (long)time(NULL); + now.tv_usec = 0; + } +#endif + return now; +} + +#elif defined(HAVE_GETTIMEOFDAY) + +struct timeval curlx_tvnow(void) +{ + /* + ** gettimeofday() is not granted to be increased monotonically, due to + ** clock drifting and external source time synchronization it can jump + ** forward or backward in time. + */ + struct timeval now; + (void)gettimeofday(&now, NULL); + return now; +} + +#else + +struct timeval curlx_tvnow(void) +{ + /* + ** time() returns the value of time in seconds since the Epoch. + */ + struct timeval now; + now.tv_sec = (long)time(NULL); + now.tv_usec = 0; + return now; +} + +#endif + +/* + * Make sure that the first argument is the more recent time, as otherwise + * we'll get a weird negative time-diff back... + * + * Returns: the time difference in number of milliseconds. + */ +long curlx_tvdiff(struct timeval newer, struct timeval older) +{ + return (newer.tv_sec-older.tv_sec)*1000+ + (newer.tv_usec-older.tv_usec)/1000; +} + +/* + * Same as curlx_tvdiff but with full usec resolution. + * + * Returns: the time difference in seconds with subsecond resolution. + */ +double curlx_tvdiff_secs(struct timeval newer, struct timeval older) +{ + if(newer.tv_sec != older.tv_sec) + return (double)(newer.tv_sec-older.tv_sec)+ + (double)(newer.tv_usec-older.tv_usec)/1000000.0; + else + return (double)(newer.tv_usec-older.tv_usec)/1000000.0; +} + +/* return the number of seconds in the given input timeval struct */ +long Curl_tvlong(struct timeval t1) +{ + return t1.tv_sec; +} diff --git a/lib/curl_transfer.c b/lib/curl_transfer.c new file mode 100644 index 000000000..a1dee1dd0 --- /dev/null +++ b/lib/curl_transfer.c @@ -0,0 +1,2338 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_strtoofft.h" +#include "curl_strequal.h" +#include "curl_rawstr.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SIGNAL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#ifndef HAVE_SOCKET +#error "We can't compile without socket() support!" +#endif + +#include "curl_urldata.h" +#include +#include "curl_netrc.h" + +#include "curl_content_encoding.h" +#include "curl_hostip.h" +#include "curl_transfer.h" +#include "curl_sendf.h" +#include "curl_speedcheck.h" +#include "curl_progress.h" +#include "curl_http.h" +#include "curl_url.h" +#include "curl_getinfo.h" +#include "curl_sslgen.h" +#include "curl_http_digest.h" +#include "curl_ntlm.h" +#include "curl_http_negotiate.h" +#include "curl_share.h" +#include "curl_memory.h" +#include "curl_select.h" +#include "curl_multiif.h" +#include "curl_connect.h" +#include "curl_non_ascii.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +/* The last #include file should be: */ +#include "curl_memdebug.h" + +#define CURL_TIMEOUT_EXPECT_100 1000 /* counting ms here */ + +/* + * This function will call the read callback to fill our buffer with data + * to upload. + */ +CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp) +{ + struct SessionHandle *data = conn->data; + size_t buffersize = (size_t)bytes; + int nread; +#ifdef CURL_DOES_CONVERSIONS + bool sending_http_headers = FALSE; + + if((conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) && + (data->state.proto.http->sending == HTTPSEND_REQUEST)) { + /* We're sending the HTTP request headers, not the data. + Remember that so we don't re-translate them into garbage. */ + sending_http_headers = TRUE; + } +#endif + + if(data->req.upload_chunky) { + /* if chunked Transfer-Encoding */ + buffersize -= (8 + 2 + 2); /* 32bit hex + CRLF + CRLF */ + data->req.upload_fromhere += (8 + 2); /* 32bit hex + CRLF */ + } + + /* this function returns a size_t, so we typecast to int to prevent warnings + with picky compilers */ + nread = (int)conn->fread_func(data->req.upload_fromhere, 1, + buffersize, conn->fread_in); + + if(nread == CURL_READFUNC_ABORT) { + failf(data, "operation aborted by callback"); + *nreadp = 0; + return CURLE_ABORTED_BY_CALLBACK; + } + else if(nread == CURL_READFUNC_PAUSE) { + struct SingleRequest *k = &data->req; + /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ + k->keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ + if(data->req.upload_chunky) { + /* Back out the preallocation done above */ + data->req.upload_fromhere -= (8 + 2); + } + *nreadp = 0; + return CURLE_OK; /* nothing was read */ + } + else if((size_t)nread > buffersize) { + /* the read function returned a too large value */ + *nreadp = 0; + failf(data, "read function returned funny value"); + return CURLE_READ_ERROR; + } + + if(!data->req.forbidchunk && data->req.upload_chunky) { + /* if chunked Transfer-Encoding + * build chunk: + * + * CRLF + * CRLF + */ + /* On non-ASCII platforms the may or may not be + translated based on set.prefer_ascii while the protocol + portion must always be translated to the network encoding. + To further complicate matters, line end conversion might be + done later on, so we need to prevent CRLFs from becoming + CRCRLFs if that's the case. To do this we use bare LFs + here, knowing they'll become CRLFs later on. + */ + + char hexbuffer[11]; + const char *endofline_native; + const char *endofline_network; + int hexlen; + + if( +#ifdef CURL_DO_LINEEND_CONV + (data->set.prefer_ascii) || +#endif + (data->set.crlf)) { + /* \n will become \r\n later on */ + endofline_native = "\n"; + endofline_network = "\x0a"; + } + else { + endofline_native = "\r\n"; + endofline_network = "\x0d\x0a"; + } + hexlen = snprintf(hexbuffer, sizeof(hexbuffer), + "%x%s", nread, endofline_native); + + /* move buffer pointer */ + data->req.upload_fromhere -= hexlen; + nread += hexlen; + + /* copy the prefix to the buffer, leaving out the NUL */ + memcpy(data->req.upload_fromhere, hexbuffer, hexlen); + + /* always append ASCII CRLF to the data */ + memcpy(data->req.upload_fromhere + nread, + endofline_network, + strlen(endofline_network)); + +#ifdef CURL_DOES_CONVERSIONS + CURLcode res; + int length; + if(data->set.prefer_ascii) { + /* translate the protocol and data */ + length = nread; + } + else { + /* just translate the protocol portion */ + length = strlen(hexbuffer); + } + res = Curl_convert_to_network(data, data->req.upload_fromhere, length); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(res) + return(res); +#endif /* CURL_DOES_CONVERSIONS */ + + if((nread - hexlen) == 0) + /* mark this as done once this chunk is transferred */ + data->req.upload_done = TRUE; + + nread+=(int)strlen(endofline_native); /* for the added end of line */ + } +#ifdef CURL_DOES_CONVERSIONS + else if((data->set.prefer_ascii) && (!sending_http_headers)) { + CURLcode res; + res = Curl_convert_to_network(data, data->req.upload_fromhere, nread); + /* Curl_convert_to_network calls failf if unsuccessful */ + if(res != CURLE_OK) + return(res); + } +#endif /* CURL_DOES_CONVERSIONS */ + + *nreadp = nread; + + return CURLE_OK; +} + + +/* + * Curl_readrewind() rewinds the read stream. This is typically used for HTTP + * POST/PUT with multi-pass authentication when a sending was denied and a + * resend is necessary. + */ +CURLcode Curl_readrewind(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + + conn->bits.rewindaftersend = FALSE; /* we rewind now */ + + /* explicitly switch off sending data on this connection now since we are + about to restart a new transfer and thus we want to avoid inadvertently + sending more data on the existing connection until the next transfer + starts */ + data->req.keepon &= ~KEEP_SEND; + + /* We have sent away data. If not using CURLOPT_POSTFIELDS or + CURLOPT_HTTPPOST, call app to rewind + */ + if(data->set.postfields || + (data->set.httpreq == HTTPREQ_POST_FORM)) + ; /* do nothing */ + else { + if(data->set.seek_func) { + int err; + + err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); + if(err) { + failf(data, "seek callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; + } + } + else if(data->set.ioctl_func) { + curlioerr err; + + err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, + data->set.ioctl_client); + infof(data, "the ioctl callback returned %d\n", (int)err); + + if(err) { + /* FIXME: convert to a human readable error message */ + failf(data, "ioctl callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; + } + } + else { + /* If no CURLOPT_READFUNCTION is used, we know that we operate on a + given FILE * stream and we can actually attempt to rewind that + ourselves with fseek() */ + if(data->set.fread_func == (curl_read_callback)fread) { + if(-1 != fseek(data->set.in, 0, SEEK_SET)) + /* successful rewind */ + return CURLE_OK; + } + + /* no callback set or failure above, makes us fail at once */ + failf(data, "necessary data rewind wasn't possible"); + return CURLE_SEND_FAIL_REWIND; + } + } + return CURLE_OK; +} + +static int data_pending(const struct connectdata *conn) +{ + /* in the case of libssh2, we can never be really sure that we have emptied + its internal buffers so we MUST always try until we get EAGAIN back */ + return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) || + Curl_ssl_data_pending(conn, FIRSTSOCKET); +} + +static void read_rewind(struct connectdata *conn, + size_t thismuch) +{ + DEBUGASSERT(conn->read_pos >= thismuch); + + conn->read_pos -= thismuch; + conn->bits.stream_was_rewound = TRUE; + +#ifdef DEBUGBUILD + { + char buf[512 + 1]; + size_t show; + + show = CURLMIN(conn->buf_len - conn->read_pos, sizeof(buf)-1); + if(conn->master_buffer) { + memcpy(buf, conn->master_buffer + conn->read_pos, show); + buf[show] = '\0'; + } + else { + buf[0] = '\0'; + } + + DEBUGF(infof(conn->data, + "Buffer after stream rewind (read_pos = %zu): [%s]\n", + conn->read_pos, buf)); + } +#endif +} + +/* + * Check to see if CURLOPT_TIMECONDITION was met by comparing the time of the + * remote document with the time provided by CURLOPT_TIMEVAL + */ +bool Curl_meets_timecondition(struct SessionHandle *data, time_t timeofdoc) +{ + if((timeofdoc == 0) || (data->set.timevalue == 0)) + return TRUE; + + switch(data->set.timecondition) { + case CURL_TIMECOND_IFMODSINCE: + default: + if(timeofdoc <= data->set.timevalue) { + infof(data, + "The requested document is not new enough\n"); + data->info.timecond = TRUE; + return FALSE; + } + break; + case CURL_TIMECOND_IFUNMODSINCE: + if(timeofdoc >= data->set.timevalue) { + infof(data, + "The requested document is not old enough\n"); + data->info.timecond = TRUE; + return FALSE; + } + break; + } + + return TRUE; +} + +/* + * Go ahead and do a read if we have a readable socket or if + * the stream was rewound (in which case we have data in a + * buffer) + */ +static CURLcode readwrite_data(struct SessionHandle *data, + struct connectdata *conn, + struct SingleRequest *k, + int *didwhat, bool *done) +{ + CURLcode result = CURLE_OK; + ssize_t nread; /* number of bytes read */ + size_t excess = 0; /* excess bytes read */ + bool is_empty_data = FALSE; + bool readmore = FALSE; /* used by RTP to signal for more data */ + + *done = FALSE; + + /* This is where we loop until we have read everything there is to + read or we get a CURLE_AGAIN */ + do { + size_t buffersize = data->set.buffer_size? + data->set.buffer_size : BUFSIZE; + size_t bytestoread = buffersize; + + if(k->size != -1 && !k->header) { + /* make sure we don't read "too much" if we can help it since we + might be pipelining and then someone else might want to read what + follows! */ + curl_off_t totalleft = k->size - k->bytecount; + if(totalleft < (curl_off_t)bytestoread) + bytestoread = (size_t)totalleft; + } + + if(bytestoread) { + /* receive data from the network! */ + result = Curl_read(conn, conn->sockfd, k->buf, bytestoread, &nread); + + /* read would've blocked */ + if(CURLE_AGAIN == result) + break; /* get out of loop */ + + if(result>0) + return result; + } + else { + /* read nothing but since we wanted nothing we consider this an OK + situation to proceed from */ + nread = 0; + } + + if((k->bytecount == 0) && (k->writebytecount == 0)) { + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + if(k->exp100 > EXP100_SEND_DATA) + /* set time stamp to compare with when waiting for the 100 */ + k->start100 = Curl_tvnow(); + } + + *didwhat |= KEEP_RECV; + /* indicates data of zero size, i.e. empty file */ + is_empty_data = ((nread == 0) && (k->bodywrites == 0)) ? TRUE : FALSE; + + /* NUL terminate, allowing string ops to be used */ + if(0 < nread || is_empty_data) { + k->buf[nread] = 0; + } + else if(0 >= nread) { + /* if we receive 0 or less here, the server closed the connection + and we bail out from this! */ + DEBUGF(infof(data, "nread <= 0, server closed connection, bailing\n")); + k->keepon &= ~KEEP_RECV; + break; + } + + /* Default buffer to use when we write the buffer, it may be changed + in the flow below before the actual storing is done. */ + k->str = k->buf; + + if(conn->handler->readwrite) { + result = conn->handler->readwrite(data, conn, &nread, &readmore); + if(result) + return result; + if(readmore) + break; + } + +#ifndef CURL_DISABLE_HTTP + /* Since this is a two-state thing, we check if we are parsing + headers at the moment or not. */ + if(k->header) { + /* we are in parse-the-header-mode */ + bool stop_reading = FALSE; + result = Curl_http_readwrite_headers(data, conn, &nread, &stop_reading); + if(result) + return result; + + if(conn->handler->readwrite && + (k->maxdownload <= 0 && nread > 0)) { + result = conn->handler->readwrite(data, conn, &nread, &readmore); + if(result) + return result; + if(readmore) + break; + } + + if(stop_reading) { + /* We've stopped dealing with input, get out of the do-while loop */ + + if(nread > 0) { + if(conn->data->multi && Curl_multi_canPipeline(conn->data->multi)) { + infof(data, + "Rewinding stream by : %zd" + " bytes on url %s (zero-length body)\n", + nread, data->state.path); + read_rewind(conn, (size_t)nread); + } + else { + infof(data, + "Excess found in a non pipelined read:" + " excess = %zd" + " url = %s (zero-length body)\n", + nread, data->state.path); + } + } + + break; + } + } +#endif /* CURL_DISABLE_HTTP */ + + + /* This is not an 'else if' since it may be a rest from the header + parsing, where the beginning of the buffer is headers and the end + is non-headers. */ + if(k->str && !k->header && (nread > 0 || is_empty_data)) { + +#ifndef CURL_DISABLE_HTTP + if(0 == k->bodywrites && !is_empty_data) { + /* These checks are only made the first time we are about to + write a piece of the body */ + if(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) { + /* HTTP-only checks */ + + if(data->req.newurl) { + if(conn->bits.close) { + /* Abort after the headers if "follow Location" is set + and we're set to close anyway. */ + k->keepon &= ~KEEP_RECV; + *done = TRUE; + return CURLE_OK; + } + /* We have a new url to load, but since we want to be able + to re-use this connection properly, we read the full + response in "ignore more" */ + k->ignorebody = TRUE; + infof(data, "Ignoring the response-body\n"); + } + if(data->state.resume_from && !k->content_range && + (data->set.httpreq==HTTPREQ_GET) && + !k->ignorebody) { + /* we wanted to resume a download, although the server doesn't + * seem to support this and we did this with a GET (if it + * wasn't a GET we did a POST or PUT resume) */ + failf(data, "HTTP server doesn't seem to support " + "byte ranges. Cannot resume."); + return CURLE_RANGE_ERROR; + } + + if(data->set.timecondition && !data->state.range) { + /* A time condition has been set AND no ranges have been + requested. This seems to be what chapter 13.3.4 of + RFC 2616 defines to be the correct action for a + HTTP/1.1 client */ + + if(!Curl_meets_timecondition(data, k->timeofdoc)) { + *done = TRUE; + /* we abort the transfer before it is completed == we ruin the + re-use ability. Close the connection */ + conn->bits.close = TRUE; + return CURLE_OK; + } + } /* we have a time condition */ + + } /* this is HTTP or RTSP */ + } /* this is the first time we write a body part */ +#endif /* CURL_DISABLE_HTTP */ + + k->bodywrites++; + + /* pass data to the debug function before it gets "dechunked" */ + if(data->set.verbose) { + if(k->badheader) { + Curl_debug(data, CURLINFO_DATA_IN, data->state.headerbuff, + (size_t)k->hbuflen, conn); + if(k->badheader == HEADER_PARTHEADER) + Curl_debug(data, CURLINFO_DATA_IN, + k->str, (size_t)nread, conn); + } + else + Curl_debug(data, CURLINFO_DATA_IN, + k->str, (size_t)nread, conn); + } + +#ifndef CURL_DISABLE_HTTP + if(k->chunk) { + /* + * Here comes a chunked transfer flying and we need to decode this + * properly. While the name says read, this function both reads + * and writes away the data. The returned 'nread' holds the number + * of actual data it wrote to the client. + */ + + CHUNKcode res = + Curl_httpchunk_read(conn, k->str, nread, &nread); + + if(CHUNKE_OK < res) { + if(CHUNKE_WRITE_ERROR == res) { + failf(data, "Failed writing data"); + return CURLE_WRITE_ERROR; + } + failf(data, "Problem (%d) in the Chunked-Encoded data", (int)res); + return CURLE_RECV_ERROR; + } + else if(CHUNKE_STOP == res) { + size_t dataleft; + /* we're done reading chunks! */ + k->keepon &= ~KEEP_RECV; /* read no more */ + + /* There are now possibly N number of bytes at the end of the + str buffer that weren't written to the client. + + We DO care about this data if we are pipelining. + Push it back to be read on the next pass. */ + + dataleft = conn->chunk.dataleft; + if(dataleft != 0) { + infof(conn->data, "Leftovers after chunking: %zu bytes\n", + dataleft); + if(conn->data->multi && + Curl_multi_canPipeline(conn->data->multi)) { + /* only attempt the rewind if we truly are pipelining */ + infof(conn->data, "Rewinding %zu bytes\n",dataleft); + read_rewind(conn, dataleft); + } + } + } + /* If it returned OK, we just keep going */ + } +#endif /* CURL_DISABLE_HTTP */ + + /* Account for body content stored in the header buffer */ + if(k->badheader && !k->ignorebody) { + DEBUGF(infof(data, "Increasing bytecount by %zu from hbuflen\n", + k->hbuflen)); + k->bytecount += k->hbuflen; + } + + if((-1 != k->maxdownload) && + (k->bytecount + nread >= k->maxdownload)) { + + excess = (size_t)(k->bytecount + nread - k->maxdownload); + if(excess > 0 && !k->ignorebody) { + if(conn->data->multi && Curl_multi_canPipeline(conn->data->multi)) { + /* The 'excess' amount below can't be more than BUFSIZE which + always will fit in a size_t */ + infof(data, + "Rewinding stream by : %zu" + " bytes on url %s (size = %" FORMAT_OFF_T + ", maxdownload = %" FORMAT_OFF_T + ", bytecount = %" FORMAT_OFF_T ", nread = %zd)\n", + excess, data->state.path, + k->size, k->maxdownload, k->bytecount, nread); + read_rewind(conn, excess); + } + else { + infof(data, + "Excess found in a non pipelined read:" + " excess = %zu" + ", size = %" FORMAT_OFF_T + ", maxdownload = %" FORMAT_OFF_T + ", bytecount = %" FORMAT_OFF_T "\n", + excess, k->size, k->maxdownload, k->bytecount); + } + } + + nread = (ssize_t) (k->maxdownload - k->bytecount); + if(nread < 0 ) /* this should be unusual */ + nread = 0; + + k->keepon &= ~KEEP_RECV; /* we're done reading */ + } + + k->bytecount += nread; + + Curl_pgrsSetDownloadCounter(data, k->bytecount); + + if(!k->chunk && (nread || k->badheader || is_empty_data)) { + /* If this is chunky transfer, it was already written */ + + if(k->badheader && !k->ignorebody) { + /* we parsed a piece of data wrongly assuming it was a header + and now we output it as body instead */ + + /* Don't let excess data pollute body writes */ + if(k->maxdownload == -1 || (curl_off_t)k->hbuflen <= k->maxdownload) + result = Curl_client_write(conn, CLIENTWRITE_BODY, + data->state.headerbuff, + k->hbuflen); + else + result = Curl_client_write(conn, CLIENTWRITE_BODY, + data->state.headerbuff, + (size_t)k->maxdownload); + + if(result) + return result; + } + if(k->badheader < HEADER_ALLBAD) { + /* This switch handles various content encodings. If there's an + error here, be sure to check over the almost identical code + in curl_http_chunks.c. + Make sure that ALL_CONTENT_ENCODINGS contains all the + encodings handled here. */ +#ifdef HAVE_LIBZ + switch (conn->data->set.http_ce_skip ? + IDENTITY : k->auto_decoding) { + case IDENTITY: +#endif + /* This is the default when the server sends no + Content-Encoding header. See Curl_readwrite_init; the + memset() call initializes k->auto_decoding to zero. */ + if(!k->ignorebody) { + +#ifndef CURL_DISABLE_POP3 + if(conn->handler->protocol&CURLPROTO_POP3) + result = Curl_pop3_write(conn, k->str, nread); + else +#endif /* CURL_DISABLE_POP3 */ + + result = Curl_client_write(conn, CLIENTWRITE_BODY, k->str, + nread); + } +#ifdef HAVE_LIBZ + break; + + case DEFLATE: + /* Assume CLIENTWRITE_BODY; headers are not encoded. */ + if(!k->ignorebody) + result = Curl_unencode_deflate_write(conn, k, nread); + break; + + case GZIP: + /* Assume CLIENTWRITE_BODY; headers are not encoded. */ + if(!k->ignorebody) + result = Curl_unencode_gzip_write(conn, k, nread); + break; + + case COMPRESS: + default: + failf (data, "Unrecognized content encoding type. " + "libcurl understands `identity', `deflate' and `gzip' " + "content encodings."); + result = CURLE_BAD_CONTENT_ENCODING; + break; + } +#endif + } + k->badheader = HEADER_NORMAL; /* taken care of now */ + + if(result) + return result; + } + + } /* if(! header and data to read ) */ + + if(conn->handler->readwrite && + (excess > 0 && !conn->bits.stream_was_rewound)) { + /* Parse the excess data */ + k->str += nread; + nread = (ssize_t)excess; + + result = conn->handler->readwrite(data, conn, &nread, &readmore); + if(result) + return result; + + if(readmore) + k->keepon |= KEEP_RECV; /* we're not done reading */ + break; + } + + if(is_empty_data) { + /* if we received nothing, the server closed the connection and we + are done */ + k->keepon &= ~KEEP_RECV; + } + + } while(data_pending(conn)); + + if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) && + conn->bits.close ) { + /* When we've read the entire thing and the close bit is set, the server + may now close the connection. If there's now any kind of sending going + on from our side, we need to stop that immediately. */ + infof(data, "we are done reading and this is set to close, stop send\n"); + k->keepon &= ~KEEP_SEND; /* no writing anymore either */ + } + + return CURLE_OK; +} + +/* + * Send data to upload to the server, when the socket is writable. + */ +static CURLcode readwrite_upload(struct SessionHandle *data, + struct connectdata *conn, + struct SingleRequest *k, + int *didwhat) +{ + ssize_t i, si; + ssize_t bytes_written; + CURLcode result; + ssize_t nread; /* number of bytes read */ + bool sending_http_headers = FALSE; + + if((k->bytecount == 0) && (k->writebytecount == 0)) + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + + *didwhat |= KEEP_SEND; + + /* + * We loop here to do the READ and SEND loop until we run out of + * data to send or until we get EWOULDBLOCK back + * + * FIXME: above comment is misleading. Currently no looping is + * actually done in do-while loop below. + */ + do { + + /* only read more data if there's no upload data already + present in the upload buffer */ + if(0 == data->req.upload_present) { + /* init the "upload from here" pointer */ + data->req.upload_fromhere = k->uploadbuf; + + if(!k->upload_done) { + /* HTTP pollution, this should be written nicer to become more + protocol agnostic. */ + int fillcount; + + if((k->exp100 == EXP100_SENDING_REQUEST) && + (data->state.proto.http->sending == HTTPSEND_BODY)) { + /* If this call is to send body data, we must take some action: + We have sent off the full HTTP 1.1 request, and we shall now + go into the Expect: 100 state and await such a header */ + k->exp100 = EXP100_AWAITING_CONTINUE; /* wait for the header */ + k->keepon &= ~KEEP_SEND; /* disable writing */ + k->start100 = Curl_tvnow(); /* timeout count starts now */ + *didwhat &= ~KEEP_SEND; /* we didn't write anything actually */ + + /* set a timeout for the multi interface */ + Curl_expire(data, CURL_TIMEOUT_EXPECT_100); + break; + } + + if(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) { + if(data->state.proto.http->sending == HTTPSEND_REQUEST) + /* We're sending the HTTP request headers, not the data. + Remember that so we don't change the line endings. */ + sending_http_headers = TRUE; + else + sending_http_headers = FALSE; + } + + result = Curl_fillreadbuffer(conn, BUFSIZE, &fillcount); + if(result) + return result; + + nread = (ssize_t)fillcount; + } + else + nread = 0; /* we're done uploading/reading */ + + if(!nread && (k->keepon & KEEP_SEND_PAUSE)) { + /* this is a paused transfer */ + break; + } + else if(nread<=0) { + /* done */ + k->keepon &= ~KEEP_SEND; /* we're done writing */ + + if(conn->bits.rewindaftersend) { + result = Curl_readrewind(conn); + if(result) + return result; + } + break; + } + + /* store number of bytes available for upload */ + data->req.upload_present = nread; + +#ifndef CURL_DISABLE_SMTP + if(conn->handler->protocol & CURLPROTO_SMTP) { + result = Curl_smtp_escape_eob(conn, nread); + if(result) + return result; + } + else +#endif /* CURL_DISABLE_SMTP */ + + /* convert LF to CRLF if so asked */ + if((!sending_http_headers) && ( +#ifdef CURL_DO_LINEEND_CONV + /* always convert if we're FTPing in ASCII mode */ + (data->set.prefer_ascii) || +#endif + (data->set.crlf))) { + if(data->state.scratch == NULL) + data->state.scratch = malloc(2*BUFSIZE); + if(data->state.scratch == NULL) { + failf (data, "Failed to alloc scratch buffer!"); + return CURLE_OUT_OF_MEMORY; + } + /* + * ASCII/EBCDIC Note: This is presumably a text (not binary) + * transfer so the data should already be in ASCII. + * That means the hex values for ASCII CR (0x0d) & LF (0x0a) + * must be used instead of the escape sequences \r & \n. + */ + for(i = 0, si = 0; i < nread; i++, si++) { + if(data->req.upload_fromhere[i] == 0x0a) { + data->state.scratch[si++] = 0x0d; + data->state.scratch[si] = 0x0a; + if(!data->set.crlf) { + /* we're here only because FTP is in ASCII mode... + bump infilesize for the LF we just added */ + data->set.infilesize++; + } + } + else + data->state.scratch[si] = data->req.upload_fromhere[i]; + } + if(si != nread) { + /* only perform the special operation if we really did replace + anything */ + nread = si; + + /* upload from the new (replaced) buffer instead */ + data->req.upload_fromhere = data->state.scratch; + + /* set the new amount too */ + data->req.upload_present = nread; + } + } + } /* if 0 == data->req.upload_present */ + else { + /* We have a partial buffer left from a previous "round". Use + that instead of reading more data */ + } + + /* write to socket (send away data) */ + result = Curl_write(conn, + conn->writesockfd, /* socket to send to */ + data->req.upload_fromhere, /* buffer pointer */ + data->req.upload_present, /* buffer size */ + &bytes_written); /* actually sent */ + + if(result) + return result; + + if(data->set.verbose) + /* show the data before we change the pointer upload_fromhere */ + Curl_debug(data, CURLINFO_DATA_OUT, data->req.upload_fromhere, + (size_t)bytes_written, conn); + + k->writebytecount += bytes_written; + + if(k->writebytecount == data->set.infilesize) { + /* we have sent all data we were supposed to */ + k->upload_done = TRUE; + infof(data, "We are completely uploaded and fine\n"); + } + + if(data->req.upload_present != bytes_written) { + /* we only wrote a part of the buffer (if anything), deal with it! */ + + /* store the amount of bytes left in the buffer to write */ + data->req.upload_present -= bytes_written; + + /* advance the pointer where to find the buffer when the next send + is to happen */ + data->req.upload_fromhere += bytes_written; + } + else { + /* we've uploaded that buffer now */ + data->req.upload_fromhere = k->uploadbuf; + data->req.upload_present = 0; /* no more bytes left */ + + if(k->upload_done) { + /* switch off writing, we're done! */ + k->keepon &= ~KEEP_SEND; /* we're done writing */ + } + } + + Curl_pgrsSetUploadCounter(data, k->writebytecount); + + } WHILE_FALSE; /* just to break out from! */ + + return CURLE_OK; +} + +/* + * Curl_readwrite() is the low-level function to be called when data is to + * be read and written to/from the connection. + */ +CURLcode Curl_readwrite(struct connectdata *conn, + bool *done) +{ + struct SessionHandle *data = conn->data; + struct SingleRequest *k = &data->req; + CURLcode result; + int didwhat=0; + + curl_socket_t fd_read; + curl_socket_t fd_write; + int select_res = conn->cselect_bits; + + conn->cselect_bits = 0; + + /* only use the proper socket if the *_HOLD bit is not set simultaneously as + then we are in rate limiting state in that transfer direction */ + + if((k->keepon & KEEP_RECVBITS) == KEEP_RECV) + fd_read = conn->sockfd; + else + fd_read = CURL_SOCKET_BAD; + + if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) + fd_write = conn->writesockfd; + else + fd_write = CURL_SOCKET_BAD; + + if(!select_res) /* Call for select()/poll() only, if read/write/error + status is not known. */ + select_res = Curl_socket_ready(fd_read, fd_write, 0); + + if(select_res == CURL_CSELECT_ERR) { + failf(data, "select/poll returned error"); + return CURLE_SEND_ERROR; + } + + /* We go ahead and do a read if we have a readable socket or if + the stream was rewound (in which case we have data in a + buffer) */ + if((k->keepon & KEEP_RECV) && + ((select_res & CURL_CSELECT_IN) || conn->bits.stream_was_rewound)) { + + result = readwrite_data(data, conn, k, &didwhat, done); + if(result || *done) + return result; + } + + /* If we still have writing to do, we check if we have a writable socket. */ + if((k->keepon & KEEP_SEND) && (select_res & CURL_CSELECT_OUT)) { + /* write */ + + result = readwrite_upload(data, conn, k, &didwhat); + if(result) + return result; + } + + k->now = Curl_tvnow(); + if(didwhat) { + /* Update read/write counters */ + if(k->bytecountp) + *k->bytecountp = k->bytecount; /* read count */ + if(k->writebytecountp) + *k->writebytecountp = k->writebytecount; /* write count */ + } + else { + /* no read no write, this is a timeout? */ + if(k->exp100 == EXP100_AWAITING_CONTINUE) { + /* This should allow some time for the header to arrive, but only a + very short time as otherwise it'll be too much wasted time too + often. */ + + /* Quoting RFC2616, section "8.2.3 Use of the 100 (Continue) Status": + + Therefore, when a client sends this header field to an origin server + (possibly via a proxy) from which it has never seen a 100 (Continue) + status, the client SHOULD NOT wait for an indefinite period before + sending the request body. + + */ + + long ms = Curl_tvdiff(k->now, k->start100); + if(ms > CURL_TIMEOUT_EXPECT_100) { + /* we've waited long enough, continue anyway */ + k->exp100 = EXP100_SEND_DATA; + k->keepon |= KEEP_SEND; + infof(data, "Done waiting for 100-continue\n"); + } + } + } + + if(Curl_pgrsUpdate(conn)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, k->now); + if(result) + return result; + + if(k->keepon) { + if(0 > Curl_timeleft(data, &k->now, FALSE)) { + if(k->size != -1) { + failf(data, "Operation timed out after %ld milliseconds with %" + FORMAT_OFF_T " out of %" FORMAT_OFF_T " bytes received", + Curl_tvdiff(k->now, data->progress.t_startsingle), k->bytecount, + k->size); + } + else { + failf(data, "Operation timed out after %ld milliseconds with %" + FORMAT_OFF_T " bytes received", + Curl_tvdiff(k->now, data->progress.t_startsingle), k->bytecount); + } + return CURLE_OPERATION_TIMEDOUT; + } + } + else { + /* + * The transfer has been performed. Just make some general checks before + * returning. + */ + + if(!(data->set.opt_no_body) && (k->size != -1) && + (k->bytecount != k->size) && +#ifdef CURL_DO_LINEEND_CONV + /* Most FTP servers don't adjust their file SIZE response for CRLFs, + so we'll check to see if the discrepancy can be explained + by the number of CRLFs we've changed to LFs. + */ + (k->bytecount != (k->size + data->state.crlf_conversions)) && +#endif /* CURL_DO_LINEEND_CONV */ + !data->req.newurl) { + failf(data, "transfer closed with %" FORMAT_OFF_T + " bytes remaining to read", + k->size - k->bytecount); + return CURLE_PARTIAL_FILE; + } + else if(!(data->set.opt_no_body) && + k->chunk && + (conn->chunk.state != CHUNK_STOP)) { + /* + * In chunked mode, return an error if the connection is closed prior to + * the empty (terminating) chunk is read. + * + * The condition above used to check for + * conn->proto.http->chunk.datasize != 0 which is true after reading + * *any* chunk, not just the empty chunk. + * + */ + failf(data, "transfer closed with outstanding read data remaining"); + return CURLE_PARTIAL_FILE; + } + if(Curl_pgrsUpdate(conn)) + return CURLE_ABORTED_BY_CALLBACK; + } + + /* Now update the "done" boolean we return */ + *done = (0 == (k->keepon&(KEEP_RECV|KEEP_SEND| + KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE; + + return CURLE_OK; +} + +/* + * Curl_single_getsock() gets called by the multi interface code when the app + * has requested to get the sockets for the current connection. This function + * will then be called once for every connection that the multi interface + * keeps track of. This function will only be called for connections that are + * in the proper state to have this information available. + */ +int Curl_single_getsock(const struct connectdata *conn, + curl_socket_t *sock, /* points to numsocks number + of sockets */ + int numsocks) +{ + const struct SessionHandle *data = conn->data; + int bitmap = GETSOCK_BLANK; + unsigned sockindex = 0; + + if(conn->handler->perform_getsock) + return conn->handler->perform_getsock(conn, sock, numsocks); + + if(numsocks < 2) + /* simple check but we might need two slots */ + return GETSOCK_BLANK; + + /* don't include HOLD and PAUSE connections */ + if((data->req.keepon & KEEP_RECVBITS) == KEEP_RECV) { + + DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); + + bitmap |= GETSOCK_READSOCK(sockindex); + sock[sockindex] = conn->sockfd; + } + + /* don't include HOLD and PAUSE connections */ + if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) { + + if((conn->sockfd != conn->writesockfd) || + !(data->req.keepon & KEEP_RECV)) { + /* only if they are not the same socket or we didn't have a readable + one, we increase index */ + if(data->req.keepon & KEEP_RECV) + sockindex++; /* increase index if we need two entries */ + + DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); + + sock[sockindex] = conn->writesockfd; + } + + bitmap |= GETSOCK_WRITESOCK(sockindex); + } + + return bitmap; +} + +/* + * Determine optimum sleep time based on configured rate, current rate, + * and packet size. + * Returns value in milliseconds. + * + * The basic idea is to adjust the desired rate up/down in this method + * based on whether we are running too slow or too fast. Then, calculate + * how many milliseconds to wait for the next packet to achieve this new + * rate. + */ +long Curl_sleep_time(curl_off_t rate_bps, curl_off_t cur_rate_bps, + int pkt_size) +{ + curl_off_t min_sleep = 0; + curl_off_t rv = 0; + + if(rate_bps == 0) + return 0; + + /* If running faster than about .1% of the desired speed, slow + * us down a bit. Use shift instead of division as the 0.1% + * cutoff is arbitrary anyway. + */ + if(cur_rate_bps > (rate_bps + (rate_bps >> 10))) { + /* running too fast, decrease target rate by 1/64th of rate */ + rate_bps -= rate_bps >> 6; + min_sleep = 1; + } + else if(cur_rate_bps < (rate_bps - (rate_bps >> 10))) { + /* running too slow, increase target rate by 1/64th of rate */ + rate_bps += rate_bps >> 6; + } + + /* Determine number of milliseconds to wait until we do + * the next packet at the adjusted rate. We should wait + * longer when using larger packets, for instance. + */ + rv = ((curl_off_t)((pkt_size * 8) * 1000) / rate_bps); + + /* Catch rounding errors and always slow down at least 1ms if + * we are running too fast. + */ + if(rv < min_sleep) + rv = min_sleep; + + /* Bound value to fit in 'long' on 32-bit platform. That's + * plenty long enough anyway! + */ + if(rv > 0x7fffffff) + rv = 0x7fffffff; + + return (long)rv; +} + + +/* + * Transfer() + * + * This function is what performs the actual transfer. It is capable of doing + * both ways simultaneously. The transfer must already have been setup by a + * call to Curl_setup_transfer(). + * + * Note that headers are created in a preallocated buffer of a default size. + * That buffer can be enlarged on demand, but it is never shrunken again. + * + */ + +static CURLcode +Transfer(struct connectdata *conn) +{ + CURLcode result; + struct SessionHandle *data = conn->data; + struct SingleRequest *k = &data->req; + bool done=FALSE; + bool first=TRUE; + long timeout_ms; + int buffersize; + long totmp; + + if((conn->sockfd == CURL_SOCKET_BAD) && + (conn->writesockfd == CURL_SOCKET_BAD)) + /* nothing to read, nothing to write, we're already OK! */ + return CURLE_OK; + + /* we want header and/or body, if neither then don't do this! */ + if(!k->getheader && data->set.opt_no_body) + return CURLE_OK; + + while(!done) { + curl_socket_t fd_read = conn->sockfd; + curl_socket_t fd_write = conn->writesockfd; + int keepon = k->keepon; + timeout_ms = 1000; + + if(conn->waitfor) { + /* if waitfor is set, get the RECV and SEND bits from that but keep the + other bits */ + keepon &= ~ (KEEP_RECV|KEEP_SEND); + keepon |= conn->waitfor & (KEEP_RECV|KEEP_SEND); + } + + /* limit-rate logic: if speed exceeds threshold, then do not include fd in + select set. The current speed is recalculated in each Curl_readwrite() + call */ + if((keepon & KEEP_SEND) && + (!data->set.max_send_speed || + (data->progress.ulspeed < data->set.max_send_speed) )) { + k->keepon &= ~KEEP_SEND_HOLD; + } + else { + if(data->set.upload && data->set.max_send_speed && + (data->progress.ulspeed > data->set.max_send_speed) ) { + /* calculate upload rate-limitation timeout. */ + buffersize = (int)(data->set.buffer_size ? + data->set.buffer_size : BUFSIZE); + totmp = Curl_sleep_time(data->set.max_send_speed, + data->progress.ulspeed, buffersize); + if(totmp < timeout_ms) + timeout_ms = totmp; + } + fd_write = CURL_SOCKET_BAD; + if(keepon & KEEP_SEND) + k->keepon |= KEEP_SEND_HOLD; /* hold it */ + } + + if((keepon & KEEP_RECV) && + (!data->set.max_recv_speed || + (data->progress.dlspeed < data->set.max_recv_speed)) ) { + k->keepon &= ~KEEP_RECV_HOLD; + } + else { + if((!data->set.upload) && data->set.max_recv_speed && + (data->progress.dlspeed > data->set.max_recv_speed)) { + /* Calculate download rate-limitation timeout. */ + buffersize = (int)(data->set.buffer_size ? + data->set.buffer_size : BUFSIZE); + totmp = Curl_sleep_time(data->set.max_recv_speed, + data->progress.dlspeed, buffersize); + if(totmp < timeout_ms) + timeout_ms = totmp; + } + fd_read = CURL_SOCKET_BAD; + if(keepon & KEEP_RECV) + k->keepon |= KEEP_RECV_HOLD; /* hold it */ + } + + /* pause logic. Don't check descriptors for paused connections */ + if(k->keepon & KEEP_RECV_PAUSE) + fd_read = CURL_SOCKET_BAD; + if(k->keepon & KEEP_SEND_PAUSE) + fd_write = CURL_SOCKET_BAD; + + /* The *_HOLD and *_PAUSE logic is necessary since even though there might + be no traffic during the select interval, we still call + Curl_readwrite() for the timeout case and if we limit transfer speed we + must make sure that this function doesn't transfer anything while in + HOLD status. + + The no timeout for the first round is for the protocols for which data + has already been slurped off the socket and thus waiting for action + won't work since it'll wait even though there is already data present + to work with. */ + if(first && + ((fd_read != CURL_SOCKET_BAD) || (fd_write != CURL_SOCKET_BAD))) + /* if this is the first lap and one of the file descriptors is fine + to work with, skip the timeout */ + timeout_ms = 0; + else { + totmp = Curl_timeleft(data, &k->now, FALSE); + if(totmp < 0) + return CURLE_OPERATION_TIMEDOUT; + else if(!totmp) + totmp = 1000; + + if(totmp < timeout_ms) + timeout_ms = totmp; + } + + switch (Curl_socket_ready(fd_read, fd_write, timeout_ms)) { + case -1: /* select() error, stop reading */ +#ifdef EINTR + /* The EINTR is not serious, and it seems you might get this more + often when using the lib in a multi-threaded environment! */ + if(SOCKERRNO == EINTR) + continue; +#endif + return CURLE_RECV_ERROR; /* indicate a network problem */ + case 0: /* timeout */ + default: /* readable descriptors */ + + result = Curl_readwrite(conn, &done); + /* "done" signals to us if the transfer(s) are ready */ + break; + } + if(result) + return result; + + first = FALSE; /* not the first lap anymore */ + } + + return CURLE_OK; +} + + +/* + * Curl_pretransfer() is called immediately before a transfer starts. + */ +CURLcode Curl_pretransfer(struct SessionHandle *data) +{ + CURLcode res; + if(!data->change.url) { + /* we can't do anything without URL */ + failf(data, "No URL set!"); + return CURLE_URL_MALFORMAT; + } + + /* Init the SSL session ID cache here. We do it here since we want to do it + after the *_setopt() calls (that could specify the size of the cache) but + before any transfer takes place. */ + res = Curl_ssl_initsessions(data, data->set.ssl.max_ssl_sessions); + if(res) + return res; + + data->set.followlocation=0; /* reset the location-follow counter */ + data->state.this_is_a_follow = FALSE; /* reset this */ + data->state.errorbuf = FALSE; /* no error has occurred */ + data->state.httpversion = 0; /* don't assume any particular server version */ + + data->state.ssl_connect_retry = FALSE; + + data->state.authproblem = FALSE; + data->state.authhost.want = data->set.httpauth; + data->state.authproxy.want = data->set.proxyauth; + Curl_safefree(data->info.wouldredirect); + data->info.wouldredirect = NULL; + + /* If there is a list of cookie files to read, do it now! */ + if(data->change.cookielist) + Curl_cookie_loadfiles(data); + + /* If there is a list of host pairs to deal with */ + if(data->change.resolve) + res = Curl_loadhostpairs(data); + + if(!res) { + /* Allow data->set.use_port to set which port to use. This needs to be + * disabled for example when we follow Location: headers to URLs using + * different ports! */ + data->state.allow_port = TRUE; + +#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) + /************************************************************* + * Tell signal handler to ignore SIGPIPE + *************************************************************/ + if(!data->set.no_signal) + data->state.prev_signal = signal(SIGPIPE, SIG_IGN); +#endif + + Curl_initinfo(data); /* reset session-specific information "variables" */ + Curl_pgrsStartNow(data); + + if(data->set.timeout) + Curl_expire(data, data->set.timeout); + + if(data->set.connecttimeout) + Curl_expire(data, data->set.connecttimeout); + + /* In case the handle is re-used and an authentication method was picked + in the session we need to make sure we only use the one(s) we now + consider to be fine */ + data->state.authhost.picked &= data->state.authhost.want; + data->state.authproxy.picked &= data->state.authproxy.want; + } + + return res; +} + +/* + * Curl_posttransfer() is called immediately after a transfer ends + */ +CURLcode Curl_posttransfer(struct SessionHandle *data) +{ +#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) + /* restore the signal handler for SIGPIPE before we get back */ + if(!data->set.no_signal) + signal(SIGPIPE, data->state.prev_signal); +#else + (void)data; /* unused parameter */ +#endif + + return CURLE_OK; +} + +#ifndef CURL_DISABLE_HTTP +/* + * strlen_url() returns the length of the given URL if the spaces within the + * URL were properly URL encoded. + */ +static size_t strlen_url(const char *url) +{ + const char *ptr; + size_t newlen=0; + bool left=TRUE; /* left side of the ? */ + + for(ptr=url; *ptr; ptr++) { + switch(*ptr) { + case '?': + left=FALSE; + /* fall through */ + default: + newlen++; + break; + case ' ': + if(left) + newlen+=3; + else + newlen++; + break; + } + } + return newlen; +} + +/* strcpy_url() copies a url to a output buffer and URL-encodes the spaces in + * the source URL accordingly. + */ +static void strcpy_url(char *output, const char *url) +{ + /* we must add this with whitespace-replacing */ + bool left=TRUE; + const char *iptr; + char *optr = output; + for(iptr = url; /* read from here */ + *iptr; /* until zero byte */ + iptr++) { + switch(*iptr) { + case '?': + left=FALSE; + /* fall through */ + default: + *optr++=*iptr; + break; + case ' ': + if(left) { + *optr++='%'; /* add a '%' */ + *optr++='2'; /* add a '2' */ + *optr++='0'; /* add a '0' */ + } + else + *optr++='+'; /* add a '+' here */ + break; + } + } + *optr=0; /* zero terminate output buffer */ + +} + +/* + * Returns true if the given URL is absolute (as opposed to relative) + */ +static bool is_absolute_url(const char *url) +{ + char prot[16]; /* URL protocol string storage */ + char letter; /* used for a silly sscanf */ + + return (2 == sscanf(url, "%15[^?&/:]://%c", prot, &letter)) ? TRUE : FALSE; +} + +/* + * Concatenate a relative URL to a base URL making it absolute. + * URL-encodes any spaces. + * The returned pointer must be freed by the caller unless NULL + * (returns NULL on out of memory). + */ +static char *concat_url(const char *base, const char *relurl) +{ + /*** + TRY to append this new path to the old URL + to the right of the host part. Oh crap, this is doomed to cause + problems in the future... + */ + char *newest; + char *protsep; + char *pathsep; + size_t newlen; + + const char *useurl = relurl; + size_t urllen; + + /* we must make our own copy of the URL to play with, as it may + point to read-only data */ + char *url_clone=strdup(base); + + if(!url_clone) + return NULL; /* skip out of this NOW */ + + /* protsep points to the start of the host name */ + protsep=strstr(url_clone, "//"); + if(!protsep) + protsep=url_clone; + else + protsep+=2; /* pass the slashes */ + + if('/' != relurl[0]) { + int level=0; + + /* First we need to find out if there's a ?-letter in the URL, + and cut it and the right-side of that off */ + pathsep = strchr(protsep, '?'); + if(pathsep) + *pathsep=0; + + /* we have a relative path to append to the last slash if there's one + available, or if the new URL is just a query string (starts with a + '?') we append the new one at the end of the entire currently worked + out URL */ + if(useurl[0] != '?') { + pathsep = strrchr(protsep, '/'); + if(pathsep) + *pathsep=0; + } + + /* Check if there's any slash after the host name, and if so, remember + that position instead */ + pathsep = strchr(protsep, '/'); + if(pathsep) + protsep = pathsep+1; + else + protsep = NULL; + + /* now deal with one "./" or any amount of "../" in the newurl + and act accordingly */ + + if((useurl[0] == '.') && (useurl[1] == '/')) + useurl+=2; /* just skip the "./" */ + + while((useurl[0] == '.') && + (useurl[1] == '.') && + (useurl[2] == '/')) { + level++; + useurl+=3; /* pass the "../" */ + } + + if(protsep) { + while(level--) { + /* cut off one more level from the right of the original URL */ + pathsep = strrchr(protsep, '/'); + if(pathsep) + *pathsep=0; + else { + *protsep=0; + break; + } + } + } + } + else { + /* We got a new absolute path for this server */ + + if((relurl[0] == '/') && (relurl[1] == '/')) { + /* the new URL starts with //, just keep the protocol part from the + original one */ + *protsep=0; + useurl = &relurl[2]; /* we keep the slashes from the original, so we + skip the new ones */ + } + else { + /* cut off the original URL from the first slash, or deal with URLs + without slash */ + pathsep = strchr(protsep, '/'); + if(pathsep) { + /* When people use badly formatted URLs, such as + "http://www.url.com?dir=/home/daniel" we must not use the first + slash, if there's a ?-letter before it! */ + char *sep = strchr(protsep, '?'); + if(sep && (sep < pathsep)) + pathsep = sep; + *pathsep=0; + } + else { + /* There was no slash. Now, since we might be operating on a badly + formatted URL, such as "http://www.url.com?id=2380" which doesn't + use a slash separator as it is supposed to, we need to check for a + ?-letter as well! */ + pathsep = strchr(protsep, '?'); + if(pathsep) + *pathsep=0; + } + } + } + + /* If the new part contains a space, this is a mighty stupid redirect + but we still make an effort to do "right". To the left of a '?' + letter we replace each space with %20 while it is replaced with '+' + on the right side of the '?' letter. + */ + newlen = strlen_url(useurl); + + urllen = strlen(url_clone); + + newest = malloc(urllen + 1 + /* possible slash */ + newlen + 1 /* zero byte */); + + if(!newest) { + free(url_clone); /* don't leak this */ + return NULL; + } + + /* copy over the root url part */ + memcpy(newest, url_clone, urllen); + + /* check if we need to append a slash */ + if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0])) + ; + else + newest[urllen++]='/'; + + /* then append the new piece on the right side */ + strcpy_url(&newest[urllen], useurl); + + free(url_clone); + + return newest; +} +#endif /* CURL_DISABLE_HTTP */ + +/* + * Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string + * as given by the remote server and set up the new URL to request. + */ +CURLcode Curl_follow(struct SessionHandle *data, + char *newurl, /* this 'newurl' is the Location: string, + and it must be malloc()ed before passed + here */ + followtype type) /* see curl_transfer.h */ +{ +#ifdef CURL_DISABLE_HTTP + (void)data; + (void)newurl; + (void)type; + /* Location: following will not happen when HTTP is disabled */ + return CURLE_TOO_MANY_REDIRECTS; +#else + + /* Location: redirect */ + bool disallowport = FALSE; + + if(type == FOLLOW_REDIR) { + if((data->set.maxredirs != -1) && + (data->set.followlocation >= data->set.maxredirs)) { + failf(data,"Maximum (%ld) redirects followed", data->set.maxredirs); + return CURLE_TOO_MANY_REDIRECTS; + } + + /* mark the next request as a followed location: */ + data->state.this_is_a_follow = TRUE; + + data->set.followlocation++; /* count location-followers */ + + if(data->set.http_auto_referer) { + /* We are asked to automatically set the previous URL as the referer + when we get the next URL. We pick the ->url field, which may or may + not be 100% correct */ + + if(data->change.referer_alloc) { + Curl_safefree(data->change.referer); + data->change.referer_alloc = FALSE; + } + + data->change.referer = strdup(data->change.url); + if(!data->change.referer) + return CURLE_OUT_OF_MEMORY; + data->change.referer_alloc = TRUE; /* yes, free this later */ + } + } + + if(!is_absolute_url(newurl)) { + /*** + *DANG* this is an RFC 2068 violation. The URL is supposed + to be absolute and this doesn't seem to be that! + */ + char *absolute = concat_url(data->change.url, newurl); + if(!absolute) + return CURLE_OUT_OF_MEMORY; + free(newurl); + newurl = absolute; + } + else { + /* This is an absolute URL, don't allow the custom port number */ + disallowport = TRUE; + + if(strchr(newurl, ' ')) { + /* This new URL contains at least one space, this is a mighty stupid + redirect but we still make an effort to do "right". */ + char *newest; + size_t newlen = strlen_url(newurl); + + newest = malloc(newlen+1); /* get memory for this */ + if(!newest) + return CURLE_OUT_OF_MEMORY; + strcpy_url(newest, newurl); /* create a space-free URL */ + + free(newurl); /* that was no good */ + newurl = newest; /* use this instead now */ + } + + } + + if(type == FOLLOW_FAKE) { + /* we're only figuring out the new url if we would've followed locations + but now we're done so we can get out! */ + data->info.wouldredirect = newurl; + return CURLE_OK; + } + + if(disallowport) + data->state.allow_port = FALSE; + + if(data->change.url_alloc) { + Curl_safefree(data->change.url); + data->change.url_alloc = FALSE; + } + + data->change.url = newurl; + data->change.url_alloc = TRUE; + newurl = NULL; /* don't free! */ + + infof(data, "Issue another request to this URL: '%s'\n", data->change.url); + + /* + * We get here when the HTTP code is 300-399 (and 401). We need to perform + * differently based on exactly what return code there was. + * + * News from 7.10.6: we can also get here on a 401 or 407, in case we act on + * a HTTP (proxy-) authentication scheme other than Basic. + */ + switch(data->info.httpcode) { + /* 401 - Act on a WWW-Authenticate, we keep on moving and do the + Authorization: XXXX header in the HTTP request code snippet */ + /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the + Proxy-Authorization: XXXX header in the HTTP request code snippet */ + /* 300 - Multiple Choices */ + /* 306 - Not used */ + /* 307 - Temporary Redirect */ + default: /* for all above (and the unknown ones) */ + /* Some codes are explicitly mentioned since I've checked RFC2616 and they + * seem to be OK to POST to. + */ + break; + case 301: /* Moved Permanently */ + /* (quote from RFC2616, section 10.3.2): + * + * When automatically redirecting a POST request after receiving a 301 + * status code, some existing HTTP/1.0 user agents will erroneously change + * it into a GET request. + * + * ---- + * + * As most of the important user agents do this obvious RFC2616 violation, + * many webservers expect this. So these servers often answers to a POST + * request with an error page. To be sure that libcurl gets the page that + * most user agents would get, libcurl has to force GET. + * + * This behavior can be overridden with CURLOPT_POSTREDIR. + */ + if((data->set.httpreq == HTTPREQ_POST + || data->set.httpreq == HTTPREQ_POST_FORM) + && !(data->set.keep_post & CURL_REDIR_POST_301)) { + infof(data, + "Violate RFC 2616/10.3.2 and switch from POST to GET\n"); + data->set.httpreq = HTTPREQ_GET; + } + break; + case 302: /* Found */ + /* (From 10.3.3) + + Note: RFC 1945 and RFC 2068 specify that the client is not allowed + to change the method on the redirected request. However, most + existing user agent implementations treat 302 as if it were a 303 + response, performing a GET on the Location field-value regardless + of the original request method. The status codes 303 and 307 have + been added for servers that wish to make unambiguously clear which + kind of reaction is expected of the client. + + (From 10.3.4) + + Note: Many pre-HTTP/1.1 user agents do not understand the 303 + status. When interoperability with such clients is a concern, the + 302 status code may be used instead, since most user agents react + to a 302 response as described here for 303. + + This behavior can be overridden with CURLOPT_POSTREDIR + */ + if((data->set.httpreq == HTTPREQ_POST + || data->set.httpreq == HTTPREQ_POST_FORM) + && !(data->set.keep_post & CURL_REDIR_POST_302)) { + infof(data, + "Violate RFC 2616/10.3.3 and switch from POST to GET\n"); + data->set.httpreq = HTTPREQ_GET; + } + break; + + case 303: /* See Other */ + /* Disable both types of POSTs, unless the user explicitely + asks for POST after POST */ + if(data->set.httpreq != HTTPREQ_GET + && !(data->set.keep_post & CURL_REDIR_POST_303)) { + data->set.httpreq = HTTPREQ_GET; /* enforce GET request */ + infof(data, "Disables POST, goes with %s\n", + data->set.opt_no_body?"HEAD":"GET"); + } + break; + case 304: /* Not Modified */ + /* 304 means we did a conditional request and it was "Not modified". + * We shouldn't get any Location: header in this response! + */ + break; + case 305: /* Use Proxy */ + /* (quote from RFC2616, section 10.3.6): + * "The requested resource MUST be accessed through the proxy given + * by the Location field. The Location field gives the URI of the + * proxy. The recipient is expected to repeat this single request + * via the proxy. 305 responses MUST only be generated by origin + * servers." + */ + break; + } + Curl_pgrsTime(data, TIMER_REDIRECT); + Curl_pgrsResetTimesSizes(data); + + return CURLE_OK; +#endif /* CURL_DISABLE_HTTP */ +} + +static CURLcode +connect_host(struct SessionHandle *data, + struct connectdata **conn) +{ + CURLcode res = CURLE_OK; + + bool async; + bool protocol_done=TRUE; /* will be TRUE always since this is only used + within the easy interface */ + Curl_pgrsTime(data, TIMER_STARTSINGLE); + res = Curl_connect(data, conn, &async, &protocol_done); + + if((CURLE_OK == res) && async) { + /* Now, if async is TRUE here, we need to wait for the name + to resolve */ + res = Curl_resolver_wait_resolv(*conn, NULL); + if(CURLE_OK == res) { + /* Resolved, continue with the connection */ + res = Curl_async_resolved(*conn, &protocol_done); + if(res) + *conn = NULL; + } + else { + /* if we can't resolve, we kill this "connection" now */ + (void)Curl_disconnect(*conn, /* dead_connection */ FALSE); + *conn = NULL; + } + } + + return res; +} + +CURLcode +Curl_reconnect_request(struct connectdata **connp) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = *connp; + struct SessionHandle *data = conn->data; + + /* This was a re-use of a connection and we got a write error in the + * DO-phase. Then we DISCONNECT this connection and have another attempt to + * CONNECT and then DO again! The retry cannot possibly find another + * connection to re-use, since we only keep one possible connection for + * each. */ + + infof(data, "Re-used connection seems dead, get a new one\n"); + + conn->bits.close = TRUE; /* enforce close of this connection */ + result = Curl_done(&conn, result, FALSE); /* we are so done with this */ + + /* conn may no longer be a good pointer, clear it to avoid mistakes by + parent functions */ + *connp = NULL; + + /* + * According to bug report #1330310. We need to check for CURLE_SEND_ERROR + * here as well. I figure this could happen when the request failed on a FTP + * connection and thus Curl_done() itself tried to use the connection + * (again). Slight Lack of feedback in the report, but I don't think this + * extra check can do much harm. + */ + if((CURLE_OK == result) || (CURLE_SEND_ERROR == result)) { + bool async; + bool protocol_done = TRUE; + + /* Now, redo the connect and get a new connection */ + result = Curl_connect(data, connp, &async, &protocol_done); + if(CURLE_OK == result) { + /* We have connected or sent away a name resolve query fine */ + + conn = *connp; /* setup conn to again point to something nice */ + if(async) { + /* Now, if async is TRUE here, we need to wait for the name + to resolve */ + result = Curl_resolver_wait_resolv(conn, NULL); + if(result) + return result; + + /* Resolved, continue with the connection */ + result = Curl_async_resolved(conn, &protocol_done); + if(result) + return result; + } + } + } + + return result; +} + +/* Returns CURLE_OK *and* sets '*url' if a request retry is wanted. + + NOTE: that the *url is malloc()ed. */ +CURLcode Curl_retry_request(struct connectdata *conn, + char **url) +{ + struct SessionHandle *data = conn->data; + + *url = NULL; + + /* if we're talking upload, we can't do the checks below, unless the protocol + is HTTP as when uploading over HTTP we will still get a response */ + if(data->set.upload && + !(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP))) + return CURLE_OK; + + if(/* workaround for broken TLS servers */ data->state.ssl_connect_retry || + ((data->req.bytecount + + data->req.headerbytecount == 0) && + conn->bits.reuse && + !data->set.opt_no_body && + data->set.rtspreq != RTSPREQ_RECEIVE)) { + /* We got no data, we attempted to re-use a connection and yet we want a + "body". This might happen if the connection was left alive when we were + done using it before, but that was closed when we wanted to read from + it again. Bad luck. Retry the same request on a fresh connect! */ + infof(conn->data, "Connection died, retrying a fresh connect\n"); + *url = strdup(conn->data->change.url); + if(!*url) + return CURLE_OUT_OF_MEMORY; + + conn->bits.close = TRUE; /* close this connection */ + conn->bits.retry = TRUE; /* mark this as a connection we're about + to retry. Marking it this way should + prevent i.e HTTP transfers to return + error just because nothing has been + transferred! */ + + + if((conn->handler->protocol&CURLPROTO_HTTP) && + data->state.proto.http->writebytecount) + return Curl_readrewind(conn); + } + return CURLE_OK; +} + +static CURLcode Curl_do_perform(struct SessionHandle *data) +{ + CURLcode res; + CURLcode res2; + struct connectdata *conn=NULL; + char *newurl = NULL; /* possibly a new URL to follow to! */ + followtype follow = FOLLOW_NONE; + + data->state.used_interface = Curl_if_easy; + + res = Curl_pretransfer(data); + if(res) + return res; + + /* + * It is important that there is NO 'return' from this function at any other + * place than falling down to the end of the function! This is because we + * have cleanup stuff that must be done before we get back, and that is only + * performed after this do-while loop. + */ + + for(;;) { + res = connect_host(data, &conn); /* primary connection */ + + if(res == CURLE_OK) { + bool do_done; + if(data->set.connect_only) { + /* keep connection open for application to use the socket */ + conn->bits.close = FALSE; + res = Curl_done(&conn, CURLE_OK, FALSE); + break; + } + res = Curl_do(&conn, &do_done); + + if(res == CURLE_OK) { + if(conn->data->set.wildcardmatch) { + if(conn->data->wildcard.state == CURLWC_DONE || + conn->data->wildcard.state == CURLWC_SKIP) { + /* keep connection open for application to use the socket */ + conn->bits.close = FALSE; + res = Curl_done(&conn, CURLE_OK, FALSE); + break; + } + } + res = Transfer(conn); /* now fetch that URL please */ + if((res == CURLE_OK) || (res == CURLE_RECV_ERROR)) { + bool retry = FALSE; + CURLcode rc = Curl_retry_request(conn, &newurl); + if(rc) + res = rc; + else + retry = (newurl?TRUE:FALSE); + + if(retry) { + /* we know (newurl != NULL) at this point */ + res = CURLE_OK; + follow = FOLLOW_RETRY; + } + else if(res == CURLE_OK) { + /* + * We must duplicate the new URL here as the connection data may + * be free()ed in the Curl_done() function. We prefer the newurl + * one since that's used for redirects or just further requests + * for retries or multi-stage HTTP auth methods etc. + */ + if(data->req.newurl) { + follow = FOLLOW_REDIR; + newurl = strdup(data->req.newurl); + if(!newurl) + res = CURLE_OUT_OF_MEMORY; + } + else if(data->req.location) { + follow = FOLLOW_FAKE; + newurl = strdup(data->req.location); + if(!newurl) + res = CURLE_OUT_OF_MEMORY; + } + } + + /* in the above cases where 'newurl' gets assigned, we have a fresh + * allocated memory pointed to */ + } + if(res != CURLE_OK) { + /* The transfer phase returned error, we mark the connection to get + * closed to prevent being re-used. This is because we can't + * possibly know if the connection is in a good shape or not now. */ + conn->bits.close = TRUE; + + if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) { + /* if we failed anywhere, we must clean up the secondary socket if + it was used */ + Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; + } + } + + /* Always run Curl_done(), even if some of the previous calls + failed, but return the previous (original) error code */ + res2 = Curl_done(&conn, res, FALSE); + + if(CURLE_OK == res) + res = res2; + } + else if(conn) + /* Curl_do() failed, clean up left-overs in the done-call, but note + that at some cases the conn pointer is NULL when Curl_do() failed + and the connection cache is very small so only call Curl_done() if + conn is still "alive". */ + /* ignore return code since we already have an error to return */ + (void)Curl_done(&conn, res, FALSE); + + /* + * Important: 'conn' cannot be used here, since it may have been closed + * in 'Curl_done' or other functions. + */ + + if((res == CURLE_OK) && follow) { + res = Curl_follow(data, newurl, follow); + if(CURLE_OK == res) { + /* if things went fine, Curl_follow() freed or otherwise took + responsibility for the newurl pointer */ + newurl = NULL; + if(follow >= FOLLOW_RETRY) { + follow = FOLLOW_NONE; + continue; + } + /* else we break out of the loop below */ + } + } + } + break; /* it only reaches here when this shouldn't loop */ + + } /* loop if Location: */ + + if(newurl) + free(newurl); + + if(res && !data->state.errorbuf) { + /* + * As an extra precaution: if no error string has been set and there was + * an error, use the strerror() string or if things are so bad that not + * even that is good, set a bad string that mentions the error code. + */ + const char *str = curl_easy_strerror(res); + if(!str) + failf(data, "unspecified error %d", (int)res); + else + failf(data, "%s", str); + } + + /* run post-transfer unconditionally, but don't clobber the return code if + we already have an error code recorder */ + res2 = Curl_posttransfer(data); + if(!res && res2) + res = res2; + + return res; +} + +/* + * Curl_perform() is the internal high-level function that gets called by the + * external curl_easy_perform() function. It inits, performs and cleans up a + * single file transfer. + */ +CURLcode Curl_perform(struct SessionHandle *data) +{ + CURLcode res; + if(!data->set.wildcardmatch) + return Curl_do_perform(data); + + /* init main wildcard structures */ + res = Curl_wildcard_init(&data->wildcard); + if(res) + return res; + + res = Curl_do_perform(data); + if(res) { + Curl_wildcard_dtor(&data->wildcard); + return res; + } + + /* wildcard loop */ + while(!res && data->wildcard.state != CURLWC_DONE) + res = Curl_do_perform(data); + + Curl_wildcard_dtor(&data->wildcard); + + /* wildcard download finished or failed */ + data->wildcard.state = CURLWC_INIT; + return res; +} + +/* + * Curl_setup_transfer() is called to setup some basic properties for the + * upcoming transfer. + */ +void +Curl_setup_transfer( + struct connectdata *conn, /* connection data */ + int sockindex, /* socket index to read from or -1 */ + curl_off_t size, /* -1 if unknown at this point */ + bool getheader, /* TRUE if header parsing is wanted */ + curl_off_t *bytecountp, /* return number of bytes read or NULL */ + int writesockindex, /* socket index to write to, it may very well be + the same we read from. -1 disables */ + curl_off_t *writecountp /* return number of bytes written or NULL */ + ) +{ + struct SessionHandle *data; + struct SingleRequest *k; + + DEBUGASSERT(conn != NULL); + + data = conn->data; + k = &data->req; + + DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); + + /* now copy all input parameters */ + conn->sockfd = sockindex == -1 ? + CURL_SOCKET_BAD : conn->sock[sockindex]; + conn->writesockfd = writesockindex == -1 ? + CURL_SOCKET_BAD:conn->sock[writesockindex]; + k->getheader = getheader; + + k->size = size; + k->bytecountp = bytecountp; + k->writebytecountp = writecountp; + + /* The code sequence below is placed in this function just because all + necessary input is not always known in do_complete() as this function may + be called after that */ + + if(!k->getheader) { + k->header = FALSE; + if(size > 0) + Curl_pgrsSetDownloadSize(data, size); + } + /* we want header and/or body, if neither then don't do this! */ + if(k->getheader || !data->set.opt_no_body) { + + if(conn->sockfd != CURL_SOCKET_BAD) + k->keepon |= KEEP_RECV; + + if(conn->writesockfd != CURL_SOCKET_BAD) { + /* HTTP 1.1 magic: + + Even if we require a 100-return code before uploading data, we might + need to write data before that since the REQUEST may not have been + finished sent off just yet. + + Thus, we must check if the request has been sent before we set the + state info where we wait for the 100-return code + */ + if((data->state.expect100header) && + (data->state.proto.http->sending == HTTPSEND_BODY)) { + /* wait with write until we either got 100-continue or a timeout */ + k->exp100 = EXP100_AWAITING_CONTINUE; + k->start100 = Curl_tvnow(); + + /* set a timeout for the multi interface */ + Curl_expire(data, CURL_TIMEOUT_EXPECT_100); + } + else { + if(data->state.expect100header) + /* when we've sent off the rest of the headers, we must await a + 100-continue but first finish sending the request */ + k->exp100 = EXP100_SENDING_REQUEST; + + /* enable the write bit when we're not waiting for continue */ + k->keepon |= KEEP_SEND; + } + } /* if(conn->writesockfd != CURL_SOCKET_BAD) */ + } /* if(k->getheader || !data->set.opt_no_body) */ + +} diff --git a/lib/curl_url.c b/lib/curl_url.c new file mode 100644 index 000000000..52badc5d7 --- /dev/null +++ b/lib/curl_url.c @@ -0,0 +1,5423 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#ifdef __VMS +#include +#include +#endif + +#ifndef HAVE_SOCKET +#error "We can't compile without socket() support!" +#endif + +#ifdef HAVE_LIMITS_H +#include +#endif + +#ifdef USE_LIBIDN +#include +#include +#include +#ifdef HAVE_IDN_FREE_H +#include +#else +/* prototype from idn-free.h, not provided by libidn 0.4.5's make install! */ +void idn_free (void *ptr); +#endif +#ifndef HAVE_IDN_FREE +/* if idn_free() was not found in this version of libidn use free() instead */ +#define idn_free(x) (free)(x) +#endif +#elif defined(USE_WIN32_IDN) +/* prototype for curl_win32_idn_to_ascii() */ +int curl_win32_idn_to_ascii(const char *in, char **out); +#endif /* USE_LIBIDN */ + +#include "curl_urldata.h" +#include "curl_netrc.h" + +#include "curl_formdata.h" +#include "curl_sslgen.h" +#include "curl_hostip.h" +#include "curl_transfer.h" +#include "curl_sendf.h" +#include "curl_progress.h" +#include "curl_cookie.h" +#include "curl_strequal.h" +#include "curl_strerror.h" +#include "curl_escape.h" +#include "curl_strtok.h" +#include "curl_share.h" +#include "curl_content_encoding.h" +#include "curl_http_digest.h" +#include "curl_http_negotiate.h" +#include "curl_select.h" +#include "curl_multiif.h" +#include "curl_easyif.h" +#include "curl_speedcheck.h" +#include "curl_rawstr.h" +#include "curl_warnless.h" +#include "curl_non_ascii.h" +#include "curl_inet_pton.h" + +/* And now for the protocols */ +#include "curl_ftp.h" +#include "curl_dict.h" +#include "curl_telnet.h" +#include "curl_tftp.h" +#include "curl_http.h" +#include "curl_file.h" +#include "curl_ldap.h" +#include "curl_ssh.h" +#include "curl_imap.h" +#include "curl_url.h" +#include "curl_connect.h" +#include "curl_inet_ntop.h" +#include "curl_ntlm.h" +#include "curl_ntlm_wb.h" +#include "curl_socks.h" +#include "curl_rtmp.h" +#include "curl_gopher.h" +#include "curl_http_proxy.h" +#include "curl_bundles.h" +#include "curl_conncache.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +/* Local static prototypes */ +static bool ConnectionKillOne(struct SessionHandle *data); +static void conn_free(struct connectdata *conn); +static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke); +static CURLcode do_init(struct connectdata *conn); +static CURLcode parse_url_userpass(struct SessionHandle *data, + struct connectdata *conn, + char *user, char *passwd); +/* + * Protocol table. + */ + +static const struct Curl_handler * const protocols[] = { + +#ifndef CURL_DISABLE_HTTP + &Curl_handler_http, +#endif + +#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + &Curl_handler_https, +#endif + +#ifndef CURL_DISABLE_FTP + &Curl_handler_ftp, +#endif + +#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) + &Curl_handler_ftps, +#endif + +#ifndef CURL_DISABLE_TELNET + &Curl_handler_telnet, +#endif + +#ifndef CURL_DISABLE_DICT + &Curl_handler_dict, +#endif + +#ifndef CURL_DISABLE_LDAP + &Curl_handler_ldap, +#if !defined(CURL_DISABLE_LDAPS) && \ + ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ + (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) + &Curl_handler_ldaps, +#endif +#endif + +#ifndef CURL_DISABLE_FILE + &Curl_handler_file, +#endif + +#ifndef CURL_DISABLE_TFTP + &Curl_handler_tftp, +#endif + +#ifdef USE_LIBSSH2 + &Curl_handler_scp, + &Curl_handler_sftp, +#endif + +#ifndef CURL_DISABLE_IMAP + &Curl_handler_imap, +#ifdef USE_SSL + &Curl_handler_imaps, +#endif +#endif + +#ifndef CURL_DISABLE_POP3 + &Curl_handler_pop3, +#ifdef USE_SSL + &Curl_handler_pop3s, +#endif +#endif + +#ifndef CURL_DISABLE_SMTP + &Curl_handler_smtp, +#ifdef USE_SSL + &Curl_handler_smtps, +#endif +#endif + +#ifndef CURL_DISABLE_RTSP + &Curl_handler_rtsp, +#endif + +#ifndef CURL_DISABLE_GOPHER + &Curl_handler_gopher, +#endif + +#ifdef USE_LIBRTMP + &Curl_handler_rtmp, + &Curl_handler_rtmpt, + &Curl_handler_rtmpe, + &Curl_handler_rtmpte, + &Curl_handler_rtmps, + &Curl_handler_rtmpts, +#endif + + (struct Curl_handler *) NULL +}; + +/* + * Dummy handler for undefined protocol schemes. + */ + +static const struct Curl_handler Curl_handler_dummy = { + "", /* scheme */ + ZERO_NULL, /* setup_connection */ + ZERO_NULL, /* do_it */ + ZERO_NULL, /* done */ + ZERO_NULL, /* do_more */ + ZERO_NULL, /* connect_it */ + ZERO_NULL, /* connecting */ + ZERO_NULL, /* doing */ + ZERO_NULL, /* proto_getsock */ + ZERO_NULL, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ZERO_NULL, /* disconnect */ + ZERO_NULL, /* readwrite */ + 0, /* defport */ + 0, /* protocol */ + PROTOPT_NONE /* flags */ +}; + +static void close_connections(struct SessionHandle *data) +{ + /* Loop through all open connections and kill them one by one */ + bool killed; + do { + killed = ConnectionKillOne(data); + } while(killed); +} + +void Curl_freeset(struct SessionHandle * data) +{ + /* Free all dynamic strings stored in the data->set substructure. */ + enum dupstring i; + for(i=(enum dupstring)0; i < STRING_LAST; i++) + Curl_safefree(data->set.str[i]); + + if(data->change.referer_alloc) { + Curl_safefree(data->change.referer); + data->change.referer_alloc = FALSE; + } + data->change.referer = NULL; +} + +static CURLcode setstropt(char **charp, char * s) +{ + /* Release the previous storage at `charp' and replace by a dynamic storage + copy of `s'. Return CURLE_OK or CURLE_OUT_OF_MEMORY. */ + + Curl_safefree(*charp); + + if(s) { + s = strdup(s); + + if(!s) + return CURLE_OUT_OF_MEMORY; + + *charp = s; + } + + return CURLE_OK; +} + +static CURLcode setstropt_userpwd(char *option, char **user_storage, + char **pwd_storage) +{ + char* separator; + CURLcode result = CURLE_OK; + + if(!option) { + /* we treat a NULL passed in as a hint to clear existing info */ + Curl_safefree(*user_storage); + *user_storage = (char *) NULL; + Curl_safefree(*pwd_storage); + *pwd_storage = (char *) NULL; + return CURLE_OK; + } + + separator = strchr(option, ':'); + if(separator != NULL) { + + /* store username part of option */ + char * p; + size_t username_len = (size_t)(separator-option); + p = malloc(username_len+1); + if(!p) + result = CURLE_OUT_OF_MEMORY; + else { + memcpy(p, option, username_len); + p[username_len] = '\0'; + Curl_safefree(*user_storage); + *user_storage = p; + } + + /* store password part of option */ + if(result == CURLE_OK) + result = setstropt(pwd_storage, separator+1); + } + else { + result = setstropt(user_storage, option); + } + return result; +} + +CURLcode Curl_dupset(struct SessionHandle * dst, struct SessionHandle * src) +{ + CURLcode r = CURLE_OK; + enum dupstring i; + + /* Copy src->set into dst->set first, then deal with the strings + afterwards */ + dst->set = src->set; + + /* clear all string pointers first */ + memset(dst->set.str, 0, STRING_LAST * sizeof(char *)); + + /* duplicate all strings */ + for(i=(enum dupstring)0; i< STRING_LAST; i++) { + r = setstropt(&dst->set.str[i], src->set.str[i]); + if(r != CURLE_OK) + break; + } + + /* If a failure occurred, freeing has to be performed externally. */ + return r; +} + +/* + * This is the internal function curl_easy_cleanup() calls. This should + * cleanup and free all resources associated with this sessionhandle. + * + * NOTE: if we ever add something that attempts to write to a socket or + * similar here, we must ignore SIGPIPE first. It is currently only done + * when curl_easy_perform() is invoked. + */ + +CURLcode Curl_close(struct SessionHandle *data) +{ + struct Curl_multi *m; + + if(!data) + return CURLE_OK; + + Curl_expire(data, 0); /* shut off timers */ + + m = data->multi; + + if(m) + /* This handle is still part of a multi handle, take care of this first + and detach this handle from there. */ + curl_multi_remove_handle(data->multi, data); + + /* Destroy the timeout list that is held in the easy handle. It is + /normally/ done by curl_multi_remove_handle() but this is "just in + case" */ + if(data->state.timeoutlist) { + Curl_llist_destroy(data->state.timeoutlist, NULL); + data->state.timeoutlist = NULL; + } + + data->magic = 0; /* force a clear AFTER the possibly enforced removal from + the multi handle, since that function uses the magic + field! */ + + if(data->state.conn_cache) { + if(data->state.conn_cache->type == CONNCACHE_PRIVATE) { + /* close all connections still alive that are in the private connection + cache, as we no longer have the pointer left to the shared one. */ + close_connections(data); + Curl_conncache_destroy(data->state.conn_cache); + data->state.conn_cache = NULL; + } + } + + if(data->dns.hostcachetype == HCACHE_PRIVATE) + Curl_hostcache_destroy(data); + + if(data->state.rangestringalloc) + free(data->state.range); + + /* Free the pathbuffer */ + Curl_safefree(data->state.pathbuffer); + data->state.path = NULL; + + Curl_safefree(data->state.proto.generic); + + /* Close down all open SSL info and sessions */ + Curl_ssl_close_all(data); + Curl_safefree(data->state.first_host); + Curl_safefree(data->state.scratch); + Curl_ssl_free_certinfo(data); + + if(data->change.referer_alloc) { + Curl_safefree(data->change.referer); + data->change.referer_alloc = FALSE; + } + data->change.referer = NULL; + + if(data->change.url_alloc) { + Curl_safefree(data->change.url); + data->change.url_alloc = FALSE; + } + data->change.url = NULL; + + Curl_safefree(data->state.headerbuff); + + Curl_flush_cookies(data, 1); + + Curl_digest_cleanup(data); + + Curl_safefree(data->info.contenttype); + Curl_safefree(data->info.wouldredirect); + + /* this destroys the channel and we cannot use it anymore after this */ + Curl_resolver_cleanup(data->state.resolver); + + Curl_convert_close(data); + + /* No longer a dirty share, if it exists */ + if(data->share) { + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + data->share->dirty--; + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + } + + Curl_freeset(data); + free(data); + return CURLE_OK; +} + +/* + * Initialize the UserDefined fields within a SessionHandle. + * This may be safely called on a new or existing SessionHandle. + */ +CURLcode Curl_init_userdefined(struct UserDefined *set) +{ + CURLcode res = CURLE_OK; + + set->out = stdout; /* default output to stdout */ + set->in = stdin; /* default input from stdin */ + set->err = stderr; /* default stderr to stderr */ + + /* use fwrite as default function to store output */ + set->fwrite_func = (curl_write_callback)fwrite; + + /* use fread as default function to read input */ + set->fread_func = (curl_read_callback)fread; + set->is_fread_set = 0; + set->is_fwrite_set = 0; + + set->seek_func = ZERO_NULL; + set->seek_client = ZERO_NULL; + + /* conversion callbacks for non-ASCII hosts */ + set->convfromnetwork = ZERO_NULL; + set->convtonetwork = ZERO_NULL; + set->convfromutf8 = ZERO_NULL; + + set->infilesize = -1; /* we don't know any size */ + set->postfieldsize = -1; /* unknown size */ + set->maxredirs = -1; /* allow any amount by default */ + + set->httpreq = HTTPREQ_GET; /* Default HTTP request */ + set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */ + set->ftp_use_epsv = TRUE; /* FTP defaults to EPSV operations */ + set->ftp_use_eprt = TRUE; /* FTP defaults to EPRT operations */ + set->ftp_use_pret = FALSE; /* mainly useful for drftpd servers */ + set->ftp_filemethod = FTPFILE_MULTICWD; + + set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */ + + /* Set the default size of the SSL session ID cache */ + set->ssl.max_ssl_sessions = 5; + + set->proxyport = CURL_DEFAULT_PROXY_PORT; /* from curl_url.h */ + set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */ + set->httpauth = CURLAUTH_BASIC; /* defaults to basic */ + set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */ + + /* make libcurl quiet by default: */ + set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */ + + /* + * libcurl 7.10 introduced SSL verification *by default*! This needs to be + * switched off unless wanted. + */ + set->ssl.verifypeer = TRUE; + set->ssl.verifyhost = TRUE; +#ifdef USE_TLS_SRP + set->ssl.authtype = CURL_TLSAUTH_NONE; +#endif + set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth + type */ + set->ssl.sessionid = TRUE; /* session ID caching enabled by default */ + + set->new_file_perms = 0644; /* Default permissions */ + set->new_directory_perms = 0755; /* Default permissions */ + + /* for the *protocols fields we don't use the CURLPROTO_ALL convenience + define since we internally only use the lower 16 bits for the passed + in bitmask to not conflict with the private bits */ + set->allowed_protocols = CURLPROTO_ALL; + set->redir_protocols = + CURLPROTO_ALL & ~(CURLPROTO_FILE|CURLPROTO_SCP); /* not FILE or SCP */ + +#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); +#elif defined(CURL_CA_PATH) + res = setstropt(&set->str[STRING_SSL_CAPATH], (char *) CURL_CA_PATH); +#endif + + set->wildcardmatch = FALSE; + set->chunk_bgn = ZERO_NULL; + set->chunk_end = ZERO_NULL; + + /* tcp keepalives are disabled by default, but provide reasonable values for + * the interval and idle times. + */ + set->tcp_keepalive = FALSE; + set->tcp_keepintvl = 60; + set->tcp_keepidle = 60; + + return res; +} + +/** + * Curl_open() + * + * @param curl is a pointer to a sessionhandle pointer that gets set by this + * function. + * @return CURLcode + */ + +CURLcode Curl_open(struct SessionHandle **curl) +{ + CURLcode res = CURLE_OK; + struct SessionHandle *data; + CURLcode status; + + /* Very simple start-up: alloc the struct, init it with zeroes and return */ + data = calloc(1, sizeof(struct SessionHandle)); + if(!data) { + /* this is a very serious error */ + DEBUGF(fprintf(stderr, "Error: calloc of SessionHandle failed\n")); + return CURLE_OUT_OF_MEMORY; + } + + data->magic = CURLEASY_MAGIC_NUMBER; + + status = Curl_resolver_init(&data->state.resolver); + if(status) { + DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); + free(data); + return status; + } + + /* We do some initial setup here, all those fields that can't be just 0 */ + + data->state.headerbuff = malloc(HEADERSIZE); + if(!data->state.headerbuff) { + DEBUGF(fprintf(stderr, "Error: malloc of headerbuff failed\n")); + res = CURLE_OUT_OF_MEMORY; + } + else { + Curl_easy_initHandleData(data); + res = Curl_init_userdefined(&data->set); + + data->state.headersize=HEADERSIZE; + + Curl_convert_init(data); + + /* most recent connection is not yet defined */ + data->state.lastconnect = NULL; + + data->progress.flags |= PGRS_HIDE; + data->state.current_speed = -1; /* init to negative == impossible */ + + data->wildcard.state = CURLWC_INIT; + data->wildcard.filelist = NULL; + data->set.fnmatch = ZERO_NULL; + /* This no longer creates a connection cache here. It is instead made on + the first call to curl_easy_perform() or when the handle is added to a + multi stack. */ + } + + + if(res) { + Curl_resolver_cleanup(data->state.resolver); + if(data->state.headerbuff) + free(data->state.headerbuff); + Curl_freeset(data); + free(data); + data = NULL; + } + else + *curl = data; + + return res; +} + +CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, + va_list param) +{ + char *argptr; + CURLcode result = CURLE_OK; + long arg; +#ifndef CURL_DISABLE_HTTP + curl_off_t bigsize; +#endif + + switch(option) { + case CURLOPT_DNS_CACHE_TIMEOUT: + data->set.dns_cache_timeout = va_arg(param, long); + break; + case CURLOPT_DNS_USE_GLOBAL_CACHE: + /* remember we want this enabled */ + arg = va_arg(param, long); + data->set.global_dns_cache = (0 != arg)?TRUE:FALSE; + break; + case CURLOPT_SSL_CIPHER_LIST: + /* set a list of cipher we want to use in the SSL connection */ + result = setstropt(&data->set.str[STRING_SSL_CIPHER_LIST], + va_arg(param, char *)); + break; + + case CURLOPT_RANDOM_FILE: + /* + * This is the path name to a file that contains random data to seed + * the random SSL stuff with. The file is only used for reading. + */ + result = setstropt(&data->set.str[STRING_SSL_RANDOM_FILE], + va_arg(param, char *)); + break; + case CURLOPT_EGDSOCKET: + /* + * The Entropy Gathering Daemon socket pathname + */ + result = setstropt(&data->set.str[STRING_SSL_EGDSOCKET], + va_arg(param, char *)); + break; + case CURLOPT_MAXCONNECTS: + /* + * Set the absolute number of maximum simultaneous alive connection that + * libcurl is allowed to have. + */ + data->set.maxconnects = va_arg(param, long); + break; + case CURLOPT_FORBID_REUSE: + /* + * When this transfer is done, it must not be left to be reused by a + * subsequent transfer but shall be closed immediately. + */ + data->set.reuse_forbid = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_FRESH_CONNECT: + /* + * This transfer shall not use a previously cached connection but + * should be made with a fresh new connect! + */ + data->set.reuse_fresh = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_VERBOSE: + /* + * Verbose means infof() calls that give a lot of information about + * the connection and transfer procedures as well as internal choices. + */ + data->set.verbose = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_HEADER: + /* + * Set to include the header in the general data output stream. + */ + data->set.include_header = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_NOPROGRESS: + /* + * Shut off the internal supported progress meter + */ + data->set.hide_progress = (0 != va_arg(param, long))?TRUE:FALSE; + if(data->set.hide_progress) + data->progress.flags |= PGRS_HIDE; + else + data->progress.flags &= ~PGRS_HIDE; + break; + case CURLOPT_NOBODY: + /* + * Do not include the body part in the output data stream. + */ + data->set.opt_no_body = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_FAILONERROR: + /* + * Don't output the >=300 error code HTML-page, but instead only + * return error. + */ + data->set.http_fail_on_error = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_UPLOAD: + case CURLOPT_PUT: + /* + * We want to sent data to the remote host. If this is HTTP, that equals + * using the PUT request. + */ + data->set.upload = (0 != va_arg(param, long))?TRUE:FALSE; + if(data->set.upload) { + /* If this is HTTP, PUT is what's needed to "upload" */ + data->set.httpreq = HTTPREQ_PUT; + data->set.opt_no_body = FALSE; /* this is implied */ + } + else + /* In HTTP, the opposite of upload is GET (unless NOBODY is true as + then this can be changed to HEAD later on) */ + data->set.httpreq = HTTPREQ_GET; + break; + case CURLOPT_FILETIME: + /* + * Try to get the file time of the remote document. The time will + * later (possibly) become available using curl_easy_getinfo(). + */ + data->set.get_filetime = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_FTP_CREATE_MISSING_DIRS: + /* + * An FTP option that modifies an upload to create missing directories on + * the server. + */ + switch(va_arg(param, long)) { + case 0: + data->set.ftp_create_missing_dirs = 0; + break; + case 1: + data->set.ftp_create_missing_dirs = 1; + break; + case 2: + data->set.ftp_create_missing_dirs = 2; + break; + default: + /* reserve other values for future use */ + result = CURLE_UNKNOWN_OPTION; + break; + } + break; + case CURLOPT_SERVER_RESPONSE_TIMEOUT: + /* + * Option that specifies how quickly an server response must be obtained + * before it is considered failure. For pingpong protocols. + */ + data->set.server_response_timeout = va_arg( param , long ) * 1000; + break; + case CURLOPT_TFTP_BLKSIZE: + /* + * TFTP option that specifies the block size to use for data transmission + */ + data->set.tftp_blksize = va_arg(param, long); + break; + case CURLOPT_DIRLISTONLY: + /* + * An option that changes the command to one that asks for a list + * only, no file info details. + */ + data->set.ftp_list_only = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_APPEND: + /* + * We want to upload and append to an existing file. + */ + data->set.ftp_append = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_FTP_FILEMETHOD: + /* + * How do access files over FTP. + */ + data->set.ftp_filemethod = (curl_ftpfile)va_arg(param, long); + break; + case CURLOPT_NETRC: + /* + * Parse the $HOME/.netrc file + */ + data->set.use_netrc = (enum CURL_NETRC_OPTION)va_arg(param, long); + break; + case CURLOPT_NETRC_FILE: + /* + * Use this file instead of the $HOME/.netrc file + */ + result = setstropt(&data->set.str[STRING_NETRC_FILE], + va_arg(param, char *)); + break; + case CURLOPT_TRANSFERTEXT: + /* + * This option was previously named 'FTPASCII'. Renamed to work with + * more protocols than merely FTP. + * + * Transfer using ASCII (instead of BINARY). + */ + data->set.prefer_ascii = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_TIMECONDITION: + /* + * Set HTTP time condition. This must be one of the defines in the + * curl/curl.h header file. + */ + data->set.timecondition = (curl_TimeCond)va_arg(param, long); + break; + case CURLOPT_TIMEVALUE: + /* + * This is the value to compare with the remote document with the + * method set with CURLOPT_TIMECONDITION + */ + data->set.timevalue = (time_t)va_arg(param, long); + break; + case CURLOPT_SSLVERSION: + /* + * Set explicit SSL version to try to connect with, as some SSL + * implementations are lame. + */ + data->set.ssl.version = va_arg(param, long); + break; + +#ifndef CURL_DISABLE_HTTP + case CURLOPT_AUTOREFERER: + /* + * Switch on automatic referer that gets set if curl follows locations. + */ + data->set.http_auto_referer = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_ACCEPT_ENCODING: + /* + * String to use at the value of Accept-Encoding header. + * + * If the encoding is set to "" we use an Accept-Encoding header that + * encompasses all the encodings we support. + * If the encoding is set to NULL we don't send an Accept-Encoding header + * and ignore an received Content-Encoding header. + * + */ + argptr = va_arg(param, char *); + result = setstropt(&data->set.str[STRING_ENCODING], + (argptr && !*argptr)? + (char *) ALL_CONTENT_ENCODINGS: argptr); + break; + + case CURLOPT_TRANSFER_ENCODING: + data->set.http_transfer_encoding = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_FOLLOWLOCATION: + /* + * Follow Location: header hints on a HTTP-server. + */ + data->set.http_follow_location = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_UNRESTRICTED_AUTH: + /* + * Send authentication (user+password) when following locations, even when + * hostname changed. + */ + data->set.http_disable_hostname_check_before_authentication = + (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_MAXREDIRS: + /* + * The maximum amount of hops you allow curl to follow Location: + * headers. This should mostly be used to detect never-ending loops. + */ + data->set.maxredirs = va_arg(param, long); + break; + + case CURLOPT_POSTREDIR: + { + /* + * Set the behaviour of POST when redirecting + * CURL_REDIR_GET_ALL - POST is changed to GET after 301 and 302 + * CURL_REDIR_POST_301 - POST is kept as POST after 301 + * CURL_REDIR_POST_302 - POST is kept as POST after 302 + * CURL_REDIR_POST_303 - POST is kept as POST after 303 + * CURL_REDIR_POST_ALL - POST is kept as POST after 301, 302 and 303 + * other - POST is kept as POST after 301 and 302 + */ + int postRedir = curlx_sltosi(va_arg(param, long)); + data->set.keep_post = postRedir & CURL_REDIR_POST_ALL; + } + break; + + case CURLOPT_POST: + /* Does this option serve a purpose anymore? Yes it does, when + CURLOPT_POSTFIELDS isn't used and the POST data is read off the + callback! */ + if(va_arg(param, long)) { + data->set.httpreq = HTTPREQ_POST; + data->set.opt_no_body = FALSE; /* this is implied */ + } + else + data->set.httpreq = HTTPREQ_GET; + break; + + case CURLOPT_COPYPOSTFIELDS: + /* + * A string with POST data. Makes curl HTTP POST. Even if it is NULL. + * If needed, CURLOPT_POSTFIELDSIZE must have been set prior to + * CURLOPT_COPYPOSTFIELDS and not altered later. + */ + argptr = va_arg(param, char *); + + if(!argptr || data->set.postfieldsize == -1) + result = setstropt(&data->set.str[STRING_COPYPOSTFIELDS], argptr); + else { + /* + * Check that requested length does not overflow the size_t type. + */ + + if((data->set.postfieldsize < 0) || + ((sizeof(curl_off_t) != sizeof(size_t)) && + (data->set.postfieldsize > (curl_off_t)((size_t)-1)))) + result = CURLE_OUT_OF_MEMORY; + else { + char * p; + + (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + + /* Allocate even when size == 0. This satisfies the need of possible + later address compare to detect the COPYPOSTFIELDS mode, and + to mark that postfields is used rather than read function or + form data. + */ + p = malloc((size_t)(data->set.postfieldsize? + data->set.postfieldsize:1)); + + if(!p) + result = CURLE_OUT_OF_MEMORY; + else { + if(data->set.postfieldsize) + memcpy(p, argptr, (size_t)data->set.postfieldsize); + + data->set.str[STRING_COPYPOSTFIELDS] = p; + } + } + } + + data->set.postfields = data->set.str[STRING_COPYPOSTFIELDS]; + data->set.httpreq = HTTPREQ_POST; + break; + + case CURLOPT_POSTFIELDS: + /* + * Like above, but use static data instead of copying it. + */ + data->set.postfields = va_arg(param, void *); + /* Release old copied data. */ + (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + data->set.httpreq = HTTPREQ_POST; + break; + + case CURLOPT_POSTFIELDSIZE: + /* + * The size of the POSTFIELD data to prevent libcurl to do strlen() to + * figure it out. Enables binary posts. + */ + bigsize = va_arg(param, long); + + if(data->set.postfieldsize < bigsize && + data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { + /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ + (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + data->set.postfields = NULL; + } + + data->set.postfieldsize = bigsize; + break; + + case CURLOPT_POSTFIELDSIZE_LARGE: + /* + * The size of the POSTFIELD data to prevent libcurl to do strlen() to + * figure it out. Enables binary posts. + */ + bigsize = va_arg(param, curl_off_t); + + if(data->set.postfieldsize < bigsize && + data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { + /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ + (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); + data->set.postfields = NULL; + } + + data->set.postfieldsize = bigsize; + break; + + case CURLOPT_HTTPPOST: + /* + * Set to make us do HTTP POST + */ + data->set.httppost = va_arg(param, struct curl_httppost *); + data->set.httpreq = HTTPREQ_POST_FORM; + data->set.opt_no_body = FALSE; /* this is implied */ + break; + + case CURLOPT_REFERER: + /* + * String to set in the HTTP Referer: field. + */ + if(data->change.referer_alloc) { + Curl_safefree(data->change.referer); + data->change.referer_alloc = FALSE; + } + result = setstropt(&data->set.str[STRING_SET_REFERER], + va_arg(param, char *)); + data->change.referer = data->set.str[STRING_SET_REFERER]; + break; + + case CURLOPT_USERAGENT: + /* + * String to use in the HTTP User-Agent field + */ + result = setstropt(&data->set.str[STRING_USERAGENT], + va_arg(param, char *)); + break; + + case CURLOPT_HTTPHEADER: + /* + * Set a list with HTTP headers to use (or replace internals with) + */ + data->set.headers = va_arg(param, struct curl_slist *); + break; + + case CURLOPT_HTTP200ALIASES: + /* + * Set a list of aliases for HTTP 200 in response header + */ + data->set.http200aliases = va_arg(param, struct curl_slist *); + break; + +#if !defined(CURL_DISABLE_COOKIES) + case CURLOPT_COOKIE: + /* + * Cookie string to send to the remote server in the request. + */ + result = setstropt(&data->set.str[STRING_COOKIE], + va_arg(param, char *)); + break; + + case CURLOPT_COOKIEFILE: + /* + * Set cookie file to read and parse. Can be used multiple times. + */ + argptr = (char *)va_arg(param, void *); + if(argptr) { + struct curl_slist *cl; + /* append the cookie file name to the list of file names, and deal with + them later */ + cl = curl_slist_append(data->change.cookielist, argptr); + if(!cl) { + curl_slist_free_all(data->change.cookielist); + data->change.cookielist = NULL; + return CURLE_OUT_OF_MEMORY; + } + data->change.cookielist = cl; /* store the list for later use */ + } + break; + + case CURLOPT_COOKIEJAR: + /* + * Set cookie file name to dump all cookies to when we're done. + */ + result = setstropt(&data->set.str[STRING_COOKIEJAR], + va_arg(param, char *)); + + /* + * Activate the cookie parser. This may or may not already + * have been made. + */ + data->cookies = Curl_cookie_init(data, NULL, data->cookies, + data->set.cookiesession); + break; + + case CURLOPT_COOKIESESSION: + /* + * Set this option to TRUE to start a new "cookie session". It will + * prevent the forthcoming read-cookies-from-file actions to accept + * cookies that are marked as being session cookies, as they belong to a + * previous session. + * + * In the original Netscape cookie spec, "session cookies" are cookies + * with no expire date set. RFC2109 describes the same action if no + * 'Max-Age' is set and RFC2965 includes the RFC2109 description and adds + * a 'Discard' action that can enforce the discard even for cookies that + * have a Max-Age. + * + * We run mostly with the original cookie spec, as hardly anyone implements + * anything else. + */ + data->set.cookiesession = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_COOKIELIST: + argptr = va_arg(param, char *); + + if(argptr == NULL) + break; + + if(Curl_raw_equal(argptr, "ALL")) { + /* clear all cookies */ + Curl_cookie_clearall(data->cookies); + break; + } + else if(Curl_raw_equal(argptr, "SESS")) { + /* clear session cookies */ + Curl_cookie_clearsess(data->cookies); + break; + } + else if(Curl_raw_equal(argptr, "FLUSH")) { + /* flush cookies to file */ + Curl_flush_cookies(data, 0); + break; + } + + if(!data->cookies) + /* if cookie engine was not running, activate it */ + data->cookies = Curl_cookie_init(data, NULL, NULL, TRUE); + + argptr = strdup(argptr); + if(!argptr) { + result = CURLE_OUT_OF_MEMORY; + break; + } + + if(checkprefix("Set-Cookie:", argptr)) + /* HTTP Header format line */ + Curl_cookie_add(data, data->cookies, TRUE, argptr + 11, NULL, NULL); + + else + /* Netscape format line */ + Curl_cookie_add(data, data->cookies, FALSE, argptr, NULL, NULL); + + free(argptr); + break; +#endif /* CURL_DISABLE_COOKIES */ + + case CURLOPT_HTTPGET: + /* + * Set to force us do HTTP GET + */ + if(va_arg(param, long)) { + data->set.httpreq = HTTPREQ_GET; + data->set.upload = FALSE; /* switch off upload */ + data->set.opt_no_body = FALSE; /* this is implied */ + } + break; + + case CURLOPT_HTTP_VERSION: + /* + * This sets a requested HTTP version to be used. The value is one of + * the listed enums in curl/curl.h. + */ + data->set.httpversion = va_arg(param, long); + break; + + case CURLOPT_HTTPAUTH: + /* + * Set HTTP Authentication type BITMASK. + */ + { + int bitcheck; + bool authbits; + unsigned long auth = va_arg(param, unsigned long); + + if(auth == CURLAUTH_NONE) { + data->set.httpauth = auth; + break; + } + + /* the DIGEST_IE bit is only used to set a special marker, for all the + rest we need to handle it as normal DIGEST */ + data->state.authhost.iestyle = (auth & CURLAUTH_DIGEST_IE)?TRUE:FALSE; + + if(auth & CURLAUTH_DIGEST_IE) { + auth |= CURLAUTH_DIGEST; /* set standard digest bit */ + auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ + } + + /* switch off bits we can't support */ +#ifndef USE_NTLM + auth &= ~CURLAUTH_NTLM; /* no NTLM support */ + auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ +#elif !defined(NTLM_WB_ENABLED) + auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ +#endif +#ifndef USE_HTTP_NEGOTIATE + auth &= ~CURLAUTH_GSSNEGOTIATE; /* no GSS-Negotiate without GSSAPI or + WINDOWS_SSPI */ +#endif + + /* check if any auth bit lower than CURLAUTH_ONLY is still set */ + bitcheck = 0; + authbits = FALSE; + while(bitcheck < 31) { + if(auth & (1UL << bitcheck++)) { + authbits = TRUE; + break; + } + } + if(!authbits) + return CURLE_NOT_BUILT_IN; /* no supported types left! */ + + data->set.httpauth = auth; + } + break; + +#endif /* CURL_DISABLE_HTTP */ + + case CURLOPT_CUSTOMREQUEST: + /* + * Set a custom string to use as request + */ + result = setstropt(&data->set.str[STRING_CUSTOMREQUEST], + va_arg(param, char *)); + + /* we don't set + data->set.httpreq = HTTPREQ_CUSTOM; + here, we continue as if we were using the already set type + and this just changes the actual request keyword */ + break; + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_HTTPPROXYTUNNEL: + /* + * Tunnel operations through the proxy instead of normal proxy use + */ + data->set.tunnel_thru_httpproxy = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_PROXYPORT: + /* + * Explicitly set HTTP proxy port number. + */ + data->set.proxyport = va_arg(param, long); + break; + + case CURLOPT_PROXYAUTH: + /* + * Set HTTP Authentication type BITMASK. + */ + { + int bitcheck; + bool authbits; + unsigned long auth = va_arg(param, unsigned long); + + if(auth == CURLAUTH_NONE) { + data->set.proxyauth = auth; + break; + } + + /* the DIGEST_IE bit is only used to set a special marker, for all the + rest we need to handle it as normal DIGEST */ + data->state.authproxy.iestyle = (auth & CURLAUTH_DIGEST_IE)?TRUE:FALSE; + + if(auth & CURLAUTH_DIGEST_IE) { + auth |= CURLAUTH_DIGEST; /* set standard digest bit */ + auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ + } + /* switch off bits we can't support */ +#ifndef USE_NTLM + auth &= ~CURLAUTH_NTLM; /* no NTLM support */ + auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ +#elif !defined(NTLM_WB_ENABLED) + auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ +#endif +#ifndef USE_HTTP_NEGOTIATE + auth &= ~CURLAUTH_GSSNEGOTIATE; /* no GSS-Negotiate without GSSAPI or + WINDOWS_SSPI */ +#endif + + /* check if any auth bit lower than CURLAUTH_ONLY is still set */ + bitcheck = 0; + authbits = FALSE; + while(bitcheck < 31) { + if(auth & (1UL << bitcheck++)) { + authbits = TRUE; + break; + } + } + if(!authbits) + return CURLE_NOT_BUILT_IN; /* no supported types left! */ + + data->set.proxyauth = auth; + } + break; + + case CURLOPT_PROXY: + /* + * Set proxy server:port to use as HTTP proxy. + * + * If the proxy is set to "" we explicitly say that we don't want to use a + * proxy (even though there might be environment variables saying so). + * + * Setting it to NULL, means no proxy but allows the environment variables + * to decide for us. + */ + result = setstropt(&data->set.str[STRING_PROXY], + va_arg(param, char *)); + break; + + case CURLOPT_PROXYTYPE: + /* + * Set proxy type. HTTP/HTTP_1_0/SOCKS4/SOCKS4a/SOCKS5/SOCKS5_HOSTNAME + */ + data->set.proxytype = (curl_proxytype)va_arg(param, long); + break; + + case CURLOPT_PROXY_TRANSFER_MODE: + /* + * set transfer mode (;type=) when doing FTP via an HTTP proxy + */ + switch (va_arg(param, long)) { + case 0: + data->set.proxy_transfer_mode = FALSE; + break; + case 1: + data->set.proxy_transfer_mode = TRUE; + break; + default: + /* reserve other values for future use */ + result = CURLE_UNKNOWN_OPTION; + break; + } + break; +#endif /* CURL_DISABLE_PROXY */ + +#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 = (0 != va_arg(param, long))?TRUE:FALSE; + break; +#endif + + case CURLOPT_WRITEHEADER: + /* + * Custom pointer to pass the header write callback function + */ + data->set.writeheader = (void *)va_arg(param, void *); + break; + case CURLOPT_ERRORBUFFER: + /* + * Error buffer provided by the caller to get the human readable + * error string in. + */ + data->set.errorbuffer = va_arg(param, char *); + break; + case CURLOPT_FILE: + /* + * FILE pointer to write to or include in the data write callback + */ + data->set.out = va_arg(param, FILE *); + break; + case CURLOPT_FTPPORT: + /* + * Use FTP PORT, this also specifies which IP address to use + */ + result = setstropt(&data->set.str[STRING_FTPPORT], + va_arg(param, char *)); + data->set.ftp_use_port = (NULL != data->set.str[STRING_FTPPORT]) ? + TRUE:FALSE; + break; + + case CURLOPT_FTP_USE_EPRT: + data->set.ftp_use_eprt = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_FTP_USE_EPSV: + data->set.ftp_use_epsv = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_FTP_USE_PRET: + data->set.ftp_use_pret = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_FTP_SSL_CCC: + data->set.ftp_ccc = (curl_ftpccc)va_arg(param, long); + break; + + case CURLOPT_FTP_SKIP_PASV_IP: + /* + * Enable or disable FTP_SKIP_PASV_IP, which will disable/enable the + * bypass of the IP address in PASV responses. + */ + data->set.ftp_skip_ip = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_INFILE: + /* + * FILE pointer to read the file to be uploaded from. Or possibly + * used as argument to the read callback. + */ + data->set.in = va_arg(param, FILE *); + break; + case CURLOPT_INFILESIZE: + /* + * If known, this should inform curl about the file size of the + * to-be-uploaded file. + */ + data->set.infilesize = va_arg(param, long); + break; + case CURLOPT_INFILESIZE_LARGE: + /* + * If known, this should inform curl about the file size of the + * to-be-uploaded file. + */ + data->set.infilesize = va_arg(param, curl_off_t); + break; + case CURLOPT_LOW_SPEED_LIMIT: + /* + * The low speed limit that if transfers are below this for + * CURLOPT_LOW_SPEED_TIME, the transfer is aborted. + */ + data->set.low_speed_limit=va_arg(param, long); + break; + case CURLOPT_MAX_SEND_SPEED_LARGE: + /* + * When transfer uploads are faster then CURLOPT_MAX_SEND_SPEED_LARGE + * bytes per second the transfer is throttled.. + */ + data->set.max_send_speed=va_arg(param, curl_off_t); + break; + case CURLOPT_MAX_RECV_SPEED_LARGE: + /* + * When receiving data faster than CURLOPT_MAX_RECV_SPEED_LARGE bytes per + * second the transfer is throttled.. + */ + data->set.max_recv_speed=va_arg(param, curl_off_t); + break; + case CURLOPT_LOW_SPEED_TIME: + /* + * The low speed time that if transfers are below the set + * CURLOPT_LOW_SPEED_LIMIT during this time, the transfer is aborted. + */ + data->set.low_speed_time=va_arg(param, long); + break; + case CURLOPT_URL: + /* + * The URL to fetch. + */ + if(data->change.url_alloc) { + /* the already set URL is allocated, free it first! */ + Curl_safefree(data->change.url); + data->change.url_alloc = FALSE; + } + result = setstropt(&data->set.str[STRING_SET_URL], + va_arg(param, char *)); + data->change.url = data->set.str[STRING_SET_URL]; + break; + case CURLOPT_PORT: + /* + * The port number to use when getting the URL + */ + data->set.use_port = va_arg(param, long); + break; + case CURLOPT_TIMEOUT: + /* + * The maximum time you allow curl to use for a single transfer + * operation. + */ + data->set.timeout = va_arg(param, long) * 1000L; + break; + + case CURLOPT_TIMEOUT_MS: + data->set.timeout = va_arg(param, long); + break; + + case CURLOPT_CONNECTTIMEOUT: + /* + * The maximum time you allow curl to use to connect. + */ + data->set.connecttimeout = va_arg(param, long) * 1000L; + break; + + case CURLOPT_CONNECTTIMEOUT_MS: + data->set.connecttimeout = va_arg(param, long); + break; + + case CURLOPT_ACCEPTTIMEOUT_MS: + /* + * The maximum time you allow curl to wait for server connect + */ + data->set.accepttimeout = va_arg(param, long); + break; + + case CURLOPT_USERPWD: + /* + * user:password to use in the operation + */ + result = setstropt_userpwd(va_arg(param, char *), + &data->set.str[STRING_USERNAME], + &data->set.str[STRING_PASSWORD]); + break; + case CURLOPT_USERNAME: + /* + * authentication user name to use in the operation + */ + result = setstropt(&data->set.str[STRING_USERNAME], + va_arg(param, char *)); + break; + case CURLOPT_PASSWORD: + /* + * authentication password to use in the operation + */ + result = setstropt(&data->set.str[STRING_PASSWORD], + va_arg(param, char *)); + break; + case CURLOPT_POSTQUOTE: + /* + * List of RAW FTP commands to use after a transfer + */ + data->set.postquote = va_arg(param, struct curl_slist *); + break; + case CURLOPT_PREQUOTE: + /* + * List of RAW FTP commands to use prior to RETR (Wesley Laxton) + */ + data->set.prequote = va_arg(param, struct curl_slist *); + break; + case CURLOPT_QUOTE: + /* + * List of RAW FTP commands to use before a transfer + */ + data->set.quote = va_arg(param, struct curl_slist *); + break; + case CURLOPT_RESOLVE: + /* + * List of NAME:[address] names to populate the DNS cache with + * Prefix the NAME with dash (-) to _remove_ the name from the cache. + * + * Names added with this API will remain in the cache until explicitly + * removed or the handle is cleaned up. + * + * This API can remove any name from the DNS cache, but only entries + * that aren't actually in use right now will be pruned immediately. + */ + data->set.resolve = va_arg(param, struct curl_slist *); + data->change.resolve = data->set.resolve; + break; + case CURLOPT_PROGRESSFUNCTION: + /* + * Progress callback function + */ + data->set.fprogress = va_arg(param, curl_progress_callback); + if(data->set.fprogress) + data->progress.callback = TRUE; /* no longer internal */ + else + data->progress.callback = FALSE; /* NULL enforces internal */ + + break; + case CURLOPT_PROGRESSDATA: + /* + * Custom client data to pass to the progress callback + */ + data->set.progress_client = va_arg(param, void *); + break; + +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXYUSERPWD: + /* + * user:password needed to use the proxy + */ + result = setstropt_userpwd(va_arg(param, char *), + &data->set.str[STRING_PROXYUSERNAME], + &data->set.str[STRING_PROXYPASSWORD]); + break; + case CURLOPT_PROXYUSERNAME: + /* + * authentication user name to use in the operation + */ + result = setstropt(&data->set.str[STRING_PROXYUSERNAME], + va_arg(param, char *)); + break; + case CURLOPT_PROXYPASSWORD: + /* + * authentication password to use in the operation + */ + result = setstropt(&data->set.str[STRING_PROXYPASSWORD], + va_arg(param, char *)); + break; + case CURLOPT_NOPROXY: + /* + * proxy exception list + */ + result = setstropt(&data->set.str[STRING_NOPROXY], + va_arg(param, char *)); + break; +#endif + + case CURLOPT_RANGE: + /* + * What range of the file you want to transfer + */ + result = setstropt(&data->set.str[STRING_SET_RANGE], + va_arg(param, char *)); + break; + case CURLOPT_RESUME_FROM: + /* + * Resume transfer at the give file position + */ + data->set.set_resume_from = va_arg(param, long); + break; + case CURLOPT_RESUME_FROM_LARGE: + /* + * Resume transfer at the give file position + */ + data->set.set_resume_from = va_arg(param, curl_off_t); + break; + case CURLOPT_DEBUGFUNCTION: + /* + * stderr write callback. + */ + data->set.fdebug = va_arg(param, curl_debug_callback); + /* + * if the callback provided is NULL, it'll use the default callback + */ + break; + case CURLOPT_DEBUGDATA: + /* + * Set to a void * that should receive all error writes. This + * defaults to CURLOPT_STDERR for normal operations. + */ + data->set.debugdata = va_arg(param, void *); + break; + case CURLOPT_STDERR: + /* + * Set to a FILE * that should receive all error writes. This + * defaults to stderr for normal operations. + */ + data->set.err = va_arg(param, FILE *); + if(!data->set.err) + data->set.err = stderr; + break; + case CURLOPT_HEADERFUNCTION: + /* + * Set header write callback + */ + data->set.fwrite_header = va_arg(param, curl_write_callback); + break; + case CURLOPT_WRITEFUNCTION: + /* + * Set data write callback + */ + data->set.fwrite_func = va_arg(param, curl_write_callback); + if(!data->set.fwrite_func) { + data->set.is_fwrite_set = 0; + /* When set to NULL, reset to our internal default function */ + data->set.fwrite_func = (curl_write_callback)fwrite; + } + else + data->set.is_fwrite_set = 1; + break; + case CURLOPT_READFUNCTION: + /* + * Read data callback + */ + data->set.fread_func = va_arg(param, curl_read_callback); + if(!data->set.fread_func) { + data->set.is_fread_set = 0; + /* When set to NULL, reset to our internal default function */ + data->set.fread_func = (curl_read_callback)fread; + } + else + data->set.is_fread_set = 1; + break; + case CURLOPT_SEEKFUNCTION: + /* + * Seek callback. Might be NULL. + */ + data->set.seek_func = va_arg(param, curl_seek_callback); + break; + case CURLOPT_SEEKDATA: + /* + * Seek control callback. Might be NULL. + */ + data->set.seek_client = va_arg(param, void *); + break; + case CURLOPT_CONV_FROM_NETWORK_FUNCTION: + /* + * "Convert from network encoding" callback + */ + data->set.convfromnetwork = va_arg(param, curl_conv_callback); + break; + case CURLOPT_CONV_TO_NETWORK_FUNCTION: + /* + * "Convert to network encoding" callback + */ + data->set.convtonetwork = va_arg(param, curl_conv_callback); + break; + case CURLOPT_CONV_FROM_UTF8_FUNCTION: + /* + * "Convert from UTF-8 encoding" callback + */ + data->set.convfromutf8 = va_arg(param, curl_conv_callback); + break; + case CURLOPT_IOCTLFUNCTION: + /* + * I/O control callback. Might be NULL. + */ + data->set.ioctl_func = va_arg(param, curl_ioctl_callback); + break; + case CURLOPT_IOCTLDATA: + /* + * I/O control data pointer. Might be NULL. + */ + data->set.ioctl_client = va_arg(param, void *); + break; + case CURLOPT_SSLCERT: + /* + * String that holds file name of the SSL certificate to use + */ + result = setstropt(&data->set.str[STRING_CERT], + va_arg(param, char *)); + break; + case CURLOPT_SSLCERTTYPE: + /* + * String that holds file type of the SSL certificate to use + */ + result = setstropt(&data->set.str[STRING_CERT_TYPE], + va_arg(param, char *)); + break; + case CURLOPT_SSLKEY: + /* + * String that holds file name of the SSL key to use + */ + result = setstropt(&data->set.str[STRING_KEY], + va_arg(param, char *)); + break; + case CURLOPT_SSLKEYTYPE: + /* + * String that holds file type of the SSL key to use + */ + result = setstropt(&data->set.str[STRING_KEY_TYPE], + va_arg(param, char *)); + break; + case CURLOPT_KEYPASSWD: + /* + * String that holds the SSL or SSH private key password. + */ + result = setstropt(&data->set.str[STRING_KEY_PASSWD], + va_arg(param, char *)); + break; + case CURLOPT_SSLENGINE: + /* + * String that holds the SSL crypto engine. + */ + argptr = va_arg(param, char *); + if(argptr && argptr[0]) + result = Curl_ssl_set_engine(data, argptr); + break; + + case CURLOPT_SSLENGINE_DEFAULT: + /* + * flag to set engine as default. + */ + result = Curl_ssl_set_engine_default(data); + break; + case CURLOPT_CRLF: + /* + * Kludgy option to enable CRLF conversions. Subject for removal. + */ + data->set.crlf = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_INTERFACE: + /* + * Set what interface or address/hostname to bind the socket to when + * performing an operation and thus what from-IP your connection will use. + */ + result = setstropt(&data->set.str[STRING_DEVICE], + va_arg(param, char *)); + break; + case CURLOPT_LOCALPORT: + /* + * Set what local port to bind the socket to when performing an operation. + */ + data->set.localport = curlx_sltous(va_arg(param, long)); + break; + case CURLOPT_LOCALPORTRANGE: + /* + * Set number of local ports to try, starting with CURLOPT_LOCALPORT. + */ + data->set.localportrange = curlx_sltosi(va_arg(param, long)); + break; + case CURLOPT_KRBLEVEL: + /* + * A string that defines the kerberos security level. + */ + result = setstropt(&data->set.str[STRING_KRB_LEVEL], + va_arg(param, char *)); + data->set.krb = (NULL != data->set.str[STRING_KRB_LEVEL])?TRUE:FALSE; + break; + case CURLOPT_GSSAPI_DELEGATION: + /* + * GSSAPI credential delegation + */ + data->set.gssapi_delegation = va_arg(param, long); + break; + case CURLOPT_SSL_VERIFYPEER: + /* + * Enable peer SSL verifying. + */ + data->set.ssl.verifypeer = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_SSL_VERIFYHOST: + /* + * Enable verification of the host name in the peer certificate + */ + arg = va_arg(param, long); + + /* Obviously people are not reading documentation and too many thought + this argument took a boolean when it wasn't and misused it. We thus ban + 1 as a sensible input and we warn about its use. Then we only have the + 2 action internally stored as TRUE. */ + + if(1 == arg) { + failf(data, "CURLOPT_SSL_VERIFYHOST no longer supports 1 as value!"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + data->set.ssl.verifyhost = (0 != arg)?TRUE:FALSE; + break; +#ifdef USE_SSLEAY + /* since these two options are only possible to use on an OpenSSL- + powered libcurl we #ifdef them on this condition so that libcurls + built against other SSL libs will return a proper error when trying + to set this option! */ + case CURLOPT_SSL_CTX_FUNCTION: + /* + * Set a SSL_CTX callback + */ + data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); + break; + case CURLOPT_SSL_CTX_DATA: + /* + * Set a SSL_CTX callback parameter pointer + */ + data->set.ssl.fsslctxp = va_arg(param, void *); + break; + case CURLOPT_CERTINFO: + data->set.ssl.certinfo = (0 != va_arg(param, long))?TRUE:FALSE; + break; +#endif + case CURLOPT_CAINFO: + /* + * Set CA info for SSL connection. Specify file name of the CA certificate + */ + result = setstropt(&data->set.str[STRING_SSL_CAFILE], + va_arg(param, char *)); + break; + case CURLOPT_CAPATH: + /* + * Set CA path info for SSL connection. Specify directory name of the CA + * certificates which have been prepared using openssl c_rehash utility. + */ + /* This does not work on windows. */ + result = setstropt(&data->set.str[STRING_SSL_CAPATH], + va_arg(param, char *)); + break; + case CURLOPT_CRLFILE: + /* + * Set CRL file info for SSL connection. Specify file name of the CRL + * to check certificates revocation + */ + result = setstropt(&data->set.str[STRING_SSL_CRLFILE], + va_arg(param, char *)); + break; + case CURLOPT_ISSUERCERT: + /* + * Set Issuer certificate file + * to check certificates issuer + */ + result = setstropt(&data->set.str[STRING_SSL_ISSUERCERT], + va_arg(param, char *)); + break; + case CURLOPT_TELNETOPTIONS: + /* + * Set a linked list of telnet options + */ + data->set.telnet_options = va_arg(param, struct curl_slist *); + break; + + case CURLOPT_BUFFERSIZE: + /* + * The application kindly asks for a differently sized receive buffer. + * If it seems reasonable, we'll use it. + */ + data->set.buffer_size = va_arg(param, long); + + if((data->set.buffer_size> (BUFSIZE -1 )) || + (data->set.buffer_size < 1)) + data->set.buffer_size = 0; /* huge internal default */ + + break; + + case CURLOPT_NOSIGNAL: + /* + * The application asks not to set any signal() or alarm() handlers, + * even when using a timeout. + */ + data->set.no_signal = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_SHARE: + { + struct Curl_share *set; + set = va_arg(param, struct Curl_share *); + + /* disconnect from old share, if any */ + if(data->share) { + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + + if(data->dns.hostcachetype == HCACHE_SHARED) { + data->dns.hostcache = NULL; + data->dns.hostcachetype = HCACHE_NONE; + } + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(data->share->cookies == data->cookies) + data->cookies = NULL; +#endif + + if(data->share->sslsession == data->state.session) + data->state.session = NULL; + + data->share->dirty--; + + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + data->share = NULL; + } + + /* use new share if it set */ + data->share = set; + if(data->share) { + + Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); + + data->share->dirty++; + + if(data->share->hostcache) { + /* use shared host cache, first free the private one if any */ + if(data->dns.hostcachetype == HCACHE_PRIVATE) + Curl_hostcache_destroy(data); + + data->dns.hostcache = data->share->hostcache; + data->dns.hostcachetype = HCACHE_SHARED; + } +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) + if(data->share->cookies) { + /* use shared cookie list, first free own one if any */ + if(data->cookies) + Curl_cookie_cleanup(data->cookies); + /* enable cookies since we now use a share that uses cookies! */ + data->cookies = data->share->cookies; + } +#endif /* CURL_DISABLE_HTTP */ + if(data->share->sslsession) { + data->set.ssl.max_ssl_sessions = data->share->max_ssl_sessions; + data->state.session = data->share->sslsession; + } + Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); + + } + /* check for host cache not needed, + * it will be done by curl_easy_perform */ + } + break; + + case CURLOPT_PRIVATE: + /* + * Set private data pointer. + */ + data->set.private_data = va_arg(param, void *); + break; + + case CURLOPT_MAXFILESIZE: + /* + * Set the maximum size of a file to download. + */ + data->set.max_filesize = va_arg(param, long); + break; + +#ifdef USE_SSL + case CURLOPT_USE_SSL: + /* + * Make transfers attempt to use SSL/TLS. + */ + data->set.use_ssl = (curl_usessl)va_arg(param, long); + break; + + case CURLOPT_SSL_OPTIONS: + arg = va_arg(param, long); + data->set.ssl_enable_beast = arg&CURLSSLOPT_ALLOW_BEAST?TRUE:FALSE; + break; + +#endif + case CURLOPT_FTPSSLAUTH: + /* + * Set a specific auth for FTP-SSL transfers. + */ + data->set.ftpsslauth = (curl_ftpauth)va_arg(param, long); + break; + + case CURLOPT_IPRESOLVE: + data->set.ipver = va_arg(param, long); + break; + + case CURLOPT_MAXFILESIZE_LARGE: + /* + * Set the maximum size of a file to download. + */ + data->set.max_filesize = va_arg(param, curl_off_t); + break; + + case CURLOPT_TCP_NODELAY: + /* + * Enable or disable TCP_NODELAY, which will disable/enable the Nagle + * algorithm + */ + data->set.tcp_nodelay = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_FTP_ACCOUNT: + result = setstropt(&data->set.str[STRING_FTP_ACCOUNT], + va_arg(param, char *)); + break; + + case CURLOPT_IGNORE_CONTENT_LENGTH: + data->set.ignorecl = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_CONNECT_ONLY: + /* + * No data transfer, set up connection and let application use the socket + */ + data->set.connect_only = (0 != va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + result = setstropt(&data->set.str[STRING_FTP_ALTERNATIVE_TO_USER], + va_arg(param, char *)); + break; + + case CURLOPT_SOCKOPTFUNCTION: + /* + * socket callback function: called after socket() but before connect() + */ + data->set.fsockopt = va_arg(param, curl_sockopt_callback); + break; + + case CURLOPT_SOCKOPTDATA: + /* + * socket callback data pointer. Might be NULL. + */ + data->set.sockopt_client = va_arg(param, void *); + break; + + case CURLOPT_OPENSOCKETFUNCTION: + /* + * open/create socket callback function: called instead of socket(), + * before connect() + */ + data->set.fopensocket = va_arg(param, curl_opensocket_callback); + break; + + case CURLOPT_OPENSOCKETDATA: + /* + * socket callback data pointer. Might be NULL. + */ + data->set.opensocket_client = va_arg(param, void *); + break; + + case CURLOPT_CLOSESOCKETFUNCTION: + /* + * close socket callback function: called instead of close() + * when shutting down a connection + */ + data->set.fclosesocket = va_arg(param, curl_closesocket_callback); + break; + + case CURLOPT_CLOSESOCKETDATA: + /* + * socket callback data pointer. Might be NULL. + */ + data->set.closesocket_client = va_arg(param, void *); + break; + + case CURLOPT_SSL_SESSIONID_CACHE: + data->set.ssl.sessionid = (0 != va_arg(param, long))?TRUE:FALSE; + break; + +#ifdef USE_LIBSSH2 + /* we only include SSH options if explicitly built to support SSH */ + case CURLOPT_SSH_AUTH_TYPES: + data->set.ssh_auth_types = va_arg(param, long); + break; + + case CURLOPT_SSH_PUBLIC_KEYFILE: + /* + * Use this file instead of the $HOME/.ssh/id_dsa.pub file + */ + result = setstropt(&data->set.str[STRING_SSH_PUBLIC_KEY], + va_arg(param, char *)); + break; + + case CURLOPT_SSH_PRIVATE_KEYFILE: + /* + * Use this file instead of the $HOME/.ssh/id_dsa file + */ + result = setstropt(&data->set.str[STRING_SSH_PRIVATE_KEY], + va_arg(param, char *)); + break; + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + /* + * Option to allow for the MD5 of the host public key to be checked + * for validation purposes. + */ + result = setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], + va_arg(param, char *)); + break; +#ifdef HAVE_LIBSSH2_KNOWNHOST_API + case CURLOPT_SSH_KNOWNHOSTS: + /* + * Store the file name to read known hosts from. + */ + result = setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], + va_arg(param, char *)); + break; + + case CURLOPT_SSH_KEYFUNCTION: + /* setting to NULL is fine since the curl_ssh.c functions themselves will + then rever to use the internal default */ + data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback); + break; + + case CURLOPT_SSH_KEYDATA: + /* + * Custom client data to pass to the SSH keyfunc callback + */ + data->set.ssh_keyfunc_userp = va_arg(param, void *); + break; +#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ + +#endif /* USE_LIBSSH2 */ + + case CURLOPT_HTTP_TRANSFER_DECODING: + /* + * disable libcurl transfer encoding is used + */ + data->set.http_te_skip = (0 == va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_HTTP_CONTENT_DECODING: + /* + * raw data passed to the application when content encoding is used + */ + data->set.http_ce_skip = (0 == va_arg(param, long))?TRUE:FALSE; + break; + + case CURLOPT_NEW_FILE_PERMS: + /* + * Uses these permissions instead of 0644 + */ + data->set.new_file_perms = va_arg(param, long); + break; + + case CURLOPT_NEW_DIRECTORY_PERMS: + /* + * Uses these permissions instead of 0755 + */ + data->set.new_directory_perms = va_arg(param, long); + break; + + case CURLOPT_ADDRESS_SCOPE: + /* + * We always get longs when passed plain numericals, but for this value we + * know that an unsigned int will always hold the value so we blindly + * typecast to this type + */ + data->set.scope = curlx_sltoui(va_arg(param, long)); + break; + + case CURLOPT_PROTOCOLS: + /* set the bitmask for the protocols that are allowed to be used for the + transfer, which thus helps the app which takes URLs from users or other + external inputs and want to restrict what protocol(s) to deal + with. Defaults to CURLPROTO_ALL. */ + data->set.allowed_protocols = va_arg(param, long); + break; + + case CURLOPT_REDIR_PROTOCOLS: + /* set the bitmask for the protocols that libcurl is allowed to follow to, + as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs + to be set in both bitmasks to be allowed to get redirected to. Defaults + to all protocols except FILE and SCP. */ + data->set.redir_protocols = va_arg(param, long); + break; + + case CURLOPT_MAIL_FROM: + result = setstropt(&data->set.str[STRING_MAIL_FROM], + va_arg(param, char *)); + break; + + case CURLOPT_MAIL_AUTH: + result = setstropt(&data->set.str[STRING_MAIL_AUTH], + va_arg(param, char *)); + break; + + case CURLOPT_MAIL_RCPT: + /* get a list of mail recipients */ + data->set.mail_rcpt = va_arg(param, struct curl_slist *); + break; + + case CURLOPT_RTSP_REQUEST: + { + /* + * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) + * Would this be better if the RTSPREQ_* were just moved into here? + */ + long curl_rtspreq = va_arg(param, long); + Curl_RtspReq rtspreq = RTSPREQ_NONE; + switch(curl_rtspreq) { + case CURL_RTSPREQ_OPTIONS: + rtspreq = RTSPREQ_OPTIONS; + break; + + case CURL_RTSPREQ_DESCRIBE: + rtspreq = RTSPREQ_DESCRIBE; + break; + + case CURL_RTSPREQ_ANNOUNCE: + rtspreq = RTSPREQ_ANNOUNCE; + break; + + case CURL_RTSPREQ_SETUP: + rtspreq = RTSPREQ_SETUP; + break; + + case CURL_RTSPREQ_PLAY: + rtspreq = RTSPREQ_PLAY; + break; + + case CURL_RTSPREQ_PAUSE: + rtspreq = RTSPREQ_PAUSE; + break; + + case CURL_RTSPREQ_TEARDOWN: + rtspreq = RTSPREQ_TEARDOWN; + break; + + case CURL_RTSPREQ_GET_PARAMETER: + rtspreq = RTSPREQ_GET_PARAMETER; + break; + + case CURL_RTSPREQ_SET_PARAMETER: + rtspreq = RTSPREQ_SET_PARAMETER; + break; + + case CURL_RTSPREQ_RECORD: + rtspreq = RTSPREQ_RECORD; + break; + + case CURL_RTSPREQ_RECEIVE: + rtspreq = RTSPREQ_RECEIVE; + break; + default: + rtspreq = RTSPREQ_NONE; + } + + data->set.rtspreq = rtspreq; + break; + } + + + case CURLOPT_RTSP_SESSION_ID: + /* + * Set the RTSP Session ID manually. Useful if the application is + * resuming a previously established RTSP session + */ + result = setstropt(&data->set.str[STRING_RTSP_SESSION_ID], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_STREAM_URI: + /* + * Set the Stream URI for the RTSP request. Unless the request is + * for generic server options, the application will need to set this. + */ + result = setstropt(&data->set.str[STRING_RTSP_STREAM_URI], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_TRANSPORT: + /* + * The content of the Transport: header for the RTSP request + */ + result = setstropt(&data->set.str[STRING_RTSP_TRANSPORT], + va_arg(param, char *)); + break; + + case CURLOPT_RTSP_CLIENT_CSEQ: + /* + * Set the CSEQ number to issue for the next RTSP request. Useful if the + * application is resuming a previously broken connection. The CSEQ + * will increment from this new number henceforth. + */ + data->state.rtsp_next_client_CSeq = va_arg(param, long); + break; + + case CURLOPT_RTSP_SERVER_CSEQ: + /* Same as the above, but for server-initiated requests */ + data->state.rtsp_next_client_CSeq = va_arg(param, long); + break; + + case CURLOPT_INTERLEAVEDATA: + data->set.rtp_out = va_arg(param, void *); + break; + case CURLOPT_INTERLEAVEFUNCTION: + /* Set the user defined RTP write function */ + data->set.fwrite_rtp = va_arg(param, curl_write_callback); + break; + + case CURLOPT_WILDCARDMATCH: + data->set.wildcardmatch = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_CHUNK_BGN_FUNCTION: + data->set.chunk_bgn = va_arg(param, curl_chunk_bgn_callback); + break; + case CURLOPT_CHUNK_END_FUNCTION: + data->set.chunk_end = va_arg(param, curl_chunk_end_callback); + break; + case CURLOPT_FNMATCH_FUNCTION: + data->set.fnmatch = va_arg(param, curl_fnmatch_callback); + break; + case CURLOPT_CHUNK_DATA: + data->wildcard.customptr = va_arg(param, void *); + break; + case CURLOPT_FNMATCH_DATA: + data->set.fnmatch_data = va_arg(param, void *); + break; +#ifdef USE_TLS_SRP + case CURLOPT_TLSAUTH_USERNAME: + result = setstropt(&data->set.str[STRING_TLSAUTH_USERNAME], + va_arg(param, char *)); + if(data->set.str[STRING_TLSAUTH_USERNAME] && !data->set.ssl.authtype) + data->set.ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ + break; + case CURLOPT_TLSAUTH_PASSWORD: + result = setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD], + va_arg(param, char *)); + if(data->set.str[STRING_TLSAUTH_USERNAME] && !data->set.ssl.authtype) + data->set.ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ + break; + case CURLOPT_TLSAUTH_TYPE: + if(strnequal((char *)va_arg(param, char *), "SRP", strlen("SRP"))) + data->set.ssl.authtype = CURL_TLSAUTH_SRP; + else + data->set.ssl.authtype = CURL_TLSAUTH_NONE; + break; +#endif + case CURLOPT_DNS_SERVERS: + result = Curl_set_dns_servers(data, va_arg(param, char *)); + break; + + case CURLOPT_TCP_KEEPALIVE: + data->set.tcp_keepalive = (0 != va_arg(param, long))?TRUE:FALSE; + break; + case CURLOPT_TCP_KEEPIDLE: + data->set.tcp_keepidle = va_arg(param, long); + break; + case CURLOPT_TCP_KEEPINTVL: + data->set.tcp_keepintvl = va_arg(param, long); + break; + + default: + /* unknown tag and its companion, just ignore: */ + result = CURLE_UNKNOWN_OPTION; + break; + } + + return result; +} + +static void conn_free(struct connectdata *conn) +{ + if(!conn) + return; + + /* possible left-overs from the async name resolvers */ + Curl_resolver_cancel(conn); + + /* close the SSL stuff before we close any sockets since they will/may + write to the sockets */ + Curl_ssl_close(conn, FIRSTSOCKET); + Curl_ssl_close(conn, SECONDARYSOCKET); + + /* close possibly still open sockets */ + if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) + Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); + if(CURL_SOCKET_BAD != conn->sock[FIRSTSOCKET]) + Curl_closesocket(conn, conn->sock[FIRSTSOCKET]); + +#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) + Curl_ntlm_wb_cleanup(conn); +#endif + + Curl_safefree(conn->user); + Curl_safefree(conn->passwd); + Curl_safefree(conn->proxyuser); + Curl_safefree(conn->proxypasswd); + Curl_safefree(conn->allocptr.proxyuserpwd); + Curl_safefree(conn->allocptr.uagent); + Curl_safefree(conn->allocptr.userpwd); + Curl_safefree(conn->allocptr.accept_encoding); + Curl_safefree(conn->allocptr.te); + Curl_safefree(conn->allocptr.rangeline); + Curl_safefree(conn->allocptr.ref); + Curl_safefree(conn->allocptr.host); + Curl_safefree(conn->allocptr.cookiehost); + Curl_safefree(conn->allocptr.rtsp_transport); + Curl_safefree(conn->trailer); + Curl_safefree(conn->host.rawalloc); /* host name buffer */ + Curl_safefree(conn->proxy.rawalloc); /* proxy name buffer */ + Curl_safefree(conn->master_buffer); + + Curl_llist_destroy(conn->send_pipe, NULL); + Curl_llist_destroy(conn->recv_pipe, NULL); + Curl_llist_destroy(conn->pend_pipe, NULL); + Curl_llist_destroy(conn->done_pipe, NULL); + + conn->send_pipe = NULL; + conn->recv_pipe = NULL; + conn->pend_pipe = NULL; + conn->done_pipe = NULL; + + Curl_safefree(conn->localdev); + Curl_free_ssl_config(&conn->ssl_config); + + free(conn); /* free all the connection oriented data */ +} + +CURLcode Curl_disconnect(struct connectdata *conn, bool dead_connection) +{ + struct SessionHandle *data; + if(!conn) + return CURLE_OK; /* this is closed and fine already */ + data = conn->data; + + if(!data) { + DEBUGF(fprintf(stderr, "DISCONNECT without easy handle, ignoring\n")); + return CURLE_OK; + } + + if(conn->dns_entry != NULL) { + Curl_resolv_unlock(data, conn->dns_entry); + conn->dns_entry = NULL; + } + + Curl_hostcache_prune(data); /* kill old DNS cache entries */ + + { + int has_host_ntlm = (conn->ntlm.state != NTLMSTATE_NONE); + int has_proxy_ntlm = (conn->proxyntlm.state != NTLMSTATE_NONE); + + /* Authentication data is a mix of connection-related and sessionhandle- + related stuff. NTLM is connection-related so when we close the shop + we shall forget. */ + + if(has_host_ntlm) { + data->state.authhost.done = FALSE; + data->state.authhost.picked = + data->state.authhost.want; + } + + if(has_proxy_ntlm) { + data->state.authproxy.done = FALSE; + data->state.authproxy.picked = + data->state.authproxy.want; + } + + if(has_host_ntlm || has_proxy_ntlm) { + data->state.authproblem = FALSE; + + Curl_http_ntlm_cleanup(conn); + } + } + + /* Cleanup possible redirect junk */ + if(data->req.newurl) { + free(data->req.newurl); + data->req.newurl = NULL; + } + + if(conn->handler->disconnect) + /* This is set if protocol-specific cleanups should be made */ + conn->handler->disconnect(conn, dead_connection); + + /* unlink ourselves! */ + infof(data, "Closing connection %d\n", conn->connection_id); + Curl_conncache_remove_conn(data->state.conn_cache, conn); + +#if defined(USE_LIBIDN) + if(conn->host.encalloc) + idn_free(conn->host.encalloc); /* encoded host name buffer, must be freed + with idn_free() since this was allocated + by libidn */ + if(conn->proxy.encalloc) + idn_free(conn->proxy.encalloc); /* encoded proxy name buffer, must be + freed with idn_free() since this was + allocated by libidn */ +#elif defined(USE_WIN32_IDN) + free(conn->host.encalloc); /* encoded host name buffer, must be freed with + idn_free() since this was allocated by + curl_win32_idn_to_ascii */ + if(conn->proxy.encalloc) + free(conn->proxy.encalloc); /* encoded proxy name buffer, must be freed + with idn_free() since this was allocated by + curl_win32_idn_to_ascii */ +#endif + + Curl_ssl_close(conn, FIRSTSOCKET); + + /* Indicate to all handles on the pipe that we're dead */ + if(Curl_isPipeliningEnabled(data)) { + signalPipeClose(conn->send_pipe, TRUE); + signalPipeClose(conn->recv_pipe, TRUE); + signalPipeClose(conn->pend_pipe, TRUE); + signalPipeClose(conn->done_pipe, FALSE); + } + + conn_free(conn); + data->state.current_conn = NULL; + Curl_speedinit(data); + + return CURLE_OK; +} + +/* + * This function should return TRUE if the socket is to be assumed to + * be dead. Most commonly this happens when the server has closed the + * connection due to inactivity. + */ +static bool SocketIsDead(curl_socket_t sock) +{ + int sval; + bool ret_val = TRUE; + + sval = Curl_socket_ready(sock, CURL_SOCKET_BAD, 0); + if(sval == 0) + /* timeout */ + ret_val = FALSE; + + return ret_val; +} + +static bool IsPipeliningPossible(const struct SessionHandle *handle, + const struct connectdata *conn) +{ + if((conn->handler->protocol & CURLPROTO_HTTP) && + handle->multi && Curl_multi_canPipeline(handle->multi) && + (handle->set.httpreq == HTTPREQ_GET || + handle->set.httpreq == HTTPREQ_HEAD) && + handle->set.httpversion != CURL_HTTP_VERSION_1_0) + return TRUE; + + return FALSE; +} + +bool Curl_isPipeliningEnabled(const struct SessionHandle *handle) +{ + if(handle->multi && Curl_multi_canPipeline(handle->multi)) + return TRUE; + + return FALSE; +} + +CURLcode Curl_addHandleToPipeline(struct SessionHandle *data, + struct curl_llist *pipeline) +{ + if(!Curl_llist_insert_next(pipeline, pipeline->tail, data)) + return CURLE_OUT_OF_MEMORY; + return CURLE_OK; +} + +int Curl_removeHandleFromPipeline(struct SessionHandle *handle, + struct curl_llist *pipeline) +{ + struct curl_llist_element *curr; + + curr = pipeline->head; + while(curr) { + if(curr->ptr == handle) { + Curl_llist_remove(pipeline, curr, NULL); + return 1; /* we removed a handle */ + } + curr = curr->next; + } + + return 0; +} + +#if 0 /* this code is saved here as it is useful for debugging purposes */ +static void Curl_printPipeline(struct curl_llist *pipeline) +{ + struct curl_llist_element *curr; + + curr = pipeline->head; + while(curr) { + struct SessionHandle *data = (struct SessionHandle *) curr->ptr; + infof(data, "Handle in pipeline: %s\n", data->state.path); + curr = curr->next; + } +} +#endif + +static struct SessionHandle* gethandleathead(struct curl_llist *pipeline) +{ + struct curl_llist_element *curr = pipeline->head; + if(curr) { + return (struct SessionHandle *) curr->ptr; + } + + return NULL; +} + +/* remove the specified connection from all (possible) pipelines and related + queues */ +void Curl_getoff_all_pipelines(struct SessionHandle *data, + struct connectdata *conn) +{ + bool recv_head = (conn->readchannel_inuse && + (gethandleathead(conn->recv_pipe) == data)) ? TRUE : FALSE; + + bool send_head = (conn->writechannel_inuse && + (gethandleathead(conn->send_pipe) == data)) ? TRUE : FALSE; + + if(Curl_removeHandleFromPipeline(data, conn->recv_pipe) && recv_head) + conn->readchannel_inuse = FALSE; + if(Curl_removeHandleFromPipeline(data, conn->send_pipe) && send_head) + conn->writechannel_inuse = FALSE; + Curl_removeHandleFromPipeline(data, conn->pend_pipe); + Curl_removeHandleFromPipeline(data, conn->done_pipe); +} + +static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke) +{ + struct curl_llist_element *curr; + + if(!pipeline) + return; + + curr = pipeline->head; + while(curr) { + struct curl_llist_element *next = curr->next; + struct SessionHandle *data = (struct SessionHandle *) curr->ptr; + +#ifdef DEBUGBUILD /* debug-only code */ + if(data->magic != CURLEASY_MAGIC_NUMBER) { + /* MAJOR BADNESS */ + infof(data, "signalPipeClose() found BAAD easy handle\n"); + } +#endif + + if(pipe_broke) + data->state.pipe_broke = TRUE; + Curl_multi_handlePipeBreak(data); + Curl_llist_remove(pipeline, curr, NULL); + curr = next; + } +} + + +/* + * Given one filled in connection struct (named needle), this function should + * detect if there already is one that has all the significant details + * exactly the same and thus should be used instead. + * + * If there is a match, this function returns TRUE - and has marked the + * connection as 'in-use'. It must later be called with ConnectionDone() to + * return back to 'idle' (unused) state. + */ +static bool +ConnectionExists(struct SessionHandle *data, + struct connectdata *needle, + struct connectdata **usethis) +{ + struct connectdata *check; + struct connectdata *chosen = 0; + bool canPipeline = IsPipeliningPossible(data, needle); + bool wantNTLM = (data->state.authhost.want==CURLAUTH_NTLM) || + (data->state.authhost.want==CURLAUTH_NTLM_WB) ? TRUE : FALSE; + struct connectbundle *bundle; + + /* Look up the bundle with all the connections to this + particular host */ + bundle = Curl_conncache_find_bundle(data->state.conn_cache, + needle->host.name); + if(bundle) { + struct curl_llist_element *curr; + + infof(data, "Found bundle for host %s: %p\n", needle->host.name, bundle); + + curr = bundle->conn_list->head; + while(curr) { + bool match = FALSE; + bool credentialsMatch = FALSE; + size_t pipeLen; + + /* + * Note that if we use a HTTP proxy, we check connections to that + * proxy and not to the actual remote server. + */ + check = curr->ptr; + curr = curr->next; + + pipeLen = check->send_pipe->size + check->recv_pipe->size; + + if(!pipeLen && !check->inuse) { + /* The check for a dead socket makes sense only if there are no + handles in pipeline and the connection isn't already marked in + use */ + bool dead; + if(check->handler->protocol & CURLPROTO_RTSP) + /* RTSP is a special case due to RTP interleaving */ + dead = Curl_rtsp_connisdead(check); + else + dead = SocketIsDead(check->sock[FIRSTSOCKET]); + + if(dead) { + check->data = data; + infof(data, "Connection %d seems to be dead!\n", + check->connection_id); + + /* disconnect resources */ + Curl_disconnect(check, /* dead_connection */ TRUE); + continue; + } + } + + if(canPipeline) { + /* Make sure the pipe has only GET requests */ + struct SessionHandle* sh = gethandleathead(check->send_pipe); + struct SessionHandle* rh = gethandleathead(check->recv_pipe); + if(sh) { + if(!IsPipeliningPossible(sh, check)) + continue; + } + else if(rh) { + if(!IsPipeliningPossible(rh, check)) + continue; + } +#ifdef DEBUGBUILD + if(pipeLen > MAX_PIPELINE_LENGTH) { + infof(data, "BAD! Connection #%ld has too big pipeline!\n", + check->connection_id); + } +#endif + } + else { + if(pipeLen > 0) { + /* can only happen within multi handles, and means that another easy + handle is using this connection */ + continue; + } + + if(Curl_resolver_asynch()) { + /* ip_addr_str[0] is NUL only if the resolving of the name hasn't + completed yet and until then we don't re-use this connection */ + if(!check->ip_addr_str[0]) { + infof(data, + "Connection #%ld is still name resolving, can't reuse\n", + check->connection_id); + continue; + } + } + + if((check->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) || + check->bits.close) { + /* Don't pick a connection that hasn't connected yet or that is going + to get closed. */ + infof(data, "Connection #%ld isn't open enough, can't reuse\n", + check->connection_id); +#ifdef DEBUGBUILD + if(check->recv_pipe->size > 0) { + infof(data, + "BAD! Unconnected #%ld has a non-empty recv pipeline!\n", + check->connection_id); + } +#endif + continue; + } + } + + if((needle->handler->flags&PROTOPT_SSL) != + (check->handler->flags&PROTOPT_SSL)) + /* don't do mixed SSL and non-SSL connections */ + if(!(needle->handler->protocol & check->handler->protocol)) + /* except protocols that have been upgraded via TLS */ + continue; + + if(needle->handler->flags&PROTOPT_SSL) { + if((data->set.ssl.verifypeer != check->verifypeer) || + (data->set.ssl.verifyhost != check->verifyhost)) + continue; + } + + if(needle->bits.proxy != check->bits.proxy) + /* don't do mixed proxy and non-proxy connections */ + continue; + + if(!canPipeline && check->inuse) + /* this request can't be pipelined but the checked connection is + already in use so we skip it */ + continue; + + if(needle->localdev || needle->localport) { + /* If we are bound to a specific local end (IP+port), we must not + re-use a random other one, although if we didn't ask for a + particular one we can reuse one that was bound. + + This comparison is a bit rough and too strict. Since the input + parameters can be specified in numerous ways and still end up the + same it would take a lot of processing to make it really accurate. + Instead, this matching will assume that re-uses of bound connections + will most likely also re-use the exact same binding parameters and + missing out a few edge cases shouldn't hurt anyone very much. + */ + if((check->localport != needle->localport) || + (check->localportrange != needle->localportrange) || + !check->localdev || + !needle->localdev || + strcmp(check->localdev, needle->localdev)) + continue; + } + + if(!needle->bits.httpproxy || needle->handler->flags&PROTOPT_SSL || + (needle->bits.httpproxy && check->bits.httpproxy && + needle->bits.tunnel_proxy && check->bits.tunnel_proxy && + Curl_raw_equal(needle->proxy.name, check->proxy.name) && + (needle->port == check->port))) { + /* The requested connection does not use a HTTP proxy or it uses SSL or + it is a non-SSL protocol tunneled over the same http proxy name and + port number or it is a non-SSL protocol which is allowed to be + upgraded via TLS */ + + if((Curl_raw_equal(needle->handler->scheme, check->handler->scheme) || + needle->handler->protocol & check->handler->protocol) && + Curl_raw_equal(needle->host.name, check->host.name) && + needle->remote_port == check->remote_port) { + if(needle->handler->flags & PROTOPT_SSL) { + /* This is a SSL connection so verify that we're using the same + SSL options as well */ + if(!Curl_ssl_config_matches(&needle->ssl_config, + &check->ssl_config)) { + DEBUGF(infof(data, + "Connection #%ld has different SSL parameters, " + "can't reuse\n", + check->connection_id)); + continue; + } + else if(check->ssl[FIRSTSOCKET].state != ssl_connection_complete) { + DEBUGF(infof(data, + "Connection #%ld has not started SSL connect, " + "can't reuse\n", + check->connection_id)); + continue; + } + } + if((needle->handler->protocol & CURLPROTO_FTP) || + ((needle->handler->protocol & CURLPROTO_HTTP) && wantNTLM)) { + /* This is FTP or HTTP+NTLM, verify that we're using the same name + and password as well */ + if(!strequal(needle->user, check->user) || + !strequal(needle->passwd, check->passwd)) { + /* one of them was different */ + continue; + } + credentialsMatch = TRUE; + } + match = TRUE; + } + } + else { /* The requested needle connection is using a proxy, + is the checked one using the same host, port and type? */ + if(check->bits.proxy && + (needle->proxytype == check->proxytype) && + (needle->bits.tunnel_proxy == check->bits.tunnel_proxy) && + Curl_raw_equal(needle->proxy.name, check->proxy.name) && + needle->port == check->port) { + /* This is the same proxy connection, use it! */ + match = TRUE; + } + } + + if(match) { + chosen = check; + + /* If we are not looking for an NTLM connection, we can choose this one + immediately. */ + if(!wantNTLM) + break; + + /* Otherwise, check if this is already authenticating with the right + credentials. If not, keep looking so that we can reuse NTLM + connections if possible. (Especially we must reuse the same + connection if partway through a handshake!) */ + if(credentialsMatch && chosen->ntlm.state != NTLMSTATE_NONE) + break; + } + } + } + + if(chosen) { + chosen->inuse = TRUE; /* mark this as being in use so that no other + handle in a multi stack may nick it */ + *usethis = chosen; + return TRUE; /* yes, we found one to use! */ + } + + return FALSE; /* no matching connecting exists */ +} + +/* + * This function kills and removes an existing connection in the connection + * cache. The connection that has been unused for the longest time. + * + * Returns FALSE if it can't find any unused connection to kill. + */ +static bool +ConnectionKillOne(struct SessionHandle *data) +{ + struct conncache *bc = data->state.conn_cache; + struct curl_hash_iterator iter; + struct curl_llist_element *curr; + struct curl_hash_element *he; + long highscore=-1; + long score; + struct timeval now; + struct connectdata *conn_candidate = NULL; + struct connectbundle *bundle; + + now = Curl_tvnow(); + + Curl_hash_start_iterate(bc->hash, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct connectdata *conn; + + bundle = he->ptr; + + curr = bundle->conn_list->head; + while(curr) { + conn = curr->ptr; + + if(!conn->inuse) { + /* Set higher score for the age passed since the connection was used */ + score = Curl_tvdiff(now, conn->now); + + if(score > highscore) { + highscore = score; + conn_candidate = conn; + } + } + curr = curr->next; + } + + he = Curl_hash_next_element(&iter); + } + + if(conn_candidate) { + /* Set the connection's owner correctly */ + conn_candidate->data = data; + + bundle = conn_candidate->bundle; + + /* the winner gets the honour of being disconnected */ + (void)Curl_disconnect(conn_candidate, /* dead_connection */ FALSE); + + return TRUE; + } + + return FALSE; +} + +/* this connection can now be marked 'idle' */ +static void +ConnectionDone(struct connectdata *conn) +{ + conn->inuse = FALSE; +} + +/* + * The given input connection struct pointer is to be stored in the connection + * cache. If the cache is already full, least interesting existing connection + * (if any) gets closed. + * + * The given connection should be unique. That must've been checked prior to + * this call. + */ +static CURLcode ConnectionStore(struct SessionHandle *data, + struct connectdata *conn) +{ + static int connection_id_counter = 0; + + CURLcode result; + + /* Assign a number to the connection for easier tracking in the log + output */ + conn->connection_id = connection_id_counter++; + + result = Curl_conncache_add_conn(data->state.conn_cache, conn); + if(result != CURLE_OK) + conn->connection_id = -1; + + return result; +} + +/* after a TCP connection to the proxy has been verified, this function does + the next magic step. + + Note: this function's sub-functions call failf() + +*/ +CURLcode Curl_connected_proxy(struct connectdata *conn) +{ + switch(conn->proxytype) { +#ifndef CURL_DISABLE_PROXY + case CURLPROXY_SOCKS5: + case CURLPROXY_SOCKS5_HOSTNAME: + return Curl_SOCKS5(conn->proxyuser, conn->proxypasswd, + conn->host.name, conn->remote_port, + FIRSTSOCKET, conn); + + case CURLPROXY_SOCKS4: + return Curl_SOCKS4(conn->proxyuser, conn->host.name, + conn->remote_port, FIRSTSOCKET, conn, FALSE); + + case CURLPROXY_SOCKS4A: + return Curl_SOCKS4(conn->proxyuser, conn->host.name, + conn->remote_port, FIRSTSOCKET, conn, TRUE); + +#endif /* CURL_DISABLE_PROXY */ + case CURLPROXY_HTTP: + case CURLPROXY_HTTP_1_0: + /* do nothing here. handled later. */ + break; + default: + break; + } /* switch proxytype */ + + return CURLE_OK; +} + +static CURLcode ConnectPlease(struct SessionHandle *data, + struct connectdata *conn, + bool *connected) +{ + CURLcode result; + Curl_addrinfo *addr; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char *hostname = conn->bits.proxy?conn->proxy.name:conn->host.name; + + infof(data, "About to connect() to %s%s port %ld (#%ld)\n", + conn->bits.proxy?"proxy ":"", + hostname, conn->port, conn->connection_id); +#else + (void)data; +#endif + + /************************************************************* + * Connect to server/proxy + *************************************************************/ + result= Curl_connecthost(conn, + conn->dns_entry, + &conn->sock[FIRSTSOCKET], + &addr, + connected); + if(CURLE_OK == result) { + /* All is cool, we store the current information */ + conn->ip_addr = addr; + + if(*connected) { + result = Curl_connected_proxy(conn); + if(!result) { + conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; + Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ + } + } + } + + if(result) + *connected = FALSE; /* mark it as not connected */ + + return result; +} + +/* + * verboseconnect() displays verbose information after a connect + */ +#ifndef CURL_DISABLE_VERBOSE_STRINGS +void Curl_verboseconnect(struct connectdata *conn) +{ + if(conn->data->set.verbose) + infof(conn->data, "Connected to %s (%s) port %ld (#%ld)\n", + conn->bits.proxy ? conn->proxy.dispname : conn->host.dispname, + conn->ip_addr_str, conn->port, conn->connection_id); +} +#endif + +int Curl_protocol_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + if(conn->handler->proto_getsock) + return conn->handler->proto_getsock(conn, socks, numsocks); + return GETSOCK_BLANK; +} + +int Curl_doing_getsock(struct connectdata *conn, + curl_socket_t *socks, + int numsocks) +{ + if(conn && conn->handler->doing_getsock) + return conn->handler->doing_getsock(conn, socks, numsocks); + return GETSOCK_BLANK; +} + +/* + * We are doing protocol-specific connecting and this is being called over and + * over from the multi interface until the connection phase is done on + * protocol layer. + */ + +CURLcode Curl_protocol_connecting(struct connectdata *conn, + bool *done) +{ + CURLcode result=CURLE_OK; + + if(conn && conn->handler->connecting) { + *done = FALSE; + result = conn->handler->connecting(conn, done); + } + else + *done = TRUE; + + return result; +} + +/* + * We are DOING this is being called over and over from the multi interface + * until the DOING phase is done on protocol layer. + */ + +CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done) +{ + CURLcode result=CURLE_OK; + + if(conn && conn->handler->doing) { + *done = FALSE; + result = conn->handler->doing(conn, done); + } + else + *done = TRUE; + + return result; +} + +/* + * We have discovered that the TCP connection has been successful, we can now + * proceed with some action. + * + */ +CURLcode Curl_protocol_connect(struct connectdata *conn, + bool *protocol_done) +{ + CURLcode result=CURLE_OK; + struct SessionHandle *data = conn->data; + + *protocol_done = FALSE; + + if(conn->bits.tcpconnect[FIRSTSOCKET] && conn->bits.protoconnstart) { + /* We already are connected, get back. This may happen when the connect + worked fine in the first call, like when we connect to a local server + or proxy. Note that we don't know if the protocol is actually done. + + Unless this protocol doesn't have any protocol-connect callback, as + then we know we're done. */ + if(!conn->handler->connecting) + *protocol_done = TRUE; + + return CURLE_OK; + } + + Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ + Curl_verboseconnect(conn); + + if(!conn->bits.protoconnstart) { + + /* Set start time here for timeout purposes in the connect procedure, it + is later set again for the progress meter purpose */ + conn->now = Curl_tvnow(); + + result = Curl_proxy_connect(conn); + if(result) + return result; + + if(conn->handler->connect_it) { + /* is there a protocol-specific connect() procedure? */ + + /* Call the protocol-specific connect function */ + result = conn->handler->connect_it(conn, protocol_done); + } + else + *protocol_done = TRUE; + + /* it has started, possibly even completed but that knowledge isn't stored + in this bit! */ + if(!result) + conn->bits.protoconnstart = TRUE; + } + + return result; /* pass back status */ +} + +/* + * Helpers for IDNA convertions. + */ +static bool is_ASCII_name(const char *hostname) +{ + const unsigned char *ch = (const unsigned char*)hostname; + + while(*ch) { + if(*ch++ & 0x80) + return FALSE; + } + return TRUE; +} + +#ifdef USE_LIBIDN +/* + * Check if characters in hostname is allowed in Top Level Domain. + */ +static bool tld_check_name(struct SessionHandle *data, + const char *ace_hostname) +{ + size_t err_pos; + char *uc_name = NULL; + int rc; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + const char *tld_errmsg = ""; +#else + (void)data; +#endif + + /* Convert (and downcase) ACE-name back into locale's character set */ + rc = idna_to_unicode_lzlz(ace_hostname, &uc_name, 0); + if(rc != IDNA_SUCCESS) + return FALSE; + + rc = tld_check_lz(uc_name, &err_pos, NULL); +#ifndef CURL_DISABLE_VERBOSE_STRINGS +#ifdef HAVE_TLD_STRERROR + if(rc != TLD_SUCCESS) + tld_errmsg = tld_strerror((Tld_rc)rc); +#endif + if(rc == TLD_INVALID) + infof(data, "WARNING: %s; pos %u = `%c'/0x%02X\n", + tld_errmsg, err_pos, uc_name[err_pos], + uc_name[err_pos] & 255); + else if(rc != TLD_SUCCESS) + infof(data, "WARNING: TLD check for %s failed; %s\n", + uc_name, tld_errmsg); +#endif /* CURL_DISABLE_VERBOSE_STRINGS */ + if(uc_name) + idn_free(uc_name); + if(rc != TLD_SUCCESS) + return FALSE; + + return TRUE; +} +#endif + +/* + * Perform any necessary IDN conversion of hostname + */ +static void fix_hostname(struct SessionHandle *data, + struct connectdata *conn, struct hostname *host) +{ +#ifndef USE_LIBIDN + (void)data; + (void)conn; +#elif defined(CURL_DISABLE_VERBOSE_STRINGS) + (void)conn; +#endif + + /* set the name we use to display the host name */ + host->dispname = host->name; + if(!is_ASCII_name(host->name)) { +#ifdef USE_LIBIDN + /************************************************************* + * Check name for non-ASCII and convert hostname to ACE form. + *************************************************************/ + if(stringprep_check_version(LIBIDN_REQUIRED_VERSION)) { + char *ace_hostname = NULL; + int rc = idna_to_ascii_lz(host->name, &ace_hostname, 0); + infof (data, "Input domain encoded as `%s'\n", + stringprep_locale_charset ()); + if(rc != IDNA_SUCCESS) + infof(data, "Failed to convert %s to ACE; %s\n", + host->name, Curl_idn_strerror(conn,rc)); + else { + /* tld_check_name() displays a warning if the host name contains + "illegal" characters for this TLD */ + (void)tld_check_name(data, ace_hostname); + + host->encalloc = ace_hostname; + /* change the name pointer to point to the encoded hostname */ + host->name = host->encalloc; + } + } +#elif defined(USE_WIN32_IDN) + /************************************************************* + * Check name for non-ASCII and convert hostname to ACE form. + *************************************************************/ + char *ace_hostname = NULL; + int rc = curl_win32_idn_to_ascii(host->name, &ace_hostname); + if(rc == 0) + infof(data, "Failed to convert %s to ACE;\n", + host->name); + else { + host->encalloc = ace_hostname; + /* change the name pointer to point to the encoded hostname */ + host->name = host->encalloc; + } +#else + infof(data, "IDN support not present, can't parse Unicode domains\n"); +#endif + } +} + +static void llist_dtor(void *user, void *element) +{ + (void)user; + (void)element; + /* Do nothing */ +} + +/* + * Allocate and initialize a new connectdata object. + */ +static struct connectdata *allocate_conn(struct SessionHandle *data) +{ + struct connectdata *conn = calloc(1, sizeof(struct connectdata)); + if(!conn) + return NULL; + + conn->handler = &Curl_handler_dummy; /* Be sure we have a handler defined + already from start to avoid NULL + situations and checks */ + + /* and we setup a few fields in case we end up actually using this struct */ + + conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ + conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ + conn->connection_id = -1; /* no ID */ + conn->port = -1; /* unknown at this point */ + + /* Default protocol-independent behavior doesn't support persistent + connections, so we set this to force-close. Protocols that support + this need to set this to FALSE in their "curl_do" functions. */ + conn->bits.close = TRUE; + + /* Store creation time to help future close decision making */ + conn->created = Curl_tvnow(); + + conn->data = data; /* Setup the association between this connection + and the SessionHandle */ + + conn->proxytype = data->set.proxytype; /* type */ + +#ifdef CURL_DISABLE_PROXY + + conn->bits.proxy = FALSE; + conn->bits.httpproxy = FALSE; + conn->bits.proxy_user_passwd = FALSE; + conn->bits.tunnel_proxy = FALSE; + +#else /* CURL_DISABLE_PROXY */ + + /* note that these two proxy bits are now just on what looks to be + requested, they may be altered down the road */ + conn->bits.proxy = (data->set.str[STRING_PROXY] && + *data->set.str[STRING_PROXY])?TRUE:FALSE; + conn->bits.httpproxy = (conn->bits.proxy && + (conn->proxytype == CURLPROXY_HTTP || + conn->proxytype == CURLPROXY_HTTP_1_0))?TRUE:FALSE; + conn->bits.proxy_user_passwd = + (NULL != data->set.str[STRING_PROXYUSERNAME])?TRUE:FALSE; + conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy; + +#endif /* CURL_DISABLE_PROXY */ + + conn->bits.user_passwd = (NULL != data->set.str[STRING_USERNAME])?TRUE:FALSE; + conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; + conn->bits.ftp_use_eprt = data->set.ftp_use_eprt; + + conn->verifypeer = data->set.ssl.verifypeer; + conn->verifyhost = data->set.ssl.verifyhost; + + conn->ip_version = data->set.ipver; + +#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) + conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; + conn->ntlm_auth_hlpr_pid = 0; + conn->challenge_header = NULL; + conn->response_header = NULL; +#endif + + if(data->multi && Curl_multi_canPipeline(data->multi) && + !conn->master_buffer) { + /* Allocate master_buffer to be used for pipelining */ + conn->master_buffer = calloc(BUFSIZE, sizeof (char)); + if(!conn->master_buffer) + goto error; + } + + /* Initialize the pipeline lists */ + conn->send_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); + conn->recv_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); + conn->pend_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); + conn->done_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); + if(!conn->send_pipe || !conn->recv_pipe || !conn->pend_pipe || + !conn->done_pipe) + goto error; + +#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) + conn->data_prot = PROT_CLEAR; +#endif + + /* Store the local bind parameters that will be used for this connection */ + if(data->set.str[STRING_DEVICE]) { + conn->localdev = strdup(data->set.str[STRING_DEVICE]); + if(!conn->localdev) + goto error; + } + conn->localportrange = data->set.localportrange; + conn->localport = data->set.localport; + + /* the close socket stuff needs to be copied to the connection struct as + it may live on without (this specific) SessionHandle */ + conn->fclosesocket = data->set.fclosesocket; + conn->closesocket_client = data->set.closesocket_client; + + return conn; + error: + + Curl_llist_destroy(conn->send_pipe, NULL); + Curl_llist_destroy(conn->recv_pipe, NULL); + Curl_llist_destroy(conn->pend_pipe, NULL); + Curl_llist_destroy(conn->done_pipe, NULL); + + conn->send_pipe = NULL; + conn->recv_pipe = NULL; + conn->pend_pipe = NULL; + conn->done_pipe = NULL; + + Curl_safefree(conn->master_buffer); + Curl_safefree(conn->localdev); + Curl_safefree(conn); + return NULL; +} + +static CURLcode findprotocol(struct SessionHandle *data, + struct connectdata *conn, + const char *protostr) +{ + const struct Curl_handler * const *pp; + const struct Curl_handler *p; + + /* Scan protocol handler table and match against 'protostr' to set a few + variables based on the URL. Now that the handler may be changed later + when the protocol specific setup function is called. */ + for(pp = protocols; (p = *pp) != NULL; pp++) { + if(Curl_raw_equal(p->scheme, protostr)) { + /* Protocol found in table. Check if allowed */ + if(!(data->set.allowed_protocols & p->protocol)) + /* nope, get out */ + break; + + /* it is allowed for "normal" request, now do an extra check if this is + the result of a redirect */ + if(data->state.this_is_a_follow && + !(data->set.redir_protocols & p->protocol)) + /* nope, get out */ + break; + + /* Perform setup complement if some. */ + conn->handler = conn->given = p; + + /* 'port' and 'remote_port' are set in setup_connection_internals() */ + return CURLE_OK; + } + } + + + /* The protocol was not found in the table, but we don't have to assign it + to anything since it is already assigned to a dummy-struct in the + create_conn() function when the connectdata struct is allocated. */ + failf(data, "Protocol %s not supported or disabled in " LIBCURL_NAME, + protostr); + + return CURLE_UNSUPPORTED_PROTOCOL; +} + +/* + * Parse URL and fill in the relevant members of the connection struct. + */ +static CURLcode parseurlandfillconn(struct SessionHandle *data, + struct connectdata *conn, + bool *prot_missing, + char *user, + char *passwd) +{ + char *at; + char *fragment; + char *path = data->state.path; + char *query; + int rc; + char protobuf[16]; + const char *protop; + CURLcode result; + + *prot_missing = FALSE; + + /************************************************************* + * Parse the URL. + * + * We need to parse the url even when using the proxy, because we will need + * the hostname and port in case we are trying to SSL connect through the + * proxy -- and we don't know if we will need to use SSL until we parse the + * url ... + ************************************************************/ + if((2 == sscanf(data->change.url, "%15[^:]:%[^\n]", + protobuf, path)) && + Curl_raw_equal(protobuf, "file")) { + if(path[0] == '/' && path[1] == '/') { + /* Allow omitted hostname (e.g. file:/). This is not strictly + * speaking a valid file: URL by RFC 1738, but treating file:/ as + * file://localhost/ is similar to how other schemes treat missing + * hostnames. See RFC 1808. */ + + /* This cannot be done with strcpy() in a portable manner, since the + memory areas overlap! */ + memmove(path, path + 2, strlen(path + 2)+1); + } + /* + * we deal with file:/// differently since it supports no + * hostname other than "localhost" and "127.0.0.1", which is unique among + * the URL protocols specified in RFC 1738 + */ + if(path[0] != '/') { + /* the URL included a host name, we ignore host names in file:// URLs + as the standards don't define what to do with them */ + char *ptr=strchr(path, '/'); + if(ptr) { + /* there was a slash present + + RFC1738 (section 3.1, page 5) says: + + The rest of the locator consists of data specific to the scheme, + and is known as the "url-path". It supplies the details of how the + specified resource can be accessed. Note that the "/" between the + host (or port) and the url-path is NOT part of the url-path. + + As most agents use file://localhost/foo to get '/foo' although the + slash preceding foo is a separator and not a slash for the path, + a URL as file://localhost//foo must be valid as well, to refer to + the same file with an absolute path. + */ + + if(ptr[1] && ('/' == ptr[1])) + /* if there was two slashes, we skip the first one as that is then + used truly as a separator */ + ptr++; + + /* This cannot be made with strcpy, as the memory chunks overlap! */ + memmove(path, ptr, strlen(ptr)+1); + } + } + + protop = "file"; /* protocol string */ + } + else { + /* clear path */ + path[0]=0; + + if(2 > sscanf(data->change.url, + "%15[^\n:]://%[^\n/?]%[^\n]", + protobuf, + conn->host.name, path)) { + + /* + * The URL was badly formatted, let's try the browser-style _without_ + * protocol specified like 'http://'. + */ + rc = sscanf(data->change.url, "%[^\n/?]%[^\n]", conn->host.name, path); + if(1 > rc) { + /* + * We couldn't even get this format. + * djgpp 2.04 has a sscanf() bug where 'conn->host.name' is + * assigned, but the return value is EOF! + */ +#if defined(__DJGPP__) && (DJGPP_MINOR == 4) + if(!(rc == -1 && *conn->host.name)) +#endif + { + failf(data, " malformed"); + return CURLE_URL_MALFORMAT; + } + } + + /* + * Since there was no protocol part specified, we guess what protocol it + * is based on the first letters of the server name. + */ + + /* Note: if you add a new protocol, please update the list in + * lib/curl_version.c too! */ + + if(checkprefix("FTP.", conn->host.name)) + protop = "ftp"; + else if(checkprefix("DICT.", conn->host.name)) + protop = "DICT"; + else if(checkprefix("LDAP.", conn->host.name)) + protop = "LDAP"; + else if(checkprefix("IMAP.", conn->host.name)) + protop = "IMAP"; + else { + protop = "http"; + } + + *prot_missing = TRUE; /* not given in URL */ + } + else + protop = protobuf; + } + + /* We search for '?' in the host name (but only on the right side of a + * @-letter to allow ?-letters in username and password) to handle things + * like http://example.com?param= (notice the missing '/'). + */ + at = strchr(conn->host.name, '@'); + if(at) + query = strchr(at+1, '?'); + else + query = strchr(conn->host.name, '?'); + + if(query) { + /* We must insert a slash before the '?'-letter in the URL. If the URL had + a slash after the '?', that is where the path currently begins and the + '?string' is still part of the host name. + + We must move the trailing part from the host name and put it first in + the path. And have it all prefixed with a slash. + */ + + size_t hostlen = strlen(query); + size_t pathlen = strlen(path); + + /* move the existing path plus the zero byte forward, to make room for + the host-name part */ + memmove(path+hostlen+1, path, pathlen+1); + + /* now copy the trailing host part in front of the existing path */ + memcpy(path+1, query, hostlen); + + path[0]='/'; /* prepend the missing slash */ + + *query=0; /* now cut off the hostname at the ? */ + } + else if(!path[0]) { + /* if there's no path set, use a single slash */ + strcpy(path, "/"); + } + + /* If the URL is malformatted (missing a '/' after hostname before path) we + * insert a slash here. The only letter except '/' we accept to start a path + * is '?'. + */ + if(path[0] == '?') { + /* We need this function to deal with overlapping memory areas. We know + that the memory area 'path' points to is 'urllen' bytes big and that + is bigger than the path. Use +1 to move the zero byte too. */ + memmove(&path[1], path, strlen(path)+1); + path[0] = '/'; + } + + /************************************************************* + * Parse a user name and password in the URL and strip it out + * of the host name + *************************************************************/ + result = parse_url_userpass(data, conn, user, passwd); + if(result != CURLE_OK) + return result; + + if(conn->host.name[0] == '[') { + /* This looks like an IPv6 address literal. See if there is an address + scope. */ + char *percent = strstr (conn->host.name, "%25"); + if(percent) { + char *endp; + unsigned long scope = strtoul (percent + 3, &endp, 10); + if(*endp == ']') { + /* The address scope was well formed. Knock it out of the + hostname. */ + memmove(percent, endp, strlen(endp)+1); + if(!data->state.this_is_a_follow) + /* Don't honour a scope given in a Location: header */ + conn->scope = (unsigned int)scope; + } + else + infof(data, "Invalid IPv6 address format\n"); + } + } + + if(data->set.scope) + /* Override any scope that was set above. */ + conn->scope = data->set.scope; + + /* Remove the fragment part of the path. Per RFC 2396, this is always the + last part of the URI. We are looking for the first '#' so that we deal + gracefully with non conformant URI such as http://example.com#foo#bar. */ + fragment = strchr(path, '#'); + if(fragment) { + *fragment = 0; + + /* we know the path part ended with a fragment, so we know the full URL + string does too and we need to cut it off from there so it isn't used + over proxy */ + fragment = strchr(data->change.url, '#'); + if(fragment) + *fragment = 0; + } + + /* + * So if the URL was A://B/C#D, + * protop is A + * conn->host.name is B + * data->state.path is /C + */ + + return findprotocol(data, conn, protop); +} + +/* + * If we're doing a resumed transfer, we need to setup our stuff + * properly. + */ +static CURLcode setup_range(struct SessionHandle *data) +{ + struct UrlState *s = &data->state; + s->resume_from = data->set.set_resume_from; + if(s->resume_from || data->set.str[STRING_SET_RANGE]) { + if(s->rangestringalloc) + free(s->range); + + if(s->resume_from) + s->range = aprintf("%" FORMAT_OFF_TU "-", s->resume_from); + else + s->range = strdup(data->set.str[STRING_SET_RANGE]); + + s->rangestringalloc = (s->range)?TRUE:FALSE; + + if(!s->range) + return CURLE_OUT_OF_MEMORY; + + /* tell ourselves to fetch this range */ + s->use_range = TRUE; /* enable range download */ + } + else + s->use_range = FALSE; /* disable range download */ + + return CURLE_OK; +} + + +/*************************************************************** +* Setup connection internals specific to the requested protocol. +* This MUST get called after proxy magic has been figured out. +***************************************************************/ +static CURLcode setup_connection_internals(struct connectdata *conn) +{ + const struct Curl_handler * p; + CURLcode result; + + conn->socktype = SOCK_STREAM; /* most of them are TCP streams */ + + /* Scan protocol handler table. */ + + /* Perform setup complement if some. */ + p = conn->handler; + + if(p->setup_connection) { + result = (*p->setup_connection)(conn); + + if(result != CURLE_OK) + return result; + + p = conn->handler; /* May have changed. */ + } + + if(conn->port < 0) + /* we check for -1 here since if proxy was detected already, this + was very likely already set to the proxy port */ + conn->port = p->defport; + conn->remote_port = (unsigned short)conn->given->defport; + + return CURLE_OK; +} + +#ifndef CURL_DISABLE_PROXY +/**************************************************************** +* Checks if the host is in the noproxy list. returns true if it matches +* and therefore the proxy should NOT be used. +****************************************************************/ +static bool check_noproxy(const char* name, const char* no_proxy) +{ + /* no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + */ + size_t tok_start; + size_t tok_end; + const char* separator = ", "; + size_t no_proxy_len; + size_t namelen; + char *endptr; + + if(no_proxy && no_proxy[0]) { + if(Curl_raw_equal("*", no_proxy)) { + return TRUE; + } + + /* NO_PROXY was specified and it wasn't just an asterisk */ + + no_proxy_len = strlen(no_proxy); + endptr = strchr(name, ':'); + if(endptr) + namelen = endptr - name; + else + namelen = strlen(name); + + for(tok_start = 0; tok_start < no_proxy_len; tok_start = tok_end + 1) { + while(tok_start < no_proxy_len && + strchr(separator, no_proxy[tok_start]) != NULL) { + /* Look for the beginning of the token. */ + ++tok_start; + } + + if(tok_start == no_proxy_len) + break; /* It was all trailing separator chars, no more tokens. */ + + for(tok_end = tok_start; tok_end < no_proxy_len && + strchr(separator, no_proxy[tok_end]) == NULL; ++tok_end) + /* Look for the end of the token. */ + ; + + /* To match previous behaviour, where it was necessary to specify + * ".local.com" to prevent matching "notlocal.com", we will leave + * the '.' off. + */ + if(no_proxy[tok_start] == '.') + ++tok_start; + + if((tok_end - tok_start) <= namelen) { + /* Match the last part of the name to the domain we are checking. */ + const char *checkn = name + namelen - (tok_end - tok_start); + if(Curl_raw_nequal(no_proxy + tok_start, checkn, + tok_end - tok_start)) { + if((tok_end - tok_start) == namelen || *(checkn - 1) == '.') { + /* We either have an exact match, or the previous character is a . + * so it is within the same domain, so no proxy for this host. + */ + return TRUE; + } + } + } /* if((tok_end - tok_start) <= namelen) */ + } /* for(tok_start = 0; tok_start < no_proxy_len; + tok_start = tok_end + 1) */ + } /* NO_PROXY was specified and it wasn't just an asterisk */ + + return FALSE; +} + +/**************************************************************** +* Detect what (if any) proxy to use. Remember that this selects a host +* name and is not limited to HTTP proxies only. +* The returned pointer must be freed by the caller (unless NULL) +****************************************************************/ +static char *detect_proxy(struct connectdata *conn) +{ + char *proxy = NULL; + +#ifndef CURL_DISABLE_HTTP + /* If proxy was not specified, we check for default proxy environment + * variables, to enable i.e Lynx compliance: + * + * http_proxy=http://some.server.dom:port/ + * https_proxy=http://some.server.dom:port/ + * ftp_proxy=http://some.server.dom:port/ + * no_proxy=domain1.dom,host.domain2.dom + * (a comma-separated list of hosts which should + * not be proxied, or an asterisk to override + * all proxy variables) + * all_proxy=http://some.server.dom:port/ + * (seems to exist for the CERN www lib. Probably + * the first to check for.) + * + * For compatibility, the all-uppercase versions of these variables are + * checked if the lowercase versions don't exist. + */ + char *no_proxy=NULL; + char proxy_env[128]; + + no_proxy=curl_getenv("no_proxy"); + if(!no_proxy) + no_proxy=curl_getenv("NO_PROXY"); + + if(!check_noproxy(conn->host.name, no_proxy)) { + /* It was not listed as without proxy */ + const char *protop = conn->handler->scheme; + char *envp = proxy_env; + char *prox; + + /* Now, build _proxy and check for such a one to use */ + while(*protop) + *envp++ = (char)tolower((int)*protop++); + + /* append _proxy */ + strcpy(envp, "_proxy"); + + /* read the protocol proxy: */ + prox=curl_getenv(proxy_env); + + /* + * We don't try the uppercase version of HTTP_PROXY because of + * security reasons: + * + * When curl is used in a webserver application + * environment (cgi or php), this environment variable can + * be controlled by the web server user by setting the + * http header 'Proxy:' to some value. + * + * This can cause 'internal' http/ftp requests to be + * arbitrarily redirected by any external attacker. + */ + if(!prox && !Curl_raw_equal("http_proxy", proxy_env)) { + /* There was no lowercase variable, try the uppercase version: */ + Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env)); + prox=curl_getenv(proxy_env); + } + + if(prox && *prox) { /* don't count "" strings */ + proxy = prox; /* use this */ + } + else { + proxy = curl_getenv("all_proxy"); /* default proxy to use */ + if(!proxy) + proxy=curl_getenv("ALL_PROXY"); + } + } /* if(!check_noproxy(conn->host.name, no_proxy)) - it wasn't specified + non-proxy */ + if(no_proxy) + free(no_proxy); + +#else /* !CURL_DISABLE_HTTP */ + + (void)conn; +#endif /* CURL_DISABLE_HTTP */ + + return proxy; +} + +/* + * If this is supposed to use a proxy, we need to figure out the proxy + * host name, so that we can re-use an existing connection + * that may exist registered to the same proxy host. + * proxy will be freed before this function returns. + */ +static CURLcode parse_proxy(struct SessionHandle *data, + struct connectdata *conn, char *proxy) +{ + char *prox_portno; + char *endofprot; + + /* We use 'proxyptr' to point to the proxy name from now on... */ + char *proxyptr; + char *portptr; + char *atsign; + + /* We do the proxy host string parsing here. We want the host name and the + * port name. Accept a protocol:// prefix + */ + + /* Parse the protocol part if present */ + endofprot = strstr(proxy, "://"); + if(endofprot) { + proxyptr = endofprot+3; + if(checkprefix("socks5h", proxy)) + conn->proxytype = CURLPROXY_SOCKS5_HOSTNAME; + else if(checkprefix("socks5", proxy)) + conn->proxytype = CURLPROXY_SOCKS5; + else if(checkprefix("socks4a", proxy)) + conn->proxytype = CURLPROXY_SOCKS4A; + else if(checkprefix("socks4", proxy) || checkprefix("socks", proxy)) + conn->proxytype = CURLPROXY_SOCKS4; + /* Any other xxx:// : change to http proxy */ + } + else + proxyptr = proxy; /* No xxx:// head: It's a HTTP proxy */ + + /* Is there a username and password given in this proxy url? */ + atsign = strchr(proxyptr, '@'); + if(atsign) { + char proxyuser[MAX_CURL_USER_LENGTH]; + char proxypasswd[MAX_CURL_PASSWORD_LENGTH]; + proxypasswd[0] = 0; + + if(1 <= sscanf(proxyptr, + "%" MAX_CURL_USER_LENGTH_TXT"[^:@]:" + "%" MAX_CURL_PASSWORD_LENGTH_TXT "[^@]", + proxyuser, proxypasswd)) { + CURLcode res = CURLE_OK; + + /* found user and password, rip them out. note that we are + unescaping them, as there is otherwise no way to have a + username or password with reserved characters like ':' in + them. */ + Curl_safefree(conn->proxyuser); + conn->proxyuser = curl_easy_unescape(data, proxyuser, 0, NULL); + + if(!conn->proxyuser) + res = CURLE_OUT_OF_MEMORY; + else { + Curl_safefree(conn->proxypasswd); + conn->proxypasswd = curl_easy_unescape(data, proxypasswd, 0, NULL); + + if(!conn->proxypasswd) + res = CURLE_OUT_OF_MEMORY; + } + + if(CURLE_OK == res) { + conn->bits.proxy_user_passwd = TRUE; /* enable it */ + atsign++; /* the right side of the @-letter */ + + if(atsign) + proxyptr = atsign; /* now use this instead */ + else + res = CURLE_OUT_OF_MEMORY; + } + + if(res) + return res; + } + } + + /* start scanning for port number at this point */ + portptr = proxyptr; + + /* detect and extract RFC2732-style IPv6-addresses */ + if(*proxyptr == '[') { + char *ptr = ++proxyptr; /* advance beyond the initial bracket */ + while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '%') || + (*ptr == '.'))) + ptr++; + if(*ptr == ']') + /* yeps, it ended nicely with a bracket as well */ + *ptr++ = 0; + else + infof(data, "Invalid IPv6 address format\n"); + portptr = ptr; + /* Note that if this didn't end with a bracket, we still advanced the + * proxyptr first, but I can't see anything wrong with that as no host + * name nor a numeric can legally start with a bracket. + */ + } + + /* Get port number off proxy.server.com:1080 */ + prox_portno = strchr(portptr, ':'); + if(prox_portno) { + *prox_portno = 0x0; /* cut off number from host name */ + prox_portno ++; + /* now set the local port number */ + conn->port = strtol(prox_portno, NULL, 10); + } + else { + if(proxyptr[0]=='/') + /* If the first character in the proxy string is a slash, fail + immediately. The following code will otherwise clear the string which + will lead to code running as if no proxy was set! */ + return CURLE_COULDNT_RESOLVE_PROXY; + + /* without a port number after the host name, some people seem to use + a slash so we strip everything from the first slash */ + atsign = strchr(proxyptr, '/'); + if(atsign) + *atsign = 0x0; /* cut off path part from host name */ + + if(data->set.proxyport) + /* None given in the proxy string, then get the default one if it is + given */ + conn->port = data->set.proxyport; + } + + /* now, clone the cleaned proxy host name */ + conn->proxy.rawalloc = strdup(proxyptr); + conn->proxy.name = conn->proxy.rawalloc; + + if(!conn->proxy.rawalloc) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +/* + * Extract the user and password from the authentication string + */ +static CURLcode parse_proxy_auth(struct SessionHandle *data, + struct connectdata *conn) +{ + char proxyuser[MAX_CURL_USER_LENGTH]=""; + char proxypasswd[MAX_CURL_PASSWORD_LENGTH]=""; + + if(data->set.str[STRING_PROXYUSERNAME] != NULL) { + strncpy(proxyuser, data->set.str[STRING_PROXYUSERNAME], + MAX_CURL_USER_LENGTH); + proxyuser[MAX_CURL_USER_LENGTH-1] = '\0'; /*To be on safe side*/ + } + if(data->set.str[STRING_PROXYPASSWORD] != NULL) { + strncpy(proxypasswd, data->set.str[STRING_PROXYPASSWORD], + MAX_CURL_PASSWORD_LENGTH); + proxypasswd[MAX_CURL_PASSWORD_LENGTH-1] = '\0'; /*To be on safe side*/ + } + + conn->proxyuser = curl_easy_unescape(data, proxyuser, 0, NULL); + if(!conn->proxyuser) + return CURLE_OUT_OF_MEMORY; + + conn->proxypasswd = curl_easy_unescape(data, proxypasswd, 0, NULL); + if(!conn->proxypasswd) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} +#endif /* CURL_DISABLE_PROXY */ + +/* + * + * Parse a user name and password in the URL and strip it out of the host name + * + * Inputs: data->set.use_netrc (CURLOPT_NETRC) + * conn->host.name + * + * Outputs: (almost :- all currently undefined) + * conn->bits.user_passwd - non-zero if non-default passwords exist + * user - non-zero length if defined + * passwd - ditto + * conn->host.name - remove user name and password + */ +static CURLcode parse_url_userpass(struct SessionHandle *data, + struct connectdata *conn, + char *user, char *passwd) +{ + /* At this point, we're hoping all the other special cases have + * been taken care of, so conn->host.name is at most + * [user[:password]]@]hostname + * + * We need somewhere to put the embedded details, so do that first. + */ + + char *ptr=strchr(conn->host.name, '@'); + char *userpass = conn->host.name; + + user[0] =0; /* to make everything well-defined */ + passwd[0]=0; + + /* We will now try to extract the + * possible user+password pair in a string like: + * ftp://user:password@ftp.my.site:8021/README */ + if(ptr != NULL) { + /* there's a user+password given here, to the left of the @ */ + + conn->host.name = ++ptr; + + /* So the hostname is sane. Only bother interpreting the + * results if we could care. It could still be wasted + * work because it might be overtaken by the programmatically + * set user/passwd, but doing that first adds more cases here :-( + */ + + conn->bits.userpwd_in_url = TRUE; + if(data->set.use_netrc != CURL_NETRC_REQUIRED) { + /* We could use the one in the URL */ + + conn->bits.user_passwd = TRUE; /* enable user+password */ + + if(*userpass != ':') { + /* the name is given, get user+password */ + sscanf(userpass, "%" MAX_CURL_USER_LENGTH_TXT "[^:@]:" + "%" MAX_CURL_PASSWORD_LENGTH_TXT "[^@]", + user, passwd); + } + else + /* no name given, get the password only */ + sscanf(userpass, ":%" MAX_CURL_PASSWORD_LENGTH_TXT "[^@]", passwd); + + if(user[0]) { + char *newname=curl_easy_unescape(data, user, 0, NULL); + if(!newname) + return CURLE_OUT_OF_MEMORY; + if(strlen(newname) < MAX_CURL_USER_LENGTH) + strcpy(user, newname); + + /* if the new name is longer than accepted, then just use + the unconverted name, it'll be wrong but what the heck */ + free(newname); + } + if(passwd[0]) { + /* we have a password found in the URL, decode it! */ + char *newpasswd=curl_easy_unescape(data, passwd, 0, NULL); + if(!newpasswd) + return CURLE_OUT_OF_MEMORY; + if(strlen(newpasswd) < MAX_CURL_PASSWORD_LENGTH) + strcpy(passwd, newpasswd); + + free(newpasswd); + } + } + } + return CURLE_OK; +} + +/************************************************************* + * Figure out the remote port number and fix it in the URL + * + * No matter if we use a proxy or not, we have to figure out the remote + * port number of various reasons. + * + * To be able to detect port number flawlessly, we must not confuse them + * IPv6-specified addresses in the [0::1] style. (RFC2732) + * + * The conn->host.name is currently [user:passwd@]host[:port] where host + * could be a hostname, IPv4 address or IPv6 address. + * + * The port number embedded in the URL is replaced, if necessary. + *************************************************************/ +static CURLcode parse_remote_port(struct SessionHandle *data, + struct connectdata *conn) +{ + char *portptr; + char endbracket; + + /* Note that at this point, the IPv6 address cannot contain any scope + suffix as that has already been removed in the parseurlandfillconn() + function */ + if((1 == sscanf(conn->host.name, "[%*45[0123456789abcdefABCDEF:.]%c", + &endbracket)) && + (']' == endbracket)) { + /* this is a RFC2732-style specified IP-address */ + conn->bits.ipv6_ip = TRUE; + + conn->host.name++; /* skip over the starting bracket */ + portptr = strchr(conn->host.name, ']'); + if(portptr) { + *portptr++ = '\0'; /* zero terminate, killing the bracket */ + if(':' != *portptr) + portptr = NULL; /* no port number available */ + } + } + else { +#ifdef ENABLE_IPV6 + struct in6_addr in6; + if(Curl_inet_pton(AF_INET6, conn->host.name, &in6) > 0) { + /* This is a numerical IPv6 address, meaning this is a wrongly formatted + URL */ + failf(data, "IPv6 numerical address used in URL without brackets"); + return CURLE_URL_MALFORMAT; + } +#endif + + portptr = strrchr(conn->host.name, ':'); + } + + if(data->set.use_port && data->state.allow_port) { + /* if set, we use this and ignore the port possibly given in the URL */ + conn->remote_port = (unsigned short)data->set.use_port; + if(portptr) + *portptr = '\0'; /* cut off the name there anyway - if there was a port + number - since the port number is to be ignored! */ + if(conn->bits.httpproxy) { + /* we need to create new URL with the new port number */ + char *url; + char type[12]=""; + + if(conn->bits.type_set) + snprintf(type, sizeof(type), ";type=%c", + data->set.prefer_ascii?'A': + (data->set.ftp_list_only?'D':'I')); + + /* + * This synthesized URL isn't always right--suffixes like ;type=A are + * stripped off. It would be better to work directly from the original + * URL and simply replace the port part of it. + */ + url = aprintf("%s://%s%s%s:%hu%s%s%s", conn->given->scheme, + conn->bits.ipv6_ip?"[":"", conn->host.name, + conn->bits.ipv6_ip?"]":"", conn->remote_port, + data->state.slash_removed?"/":"", data->state.path, + type); + if(!url) + return CURLE_OUT_OF_MEMORY; + + if(data->change.url_alloc) { + Curl_safefree(data->change.url); + data->change.url_alloc = FALSE; + } + + data->change.url = url; + data->change.url_alloc = TRUE; + } + } + else if(portptr) { + /* no CURLOPT_PORT given, extract the one from the URL */ + + char *rest; + unsigned long port; + + port=strtoul(portptr+1, &rest, 10); /* Port number must be decimal */ + + if(rest != (portptr+1) && *rest == '\0') { + /* The colon really did have only digits after it, + * so it is either a port number or a mistake */ + + if(port > 0xffff) { /* Single unix standard says port numbers are + * 16 bits long */ + failf(data, "Port number too large: %lu", port); + return CURLE_URL_MALFORMAT; + } + + *portptr = '\0'; /* cut off the name there */ + conn->remote_port = curlx_ultous(port); + } + else if(!port) + /* Browser behavior adaptation. If there's a colon with no digits after, + just cut off the name there which makes us ignore the colon and just + use the default port. Firefox and Chrome both do that. */ + *portptr = '\0'; + } + return CURLE_OK; +} + +/* + * Override a user name and password from the URL with that in the + * CURLOPT_USERPWD option or a .netrc file, if applicable. + */ +static void override_userpass(struct SessionHandle *data, + struct connectdata *conn, + char *user, char *passwd) +{ + if(data->set.str[STRING_USERNAME] != NULL) { + strncpy(user, data->set.str[STRING_USERNAME], MAX_CURL_USER_LENGTH); + user[MAX_CURL_USER_LENGTH-1] = '\0'; /*To be on safe side*/ + } + if(data->set.str[STRING_PASSWORD] != NULL) { + strncpy(passwd, data->set.str[STRING_PASSWORD], MAX_CURL_PASSWORD_LENGTH); + passwd[MAX_CURL_PASSWORD_LENGTH-1] = '\0'; /*To be on safe side*/ + } + + conn->bits.netrc = FALSE; + if(data->set.use_netrc != CURL_NETRC_IGNORED) { + if(Curl_parsenetrc(conn->host.name, + user, passwd, + data->set.str[STRING_NETRC_FILE])) { + infof(data, "Couldn't find host %s in the " + DOT_CHAR "netrc file; using defaults\n", + conn->host.name); + } + else { + /* set bits.netrc TRUE to remember that we got the name from a .netrc + file, so that it is safe to use even if we followed a Location: to a + different host or similar. */ + conn->bits.netrc = TRUE; + + conn->bits.user_passwd = TRUE; /* enable user+password */ + } + } +} + +/* + * Set password so it's available in the connection. + */ +static CURLcode set_userpass(struct connectdata *conn, + const char *user, const char *passwd) +{ + /* If our protocol needs a password and we have none, use the defaults */ + if((conn->handler->flags & PROTOPT_NEEDSPWD) && + !conn->bits.user_passwd) { + + conn->user = strdup(CURL_DEFAULT_USER); + if(conn->user) + conn->passwd = strdup(CURL_DEFAULT_PASSWORD); + else + conn->passwd = NULL; + /* This is the default password, so DON'T set conn->bits.user_passwd */ + } + else { + /* store user + password, zero-length if not set */ + conn->user = strdup(user); + if(conn->user) + conn->passwd = strdup(passwd); + else + conn->passwd = NULL; + } + if(!conn->user || !conn->passwd) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +} + +/************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ +static CURLcode resolve_server(struct SessionHandle *data, + struct connectdata *conn, + bool *async) +{ + CURLcode result=CURLE_OK; + long timeout_ms = Curl_timeleft(data, NULL, TRUE); + + /************************************************************* + * Resolve the name of the server or proxy + *************************************************************/ + if(conn->bits.reuse) + /* We're reusing the connection - no need to resolve anything, and + fix_hostname() was called already in create_conn() for the re-use + case. */ + *async = FALSE; + + else { + /* this is a fresh connect */ + int rc; + struct Curl_dns_entry *hostaddr; + + /* set a pointer to the hostname we display */ + fix_hostname(data, conn, &conn->host); + + if(!conn->proxy.name || !*conn->proxy.name) { + /* If not connecting via a proxy, extract the port from the URL, if it is + * there, thus overriding any defaults that might have been set above. */ + conn->port = conn->remote_port; /* it is the same port */ + + /* Resolve target host right on */ + rc = Curl_resolv_timeout(conn, conn->host.name, (int)conn->port, + &hostaddr, timeout_ms); + if(rc == CURLRESOLV_PENDING) + *async = TRUE; + + else if(rc == CURLRESOLV_TIMEDOUT) + result = CURLE_OPERATION_TIMEDOUT; + + else if(!hostaddr) { + failf(data, "Couldn't resolve host '%s'", conn->host.dispname); + result = CURLE_COULDNT_RESOLVE_HOST; + /* don't return yet, we need to clean up the timeout first */ + } + } + else { + /* This is a proxy that hasn't been resolved yet. */ + + /* IDN-fix the proxy name */ + fix_hostname(data, conn, &conn->proxy); + + /* resolve proxy */ + rc = Curl_resolv_timeout(conn, conn->proxy.name, (int)conn->port, + &hostaddr, timeout_ms); + + if(rc == CURLRESOLV_PENDING) + *async = TRUE; + + else if(rc == CURLRESOLV_TIMEDOUT) + result = CURLE_OPERATION_TIMEDOUT; + + else if(!hostaddr) { + failf(data, "Couldn't resolve proxy '%s'", conn->proxy.dispname); + result = CURLE_COULDNT_RESOLVE_PROXY; + /* don't return yet, we need to clean up the timeout first */ + } + } + DEBUGASSERT(conn->dns_entry == NULL); + conn->dns_entry = hostaddr; + } + + return result; +} + +/* + * Cleanup the connection just allocated before we can move along and use the + * previously existing one. All relevant data is copied over and old_conn is + * ready for freeing once this function returns. + */ +static void reuse_conn(struct connectdata *old_conn, + struct connectdata *conn) +{ + if(old_conn->proxy.rawalloc) + free(old_conn->proxy.rawalloc); + + /* free the SSL config struct from this connection struct as this was + allocated in vain and is targeted for destruction */ + Curl_free_ssl_config(&old_conn->ssl_config); + + conn->data = old_conn->data; + + /* get the user+password information from the old_conn struct since it may + * be new for this request even when we re-use an existing connection */ + conn->bits.user_passwd = old_conn->bits.user_passwd; + if(conn->bits.user_passwd) { + /* use the new user name and password though */ + Curl_safefree(conn->user); + Curl_safefree(conn->passwd); + conn->user = old_conn->user; + conn->passwd = old_conn->passwd; + old_conn->user = NULL; + old_conn->passwd = NULL; + } + + conn->bits.proxy_user_passwd = old_conn->bits.proxy_user_passwd; + if(conn->bits.proxy_user_passwd) { + /* use the new proxy user name and proxy password though */ + Curl_safefree(conn->proxyuser); + Curl_safefree(conn->proxypasswd); + conn->proxyuser = old_conn->proxyuser; + conn->proxypasswd = old_conn->proxypasswd; + old_conn->proxyuser = NULL; + old_conn->proxypasswd = NULL; + } + + /* host can change, when doing keepalive with a proxy or if the case is + different this time etc */ + Curl_safefree(conn->host.rawalloc); + conn->host=old_conn->host; + + /* persist connection info in session handle */ + Curl_persistconninfo(conn); + + /* re-use init */ + conn->bits.reuse = TRUE; /* yes, we're re-using here */ + + Curl_safefree(old_conn->user); + Curl_safefree(old_conn->passwd); + Curl_safefree(old_conn->proxyuser); + Curl_safefree(old_conn->proxypasswd); + Curl_safefree(old_conn->localdev); + + Curl_llist_destroy(old_conn->send_pipe, NULL); + Curl_llist_destroy(old_conn->recv_pipe, NULL); + Curl_llist_destroy(old_conn->pend_pipe, NULL); + Curl_llist_destroy(old_conn->done_pipe, NULL); + + old_conn->send_pipe = NULL; + old_conn->recv_pipe = NULL; + old_conn->pend_pipe = NULL; + old_conn->done_pipe = NULL; + + Curl_safefree(old_conn->master_buffer); +} + +/** + * create_conn() sets up a new connectdata struct, or re-uses an already + * existing one, and resolves host name. + * + * if this function returns CURLE_OK and *async is set to TRUE, the resolve + * response will be coming asynchronously. If *async is FALSE, the name is + * already resolved. + * + * @param data The sessionhandle pointer + * @param in_connect is set to the next connection data pointer + * @param async is set TRUE when an async DNS resolution is pending + * @see Curl_setup_conn() + * + * *NOTE* this function assigns the conn->data pointer! + */ + +static CURLcode create_conn(struct SessionHandle *data, + struct connectdata **in_connect, + bool *async) +{ + CURLcode result=CURLE_OK; + struct connectdata *conn; + struct connectdata *conn_temp = NULL; + size_t urllen; + char user[MAX_CURL_USER_LENGTH]; + char passwd[MAX_CURL_PASSWORD_LENGTH]; + bool reuse; + char *proxy = NULL; + bool prot_missing = FALSE; + + *async = FALSE; + + /************************************************************* + * Check input data + *************************************************************/ + + if(!data->change.url) + return CURLE_URL_MALFORMAT; + + /* First, split up the current URL in parts so that we can use the + parts for checking against the already present connections. In order + to not have to modify everything at once, we allocate a temporary + connection data struct and fill in for comparison purposes. */ + conn = allocate_conn(data); + + if(!conn) + return CURLE_OUT_OF_MEMORY; + + /* We must set the return variable as soon as possible, so that our + parent can cleanup any possible allocs we may have done before + any failure */ + *in_connect = conn; + + /* This initing continues below, see the comment "Continue connectdata + * initialization here" */ + + /*********************************************************** + * We need to allocate memory to store the path in. We get the size of the + * full URL to be sure, and we need to make it at least 256 bytes since + * other parts of the code will rely on this fact + ***********************************************************/ +#define LEAST_PATH_ALLOC 256 + urllen=strlen(data->change.url); + if(urllen < LEAST_PATH_ALLOC) + urllen=LEAST_PATH_ALLOC; + + /* + * We malloc() the buffers below urllen+2 to make room for 2 possibilities: + * 1 - an extra terminating zero + * 2 - an extra slash (in case a syntax like "www.host.com?moo" is used) + */ + + Curl_safefree(data->state.pathbuffer); + data->state.path = NULL; + + data->state.pathbuffer = malloc(urllen+2); + if(NULL == data->state.pathbuffer) + return CURLE_OUT_OF_MEMORY; /* really bad error */ + data->state.path = data->state.pathbuffer; + + conn->host.rawalloc = malloc(urllen+2); + if(NULL == conn->host.rawalloc) { + Curl_safefree(data->state.pathbuffer); + data->state.path = NULL; + return CURLE_OUT_OF_MEMORY; + } + + conn->host.name = conn->host.rawalloc; + conn->host.name[0] = 0; + + result = parseurlandfillconn(data, conn, &prot_missing, user, passwd); + if(result != CURLE_OK) + return result; + + /************************************************************* + * No protocol part in URL was used, add it! + *************************************************************/ + if(prot_missing) { + /* We're guessing prefixes here and if we're told to use a proxy or if + we're gonna follow a Location: later or... then we need the protocol + part added so that we have a valid URL. */ + char *reurl; + + reurl = aprintf("%s://%s", conn->handler->scheme, data->change.url); + + if(!reurl) { + Curl_safefree(proxy); + return CURLE_OUT_OF_MEMORY; + } + + if(data->change.url_alloc) { + Curl_safefree(data->change.url); + data->change.url_alloc = FALSE; + } + + data->change.url = reurl; + data->change.url_alloc = TRUE; /* free this later */ + } + + /************************************************************* + * If the protocol can't handle url query strings, then cut + * of the unhandable part + *************************************************************/ + if((conn->given->flags&PROTOPT_NOURLQUERY)) { + char *path_q_sep = strchr(conn->data->state.path, '?'); + if(path_q_sep) { + /* according to rfc3986, allow the query (?foo=bar) + also on protocols that can't handle it. + + cut the string-part after '?' + */ + + /* terminate the string */ + path_q_sep[0] = 0; + } + } + +#ifndef CURL_DISABLE_PROXY + /************************************************************* + * Extract the user and password from the authentication string + *************************************************************/ + if(conn->bits.proxy_user_passwd) { + result = parse_proxy_auth(data, conn); + if(result != CURLE_OK) + return result; + } + + /************************************************************* + * Detect what (if any) proxy to use + *************************************************************/ + if(data->set.str[STRING_PROXY]) { + proxy = strdup(data->set.str[STRING_PROXY]); + /* if global proxy is set, this is it */ + if(NULL == proxy) { + failf(data, "memory shortage"); + return CURLE_OUT_OF_MEMORY; + } + } + + if(data->set.str[STRING_NOPROXY] && + check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY])) { + if(proxy) { + free(proxy); /* proxy is in exception list */ + proxy = NULL; + } + } + else if(!proxy) + proxy = detect_proxy(conn); + + if(proxy && (!*proxy || (conn->handler->flags & PROTOPT_NONETWORK))) { + free(proxy); /* Don't bother with an empty proxy string or if the + protocol doesn't work with network */ + proxy = NULL; + } + + /*********************************************************************** + * If this is supposed to use a proxy, we need to figure out the proxy host + * name, proxy type and port number, so that we can re-use an existing + * connection that may exist registered to the same proxy host. + ***********************************************************************/ + if(proxy) { + result = parse_proxy(data, conn, proxy); + + free(proxy); /* parse_proxy copies the proxy string */ + + if(result) + return result; + + if((conn->proxytype == CURLPROXY_HTTP) || + (conn->proxytype == CURLPROXY_HTTP_1_0)) { +#ifdef CURL_DISABLE_HTTP + /* asking for a HTTP proxy is a bit funny when HTTP is disabled... */ + return CURLE_UNSUPPORTED_PROTOCOL; +#else + /* force this connection's protocol to become HTTP if not already + compatible - if it isn't tunneling through */ + if(!(conn->handler->protocol & CURLPROTO_HTTP) && + !conn->bits.tunnel_proxy) + conn->handler = &Curl_handler_http; + + conn->bits.httpproxy = TRUE; +#endif + } + else + conn->bits.httpproxy = FALSE; /* not a HTTP proxy */ + conn->bits.proxy = TRUE; + } + else { + /* we aren't using the proxy after all... */ + conn->bits.proxy = FALSE; + conn->bits.httpproxy = FALSE; + conn->bits.proxy_user_passwd = FALSE; + conn->bits.tunnel_proxy = FALSE; + } + +#endif /* CURL_DISABLE_PROXY */ + + /************************************************************* + * Setup internals depending on protocol. Needs to be done after + * we figured out what/if proxy to use. + *************************************************************/ + result = setup_connection_internals(conn); + if(result != CURLE_OK) { + Curl_safefree(proxy); + return result; + } + + conn->recv[FIRSTSOCKET] = Curl_recv_plain; + conn->send[FIRSTSOCKET] = Curl_send_plain; + conn->recv[SECONDARYSOCKET] = Curl_recv_plain; + conn->send[SECONDARYSOCKET] = Curl_send_plain; + + /*********************************************************************** + * file: is a special case in that it doesn't need a network connection + ***********************************************************************/ +#ifndef CURL_DISABLE_FILE + if(conn->handler->flags & PROTOPT_NONETWORK) { + bool done; + /* this is supposed to be the connect function so we better at least check + that the file is present here! */ + DEBUGASSERT(conn->handler->connect_it); + result = conn->handler->connect_it(conn, &done); + + /* Setup a "faked" transfer that'll do nothing */ + if(CURLE_OK == result) { + conn->data = data; + conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; /* we are "connected */ + + ConnectionStore(data, conn); + + /* + * Setup whatever necessary for a resumed transfer + */ + result = setup_range(data); + if(result) { + DEBUGASSERT(conn->handler->done); + /* we ignore the return code for the protocol-specific DONE */ + (void)conn->handler->done(conn, result, FALSE); + return result; + } + + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ + -1, NULL); /* no upload */ + } + + return result; + } +#endif + + /************************************************************* + * If the protocol is using SSL and HTTP proxy is used, we set + * the tunnel_proxy bit. + *************************************************************/ + if((conn->given->flags&PROTOPT_SSL) && conn->bits.httpproxy) + conn->bits.tunnel_proxy = TRUE; + + /************************************************************* + * Figure out the remote port number and fix it in the URL + *************************************************************/ + result = parse_remote_port(data, conn); + if(result != CURLE_OK) + return result; + + /************************************************************* + * Check for an overridden user name and password, then set it + * for use + *************************************************************/ + override_userpass(data, conn, user, passwd); + result = set_userpass(conn, user, passwd); + if(result != CURLE_OK) + return result; + + /* Get a cloned copy of the SSL config situation stored in the + connection struct. But to get this going nicely, we must first make + sure that the strings in the master copy are pointing to the correct + strings in the session handle strings array! + + Keep in mind that the pointers in the master copy are pointing to strings + that will be freed as part of the SessionHandle struct, but all cloned + copies will be separately allocated. + */ + data->set.ssl.CApath = data->set.str[STRING_SSL_CAPATH]; + data->set.ssl.CAfile = data->set.str[STRING_SSL_CAFILE]; + data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE]; + data->set.ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; + data->set.ssl.random_file = data->set.str[STRING_SSL_RANDOM_FILE]; + data->set.ssl.egdsocket = data->set.str[STRING_SSL_EGDSOCKET]; + data->set.ssl.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST]; +#ifdef USE_TLS_SRP + data->set.ssl.username = data->set.str[STRING_TLSAUTH_USERNAME]; + data->set.ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD]; +#endif + + if(!Curl_clone_ssl_config(&data->set.ssl, &conn->ssl_config)) + return CURLE_OUT_OF_MEMORY; + + /************************************************************* + * Check the current list of connections to see if we can + * re-use an already existing one or if we have to create a + * new one. + *************************************************************/ + + /* reuse_fresh is TRUE if we are told to use a new connection by force, but + we only acknowledge this option if this is not a re-used connection + already (which happens due to follow-location or during a HTTP + authentication phase). */ + if(data->set.reuse_fresh && !data->state.this_is_a_follow) + reuse = FALSE; + else + reuse = ConnectionExists(data, conn, &conn_temp); + + if(reuse) { + /* + * We already have a connection for this, we got the former connection + * in the conn_temp variable and thus we need to cleanup the one we + * just allocated before we can move along and use the previously + * existing one. + */ + reuse_conn(conn, conn_temp); + free(conn); /* we don't need this anymore */ + conn = conn_temp; + *in_connect = conn; + + /* set a pointer to the hostname we display */ + fix_hostname(data, conn, &conn->host); + + infof(data, "Re-using existing connection! (#%ld) with host %s\n", + conn->connection_id, + conn->proxy.name?conn->proxy.dispname:conn->host.dispname); + } + else { + /* + * This is a brand new connection, so let's store it in the connection + * cache of ours! + */ + ConnectionStore(data, conn); + } + + /* Setup and init stuff before DO starts, in preparing for the transfer. */ + do_init(conn); + + /* + * Setup whatever necessary for a resumed transfer + */ + result = setup_range(data); + if(result) + return result; + + /* Continue connectdata initialization here. */ + + /* + * Inherit the proper values from the urldata struct AFTER we have arranged + * the persistent connection stuff + */ + conn->fread_func = data->set.fread_func; + conn->fread_in = data->set.in; + conn->seek_func = data->set.seek_func; + conn->seek_client = data->set.seek_client; + + /************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ + result = resolve_server(data, conn, async); + + return result; +} + +/* Curl_setup_conn() is called after the name resolve initiated in + * create_conn() is all done. + * + * Curl_setup_conn() also handles reused connections + * + * conn->data MUST already have been setup fine (in create_conn) + */ + +CURLcode Curl_setup_conn(struct connectdata *conn, + bool *protocol_done) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + Curl_pgrsTime(data, TIMER_NAMELOOKUP); + + if(conn->handler->flags & PROTOPT_NONETWORK) { + /* nothing to setup when not using a network */ + *protocol_done = TRUE; + return result; + } + *protocol_done = FALSE; /* default to not done */ + + /* set proxy_connect_closed to false unconditionally already here since it + is used strictly to provide extra information to a parent function in the + case of proxy CONNECT failures and we must make sure we don't have it + lingering set from a previous invoke */ + conn->bits.proxy_connect_closed = FALSE; + + /* + * Set user-agent. Used for HTTP, but since we can attempt to tunnel + * basically anything through a http proxy we can't limit this based on + * protocol. + */ + if(data->set.str[STRING_USERAGENT]) { + Curl_safefree(conn->allocptr.uagent); + conn->allocptr.uagent = + aprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); + if(!conn->allocptr.uagent) + return CURLE_OUT_OF_MEMORY; + } + + data->req.headerbytecount = 0; + +#ifdef CURL_DO_LINEEND_CONV + data->state.crlf_conversions = 0; /* reset CRLF conversion counter */ +#endif /* CURL_DO_LINEEND_CONV */ + + for(;;) { + /* loop for CURL_SERVER_CLOSED_CONNECTION */ + + if(CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET]) { + /* Try to connect only if not already connected */ + bool connected = FALSE; + + result = ConnectPlease(data, conn, &connected); + + if(result && !conn->ip_addr) { + /* transport connection failure not related with authentication */ + conn->bits.tcpconnect[FIRSTSOCKET] = FALSE; + return result; + } + + if(connected) { + result = Curl_protocol_connect(conn, protocol_done); + if(CURLE_OK == result) + conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; + } + else + conn->bits.tcpconnect[FIRSTSOCKET] = FALSE; + + /* if the connection was closed by the server while exchanging + authentication informations, retry with the new set + authentication information */ + if(conn->bits.proxy_connect_closed) { + /* reset the error buffer */ + if(data->set.errorbuffer) + data->set.errorbuffer[0] = '\0'; + data->state.errorbuf = FALSE; + continue; + } + + if(CURLE_OK != result) + return result; + } + else { + Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */ + Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ + conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; + *protocol_done = TRUE; + Curl_verboseconnect(conn); + Curl_updateconninfo(conn, conn->sock[FIRSTSOCKET]); + } + /* Stop the loop now */ + break; + } + + conn->now = Curl_tvnow(); /* time this *after* the connect is done, we + set this here perhaps a second time */ + +#ifdef __EMX__ + /* + * This check is quite a hack. We're calling _fsetmode to fix the problem + * with fwrite converting newline characters (you get mangled text files, + * and corrupted binary files when you download to stdout and redirect it to + * a file). + */ + + if((data->set.out)->_handle == NULL) { + _fsetmode(stdout, "b"); + } +#endif + + return result; +} + +CURLcode Curl_connect(struct SessionHandle *data, + struct connectdata **in_connect, + bool *asyncp, + bool *protocol_done) +{ + CURLcode code; + + *asyncp = FALSE; /* assume synchronous resolves by default */ + + /* call the stuff that needs to be called */ + code = create_conn(data, in_connect, asyncp); + + if(CURLE_OK == code) { + /* no error */ + if((*in_connect)->send_pipe->size || (*in_connect)->recv_pipe->size) + /* pipelining */ + *protocol_done = TRUE; + else if(!*asyncp) { + /* DNS resolution is done: that's either because this is a reused + connection, in which case DNS was unnecessary, or because DNS + really did finish already (synch resolver/fast async resolve) */ + code = Curl_setup_conn(*in_connect, protocol_done); + } + } + + if(code && *in_connect) { + /* We're not allowed to return failure with memory left allocated + in the connectdata struct, free those here */ + Curl_disconnect(*in_connect, FALSE); /* close the connection */ + *in_connect = NULL; /* return a NULL */ + } + + return code; +} + +CURLcode Curl_done(struct connectdata **connp, + CURLcode status, /* an error if this is called after an + error was detected */ + bool premature) +{ + CURLcode result; + struct connectdata *conn; + struct SessionHandle *data; + + DEBUGASSERT(*connp); + + conn = *connp; + data = conn->data; + + if(conn->bits.done) + /* Stop if Curl_done() has already been called */ + return CURLE_OK; + + Curl_getoff_all_pipelines(data, conn); + + if((conn->send_pipe->size + conn->recv_pipe->size != 0 && + !data->set.reuse_forbid && + !conn->bits.close)) + /* Stop if pipeline is not empty and we do not have to close + connection. */ + return CURLE_OK; + + conn->bits.done = TRUE; /* called just now! */ + + /* Cleanup possible redirect junk */ + if(data->req.newurl) { + free(data->req.newurl); + data->req.newurl = NULL; + } + if(data->req.location) { + free(data->req.location); + data->req.location = NULL; + } + + Curl_resolver_cancel(conn); + + if(conn->dns_entry) { + Curl_resolv_unlock(data, conn->dns_entry); /* done with this */ + conn->dns_entry = NULL; + } + + /* this calls the protocol-specific function pointer previously set */ + if(conn->handler->done) + result = conn->handler->done(conn, status, premature); + else + result = CURLE_OK; + + if(Curl_pgrsDone(conn) && !result) + result = CURLE_ABORTED_BY_CALLBACK; + + /* if the transfer was completed in a paused state there can be buffered + data left to write and then kill */ + if(data->state.tempwrite) { + free(data->state.tempwrite); + data->state.tempwrite = NULL; + } + + /* if data->set.reuse_forbid is TRUE, it means the libcurl client has + forced us to close this no matter what we think. + + if conn->bits.close is TRUE, it means that the connection should be + closed in spite of all our efforts to be nice, due to protocol + restrictions in our or the server's end + + if premature is TRUE, it means this connection was said to be DONE before + the entire request operation is complete and thus we can't know in what + state it is for re-using, so we're forced to close it. In a perfect world + we can add code that keep track of if we really must close it here or not, + but currently we have no such detail knowledge. + + connection_id == -1 here means that the connection has not been added + to the connection cache (OOM) and thus we must disconnect it here. + */ + if(data->set.reuse_forbid || conn->bits.close || premature || + (-1 == conn->connection_id)) { + CURLcode res2 = Curl_disconnect(conn, premature); /* close connection */ + + /* If we had an error already, make sure we return that one. But + if we got a new error, return that. */ + if(!result && res2) + result = res2; + } + else { + ConnectionDone(conn); /* the connection is no longer in use */ + + /* remember the most recently used connection */ + data->state.lastconnect = conn; + + infof(data, "Connection #%ld to host %s left intact\n", + conn->connection_id, + conn->bits.httpproxy?conn->proxy.dispname:conn->host.dispname); + } + + *connp = NULL; /* to make the caller of this function better detect that + this was either closed or handed over to the connection + cache here, and therefore cannot be used from this point on + */ + + return result; +} + +/* + * do_init() inits the readwrite session. This is inited each time (in the DO + * function before the protocol-specific DO functions are invoked) for a + * transfer, sometimes multiple times on the same SessionHandle. Make sure + * nothing in here depends on stuff that are setup dynamically for the + * transfer. + */ + +static CURLcode do_init(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + struct SingleRequest *k = &data->req; + + conn->bits.done = FALSE; /* Curl_done() is not called yet */ + conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to use */ + data->state.expect100header = FALSE; + + if(data->set.opt_no_body) + /* in HTTP lingo, no body means using the HEAD request... */ + data->set.httpreq = HTTPREQ_HEAD; + else if(HTTPREQ_HEAD == data->set.httpreq) + /* ... but if unset there really is no perfect method that is the + "opposite" of HEAD but in reality most people probably think GET + then. The important thing is that we can't let it remain HEAD if the + opt_no_body is set FALSE since then we'll behave wrong when getting + HTTP. */ + data->set.httpreq = HTTPREQ_GET; + + /* NB: the content encoding software depends on this initialization */ + Curl_easy_initHandleData(data); + + k->start = Curl_tvnow(); /* start time */ + k->now = k->start; /* current time is now */ + k->header = TRUE; /* assume header */ + + k->bytecount = 0; + + k->buf = data->state.buffer; + k->uploadbuf = data->state.uploadbuffer; + k->hbufp = data->state.headerbuff; + k->ignorebody=FALSE; + + Curl_speedinit(data); + + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + + return CURLE_OK; +} + +/* + * do_complete is called when the DO actions are complete. + * + * We init chunking and trailer bits to their default values here immediately + * before receiving any header data for the current request in the pipeline. + */ +static void do_complete(struct connectdata *conn) +{ + conn->data->req.chunk=FALSE; + conn->data->req.maxfd = (conn->sockfd>conn->writesockfd? + conn->sockfd:conn->writesockfd)+1; + Curl_pgrsTime(conn->data, TIMER_PRETRANSFER); +} + +CURLcode Curl_do(struct connectdata **connp, bool *done) +{ + CURLcode result=CURLE_OK; + struct connectdata *conn = *connp; + struct SessionHandle *data = conn->data; + + if(conn->handler->do_it) { + /* generic protocol-specific function pointer set in curl_connect() */ + result = conn->handler->do_it(conn, done); + + /* This was formerly done in curl_transfer.c, but we better do it here */ + if((CURLE_SEND_ERROR == result) && conn->bits.reuse) { + /* + * If the connection is using an easy handle, call reconnect + * to re-establish the connection. Otherwise, let the multi logic + * figure out how to re-establish the connection. + */ + if(!data->multi) { + result = Curl_reconnect_request(connp); + + if(result == CURLE_OK) { + /* ... finally back to actually retry the DO phase */ + conn = *connp; /* re-assign conn since Curl_reconnect_request + creates a new connection */ + result = conn->handler->do_it(conn, done); + } + } + else + return result; + } + + if((result == CURLE_OK) && *done) + /* do_complete must be called after the protocol-specific DO function */ + do_complete(conn); + } + return result; +} + +/* + * Curl_do_more() is called during the DO_MORE multi state. It is basically a + * second stage DO state which (wrongly) was introduced to support FTP's + * second connection. + * + * TODO: A future libcurl should be able to work away this state. + * + */ + +CURLcode Curl_do_more(struct connectdata *conn, bool *completed) +{ + CURLcode result=CURLE_OK; + + *completed = FALSE; + + if(conn->handler->do_more) + result = conn->handler->do_more(conn, completed); + + if(!result && *completed) + /* do_complete must be called after the protocol-specific DO function */ + do_complete(conn); + + return result; +} + +/* Called on connect, and if there's already a protocol-specific struct + allocated for a different connection, this frees it that it can be setup + properly later on. */ +void Curl_reset_reqproto(struct connectdata *conn) +{ + struct SessionHandle *data = conn->data; + if(data->state.proto.generic && data->state.current_conn != conn) { + free(data->state.proto.generic); + data->state.proto.generic = NULL; + } + data->state.current_conn = conn; +} diff --git a/lib/curl_version.c b/lib/curl_version.c new file mode 100644 index 000000000..fe1f73660 --- /dev/null +++ b/lib/curl_version.c @@ -0,0 +1,343 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include +#include "curl_urldata.h" +#include "curl_sslgen.h" + +#define _MPRINTF_REPLACE /* use the internal *printf() functions */ +#include + +#ifdef USE_ARES +# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ + (defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__)) +# define CARES_STATICLIB +# endif +# include +#endif + +#ifdef USE_LIBIDN +#include +#endif + +#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) +#include +#endif + +#ifdef USE_LIBRTMP +#include +#endif + +#ifdef USE_LIBSSH2 +#include +#endif + +#ifdef HAVE_LIBSSH2_VERSION +/* get it run-time if possible */ +#define CURL_LIBSSH2_VERSION libssh2_version(0) +#else +/* use build-time if run-time not possible */ +#define CURL_LIBSSH2_VERSION LIBSSH2_VERSION +#endif + +char *curl_version(void) +{ + static char version[200]; + char *ptr = version; + size_t len; + size_t left = sizeof(version); + + strcpy(ptr, LIBCURL_NAME "/" LIBCURL_VERSION); + len = strlen(ptr); + left -= len; + ptr += len; + + if(left > 1) { + len = Curl_ssl_version(ptr + 1, left - 1); + + if(len > 0) { + *ptr = ' '; + left -= ++len; + ptr += len; + } + } + +#ifdef HAVE_LIBZ + len = snprintf(ptr, left, " zlib/%s", zlibVersion()); + left -= len; + ptr += len; +#endif +#ifdef USE_ARES + /* this function is only present in c-ares, not in the original ares */ + len = snprintf(ptr, left, " c-ares/%s", ares_version(NULL)); + left -= len; + ptr += len; +#endif +#ifdef USE_LIBIDN + if(stringprep_check_version(LIBIDN_REQUIRED_VERSION)) { + len = snprintf(ptr, left, " libidn/%s", stringprep_check_version(NULL)); + left -= len; + ptr += len; + } +#endif +#ifdef USE_WIN32_IDN + len = snprintf(ptr, left, " WinIDN"); + left -= len; + ptr += len; +#endif +#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) +#ifdef _LIBICONV_VERSION + len = snprintf(ptr, left, " iconv/%d.%d", + _LIBICONV_VERSION >> 8, _LIBICONV_VERSION & 255); +#else + /* version unknown */ + len = snprintf(ptr, left, " iconv"); +#endif /* _LIBICONV_VERSION */ + left -= len; + ptr += len; +#endif +#ifdef USE_LIBSSH2 + len = snprintf(ptr, left, " libssh2/%s", CURL_LIBSSH2_VERSION); + left -= len; + ptr += len; +#endif +#ifdef USE_LIBRTMP + { + char suff[2]; + if(RTMP_LIB_VERSION & 0xff) { + suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; + suff[1] = '\0'; + } + else + suff[0] = '\0'; + + snprintf(ptr, left, " librtmp/%d.%d%s", + RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, + suff); +/* + If another lib version is added below this one, this code would + also have to do: + + len = what snprintf() returned + + left -= len; + ptr += len; +*/ + } +#endif + + return version; +} + +/* data for curl_version_info + + Keep the list sorted alphabetically. It is also written so that each + protocol line has its own #if line to make things easier on the eye. + */ + +static const char * const protocols[] = { +#ifndef CURL_DISABLE_DICT + "dict", +#endif +#ifndef CURL_DISABLE_FILE + "file", +#endif +#ifndef CURL_DISABLE_FTP + "ftp", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) + "ftps", +#endif +#ifndef CURL_DISABLE_GOPHER + "gopher", +#endif +#ifndef CURL_DISABLE_HTTP + "http", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) + "https", +#endif +#ifndef CURL_DISABLE_IMAP + "imap", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_IMAP) + "imaps", +#endif +#ifndef CURL_DISABLE_LDAP + "ldap", +#if !defined(CURL_DISABLE_LDAPS) && \ + ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ + (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) + "ldaps", +#endif +#endif +#ifndef CURL_DISABLE_POP3 + "pop3", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_POP3) + "pop3s", +#endif +#ifdef USE_LIBRTMP + "rtmp", +#endif +#ifndef CURL_DISABLE_RTSP + "rtsp", +#endif +#ifdef USE_LIBSSH2 + "scp", +#endif +#ifdef USE_LIBSSH2 + "sftp", +#endif +#ifndef CURL_DISABLE_SMTP + "smtp", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_SMTP) + "smtps", +#endif +#ifndef CURL_DISABLE_TELNET + "telnet", +#endif +#ifndef CURL_DISABLE_TFTP + "tftp", +#endif + + NULL +}; + +static curl_version_info_data version_info = { + CURLVERSION_NOW, + LIBCURL_VERSION, + LIBCURL_VERSION_NUM, + OS, /* as found by configure or set by hand at build-time */ + 0 /* features is 0 by default */ +#ifdef ENABLE_IPV6 + | CURL_VERSION_IPV6 +#endif +#ifdef HAVE_KRB4 + | CURL_VERSION_KERBEROS4 +#endif +#ifdef USE_SSL + | CURL_VERSION_SSL +#endif +#ifdef USE_NTLM + | CURL_VERSION_NTLM +#endif +#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) + | CURL_VERSION_NTLM_WB +#endif +#ifdef USE_WINDOWS_SSPI + | CURL_VERSION_SSPI +#endif +#ifdef HAVE_LIBZ + | CURL_VERSION_LIBZ +#endif +#ifdef USE_HTTP_NEGOTIATE + | CURL_VERSION_GSSNEGOTIATE +#endif +#ifdef DEBUGBUILD + | CURL_VERSION_DEBUG +#endif +#ifdef CURLDEBUG + | CURL_VERSION_CURLDEBUG +#endif +#ifdef CURLRES_ASYNCH + | CURL_VERSION_ASYNCHDNS +#endif +#ifdef HAVE_SPNEGO + | CURL_VERSION_SPNEGO +#endif +#if (CURL_SIZEOF_CURL_OFF_T > 4) && \ + ( (SIZEOF_OFF_T > 4) || defined(USE_WIN32_LARGE_FILES) ) + | CURL_VERSION_LARGEFILE +#endif +#if defined(CURL_DOES_CONVERSIONS) + | CURL_VERSION_CONV +#endif +#if defined(USE_TLS_SRP) + | CURL_VERSION_TLSAUTH_SRP +#endif + , + NULL, /* ssl_version */ + 0, /* ssl_version_num, this is kept at zero */ + NULL, /* zlib_version */ + protocols, + NULL, /* c-ares version */ + 0, /* c-ares version numerical */ + NULL, /* libidn version */ + 0, /* iconv version */ + NULL, /* ssh lib version */ +}; + +curl_version_info_data *curl_version_info(CURLversion stamp) +{ +#ifdef USE_LIBSSH2 + static char ssh_buffer[80]; +#endif + +#ifdef USE_SSL + static char ssl_buffer[80]; + Curl_ssl_version(ssl_buffer, sizeof(ssl_buffer)); + version_info.ssl_version = ssl_buffer; +#endif + +#ifdef HAVE_LIBZ + version_info.libz_version = zlibVersion(); + /* libz left NULL if non-existing */ +#endif +#ifdef USE_ARES + { + int aresnum; + version_info.ares = ares_version(&aresnum); + version_info.ares_num = aresnum; + } +#endif +#ifdef USE_LIBIDN + /* This returns a version string if we use the given version or later, + otherwise it returns NULL */ + version_info.libidn = stringprep_check_version(LIBIDN_REQUIRED_VERSION); + if(version_info.libidn) + version_info.features |= CURL_VERSION_IDN; +#elif defined(USE_WIN32_IDN) + version_info.features |= CURL_VERSION_IDN; +#endif + +#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) +#ifdef _LIBICONV_VERSION + version_info.iconv_ver_num = _LIBICONV_VERSION; +#else + /* version unknown */ + version_info.iconv_ver_num = -1; +#endif /* _LIBICONV_VERSION */ +#endif + +#ifdef USE_LIBSSH2 + snprintf(ssh_buffer, sizeof(ssh_buffer), "libssh2/%s", LIBSSH2_VERSION); + version_info.libssh_version = ssh_buffer; +#endif + + (void)stamp; /* avoid compiler warnings, we don't use this */ + + return &version_info; +} diff --git a/lib/curl_warnless.c b/lib/curl_warnless.c new file mode 100644 index 000000000..30cdbe6f0 --- /dev/null +++ b/lib/curl_warnless.c @@ -0,0 +1,431 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(__INTEL_COMPILER) && defined(__unix__) + +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#endif /* __INTEL_COMPILER && __unix__ */ + +#define BUILDING_WARNLESS_C 1 + +#include "curl_warnless.h" + +#define CURL_MASK_SCHAR 0x7F +#define CURL_MASK_UCHAR 0xFF + +#if (SIZEOF_SHORT == 2) +# define CURL_MASK_SSHORT 0x7FFF +# define CURL_MASK_USHORT 0xFFFF +#elif (SIZEOF_SHORT == 4) +# define CURL_MASK_SSHORT 0x7FFFFFFF +# define CURL_MASK_USHORT 0xFFFFFFFF +#elif (SIZEOF_SHORT == 8) +# define CURL_MASK_SSHORT 0x7FFFFFFFFFFFFFFF +# define CURL_MASK_USHORT 0xFFFFFFFFFFFFFFFF +#else +# error "SIZEOF_SHORT not defined" +#endif + +#if (SIZEOF_INT == 2) +# define CURL_MASK_SINT 0x7FFF +# define CURL_MASK_UINT 0xFFFF +#elif (SIZEOF_INT == 4) +# define CURL_MASK_SINT 0x7FFFFFFF +# define CURL_MASK_UINT 0xFFFFFFFF +#elif (SIZEOF_INT == 8) +# define CURL_MASK_SINT 0x7FFFFFFFFFFFFFFF +# define CURL_MASK_UINT 0xFFFFFFFFFFFFFFFF +#elif (SIZEOF_INT == 16) +# define CURL_MASK_SINT 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +# define CURL_MASK_UINT 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +#else +# error "SIZEOF_INT not defined" +#endif + +#if (CURL_SIZEOF_LONG == 2) +# define CURL_MASK_SLONG 0x7FFFL +# define CURL_MASK_ULONG 0xFFFFUL +#elif (CURL_SIZEOF_LONG == 4) +# define CURL_MASK_SLONG 0x7FFFFFFFL +# define CURL_MASK_ULONG 0xFFFFFFFFUL +#elif (CURL_SIZEOF_LONG == 8) +# define CURL_MASK_SLONG 0x7FFFFFFFFFFFFFFFL +# define CURL_MASK_ULONG 0xFFFFFFFFFFFFFFFFUL +#elif (CURL_SIZEOF_LONG == 16) +# define CURL_MASK_SLONG 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL +# define CURL_MASK_ULONG 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFUL +#else +# error "CURL_SIZEOF_LONG not defined" +#endif + +#if (CURL_SIZEOF_CURL_OFF_T == 2) +# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFF) +# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFF) +#elif (CURL_SIZEOF_CURL_OFF_T == 4) +# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFF) +# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFF) +#elif (CURL_SIZEOF_CURL_OFF_T == 8) +# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFFFFFFFFFF) +# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFFFFFFFFFF) +#elif (CURL_SIZEOF_CURL_OFF_T == 16) +# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) +# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) +#else +# error "CURL_SIZEOF_CURL_OFF_T not defined" +#endif + +#if (SIZEOF_SIZE_T == SIZEOF_SHORT) +# define CURL_MASK_SSIZE_T CURL_MASK_SSHORT +# define CURL_MASK_USIZE_T CURL_MASK_USHORT +#elif (SIZEOF_SIZE_T == SIZEOF_INT) +# define CURL_MASK_SSIZE_T CURL_MASK_SINT +# define CURL_MASK_USIZE_T CURL_MASK_UINT +#elif (SIZEOF_SIZE_T == CURL_SIZEOF_LONG) +# define CURL_MASK_SSIZE_T CURL_MASK_SLONG +# define CURL_MASK_USIZE_T CURL_MASK_ULONG +#elif (SIZEOF_SIZE_T == CURL_SIZEOF_CURL_OFF_T) +# define CURL_MASK_SSIZE_T CURL_MASK_SCOFFT +# define CURL_MASK_USIZE_T CURL_MASK_UCOFFT +#else +# error "SIZEOF_SIZE_T not defined" +#endif + +/* +** unsigned long to unsigned short +*/ + +unsigned short curlx_ultous(unsigned long ulnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_USHORT); + return (unsigned short)(ulnum & (unsigned long) CURL_MASK_USHORT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned long to unsigned char +*/ + +unsigned char curlx_ultouc(unsigned long ulnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_UCHAR); + return (unsigned char)(ulnum & (unsigned long) CURL_MASK_UCHAR); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned long to signed int +*/ + +int curlx_ultosi(unsigned long ulnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_SINT); + return (int)(ulnum & (unsigned long) CURL_MASK_SINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to signed int +*/ + +int curlx_uztosi(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(uznum <= (size_t) CURL_MASK_SINT); + return (int)(uznum & (size_t) CURL_MASK_SINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to unsigned long +*/ + +unsigned long curlx_uztoul(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + +#if (CURL_SIZEOF_LONG < SIZEOF_SIZE_T) + DEBUGASSERT(uznum <= (size_t) CURL_MASK_ULONG); +#endif + return (unsigned long)(uznum & (size_t) CURL_MASK_ULONG); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to unsigned int +*/ + +unsigned int curlx_uztoui(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + +#if (SIZEOF_INT < SIZEOF_SIZE_T) + DEBUGASSERT(uznum <= (size_t) CURL_MASK_UINT); +#endif + return (unsigned int)(uznum & (size_t) CURL_MASK_UINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed long to signed int +*/ + +int curlx_sltosi(long slnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(slnum >= 0); +#if (SIZEOF_INT < CURL_SIZEOF_LONG) + DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_SINT); +#endif + return (int)(slnum & (long) CURL_MASK_SINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed long to unsigned int +*/ + +unsigned int curlx_sltoui(long slnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(slnum >= 0); +#if (SIZEOF_INT < CURL_SIZEOF_LONG) + DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_UINT); +#endif + return (unsigned int)(slnum & (long) CURL_MASK_UINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed long to unsigned short +*/ + +unsigned short curlx_sltous(long slnum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(slnum >= 0); + DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_USHORT); + return (unsigned short)(slnum & (long) CURL_MASK_USHORT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** unsigned size_t to signed ssize_t +*/ + +ssize_t curlx_uztosz(size_t uznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(uznum <= (size_t) CURL_MASK_SSIZE_T); + return (ssize_t)(uznum & (size_t) CURL_MASK_SSIZE_T); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed curl_off_t to unsigned size_t +*/ + +size_t curlx_sotouz(curl_off_t sonum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(sonum >= 0); + return (size_t)(sonum & (curl_off_t) CURL_MASK_USIZE_T); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed ssize_t to signed int +*/ + +int curlx_sztosi(ssize_t sznum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(sznum >= 0); +#if (SIZEOF_INT < SIZEOF_SIZE_T) + DEBUGASSERT((size_t) sznum <= (size_t) CURL_MASK_SINT); +#endif + return (int)(sznum & (ssize_t) CURL_MASK_SINT); + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +/* +** signed int to unsigned size_t +*/ + +size_t curlx_sitouz(int sinum) +{ +#ifdef __INTEL_COMPILER +# pragma warning(push) +# pragma warning(disable:810) /* conversion may lose significant bits */ +#endif + + DEBUGASSERT(sinum >= 0); + return (size_t) sinum; + +#ifdef __INTEL_COMPILER +# pragma warning(pop) +#endif +} + +#if defined(__INTEL_COMPILER) && defined(__unix__) + +int curlx_FD_ISSET(int fd, fd_set *fdset) +{ + #pragma warning(push) + #pragma warning(disable:1469) /* clobber ignored */ + return FD_ISSET(fd, fdset); + #pragma warning(pop) +} + +void curlx_FD_SET(int fd, fd_set *fdset) +{ + #pragma warning(push) + #pragma warning(disable:1469) /* clobber ignored */ + FD_SET(fd, fdset); + #pragma warning(pop) +} + +void curlx_FD_ZERO(fd_set *fdset) +{ + #pragma warning(push) + #pragma warning(disable:593) /* variable was set but never used */ + FD_ZERO(fdset); + #pragma warning(pop) +} + +unsigned short curlx_htons(unsigned short usnum) +{ +#if (__INTEL_COMPILER == 910) && defined(__i386__) + return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); +#else + #pragma warning(push) + #pragma warning(disable:810) /* conversion may lose significant bits */ + return htons(usnum); + #pragma warning(pop) +#endif +} + +unsigned short curlx_ntohs(unsigned short usnum) +{ +#if (__INTEL_COMPILER == 910) && defined(__i386__) + return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); +#else + #pragma warning(push) + #pragma warning(disable:810) /* conversion may lose significant bits */ + return ntohs(usnum); + #pragma warning(pop) +#endif +} + +#endif /* __INTEL_COMPILER && __unix__ */ diff --git a/lib/curl_wildcard.c b/lib/curl_wildcard.c new file mode 100644 index 000000000..d6ba2b276 --- /dev/null +++ b/lib/curl_wildcard.c @@ -0,0 +1,77 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2010, 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 + * 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "curl_wildcard.h" +#include "curl_llist.h" +#include "curl_fileinfo.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "curl_memory.h" +/* The last #include file should be: */ +#include "curl_memdebug.h" + +CURLcode Curl_wildcard_init(struct WildcardData *wc) +{ + DEBUGASSERT(wc->filelist == NULL); + /* now allocate only wc->filelist, everything else + will be allocated if it is needed. */ + wc->filelist = Curl_llist_alloc(Curl_fileinfo_dtor); + if(!wc->filelist) {; + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} + +void Curl_wildcard_dtor(struct WildcardData *wc) +{ + if(!wc) + return; + + if(wc->tmp_dtor) { + wc->tmp_dtor(wc->tmp); + wc->tmp_dtor = ZERO_NULL; + wc->tmp = NULL; + } + DEBUGASSERT(wc->tmp == NULL); + + if(wc->filelist) { + Curl_llist_destroy(wc->filelist, NULL); + wc->filelist = NULL; + } + + if(wc->path) { + free(wc->path); + wc->path = NULL; + } + + if(wc->pattern) { + free(wc->pattern); + wc->pattern = NULL; + } + + wc->customptr = NULL; + wc->state = CURLWC_INIT; +} diff --git a/lib/cyassl.c b/lib/cyassl.c deleted file mode 100644 index 32f1cfe5c..000000000 --- a/lib/cyassl.c +++ /dev/null @@ -1,611 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -/* - * Source file for all CyaSSL-specific code for the TLS/SSL layer. No code - * but curl_sslgen.c should ever call or use these functions. - * - */ - -#include "curl_setup.h" - -#ifdef USE_CYASSL - -#ifdef HAVE_LIMITS_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_inet_pton.h" -#include "curl_cyassl.h" -#include "curl_sslgen.h" -#include "curl_parsedate.h" -#include "curl_connect.h" /* for the connect timeout */ -#include "curl_select.h" -#include "curl_rawstr.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" -#include -#include - - -static Curl_recv cyassl_recv; -static Curl_send cyassl_send; - - -static int do_file_type(const char *type) -{ - if(!type || !type[0]) - return SSL_FILETYPE_PEM; - if(Curl_raw_equal(type, "PEM")) - return SSL_FILETYPE_PEM; - if(Curl_raw_equal(type, "DER")) - return SSL_FILETYPE_ASN1; - return -1; -} - -/* - * This function loads all the client/CA certificates and CRLs. Setup the TLS - * layer and do all necessary magic. - */ -static CURLcode -cyassl_connect_step1(struct connectdata *conn, - int sockindex) -{ - struct SessionHandle *data = conn->data; - struct ssl_connect_data* conssl = &conn->ssl[sockindex]; - SSL_METHOD* req_method = NULL; - void* ssl_sessionid = NULL; - curl_socket_t sockfd = conn->sock[sockindex]; - - if(conssl->state == ssl_connection_complete) - return CURLE_OK; - - /* CyaSSL doesn't support SSLv2 */ - if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) { - failf(data, "CyaSSL does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - - /* check to see if we've been told to use an explicit SSL/TLS version */ - switch(data->set.ssl.version) { - case CURL_SSLVERSION_DEFAULT: - /* we try to figure out version */ - req_method = SSLv23_client_method(); - break; - case CURL_SSLVERSION_TLSv1: - req_method = TLSv1_client_method(); - break; - case CURL_SSLVERSION_SSLv3: - req_method = SSLv3_client_method(); - break; - default: - req_method = TLSv1_client_method(); - } - - if(!req_method) { - failf(data, "SSL: couldn't create a method!"); - return CURLE_OUT_OF_MEMORY; - } - - if(conssl->ctx) - SSL_CTX_free(conssl->ctx); - conssl->ctx = SSL_CTX_new(req_method); - - if(!conssl->ctx) { - failf(data, "SSL: couldn't create a context!"); - return CURLE_OUT_OF_MEMORY; - } - -#ifndef NO_FILESYSTEM - /* load trusted cacert */ - if(data->set.str[STRING_SSL_CAFILE]) { - if(!SSL_CTX_load_verify_locations(conssl->ctx, - data->set.str[STRING_SSL_CAFILE], - data->set.str[STRING_SSL_CAPATH])) { - if(data->set.ssl.verifypeer) { - /* Fail if we insiste on successfully verifying the server. */ - failf(data,"error setting certificate verify locations:\n" - " CAfile: %s\n CApath: %s", - data->set.str[STRING_SSL_CAFILE]? - data->set.str[STRING_SSL_CAFILE]: "none", - data->set.str[STRING_SSL_CAPATH]? - data->set.str[STRING_SSL_CAPATH] : "none"); - return CURLE_SSL_CACERT_BADFILE; - } - else { - /* Just continue with a warning if no strict certificate - verification is required. */ - infof(data, "error setting certificate verify locations," - " continuing anyway:\n"); - } - } - else { - /* Everything is fine. */ - infof(data, "successfully set certificate verify locations:\n"); - } - infof(data, - " CAfile: %s\n" - " CApath: %s\n", - data->set.str[STRING_SSL_CAFILE] ? data->set.str[STRING_SSL_CAFILE]: - "none", - data->set.str[STRING_SSL_CAPATH] ? data->set.str[STRING_SSL_CAPATH]: - "none"); - } - - /* Load the client certificate, and private key */ - if(data->set.str[STRING_CERT] && data->set.str[STRING_KEY]) { - int file_type = do_file_type(data->set.str[STRING_CERT_TYPE]); - - if(SSL_CTX_use_certificate_file(conssl->ctx, data->set.str[STRING_CERT], - file_type) != 1) { - failf(data, "unable to use client certificate (no key or wrong pass" - " phrase?)"); - return CURLE_SSL_CONNECT_ERROR; - } - - file_type = do_file_type(data->set.str[STRING_KEY_TYPE]); - if(SSL_CTX_use_PrivateKey_file(conssl->ctx, data->set.str[STRING_KEY], - file_type) != 1) { - failf(data, "unable to set private key"); - return CURLE_SSL_CONNECT_ERROR; - } - } -#else - if(CyaSSL_no_filesystem_verify(conssl->ctx)!= SSL_SUCCESS) { - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* NO_FILESYSTEM */ - - /* SSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(conssl->ctx, - data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE, - NULL); - - /* Let's make an SSL structure */ - if(conssl->handle) - SSL_free(conssl->handle); - conssl->handle = SSL_new(conssl->ctx); - if(!conssl->handle) { - failf(data, "SSL: couldn't create a context (handle)!"); - return CURLE_OUT_OF_MEMORY; - } - - /* Check if there's a cached ID we can/should use here! */ - if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) { - /* we got a session id, use it! */ - if(!SSL_set_session(conssl->handle, ssl_sessionid)) { - failf(data, "SSL: SSL_set_session failed: %s", - ERR_error_string(SSL_get_error(conssl->handle, 0),NULL)); - return CURLE_SSL_CONNECT_ERROR; - } - /* Informational message */ - infof (data, "SSL re-using session ID\n"); - } - - /* pass the raw socket into the SSL layer */ - if(!SSL_set_fd(conssl->handle, (int)sockfd)) { - failf(data, "SSL: SSL_set_fd failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - conssl->connecting_state = ssl_connect_2; - return CURLE_OK; -} - - -static CURLcode -cyassl_connect_step2(struct connectdata *conn, - int sockindex) -{ - int ret = -1; - struct SessionHandle *data = conn->data; - struct ssl_connect_data* conssl = &conn->ssl[sockindex]; - - infof(data, "CyaSSL: Connecting to %s:%d\n", - conn->host.name, conn->remote_port); - - conn->recv[sockindex] = cyassl_recv; - conn->send[sockindex] = cyassl_send; - - /* Enable RFC2818 checks */ - if(data->set.ssl.verifyhost) { - ret = CyaSSL_check_domain_name(conssl->handle, conn->host.name); - if(ret == SSL_FAILURE) - return CURLE_OUT_OF_MEMORY; - } - - ret = SSL_connect(conssl->handle); - if(ret != 1) { - char error_buffer[80]; - int detail = SSL_get_error(conssl->handle, ret); - - if(SSL_ERROR_WANT_READ == detail) { - conssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - else if(SSL_ERROR_WANT_WRITE == detail) { - conssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; - } - /* There is no easy way to override only the CN matching. - * This will enable the override of both mismatching SubjectAltNames - * as also mismatching CN fields */ - else if(DOMAIN_NAME_MISMATCH == detail) { -#if 1 - failf(data, "\tsubject alt name(s) or common name do not match \"%s\"\n", - conn->host.dispname); - return CURLE_PEER_FAILED_VERIFICATION; -#else - /* When the CyaSSL_check_domain_name() is used and you desire to continue - * on a DOMAIN_NAME_MISMATCH, i.e. 'data->set.ssl.verifyhost == 0', - * CyaSSL version 2.4.0 will fail with an INCOMPLETE_DATA error. The only - * way to do this is currently to switch the CyaSSL_check_domain_name() - * in and out based on the 'data->set.ssl.verifyhost' value. */ - if(data->set.ssl.verifyhost) { - failf(data, - "\tsubject alt name(s) or common name do not match \"%s\"\n", - conn->host.dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - else { - infof(data, - "\tsubject alt name(s) and/or common name do not match \"%s\"\n", - conn->host.dispname); - return CURLE_OK; - } -#endif - } - else { - failf(data, "SSL_connect failed with error %d: %s", detail, - ERR_error_string(detail, error_buffer)); - return CURLE_SSL_CONNECT_ERROR; - } - } - - conssl->connecting_state = ssl_connect_3; - infof(data, "SSL connected\n"); - - return CURLE_OK; -} - - -static CURLcode -cyassl_connect_step3(struct connectdata *conn, - int sockindex) -{ - CURLcode retcode = CURLE_OK; - void *old_ssl_sessionid=NULL; - struct SessionHandle *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - int incache; - SSL_SESSION *our_ssl_sessionid; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - - our_ssl_sessionid = SSL_get_session(connssl->handle); - - incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL)); - if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing\n"); - Curl_ssl_delsessionid(conn, old_ssl_sessionid); - incache = FALSE; - } - } - if(!incache) { - retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid, - 0 /* unknown size */); - if(retcode) { - failf(data, "failed to store ssl session"); - return retcode; - } - } - - connssl->connecting_state = ssl_connect_done; - - return retcode; -} - - -static ssize_t cyassl_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - char error_buffer[80]; - int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - int rc = SSL_write(conn->ssl[sockindex].handle, mem, memlen); - - if(rc < 0) { - int err = SSL_get_error(conn->ssl[sockindex].handle, rc); - - switch(err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_write() */ - *curlcode = CURLE_AGAIN; - return -1; - default: - failf(conn->data, "SSL write: %s, errno %d", - ERR_error_string(err, error_buffer), - SOCKERRNO); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - } - return rc; -} - -void Curl_cyassl_close_all(struct SessionHandle *data) -{ - (void)data; -} - -void Curl_cyassl_close(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *conssl = &conn->ssl[sockindex]; - - if(conssl->handle) { - (void)SSL_shutdown(conssl->handle); - SSL_free (conssl->handle); - conssl->handle = NULL; - } - if(conssl->ctx) { - SSL_CTX_free (conssl->ctx); - conssl->ctx = NULL; - } -} - -static ssize_t cyassl_recv(struct connectdata *conn, - int num, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - char error_buffer[80]; - int buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - int nread = SSL_read(conn->ssl[num].handle, buf, buffsize); - - if(nread < 0) { - int err = SSL_get_error(conn->ssl[num].handle, nread); - - switch(err) { - case SSL_ERROR_ZERO_RETURN: /* no more data */ - break; - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ - *curlcode = CURLE_AGAIN; - return -1; - default: - failf(conn->data, "SSL read: %s, errno %d", - ERR_error_string(err, error_buffer), - SOCKERRNO); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - } - return nread; -} - - -void Curl_cyassl_session_free(void *ptr) -{ - (void)ptr; - /* CyaSSL reuses sessions on own, no free */ -} - - -size_t Curl_cyassl_version(char *buffer, size_t size) -{ -#ifdef CYASSL_VERSION - return snprintf(buffer, size, "CyaSSL/%s", CYASSL_VERSION); -#else - return snprintf(buffer, size, "CyaSSL/%s", "<1.8.8"); -#endif -} - - -int Curl_cyassl_init(void) -{ - if(CyaSSL_Init() == 0) - return 1; - - return -1; -} - - -bool Curl_cyassl_data_pending(const struct connectdata* conn, int connindex) -{ - if(conn->ssl[connindex].handle) /* SSL is in use */ - return (0 != SSL_pending(conn->ssl[connindex].handle)) ? TRUE : FALSE; - else - return FALSE; -} - - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -int Curl_cyassl_shutdown(struct connectdata *conn, int sockindex) -{ - int retval = 0; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - if(connssl->handle) { - SSL_free (connssl->handle); - connssl->handle = NULL; - } - return retval; -} - - -static CURLcode -cyassl_connect_common(struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode retcode; - struct SessionHandle *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - long timeout_ms; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1==connssl->connecting_state) { - /* Find out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - retcode = cyassl_connect_step1(conn, sockindex); - if(retcode) - return retcode; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing== - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading== - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ - retcode = cyassl_connect_step2(conn, sockindex); - if(retcode || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return retcode; - - } /* repeat step2 until all transactions are done. */ - - if(ssl_connect_3==connssl->connecting_state) { - retcode = cyassl_connect_step3(conn, sockindex); - if(retcode) - return retcode; - } - - if(ssl_connect_done==connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = cyassl_recv; - conn->send[sockindex] = cyassl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - - -CURLcode -Curl_cyassl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) -{ - return cyassl_connect_common(conn, sockindex, TRUE, done); -} - - -CURLcode -Curl_cyassl_connect(struct connectdata *conn, - int sockindex) -{ - CURLcode retcode; - bool done = FALSE; - - retcode = cyassl_connect_common(conn, sockindex, FALSE, &done); - if(retcode) - return retcode; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -#endif diff --git a/lib/dict.c b/lib/dict.c deleted file mode 100644 index 114ef7c72..000000000 --- a/lib/dict.c +++ /dev/null @@ -1,284 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_DICT - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#include "curl_urldata.h" -#include -#include "curl_transfer.h" -#include "curl_sendf.h" - -#include "curl_progress.h" -#include "curl_strequal.h" -#include "curl_dict.h" -#include "curl_rawstr.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - * Forward declarations. - */ - -static CURLcode dict_do(struct connectdata *conn, bool *done); - -/* - * DICT protocol handler. - */ - -const struct Curl_handler Curl_handler_dict = { - "DICT", /* scheme */ - ZERO_NULL, /* setup_connection */ - dict_do, /* do_it */ - ZERO_NULL, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_DICT, /* defport */ - CURLPROTO_DICT, /* protocol */ - PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ -}; - -static char *unescape_word(struct SessionHandle *data, const char *inputbuff) -{ - char *newp; - char *dictp; - char *ptr; - int len; - char byte; - int olen=0; - - newp = curl_easy_unescape(data, inputbuff, 0, &len); - if(!newp) - return NULL; - - dictp = malloc(((size_t)len)*2 + 1); /* add one for terminating zero */ - if(dictp) { - /* According to RFC2229 section 2.2, these letters need to be escaped with - \[letter] */ - for(ptr = newp; - (byte = *ptr) != 0; - ptr++) { - if((byte <= 32) || (byte == 127) || - (byte == '\'') || (byte == '\"') || (byte == '\\')) { - dictp[olen++] = '\\'; - } - dictp[olen++] = byte; - } - dictp[olen]=0; - - free(newp); - } - return dictp; -} - -static CURLcode dict_do(struct connectdata *conn, bool *done) -{ - char *word; - char *eword; - char *ppath; - char *database = NULL; - char *strategy = NULL; - char *nthdef = NULL; /* This is not part of the protocol, but required - by RFC 2229 */ - CURLcode result=CURLE_OK; - struct SessionHandle *data=conn->data; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - - char *path = data->state.path; - curl_off_t *bytecount = &data->req.bytecount; - - *done = TRUE; /* unconditionally */ - - if(conn->bits.user_passwd) { - /* AUTH is missing */ - } - - if(Curl_raw_nequal(path, DICT_MATCH, sizeof(DICT_MATCH)-1) || - Curl_raw_nequal(path, DICT_MATCH2, sizeof(DICT_MATCH2)-1) || - Curl_raw_nequal(path, DICT_MATCH3, sizeof(DICT_MATCH3)-1)) { - - word = strchr(path, ':'); - if(word) { - word++; - database = strchr(word, ':'); - if(database) { - *database++ = (char)0; - strategy = strchr(database, ':'); - if(strategy) { - *strategy++ = (char)0; - nthdef = strchr(strategy, ':'); - if(nthdef) { - *nthdef = (char)0; - } - } - } - } - - if((word == NULL) || (*word == (char)0)) { - infof(data, "lookup word is missing\n"); - word=(char *)"default"; - } - if((database == NULL) || (*database == (char)0)) { - database = (char *)"!"; - } - if((strategy == NULL) || (*strategy == (char)0)) { - strategy = (char *)"."; - } - - eword = unescape_word(data, word); - if(!eword) - return CURLE_OUT_OF_MEMORY; - - result = Curl_sendf(sockfd, conn, - "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" - "MATCH " - "%s " /* database */ - "%s " /* strategy */ - "%s\r\n" /* word */ - "QUIT\r\n", - - database, - strategy, - eword - ); - - free(eword); - - if(result) { - failf(data, "Failed sending DICT request"); - return result; - } - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, - -1, NULL); /* no upload */ - } - else if(Curl_raw_nequal(path, DICT_DEFINE, sizeof(DICT_DEFINE)-1) || - Curl_raw_nequal(path, DICT_DEFINE2, sizeof(DICT_DEFINE2)-1) || - Curl_raw_nequal(path, DICT_DEFINE3, sizeof(DICT_DEFINE3)-1)) { - - word = strchr(path, ':'); - if(word) { - word++; - database = strchr(word, ':'); - if(database) { - *database++ = (char)0; - nthdef = strchr(database, ':'); - if(nthdef) { - *nthdef = (char)0; - } - } - } - - if((word == NULL) || (*word == (char)0)) { - infof(data, "lookup word is missing\n"); - word=(char *)"default"; - } - if((database == NULL) || (*database == (char)0)) { - database = (char *)"!"; - } - - eword = unescape_word(data, word); - if(!eword) - return CURLE_OUT_OF_MEMORY; - - result = Curl_sendf(sockfd, conn, - "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" - "DEFINE " - "%s " /* database */ - "%s\r\n" /* word */ - "QUIT\r\n", - database, - eword); - - free(eword); - - if(result) { - failf(data, "Failed sending DICT request"); - return result; - } - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, - -1, NULL); /* no upload */ - } - else { - - ppath = strchr(path, '/'); - if(ppath) { - int i; - - ppath++; - for(i = 0; ppath[i]; i++) { - if(ppath[i] == ':') - ppath[i] = ' '; - } - result = Curl_sendf(sockfd, conn, - "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" - "%s\r\n" - "QUIT\r\n", ppath); - if(result) { - failf(data, "Failed sending DICT request"); - return result; - } - - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, -1, NULL); - } - } - - return CURLE_OK; -} -#endif /*CURL_DISABLE_DICT*/ diff --git a/lib/easy.c b/lib/easy.c deleted file mode 100644 index a2181cc4d..000000000 --- a/lib/easy.c +++ /dev/null @@ -1,897 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#include "curl_strequal.h" -#include "curl_urldata.h" -#include -#include "curl_transfer.h" -#include "curl_sslgen.h" -#include "curl_url.h" -#include "curl_getinfo.h" -#include "curl_hostip.h" -#include "curl_share.h" -#include "curl_strdup.h" -#include "curl_memory.h" -#include "curl_progress.h" -#include "curl_easyif.h" -#include "curl_select.h" -#include "curl_sendf.h" /* for failf function prototype */ -#include "curl_ntlm.h" -#include "curl_connect.h" /* for Curl_getconnectinfo */ -#include "curl_slist.h" -#include "curl_amigaos.h" -#include "curl_rand.h" -#include "curl_non_ascii.h" -#include "curl_warnless.h" -#include "curl_conncache.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* win32_cleanup() is for win32 socket cleanup functionality, the opposite - of win32_init() */ -static void win32_cleanup(void) -{ -#ifdef USE_WINSOCK - WSACleanup(); -#endif -#ifdef USE_WINDOWS_SSPI - Curl_sspi_global_cleanup(); -#endif -} - -/* win32_init() performs win32 socket initialization to properly setup the - stack to allow networking */ -static CURLcode win32_init(void) -{ -#ifdef USE_WINSOCK - WORD wVersionRequested; - WSADATA wsaData; - int res; - -#if defined(ENABLE_IPV6) && (USE_WINSOCK < 2) - Error IPV6_requires_winsock2 -#endif - - wVersionRequested = MAKEWORD(USE_WINSOCK, USE_WINSOCK); - - res = WSAStartup(wVersionRequested, &wsaData); - - if(res != 0) - /* Tell the user that we couldn't find a useable */ - /* winsock.dll. */ - return CURLE_FAILED_INIT; - - /* Confirm that the Windows Sockets DLL supports what we need.*/ - /* Note that if the DLL supports versions greater */ - /* than wVersionRequested, it will still return */ - /* wVersionRequested in wVersion. wHighVersion contains the */ - /* highest supported version. */ - - if(LOBYTE( wsaData.wVersion ) != LOBYTE(wVersionRequested) || - HIBYTE( wsaData.wVersion ) != HIBYTE(wVersionRequested) ) { - /* Tell the user that we couldn't find a useable */ - - /* winsock.dll. */ - WSACleanup(); - return CURLE_FAILED_INIT; - } - /* The Windows Sockets DLL is acceptable. Proceed. */ -#elif defined(USE_LWIPSOCK) - lwip_init(); -#endif - -#ifdef USE_WINDOWS_SSPI - { - CURLcode err = Curl_sspi_global_init(); - if(err != CURLE_OK) - return err; - } -#endif - - return CURLE_OK; -} - -#ifdef USE_LIBIDN -/* - * Initialise use of IDNA library. - * It falls back to ASCII if $CHARSET isn't defined. This doesn't work for - * idna_to_ascii_lz(). - */ -static void idna_init (void) -{ -#ifdef WIN32 - char buf[60]; - UINT cp = GetACP(); - - if(!getenv("CHARSET") && cp > 0) { - snprintf(buf, sizeof(buf), "CHARSET=cp%u", cp); - putenv(buf); - } -#else - /* to do? */ -#endif -} -#endif /* USE_LIBIDN */ - -/* true globals -- for curl_global_init() and curl_global_cleanup() */ -static unsigned int initialized; -static long init_flags; - -/* - * strdup (and other memory functions) is redefined in complicated - * ways, but at this point it must be defined as the system-supplied strdup - * so the callback pointer is initialized correctly. - */ -#if defined(_WIN32_WCE) -#define system_strdup _strdup -#elif !defined(HAVE_STRDUP) -#define system_strdup curlx_strdup -#else -#define system_strdup strdup -#endif - -#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) -# pragma warning(disable:4232) /* MSVC extension, dllimport identity */ -#endif - -#ifndef __SYMBIAN32__ -/* - * If a memory-using function (like curl_getenv) is used before - * curl_global_init() is called, we need to have these pointers set already. - */ -curl_malloc_callback Curl_cmalloc = (curl_malloc_callback)malloc; -curl_free_callback Curl_cfree = (curl_free_callback)free; -curl_realloc_callback Curl_crealloc = (curl_realloc_callback)realloc; -curl_strdup_callback Curl_cstrdup = (curl_strdup_callback)system_strdup; -curl_calloc_callback Curl_ccalloc = (curl_calloc_callback)calloc; -#else -/* - * Symbian OS doesn't support initialization to code in writeable static data. - * Initialization will occur in the curl_global_init() call. - */ -curl_malloc_callback Curl_cmalloc; -curl_free_callback Curl_cfree; -curl_realloc_callback Curl_crealloc; -curl_strdup_callback Curl_cstrdup; -curl_calloc_callback Curl_ccalloc; -#endif - -#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) -# pragma warning(default:4232) /* MSVC extension, dllimport identity */ -#endif - -/** - * curl_global_init() globally initializes cURL given a bitwise set of the - * different features of what to initialize. - */ -CURLcode curl_global_init(long flags) -{ - if(initialized++) - return CURLE_OK; - - /* Setup the default memory functions here (again) */ - Curl_cmalloc = (curl_malloc_callback)malloc; - Curl_cfree = (curl_free_callback)free; - Curl_crealloc = (curl_realloc_callback)realloc; - Curl_cstrdup = (curl_strdup_callback)system_strdup; - Curl_ccalloc = (curl_calloc_callback)calloc; - - if(flags & CURL_GLOBAL_SSL) - if(!Curl_ssl_init()) { - DEBUGF(fprintf(stderr, "Error: Curl_ssl_init failed\n")); - return CURLE_FAILED_INIT; - } - - if(flags & CURL_GLOBAL_WIN32) - if(win32_init() != CURLE_OK) { - DEBUGF(fprintf(stderr, "Error: win32_init failed\n")); - return CURLE_FAILED_INIT; - } - -#ifdef __AMIGA__ - if(!Curl_amiga_init()) { - DEBUGF(fprintf(stderr, "Error: Curl_amiga_init failed\n")); - return CURLE_FAILED_INIT; - } -#endif - -#ifdef NETWARE - if(netware_init()) { - DEBUGF(fprintf(stderr, "Warning: LONG namespace not available\n")); - } -#endif - -#ifdef USE_LIBIDN - idna_init(); -#endif - - if(Curl_resolver_global_init() != CURLE_OK) { - DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n")); - return CURLE_FAILED_INIT; - } - -#if defined(USE_LIBSSH2) && defined(HAVE_LIBSSH2_INIT) - if(libssh2_init(0)) { - DEBUGF(fprintf(stderr, "Error: libssh2_init failed\n")); - return CURLE_FAILED_INIT; - } -#endif - - init_flags = flags; - - /* Preset pseudo-random number sequence. */ - - Curl_srand(); - - return CURLE_OK; -} - -/* - * curl_global_init_mem() globally initializes cURL and also registers the - * user provided callback routines. - */ -CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, - curl_free_callback f, curl_realloc_callback r, - curl_strdup_callback s, curl_calloc_callback c) -{ - CURLcode code = CURLE_OK; - - /* Invalid input, return immediately */ - if(!m || !f || !r || !s || !c) - return CURLE_FAILED_INIT; - - /* Already initialized, don't do it again */ - if(initialized) - return CURLE_OK; - - /* Call the actual init function first */ - code = curl_global_init(flags); - if(code == CURLE_OK) { - Curl_cmalloc = m; - Curl_cfree = f; - Curl_cstrdup = s; - Curl_crealloc = r; - Curl_ccalloc = c; - } - - return code; -} - -/** - * curl_global_cleanup() globally cleanups cURL, uses the value of - * "init_flags" to determine what needs to be cleaned up and what doesn't. - */ -void curl_global_cleanup(void) -{ - if(!initialized) - return; - - if(--initialized) - return; - - Curl_global_host_cache_dtor(); - - if(init_flags & CURL_GLOBAL_SSL) - Curl_ssl_cleanup(); - - Curl_resolver_global_cleanup(); - - if(init_flags & CURL_GLOBAL_WIN32) - win32_cleanup(); - - Curl_amiga_cleanup(); - -#if defined(USE_LIBSSH2) && defined(HAVE_LIBSSH2_EXIT) - (void)libssh2_exit(); -#endif - - init_flags = 0; -} - -/* - * curl_easy_init() is the external interface to alloc, setup and init an - * easy handle that is returned. If anything goes wrong, NULL is returned. - */ -CURL *curl_easy_init(void) -{ - CURLcode res; - struct SessionHandle *data; - - /* Make sure we inited the global SSL stuff */ - if(!initialized) { - res = curl_global_init(CURL_GLOBAL_DEFAULT); - if(res) { - /* something in the global init failed, return nothing */ - DEBUGF(fprintf(stderr, "Error: curl_global_init failed\n")); - return NULL; - } - } - - /* We use curl_open() with undefined URL so far */ - res = Curl_open(&data); - if(res != CURLE_OK) { - DEBUGF(fprintf(stderr, "Error: Curl_open failed\n")); - return NULL; - } - - return data; -} - -/* - * curl_easy_setopt() is the external interface for setting options on an - * easy handle. - */ - -#undef curl_easy_setopt -CURLcode curl_easy_setopt(CURL *curl, CURLoption tag, ...) -{ - va_list arg; - struct SessionHandle *data = curl; - CURLcode ret; - - if(!curl) - return CURLE_BAD_FUNCTION_ARGUMENT; - - va_start(arg, tag); - - ret = Curl_setopt(data, tag, arg); - - va_end(arg); - return ret; -} - -#ifdef CURL_MULTIEASY -/*************************************************************************** - * This function is still only for testing purposes. It makes a great way - * to run the full test suite on the multi interface instead of the easy one. - *************************************************************************** - * - * The *new* curl_easy_perform() is the external interface that performs a - * transfer previously setup. - * - * Wrapper-function that: creates a multi handle, adds the easy handle to it, - * runs curl_multi_perform() until the transfer is done, then detaches the - * easy handle, destroys the multi handle and returns the easy handle's return - * code. This will make everything internally use and assume multi interface. - */ -CURLcode curl_easy_perform(CURL *easy) -{ - CURLM *multi; - CURLMcode mcode; - CURLcode code = CURLE_OK; - int still_running; - struct timeval timeout; - int rc; - CURLMsg *msg; - fd_set fdread; - fd_set fdwrite; - fd_set fdexcep; - int maxfd; - - if(!easy) - return CURLE_BAD_FUNCTION_ARGUMENT; - - multi = curl_multi_init(); - if(!multi) - return CURLE_OUT_OF_MEMORY; - - mcode = curl_multi_add_handle(multi, easy); - if(mcode) { - curl_multi_cleanup(multi); - if(mcode == CURLM_OUT_OF_MEMORY) - return CURLE_OUT_OF_MEMORY; - else - return CURLE_FAILED_INIT; - } - - /* we start some action by calling perform right away */ - - do { - while(CURLM_CALL_MULTI_PERFORM == - curl_multi_perform(multi, &still_running)); - - if(!still_running) - break; - - FD_ZERO(&fdread); - FD_ZERO(&fdwrite); - FD_ZERO(&fdexcep); - - /* timeout once per second */ - timeout.tv_sec = 1; - timeout.tv_usec = 0; - - /* Old deprecated style: get file descriptors from the transfers */ - curl_multi_fdset(multi, &fdread, &fdwrite, &fdexcep, &maxfd); - rc = Curl_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout); - - /* The way is to extract the sockets and wait for them without using - select. This whole alternative version should probably rather use the - curl_multi_socket() approach. */ - - if(rc == -1) - /* select error */ - break; - - /* timeout or data to send/receive => loop! */ - } while(still_running); - - msg = curl_multi_info_read(multi, &rc); - if(msg) - code = msg->data.result; - - mcode = curl_multi_remove_handle(multi, easy); - /* what to do if it fails? */ - - mcode = curl_multi_cleanup(multi); - /* what to do if it fails? */ - - return code; -} -#else -/* - * curl_easy_perform() is the external interface that performs a transfer - * previously setup. - */ -CURLcode curl_easy_perform(CURL *curl) -{ - struct SessionHandle *data = (struct SessionHandle *)curl; - - if(!data) - return CURLE_BAD_FUNCTION_ARGUMENT; - - if(! (data->share && data->share->hostcache)) { - /* this handle is not using a shared dns cache */ - - if(data->set.global_dns_cache && - (data->dns.hostcachetype != HCACHE_GLOBAL)) { - /* global dns cache was requested but still isn't */ - struct curl_hash *ptr; - - if(data->dns.hostcachetype == HCACHE_PRIVATE) { - /* if the current cache is private, kill it first */ - Curl_hash_destroy(data->dns.hostcache); - data->dns.hostcachetype = HCACHE_NONE; - data->dns.hostcache = NULL; - } - - ptr = Curl_global_host_cache_init(); - if(ptr) { - /* only do this if the global cache init works */ - data->dns.hostcache = ptr; - data->dns.hostcachetype = HCACHE_GLOBAL; - } - } - - if(!data->dns.hostcache) { - data->dns.hostcachetype = HCACHE_PRIVATE; - data->dns.hostcache = Curl_mk_dnscache(); - - if(!data->dns.hostcache) - /* While we possibly could survive and do good without a host cache, - the fact that creating it failed indicates that things are truly - screwed up and we should bail out! */ - return CURLE_OUT_OF_MEMORY; - } - - } - - if(!data->state.conn_cache) { - /* Oops, no connection cache, create one */ - data->state.conn_cache = Curl_conncache_init(CONNCACHE_PRIVATE); - if(!data->state.conn_cache) - return CURLE_OUT_OF_MEMORY; - } - - return Curl_perform(data); -} -#endif - -/* - * curl_easy_cleanup() is the external interface to cleaning/freeing the given - * easy handle. - */ -void curl_easy_cleanup(CURL *curl) -{ - struct SessionHandle *data = (struct SessionHandle *)curl; - - if(!data) - return; - - Curl_close(data); -} - -/* - * Store a pointed to the multi handle within the easy handle's data struct. - */ -void Curl_easy_addmulti(struct SessionHandle *data, - void *multi) -{ - data->multi = multi; - if(multi == NULL) - /* the association is cleared, mark the easy handle as not used by an - interface */ - data->state.used_interface = Curl_if_none; -} - -void Curl_easy_initHandleData(struct SessionHandle *data) -{ - memset(&data->req, 0, sizeof(struct SingleRequest)); - - data->req.maxdownload = -1; -} - -/* - * curl_easy_getinfo() is an external interface that allows an app to retrieve - * information from a performed transfer and similar. - */ -#undef curl_easy_getinfo -CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...) -{ - va_list arg; - void *paramp; - struct SessionHandle *data = (struct SessionHandle *)curl; - - va_start(arg, info); - paramp = va_arg(arg, void *); - - return Curl_getinfo(data, info, paramp); -} - -/* - * curl_easy_duphandle() is an external interface to allow duplication of a - * given input easy handle. The returned handle will be a new working handle - * with all options set exactly as the input source handle. - */ -CURL *curl_easy_duphandle(CURL *incurl) -{ - struct SessionHandle *data=(struct SessionHandle *)incurl; - - struct SessionHandle *outcurl = calloc(1, sizeof(struct SessionHandle)); - if(NULL == outcurl) - goto fail; - - /* - * We setup a few buffers we need. We should probably make them - * get setup on-demand in the code, as that would probably decrease - * the likeliness of us forgetting to init a buffer here in the future. - */ - outcurl->state.headerbuff = malloc(HEADERSIZE); - if(!outcurl->state.headerbuff) - goto fail; - outcurl->state.headersize = HEADERSIZE; - - /* copy all userdefined values */ - if(Curl_dupset(outcurl, data) != CURLE_OK) - goto fail; - - /* the connection cache is setup on demand */ - outcurl->state.conn_cache = NULL; - - outcurl->state.lastconnect = NULL; - - outcurl->progress.flags = data->progress.flags; - outcurl->progress.callback = data->progress.callback; - - if(data->cookies) { - /* If cookies are enabled in the parent handle, we enable them - in the clone as well! */ - outcurl->cookies = Curl_cookie_init(data, - data->cookies->filename, - outcurl->cookies, - data->set.cookiesession); - if(!outcurl->cookies) - goto fail; - } - - /* duplicate all values in 'change' */ - if(data->change.cookielist) { - outcurl->change.cookielist = - Curl_slist_duplicate(data->change.cookielist); - if(!outcurl->change.cookielist) - goto fail; - } - - if(data->change.url) { - outcurl->change.url = strdup(data->change.url); - if(!outcurl->change.url) - goto fail; - outcurl->change.url_alloc = TRUE; - } - - if(data->change.referer) { - outcurl->change.referer = strdup(data->change.referer); - if(!outcurl->change.referer) - goto fail; - outcurl->change.referer_alloc = TRUE; - } - - /* Clone the resolver handle, if present, for the new handle */ - if(Curl_resolver_duphandle(&outcurl->state.resolver, - data->state.resolver) != CURLE_OK) - goto fail; - - Curl_convert_setup(outcurl); - - Curl_easy_initHandleData(outcurl); - - outcurl->magic = CURLEASY_MAGIC_NUMBER; - - /* we reach this point and thus we are OK */ - - return outcurl; - - fail: - - if(outcurl) { - curl_slist_free_all(outcurl->change.cookielist); - outcurl->change.cookielist = NULL; - Curl_safefree(outcurl->state.headerbuff); - Curl_safefree(outcurl->change.url); - Curl_safefree(outcurl->change.referer); - Curl_freeset(outcurl); - free(outcurl); - } - - return NULL; -} - -/* - * curl_easy_reset() is an external interface that allows an app to re- - * initialize a session handle to the default values. - */ -void curl_easy_reset(CURL *curl) -{ - struct SessionHandle *data = (struct SessionHandle *)curl; - - Curl_safefree(data->state.pathbuffer); - - data->state.path = NULL; - - Curl_safefree(data->state.proto.generic); - - /* zero out UserDefined data: */ - Curl_freeset(data); - memset(&data->set, 0, sizeof(struct UserDefined)); - (void)Curl_init_userdefined(&data->set); - - /* zero out Progress data: */ - memset(&data->progress, 0, sizeof(struct Progress)); - - /* init Handle data */ - Curl_easy_initHandleData(data); - - data->progress.flags |= PGRS_HIDE; - data->state.current_speed = -1; /* init to negative == impossible */ -} - -/* - * curl_easy_pause() allows an application to pause or unpause a specific - * transfer and direction. This function sets the full new state for the - * current connection this easy handle operates on. - * - * NOTE: if you have the receiving paused and you call this function to remove - * the pausing, you may get your write callback called at this point. - * - * Action is a bitmask consisting of CURLPAUSE_* bits in curl/curl.h - */ -CURLcode curl_easy_pause(CURL *curl, int action) -{ - struct SessionHandle *data = (struct SessionHandle *)curl; - struct SingleRequest *k = &data->req; - CURLcode result = CURLE_OK; - - /* first switch off both pause bits */ - int newstate = k->keepon &~ (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE); - - /* set the new desired pause bits */ - newstate |= ((action & CURLPAUSE_RECV)?KEEP_RECV_PAUSE:0) | - ((action & CURLPAUSE_SEND)?KEEP_SEND_PAUSE:0); - - /* put it back in the keepon */ - k->keepon = newstate; - - if(!(newstate & KEEP_RECV_PAUSE) && data->state.tempwrite) { - /* we have a buffer for sending that we now seem to be able to deliver - since the receive pausing is lifted! */ - - /* get the pointer, type and length in local copies since the function may - return PAUSE again and then we'll get a new copy allocted and stored in - the tempwrite variables */ - char *tempwrite = data->state.tempwrite; - char *freewrite = tempwrite; /* store this pointer to free it later */ - size_t tempsize = data->state.tempwritesize; - int temptype = data->state.tempwritetype; - size_t chunklen; - - /* clear tempwrite here just to make sure it gets cleared if there's no - further use of it, and make sure we don't clear it after the function - invoke as it may have been set to a new value by then */ - data->state.tempwrite = NULL; - - /* since the write callback API is define to never exceed - CURL_MAX_WRITE_SIZE bytes in a single call, and since we may in fact - have more data than that in our buffer here, we must loop sending the - data in multiple calls until there's no data left or we get another - pause returned. - - A tricky part is that the function we call will "buffer" the data - itself when it pauses on a particular buffer, so we may need to do some - extra trickery if we get a pause return here. - */ - do { - chunklen = (tempsize > CURL_MAX_WRITE_SIZE)?CURL_MAX_WRITE_SIZE:tempsize; - - result = Curl_client_write(data->state.current_conn, - temptype, tempwrite, chunklen); - if(result) - /* failures abort the loop at once */ - break; - - if(data->state.tempwrite && (tempsize - chunklen)) { - /* Ouch, the reading is again paused and the block we send is now - "cached". If this is the final chunk we can leave it like this, but - if we have more chunks that are cached after this, we need to free - the newly cached one and put back a version that is truly the entire - contents that is saved for later - */ - char *newptr; - - /* note that tempsize is still the size as before the callback was - used, and thus the whole piece of data to keep */ - newptr = realloc(data->state.tempwrite, tempsize); - - if(!newptr) { - free(data->state.tempwrite); /* free old area */ - data->state.tempwrite = NULL; - result = CURLE_OUT_OF_MEMORY; - /* tempwrite will be freed further down */ - break; - } - data->state.tempwrite = newptr; /* store new pointer */ - memcpy(newptr, tempwrite, tempsize); - data->state.tempwritesize = tempsize; /* store new size */ - /* tempwrite will be freed further down */ - break; /* go back to pausing until further notice */ - } - else { - tempsize -= chunklen; /* left after the call above */ - tempwrite += chunklen; /* advance the pointer */ - } - - } while((result == CURLE_OK) && tempsize); - - free(freewrite); /* this is unconditionally no longer used */ - } - - return result; -} - - -static CURLcode easy_connection(struct SessionHandle *data, - curl_socket_t *sfd, - struct connectdata **connp) -{ - if(data == NULL) - return CURLE_BAD_FUNCTION_ARGUMENT; - - /* only allow these to be called on handles with CURLOPT_CONNECT_ONLY */ - if(!data->set.connect_only) { - failf(data, "CONNECT_ONLY is required!"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - - *sfd = Curl_getconnectinfo(data, connp); - - if(*sfd == CURL_SOCKET_BAD) { - failf(data, "Failed to get recent socket"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - - return CURLE_OK; -} - -/* - * Receives data from the connected socket. Use after successful - * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. - * Returns CURLE_OK on success, error code on error. - */ -CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, size_t *n) -{ - curl_socket_t sfd; - CURLcode ret; - ssize_t n1; - struct connectdata *c; - struct SessionHandle *data = (struct SessionHandle *)curl; - - ret = easy_connection(data, &sfd, &c); - if(ret) - return ret; - - *n = 0; - ret = Curl_read(c, sfd, buffer, buflen, &n1); - - if(ret != CURLE_OK) - return ret; - - *n = (size_t)n1; - - return CURLE_OK; -} - -/* - * Sends data over the connected socket. Use after successful - * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. - */ -CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, - size_t *n) -{ - curl_socket_t sfd; - CURLcode ret; - ssize_t n1; - struct connectdata *c = NULL; - struct SessionHandle *data = (struct SessionHandle *)curl; - - ret = easy_connection(data, &sfd, &c); - if(ret) - return ret; - - *n = 0; - ret = Curl_write(c, sfd, buffer, buflen, &n1); - - if(n1 == -1) - return CURLE_SEND_ERROR; - - /* detect EAGAIN */ - if((CURLE_OK == ret) && (0 == n1)) - return CURLE_AGAIN; - - *n = (size_t)n1; - - return ret; -} diff --git a/lib/escape.c b/lib/escape.c deleted file mode 100644 index c0ed571bd..000000000 --- a/lib/escape.c +++ /dev/null @@ -1,233 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -/* Escape and unescape URL encoding in strings. The functions return a new - * allocated string or NULL if an error occurred. */ - -#include "curl_setup.h" - -#include - -#include "curl_memory.h" -#include "curl_urldata.h" -#include "curl_warnless.h" -#include "curl_non_ascii.h" -#include "curl_escape.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* Portable character check (remember EBCDIC). Do not use isalnum() because - its behavior is altered by the current locale. - See http://tools.ietf.org/html/rfc3986#section-2.3 -*/ -static bool Curl_isunreserved(unsigned char in) -{ - switch (in) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'a': case 'b': case 'c': case 'd': case 'e': - case 'f': case 'g': case 'h': case 'i': case 'j': - case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': - case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': - case 'K': case 'L': case 'M': case 'N': case 'O': - case 'P': case 'Q': case 'R': case 'S': case 'T': - case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '-': case '.': case '_': case '~': - return TRUE; - default: - break; - } - return FALSE; -} - -/* for ABI-compatibility with previous versions */ -char *curl_escape(const char *string, int inlength) -{ - return curl_easy_escape(NULL, string, inlength); -} - -/* for ABI-compatibility with previous versions */ -char *curl_unescape(const char *string, int length) -{ - return curl_easy_unescape(NULL, string, length, NULL); -} - -char *curl_easy_escape(CURL *handle, const char *string, int inlength) -{ - size_t alloc = (inlength?(size_t)inlength:strlen(string))+1; - char *ns; - char *testing_ptr = NULL; - unsigned char in; /* we need to treat the characters unsigned */ - size_t newlen = alloc; - size_t strindex=0; - size_t length; - CURLcode res; - - ns = malloc(alloc); - if(!ns) - return NULL; - - length = alloc-1; - while(length--) { - in = *string; - - if(Curl_isunreserved(in)) - /* just copy this */ - ns[strindex++]=in; - else { - /* encode it */ - newlen += 2; /* the size grows with two, since this'll become a %XX */ - if(newlen > alloc) { - alloc *= 2; - testing_ptr = realloc(ns, alloc); - if(!testing_ptr) { - free( ns ); - return NULL; - } - else { - ns = testing_ptr; - } - } - - res = Curl_convert_to_network(handle, &in, 1); - if(res) { - /* Curl_convert_to_network calls failf if unsuccessful */ - free(ns); - return NULL; - } - - snprintf(&ns[strindex], 4, "%%%02X", in); - - strindex+=3; - } - string++; - } - ns[strindex]=0; /* terminate it */ - return ns; -} - -/* - * Curl_urldecode() URL decodes the given string. - * - * Optionally detects control characters (byte codes lower than 32) in the - * data and rejects such data. - * - * Returns a pointer to a malloced string in *ostring with length given in - * *olen. If length == 0, the length is assumed to be strlen(string). - * - */ -CURLcode Curl_urldecode(struct SessionHandle *data, - const char *string, size_t length, - char **ostring, size_t *olen, - bool reject_ctrl) -{ - size_t alloc = (length?length:strlen(string))+1; - char *ns = malloc(alloc); - unsigned char in; - size_t strindex=0; - unsigned long hex; - CURLcode res; - - if(!ns) - return CURLE_OUT_OF_MEMORY; - - while(--alloc > 0) { - in = *string; - if(('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { - /* this is two hexadecimal digits following a '%' */ - char hexstr[3]; - char *ptr; - hexstr[0] = string[1]; - hexstr[1] = string[2]; - hexstr[2] = 0; - - hex = strtoul(hexstr, &ptr, 16); - - in = curlx_ultouc(hex); /* this long is never bigger than 255 anyway */ - - res = Curl_convert_from_network(data, &in, 1); - if(res) { - /* Curl_convert_from_network calls failf if unsuccessful */ - free(ns); - return res; - } - - string+=2; - alloc-=2; - } - if(reject_ctrl && (in < 0x20)) { - free(ns); - return CURLE_URL_MALFORMAT; - } - - ns[strindex++] = in; - string++; - } - ns[strindex]=0; /* terminate it */ - - if(olen) - /* store output size */ - *olen = strindex; - - if(ostring) - /* store output string */ - *ostring = ns; - - return CURLE_OK; -} - -/* - * Unescapes the given URL escaped string of given length. Returns a - * pointer to a malloced string with length given in *olen. - * If length == 0, the length is assumed to be strlen(string). - * If olen == NULL, no output length is stored. - */ -char *curl_easy_unescape(CURL *handle, const char *string, int length, - int *olen) -{ - char *str = NULL; - size_t inputlen = length; - size_t outputlen; - CURLcode res = Curl_urldecode(handle, string, inputlen, &str, &outputlen, - FALSE); - if(res) - return NULL; - if(olen) - *olen = curlx_uztosi(outputlen); - return str; -} - -/* For operating systems/environments that use different malloc/free - systems for the app and for this library, we provide a free that uses - the library's memory system */ -void curl_free(void *p) -{ - if(p) - free(p); -} diff --git a/lib/file.c b/lib/file.c deleted file mode 100644 index 6ea2bd769..000000000 --- a/lib/file.c +++ /dev/null @@ -1,591 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_FILE - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#ifdef HAVE_FCNTL_H -#include -#endif - -#include "curl_strtoofft.h" -#include "curl_urldata.h" -#include -#include "curl_progress.h" -#include "curl_sendf.h" -#include "curl_escape.h" -#include "curl_file.h" -#include "curl_speedcheck.h" -#include "curl_getinfo.h" -#include "curl_transfer.h" -#include "curl_url.h" -#include "curl_memory.h" -#include "curl_parsedate.h" /* for the week day and month names */ -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#if defined(WIN32) || defined(MSDOS) || defined(__EMX__) || \ - defined(__SYMBIAN32__) -#define DOS_FILESYSTEM 1 -#endif - -#ifdef OPEN_NEEDS_ARG3 -# define open_readonly(p,f) open((p),(f),(0)) -#else -# define open_readonly(p,f) open((p),(f)) -#endif - -/* - * Forward declarations. - */ - -static CURLcode file_do(struct connectdata *, bool *done); -static CURLcode file_done(struct connectdata *conn, - CURLcode status, bool premature); -static CURLcode file_connect(struct connectdata *conn, bool *done); -static CURLcode file_disconnect(struct connectdata *conn, - bool dead_connection); - - -/* - * FILE scheme handler. - */ - -const struct Curl_handler Curl_handler_file = { - "FILE", /* scheme */ - ZERO_NULL, /* setup_connection */ - file_do, /* do_it */ - file_done, /* done */ - ZERO_NULL, /* do_more */ - file_connect, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - file_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - 0, /* defport */ - CURLPROTO_FILE, /* protocol */ - PROTOPT_NONETWORK | PROTOPT_NOURLQUERY /* flags */ -}; - - - /* - Check if this is a range download, and if so, set the internal variables - properly. This code is copied from the FTP implementation and might as - well be factored out. - */ -static CURLcode file_range(struct connectdata *conn) -{ - curl_off_t from, to; - curl_off_t totalsize=-1; - char *ptr; - char *ptr2; - struct SessionHandle *data = conn->data; - - if(data->state.use_range && data->state.range) { - from=curlx_strtoofft(data->state.range, &ptr, 0); - while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) - ptr++; - to=curlx_strtoofft(ptr, &ptr2, 0); - if(ptr == ptr2) { - /* we didn't get any digit */ - to=-1; - } - if((-1 == to) && (from>=0)) { - /* X - */ - data->state.resume_from = from; - DEBUGF(infof(data, "RANGE %" FORMAT_OFF_T " to end of file\n", - from)); - } - else if(from < 0) { - /* -Y */ - data->req.maxdownload = -from; - data->state.resume_from = from; - DEBUGF(infof(data, "RANGE the last %" FORMAT_OFF_T " bytes\n", - -from)); - } - else { - /* X-Y */ - totalsize = to-from; - data->req.maxdownload = totalsize+1; /* include last byte */ - data->state.resume_from = from; - DEBUGF(infof(data, "RANGE from %" FORMAT_OFF_T - " getting %" FORMAT_OFF_T " bytes\n", - from, data->req.maxdownload)); - } - DEBUGF(infof(data, "range-download from %" FORMAT_OFF_T - " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n", - from, to, data->req.maxdownload)); - } - else - data->req.maxdownload = -1; - return CURLE_OK; -} - -/* - * file_connect() gets called from Curl_protocol_connect() to allow us to - * do protocol-specific actions at connect-time. We emulate a - * connect-then-transfer protocol and "connect" to the file here - */ -static CURLcode file_connect(struct connectdata *conn, bool *done) -{ - struct SessionHandle *data = conn->data; - char *real_path; - struct FILEPROTO *file; - int fd; -#ifdef DOS_FILESYSTEM - int i; - char *actual_path; -#endif - - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - real_path = curl_easy_unescape(data, data->state.path, 0, NULL); - if(!real_path) - return CURLE_OUT_OF_MEMORY; - - if(!data->state.proto.file) { - file = calloc(1, sizeof(struct FILEPROTO)); - if(!file) { - free(real_path); - return CURLE_OUT_OF_MEMORY; - } - data->state.proto.file = file; - } - else { - /* file is not a protocol that can deal with "persistancy" */ - file = data->state.proto.file; - Curl_safefree(file->freepath); - file->path = NULL; - if(file->fd != -1) - close(file->fd); - file->fd = -1; - } - -#ifdef DOS_FILESYSTEM - /* If the first character is a slash, and there's - something that looks like a drive at the beginning of - the path, skip the slash. If we remove the initial - slash in all cases, paths without drive letters end up - relative to the current directory which isn't how - browsers work. - - Some browsers accept | instead of : as the drive letter - separator, so we do too. - - On other platforms, we need the slash to indicate an - absolute pathname. On Windows, absolute paths start - with a drive letter. - */ - actual_path = real_path; - if((actual_path[0] == '/') && - actual_path[1] && - (actual_path[2] == ':' || actual_path[2] == '|')) { - actual_path[2] = ':'; - actual_path++; - } - - /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */ - for(i=0; actual_path[i] != '\0'; ++i) - if(actual_path[i] == '/') - actual_path[i] = '\\'; - - fd = open_readonly(actual_path, O_RDONLY|O_BINARY); - file->path = actual_path; -#else - fd = open_readonly(real_path, O_RDONLY); - file->path = real_path; -#endif - file->freepath = real_path; /* free this when done */ - - file->fd = fd; - if(!data->set.upload && (fd == -1)) { - failf(data, "Couldn't open file %s", data->state.path); - file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE); - return CURLE_FILE_COULDNT_READ_FILE; - } - *done = TRUE; - - return CURLE_OK; -} - -static CURLcode file_done(struct connectdata *conn, - CURLcode status, bool premature) -{ - struct FILEPROTO *file = conn->data->state.proto.file; - (void)status; /* not used */ - (void)premature; /* not used */ - - if(file) { - Curl_safefree(file->freepath); - file->path = NULL; - if(file->fd != -1) - close(file->fd); - file->fd = -1; - } - - return CURLE_OK; -} - -static CURLcode file_disconnect(struct connectdata *conn, - bool dead_connection) -{ - struct FILEPROTO *file = conn->data->state.proto.file; - (void)dead_connection; /* not used */ - - if(file) { - Curl_safefree(file->freepath); - file->path = NULL; - if(file->fd != -1) - close(file->fd); - file->fd = -1; - } - - return CURLE_OK; -} - -#ifdef DOS_FILESYSTEM -#define DIRSEP '\\' -#else -#define DIRSEP '/' -#endif - -static CURLcode file_upload(struct connectdata *conn) -{ - struct FILEPROTO *file = conn->data->state.proto.file; - const char *dir = strchr(file->path, DIRSEP); - int fd; - int mode; - CURLcode res=CURLE_OK; - struct SessionHandle *data = conn->data; - char *buf = data->state.buffer; - size_t nread; - size_t nwrite; - curl_off_t bytecount = 0; - struct timeval now = Curl_tvnow(); - struct_stat file_stat; - const char* buf2; - - /* - * Since FILE: doesn't do the full init, we need to provide some extra - * assignments here. - */ - conn->fread_func = data->set.fread_func; - conn->fread_in = data->set.in; - conn->data->req.upload_fromhere = buf; - - if(!dir) - return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ - - if(!dir[1]) - return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ - -#ifdef O_BINARY -#define MODE_DEFAULT O_WRONLY|O_CREAT|O_BINARY -#else -#define MODE_DEFAULT O_WRONLY|O_CREAT -#endif - - if(data->state.resume_from) - mode = MODE_DEFAULT|O_APPEND; - else - mode = MODE_DEFAULT|O_TRUNC; - - fd = open(file->path, mode, conn->data->set.new_file_perms); - if(fd < 0) { - failf(data, "Can't open %s for writing", file->path); - return CURLE_WRITE_ERROR; - } - - if(-1 != data->set.infilesize) - /* known size of data to "upload" */ - Curl_pgrsSetUploadSize(data, data->set.infilesize); - - /* treat the negative resume offset value as the case of "-" */ - if(data->state.resume_from < 0) { - if(fstat(fd, &file_stat)) { - close(fd); - failf(data, "Can't get the size of %s", file->path); - return CURLE_WRITE_ERROR; - } - else - data->state.resume_from = (curl_off_t)file_stat.st_size; - } - - while(res == CURLE_OK) { - int readcount; - res = Curl_fillreadbuffer(conn, BUFSIZE, &readcount); - if(res) - break; - - if(readcount <= 0) /* fix questionable compare error. curlvms */ - break; - - nread = (size_t)readcount; - - /*skip bytes before resume point*/ - if(data->state.resume_from) { - if((curl_off_t)nread <= data->state.resume_from ) { - data->state.resume_from -= nread; - nread = 0; - buf2 = buf; - } - else { - buf2 = buf + data->state.resume_from; - nread -= (size_t)data->state.resume_from; - data->state.resume_from = 0; - } - } - else - buf2 = buf; - - /* write the data to the target */ - nwrite = write(fd, buf2, nread); - if(nwrite != nread) { - res = CURLE_SEND_ERROR; - break; - } - - bytecount += nread; - - Curl_pgrsSetUploadCounter(data, bytecount); - - if(Curl_pgrsUpdate(conn)) - res = CURLE_ABORTED_BY_CALLBACK; - else - res = Curl_speedcheck(data, now); - } - if(!res && Curl_pgrsUpdate(conn)) - res = CURLE_ABORTED_BY_CALLBACK; - - close(fd); - - return res; -} - -/* - * file_do() is the protocol-specific function for the do-phase, separated - * from the connect-phase above. Other protocols merely setup the transfer in - * the do-phase, to have it done in the main transfer loop but since some - * platforms we support don't allow select()ing etc on file handles (as - * opposed to sockets) we instead perform the whole do-operation in this - * function. - */ -static CURLcode file_do(struct connectdata *conn, bool *done) -{ - /* This implementation ignores the host name in conformance with - RFC 1738. Only local files (reachable via the standard file system) - are supported. This means that files on remotely mounted directories - (via NFS, Samba, NT sharing) can be accessed through a file:// URL - */ - CURLcode res = CURLE_OK; - struct_stat statbuf; /* struct_stat instead of struct stat just to allow the - Windows version to have a different struct without - having to redefine the simple word 'stat' */ - curl_off_t expected_size=0; - bool fstated=FALSE; - ssize_t nread; - struct SessionHandle *data = conn->data; - char *buf = data->state.buffer; - curl_off_t bytecount = 0; - int fd; - struct timeval now = Curl_tvnow(); - - *done = TRUE; /* unconditionally */ - - Curl_initinfo(data); - Curl_pgrsStartNow(data); - - if(data->set.upload) - return file_upload(conn); - - /* get the fd from the connection phase */ - fd = conn->data->state.proto.file->fd; - - /* VMS: This only works reliable for STREAMLF files */ - if(-1 != fstat(fd, &statbuf)) { - /* we could stat it, then read out the size */ - expected_size = statbuf.st_size; - /* and store the modification time */ - data->info.filetime = (long)statbuf.st_mtime; - fstated = TRUE; - } - - if(fstated && !data->state.range && data->set.timecondition) { - if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) { - *done = TRUE; - return CURLE_OK; - } - } - - /* If we have selected NOBODY and HEADER, it means that we only want file - information. Which for FILE can't be much more than the file size and - date. */ - if(data->set.opt_no_body && data->set.include_header && fstated) { - CURLcode result; - snprintf(buf, sizeof(data->state.buffer), - "Content-Length: %" FORMAT_OFF_T "\r\n", expected_size); - result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); - if(result) - return result; - - result = Curl_client_write(conn, CLIENTWRITE_BOTH, - (char *)"Accept-ranges: bytes\r\n", 0); - if(result) - return result; - - if(fstated) { - time_t filetime = (time_t)statbuf.st_mtime; - struct tm buffer; - const struct tm *tm = &buffer; - result = Curl_gmtime(filetime, &buffer); - if(result) - return result; - - /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ - snprintf(buf, BUFSIZE-1, - "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); - } - /* if we fstat()ed the file, set the file size to make it available post- - transfer */ - if(fstated) - Curl_pgrsSetDownloadSize(data, expected_size); - return result; - } - - /* Check whether file range has been specified */ - file_range(conn); - - /* Adjust the start offset in case we want to get the N last bytes - * of the stream iff the filesize could be determined */ - if(data->state.resume_from < 0) { - if(!fstated) { - failf(data, "Can't get the size of file."); - return CURLE_READ_ERROR; - } - else - data->state.resume_from += (curl_off_t)statbuf.st_size; - } - - if(data->state.resume_from <= expected_size) - expected_size -= data->state.resume_from; - else { - failf(data, "failed to resume file:// transfer"); - return CURLE_BAD_DOWNLOAD_RESUME; - } - - /* A high water mark has been specified so we obey... */ - if(data->req.maxdownload > 0) - expected_size = data->req.maxdownload; - - if(fstated && (expected_size == 0)) - return CURLE_OK; - - /* The following is a shortcut implementation of file reading - this is both more efficient than the former call to download() and - it avoids problems with select() and recv() on file descriptors - in Winsock */ - if(fstated) - Curl_pgrsSetDownloadSize(data, expected_size); - - if(data->state.resume_from) { - if(data->state.resume_from != - lseek(fd, data->state.resume_from, SEEK_SET)) - return CURLE_BAD_DOWNLOAD_RESUME; - } - - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - - while(res == CURLE_OK) { - /* Don't fill a whole buffer if we want less than all data */ - size_t bytestoread = - (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ? - curlx_sotouz(expected_size) : BUFSIZE - 1; - - nread = read(fd, buf, bytestoread); - - if(nread > 0) - buf[nread] = 0; - - if(nread <= 0 || expected_size == 0) - break; - - bytecount += nread; - expected_size -= nread; - - res = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread); - if(res) - return res; - - Curl_pgrsSetDownloadCounter(data, bytecount); - - if(Curl_pgrsUpdate(conn)) - res = CURLE_ABORTED_BY_CALLBACK; - else - res = Curl_speedcheck(data, now); - } - if(Curl_pgrsUpdate(conn)) - res = CURLE_ABORTED_BY_CALLBACK; - - return res; -} - -#endif diff --git a/lib/fileinfo.c b/lib/fileinfo.c deleted file mode 100644 index 433c709b5..000000000 --- a/lib/fileinfo.c +++ /dev/null @@ -1,54 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2010-2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_strdup.h" -#include "curl_fileinfo.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -struct curl_fileinfo *Curl_fileinfo_alloc(void) -{ - struct curl_fileinfo *tmp = malloc(sizeof(struct curl_fileinfo)); - if(!tmp) - return NULL; - memset(tmp, 0, sizeof(struct curl_fileinfo)); - return tmp; -} - -void Curl_fileinfo_dtor(void *user, void *element) -{ - struct curl_fileinfo *finfo = element; - (void) user; - if(!finfo) - return; - - Curl_safefree(finfo->b_data); - - free(finfo); -} diff --git a/lib/formdata.c b/lib/formdata.c deleted file mode 100644 index c7d85c4b4..000000000 --- a/lib/formdata.c +++ /dev/null @@ -1,1494 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include - -/* Length of the random boundary string. */ -#define BOUNDARY_LENGTH 40 - -#if !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY) - -#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME) -#include -#endif - -#include "curl_urldata.h" /* for struct SessionHandle */ -#include "curl_formdata.h" -#include "curl_rand.h" -#include "curl_strequal.h" -#include "curl_memory.h" -#include "curl_sendf.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#endif /* !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY) */ - -#ifndef CURL_DISABLE_HTTP - -#ifndef HAVE_BASENAME -static char *Curl_basename(char *path); -#define basename(x) Curl_basename((x)) -#endif - -static size_t readfromfile(struct Form *form, char *buffer, size_t size); - -/* What kind of Content-Type to use on un-specified files with unrecognized - extensions. */ -#define HTTPPOST_CONTENTTYPE_DEFAULT "application/octet-stream" - -#define FORM_FILE_SEPARATOR ',' -#define FORM_TYPE_SEPARATOR ';' - -/*************************************************************************** - * - * AddHttpPost() - * - * Adds a HttpPost structure to the list, if parent_post is given becomes - * a subpost of parent_post instead of a direct list element. - * - * Returns newly allocated HttpPost on success and NULL if malloc failed. - * - ***************************************************************************/ -static struct curl_httppost * -AddHttpPost(char *name, size_t namelength, - char *value, size_t contentslength, - char *buffer, size_t bufferlength, - char *contenttype, - long flags, - struct curl_slist* contentHeader, - char *showfilename, char *userp, - struct curl_httppost *parent_post, - struct curl_httppost **httppost, - struct curl_httppost **last_post) -{ - struct curl_httppost *post; - post = calloc(1, sizeof(struct curl_httppost)); - if(post) { - post->name = name; - post->namelength = (long)(name?(namelength?namelength:strlen(name)):0); - post->contents = value; - post->contentslength = (long)contentslength; - post->buffer = buffer; - post->bufferlength = (long)bufferlength; - post->contenttype = contenttype; - post->contentheader = contentHeader; - post->showfilename = showfilename; - post->userp = userp, - post->flags = flags; - } - else - return NULL; - - if(parent_post) { - /* now, point our 'more' to the original 'more' */ - post->more = parent_post->more; - - /* then move the original 'more' to point to ourselves */ - parent_post->more = post; - } - else { - /* make the previous point to this */ - if(*last_post) - (*last_post)->next = post; - else - (*httppost) = post; - - (*last_post) = post; - } - return post; -} - -/*************************************************************************** - * - * AddFormInfo() - * - * Adds a FormInfo structure to the list presented by parent_form_info. - * - * Returns newly allocated FormInfo on success and NULL if malloc failed/ - * parent_form_info is NULL. - * - ***************************************************************************/ -static FormInfo * AddFormInfo(char *value, - char *contenttype, - FormInfo *parent_form_info) -{ - FormInfo *form_info; - form_info = calloc(1, sizeof(struct FormInfo)); - if(form_info) { - if(value) - form_info->value = value; - if(contenttype) - form_info->contenttype = contenttype; - form_info->flags = HTTPPOST_FILENAME; - } - else - return NULL; - - if(parent_form_info) { - /* now, point our 'more' to the original 'more' */ - form_info->more = parent_form_info->more; - - /* then move the original 'more' to point to ourselves */ - parent_form_info->more = form_info; - } - - return form_info; -} - -/*************************************************************************** - * - * ContentTypeForFilename() - * - * Provides content type for filename if one of the known types (else - * (either the prevtype or the default is returned). - * - * Returns some valid contenttype for filename. - * - ***************************************************************************/ -static const char * ContentTypeForFilename (const char *filename, - const char *prevtype) -{ - const char *contenttype = NULL; - unsigned int i; - /* - * No type was specified, we scan through a few well-known - * extensions and pick the first we match! - */ - struct ContentType { - char extension[6]; - const char *type; - }; - static const struct ContentType ctts[]={ - {".gif", "image/gif"}, - {".jpg", "image/jpeg"}, - {".jpeg", "image/jpeg"}, - {".txt", "text/plain"}, - {".html", "text/html"}, - {".xml", "application/xml"} - }; - - if(prevtype) - /* default to the previously set/used! */ - contenttype = prevtype; - else - contenttype = HTTPPOST_CONTENTTYPE_DEFAULT; - - if(filename) { /* in case a NULL was passed in */ - for(i=0; i= strlen(ctts[i].extension)) { - if(strequal(filename + - strlen(filename) - strlen(ctts[i].extension), - ctts[i].extension)) { - contenttype = ctts[i].type; - break; - } - } - } - } - /* we have a contenttype by now */ - return contenttype; -} - -/*************************************************************************** - * - * memdup() - * - * Copies the 'source' data to a newly allocated buffer buffer (that is - * returned). Uses buffer_length if not null, else uses strlen to determine - * the length of the buffer to be copied - * - * Returns the new pointer or NULL on failure. - * - ***************************************************************************/ -static char *memdup(const char *src, size_t buffer_length) -{ - size_t length; - bool add = FALSE; - char *buffer; - - if(buffer_length) - length = buffer_length; - else if(src) { - length = strlen(src); - add = TRUE; - } - else - /* no length and a NULL src pointer! */ - return strdup(""); - - buffer = malloc(length+add); - if(!buffer) - return NULL; /* fail */ - - memcpy(buffer, src, length); - - /* if length unknown do null termination */ - if(add) - buffer[length] = '\0'; - - return buffer; -} - -/*************************************************************************** - * - * FormAdd() - * - * Stores a formpost parameter and builds the appropriate linked list. - * - * Has two principal functionalities: using files and byte arrays as - * post parts. Byte arrays are either copied or just the pointer is stored - * (as the user requests) while for files only the filename and not the - * content is stored. - * - * While you may have only one byte array for each name, multiple filenames - * are allowed (and because of this feature CURLFORM_END is needed after - * using CURLFORM_FILE). - * - * Examples: - * - * Simple name/value pair with copied contents: - * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", - * CURLFORM_COPYCONTENTS, "value", CURLFORM_END); - * - * name/value pair where only the content pointer is remembered: - * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", - * CURLFORM_PTRCONTENTS, ptr, CURLFORM_CONTENTSLENGTH, 10, CURLFORM_END); - * (if CURLFORM_CONTENTSLENGTH is missing strlen () is used) - * - * storing a filename (CONTENTTYPE is optional!): - * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", - * CURLFORM_FILE, "filename1", CURLFORM_CONTENTTYPE, "plain/text", - * CURLFORM_END); - * - * storing multiple filenames: - * curl_formadd (&post, &last, CURLFORM_COPYNAME, "name", - * CURLFORM_FILE, "filename1", CURLFORM_FILE, "filename2", CURLFORM_END); - * - * Returns: - * CURL_FORMADD_OK on success - * CURL_FORMADD_MEMORY if the FormInfo allocation fails - * CURL_FORMADD_OPTION_TWICE if one option is given twice for one Form - * CURL_FORMADD_NULL if a null pointer was given for a char - * CURL_FORMADD_MEMORY if the allocation of a FormInfo struct failed - * CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used - * CURL_FORMADD_INCOMPLETE if the some FormInfo is not complete (or error) - * CURL_FORMADD_MEMORY if a HttpPost struct cannot be allocated - * CURL_FORMADD_MEMORY if some allocation for string copying failed. - * CURL_FORMADD_ILLEGAL_ARRAY if an illegal option is used in an array - * - ***************************************************************************/ - -static -CURLFORMcode FormAdd(struct curl_httppost **httppost, - struct curl_httppost **last_post, - va_list params) -{ - FormInfo *first_form, *current_form, *form = NULL; - CURLFORMcode return_value = CURL_FORMADD_OK; - const char *prevtype = NULL; - struct curl_httppost *post = NULL; - CURLformoption option; - struct curl_forms *forms = NULL; - char *array_value=NULL; /* value read from an array */ - - /* This is a state variable, that if TRUE means that we're parsing an - array that we got passed to us. If FALSE we're parsing the input - va_list arguments. */ - bool array_state = FALSE; - - /* - * We need to allocate the first struct to fill in. - */ - first_form = calloc(1, sizeof(struct FormInfo)); - if(!first_form) - return CURL_FORMADD_MEMORY; - - current_form = first_form; - - /* - * Loop through all the options set. Break if we have an error to report. - */ - while(return_value == CURL_FORMADD_OK) { - - /* first see if we have more parts of the array param */ - if(array_state && forms) { - /* get the upcoming option from the given array */ - option = forms->option; - array_value = (char *)forms->value; - - forms++; /* advance this to next entry */ - if(CURLFORM_END == option) { - /* end of array state */ - array_state = FALSE; - continue; - } - } - else { - /* This is not array-state, get next option */ - option = va_arg(params, CURLformoption); - if(CURLFORM_END == option) - break; - } - - switch (option) { - case CURLFORM_ARRAY: - if(array_state) - /* we don't support an array from within an array */ - return_value = CURL_FORMADD_ILLEGAL_ARRAY; - else { - forms = va_arg(params, struct curl_forms *); - if(forms) - array_state = TRUE; - else - return_value = CURL_FORMADD_NULL; - } - break; - - /* - * Set the Name property. - */ - case CURLFORM_PTRNAME: -#ifdef CURL_DOES_CONVERSIONS - /* Treat CURLFORM_PTR like CURLFORM_COPYNAME so that libcurl will copy - * the data in all cases so that we'll have safe memory for the eventual - * conversion. - */ -#else - current_form->flags |= HTTPPOST_PTRNAME; /* fall through */ -#endif - case CURLFORM_COPYNAME: - if(current_form->name) - return_value = CURL_FORMADD_OPTION_TWICE; - else { - char *name = array_state? - array_value:va_arg(params, char *); - if(name) - current_form->name = name; /* store for the moment */ - else - return_value = CURL_FORMADD_NULL; - } - break; - case CURLFORM_NAMELENGTH: - if(current_form->namelength) - return_value = CURL_FORMADD_OPTION_TWICE; - else - current_form->namelength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); - break; - - /* - * Set the contents property. - */ - case CURLFORM_PTRCONTENTS: - current_form->flags |= HTTPPOST_PTRCONTENTS; /* fall through */ - case CURLFORM_COPYCONTENTS: - if(current_form->value) - return_value = CURL_FORMADD_OPTION_TWICE; - else { - char *value = - array_state?array_value:va_arg(params, char *); - if(value) - current_form->value = value; /* store for the moment */ - else - return_value = CURL_FORMADD_NULL; - } - break; - case CURLFORM_CONTENTSLENGTH: - if(current_form->contentslength) - return_value = CURL_FORMADD_OPTION_TWICE; - else - current_form->contentslength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); - break; - - /* Get contents from a given file name */ - case CURLFORM_FILECONTENT: - if(current_form->flags != 0) - return_value = CURL_FORMADD_OPTION_TWICE; - else { - const char *filename = array_state? - array_value:va_arg(params, char *); - if(filename) { - current_form->value = strdup(filename); - if(!current_form->value) - return_value = CURL_FORMADD_MEMORY; - else { - current_form->flags |= HTTPPOST_READFILE; - current_form->value_alloc = TRUE; - } - } - else - return_value = CURL_FORMADD_NULL; - } - break; - - /* We upload a file */ - case CURLFORM_FILE: - { - const char *filename = array_state?array_value: - va_arg(params, char *); - - if(current_form->value) { - if(current_form->flags & HTTPPOST_FILENAME) { - if(filename) { - char *fname = strdup(filename); - if(!fname) - return_value = CURL_FORMADD_MEMORY; - else { - form = AddFormInfo(fname, NULL, current_form); - if(!form) { - Curl_safefree(fname); - return_value = CURL_FORMADD_MEMORY; - } - else { - form->value_alloc = TRUE; - current_form = form; - form = NULL; - } - } - } - else - return_value = CURL_FORMADD_NULL; - } - else - return_value = CURL_FORMADD_OPTION_TWICE; - } - else { - if(filename) { - current_form->value = strdup(filename); - if(!current_form->value) - return_value = CURL_FORMADD_MEMORY; - else { - current_form->flags |= HTTPPOST_FILENAME; - current_form->value_alloc = TRUE; - } - } - else - return_value = CURL_FORMADD_NULL; - } - break; - } - - case CURLFORM_BUFFERPTR: - current_form->flags |= HTTPPOST_PTRBUFFER|HTTPPOST_BUFFER; - if(current_form->buffer) - return_value = CURL_FORMADD_OPTION_TWICE; - else { - char *buffer = - array_state?array_value:va_arg(params, char *); - if(buffer) { - current_form->buffer = buffer; /* store for the moment */ - current_form->value = buffer; /* make it non-NULL to be accepted - as fine */ - } - else - return_value = CURL_FORMADD_NULL; - } - break; - - case CURLFORM_BUFFERLENGTH: - if(current_form->bufferlength) - return_value = CURL_FORMADD_OPTION_TWICE; - else - current_form->bufferlength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); - break; - - case CURLFORM_STREAM: - current_form->flags |= HTTPPOST_CALLBACK; - if(current_form->userp) - return_value = CURL_FORMADD_OPTION_TWICE; - else { - char *userp = - array_state?array_value:va_arg(params, char *); - if(userp) { - current_form->userp = userp; - current_form->value = userp; /* this isn't strictly true but we - derive a value from this later on - and we need this non-NULL to be - accepted as a fine form part */ - } - else - return_value = CURL_FORMADD_NULL; - } - break; - - case CURLFORM_CONTENTTYPE: - { - const char *contenttype = - array_state?array_value:va_arg(params, char *); - if(current_form->contenttype) { - if(current_form->flags & HTTPPOST_FILENAME) { - if(contenttype) { - char *type = strdup(contenttype); - if(!type) - return_value = CURL_FORMADD_MEMORY; - else { - form = AddFormInfo(NULL, type, current_form); - if(!form) { - Curl_safefree(type); - return_value = CURL_FORMADD_MEMORY; - } - else { - form->contenttype_alloc = TRUE; - current_form = form; - form = NULL; - } - } - } - else - return_value = CURL_FORMADD_NULL; - } - else - return_value = CURL_FORMADD_OPTION_TWICE; - } - else { - if(contenttype) { - current_form->contenttype = strdup(contenttype); - if(!current_form->contenttype) - return_value = CURL_FORMADD_MEMORY; - else - current_form->contenttype_alloc = TRUE; - } - else - return_value = CURL_FORMADD_NULL; - } - break; - } - case CURLFORM_CONTENTHEADER: - { - /* this "cast increases required alignment of target type" but - we consider it OK anyway */ - struct curl_slist* list = array_state? - (struct curl_slist*)array_value: - va_arg(params, struct curl_slist*); - - if(current_form->contentheader) - return_value = CURL_FORMADD_OPTION_TWICE; - else - current_form->contentheader = list; - - break; - } - case CURLFORM_FILENAME: - case CURLFORM_BUFFER: - { - const char *filename = array_state?array_value: - va_arg(params, char *); - if(current_form->showfilename) - return_value = CURL_FORMADD_OPTION_TWICE; - else { - current_form->showfilename = strdup(filename); - if(!current_form->showfilename) - return_value = CURL_FORMADD_MEMORY; - else - current_form->showfilename_alloc = TRUE; - } - break; - } - default: - return_value = CURL_FORMADD_UNKNOWN_OPTION; - break; - } - } - - if(CURL_FORMADD_OK != return_value) { - /* On error, free allocated fields for all nodes of the FormInfo linked - list without deallocating nodes. List nodes are deallocated later on */ - FormInfo *ptr; - for(ptr = first_form; ptr != NULL; ptr = ptr->more) { - if(ptr->name_alloc) { - Curl_safefree(ptr->name); - ptr->name_alloc = FALSE; - } - if(ptr->value_alloc) { - Curl_safefree(ptr->value); - ptr->value_alloc = FALSE; - } - if(ptr->contenttype_alloc) { - Curl_safefree(ptr->contenttype); - ptr->contenttype_alloc = FALSE; - } - if(ptr->showfilename_alloc) { - Curl_safefree(ptr->showfilename); - ptr->showfilename_alloc = FALSE; - } - } - } - - if(CURL_FORMADD_OK == return_value) { - /* go through the list, check for completeness and if everything is - * alright add the HttpPost item otherwise set return_value accordingly */ - - post = NULL; - for(form = first_form; - form != NULL; - form = form->more) { - if(((!form->name || !form->value) && !post) || - ( (form->contentslength) && - (form->flags & HTTPPOST_FILENAME) ) || - ( (form->flags & HTTPPOST_FILENAME) && - (form->flags & HTTPPOST_PTRCONTENTS) ) || - - ( (!form->buffer) && - (form->flags & HTTPPOST_BUFFER) && - (form->flags & HTTPPOST_PTRBUFFER) ) || - - ( (form->flags & HTTPPOST_READFILE) && - (form->flags & HTTPPOST_PTRCONTENTS) ) - ) { - return_value = CURL_FORMADD_INCOMPLETE; - break; - } - else { - if(((form->flags & HTTPPOST_FILENAME) || - (form->flags & HTTPPOST_BUFFER)) && - !form->contenttype ) { - /* our contenttype is missing */ - form->contenttype - = strdup(ContentTypeForFilename(form->value, prevtype)); - if(!form->contenttype) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->contenttype_alloc = TRUE; - } - if(!(form->flags & HTTPPOST_PTRNAME) && - (form == first_form) ) { - /* Note that there's small risk that form->name is NULL here if the - app passed in a bad combo, so we better check for that first. */ - if(form->name) - /* copy name (without strdup; possibly contains null characters) */ - form->name = memdup(form->name, form->namelength); - if(!form->name) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->name_alloc = TRUE; - } - if(!(form->flags & (HTTPPOST_FILENAME | HTTPPOST_READFILE | - HTTPPOST_PTRCONTENTS | HTTPPOST_PTRBUFFER | - HTTPPOST_CALLBACK)) ) { - /* copy value (without strdup; possibly contains null characters) */ - form->value = memdup(form->value, form->contentslength); - if(!form->value) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->value_alloc = TRUE; - } - post = AddHttpPost(form->name, form->namelength, - form->value, form->contentslength, - form->buffer, form->bufferlength, - form->contenttype, form->flags, - form->contentheader, form->showfilename, - form->userp, - post, httppost, - last_post); - - if(!post) { - return_value = CURL_FORMADD_MEMORY; - break; - } - - if(form->contenttype) - prevtype = form->contenttype; - } - } - if(CURL_FORMADD_OK != return_value) { - /* On error, free allocated fields for nodes of the FormInfo linked - list which are not already owned by the httppost linked list - without deallocating nodes. List nodes are deallocated later on */ - FormInfo *ptr; - for(ptr = form; ptr != NULL; ptr = ptr->more) { - if(ptr->name_alloc) { - Curl_safefree(ptr->name); - ptr->name_alloc = FALSE; - } - if(ptr->value_alloc) { - Curl_safefree(ptr->value); - ptr->value_alloc = FALSE; - } - if(ptr->contenttype_alloc) { - Curl_safefree(ptr->contenttype); - ptr->contenttype_alloc = FALSE; - } - if(ptr->showfilename_alloc) { - Curl_safefree(ptr->showfilename); - ptr->showfilename_alloc = FALSE; - } - } - } - } - - /* Always deallocate FormInfo linked list nodes without touching node - fields given that these have either been deallocated or are owned - now by the httppost linked list */ - while(first_form) { - FormInfo *ptr = first_form->more; - Curl_safefree(first_form); - first_form = ptr; - } - - return return_value; -} - -/* - * curl_formadd() is a public API to add a section to the multipart formpost. - * - * @unittest: 1308 - */ - -CURLFORMcode curl_formadd(struct curl_httppost **httppost, - struct curl_httppost **last_post, - ...) -{ - va_list arg; - CURLFORMcode result; - va_start(arg, last_post); - result = FormAdd(httppost, last_post, arg); - va_end(arg); - return result; -} - -/* - * AddFormData() adds a chunk of data to the FormData linked list. - * - * size is incremented by the chunk length, unless it is NULL - */ -static CURLcode AddFormData(struct FormData **formp, - enum formtype type, - const void *line, - size_t length, - curl_off_t *size) -{ - struct FormData *newform = malloc(sizeof(struct FormData)); - if(!newform) - return CURLE_OUT_OF_MEMORY; - newform->next = NULL; - - if(type <= FORM_CONTENT) { - /* we make it easier for plain strings: */ - if(!length) - length = strlen((char *)line); - - newform->line = malloc(length+1); - if(!newform->line) { - free(newform); - return CURLE_OUT_OF_MEMORY; - } - memcpy(newform->line, line, length); - newform->length = length; - newform->line[length]=0; /* zero terminate for easier debugging */ - } - else - /* For callbacks and files we don't have any actual data so we just keep a - pointer to whatever this points to */ - newform->line = (char *)line; - - newform->type = type; - - if(*formp) { - (*formp)->next = newform; - *formp = newform; - } - else - *formp = newform; - - if(size) { - if(type != FORM_FILE) - /* for static content as well as callback data we add the size given - as input argument */ - *size += length; - else { - /* Since this is a file to be uploaded here, add the size of the actual - file */ - if(!strequal("-", newform->line)) { - struct_stat file; - if(!stat(newform->line, &file)) { - *size += file.st_size; - } - } - } - } - return CURLE_OK; -} - -/* - * AddFormDataf() adds printf()-style formatted data to the formdata chain. - */ - -static CURLcode AddFormDataf(struct FormData **formp, - curl_off_t *size, - const char *fmt, ...) -{ - char s[4096]; - va_list ap; - va_start(ap, fmt); - vsnprintf(s, sizeof(s), fmt, ap); - va_end(ap); - - return AddFormData(formp, FORM_DATA, s, 0, size); -} - -/* - * Curl_formclean() is used from curl_http.c, this cleans a built FormData - * linked list - */ -void Curl_formclean(struct FormData **form_ptr) -{ - struct FormData *next, *form; - - form = *form_ptr; - if(!form) - return; - - do { - next=form->next; /* the following form line */ - if(form->type <= FORM_CONTENT) - free(form->line); /* free the line */ - free(form); /* free the struct */ - - } while((form = next) != NULL); /* continue */ - - *form_ptr = NULL; -} - -/* - * curl_formget() - * Serialize a curl_httppost struct. - * Returns 0 on success. - * - * @unittest: 1308 - */ -int curl_formget(struct curl_httppost *form, void *arg, - curl_formget_callback append) -{ - CURLcode rc; - curl_off_t size; - struct FormData *data, *ptr; - - rc = Curl_getformdata(NULL, &data, form, NULL, &size); - if(rc != CURLE_OK) - return (int)rc; - - for(ptr = data; ptr; ptr = ptr->next) { - if((ptr->type == FORM_FILE) || (ptr->type == FORM_CALLBACK)) { - char buffer[8192]; - size_t nread; - struct Form temp; - - Curl_FormInit(&temp, ptr); - - do { - nread = readfromfile(&temp, buffer, sizeof(buffer)); - if((nread == (size_t) -1) || - (nread > sizeof(buffer)) || - (nread != append(arg, buffer, nread))) { - if(temp.fp) - fclose(temp.fp); - Curl_formclean(&data); - return -1; - } - } while(nread); - } - else { - if(ptr->length != append(arg, ptr->line, ptr->length)) { - Curl_formclean(&data); - return -1; - } - } - } - Curl_formclean(&data); - return 0; -} - -/* - * curl_formfree() is an external function to free up a whole form post - * chain - */ -void curl_formfree(struct curl_httppost *form) -{ - struct curl_httppost *next; - - if(!form) - /* no form to free, just get out of this */ - return; - - do { - next=form->next; /* the following form line */ - - /* recurse to sub-contents */ - if(form->more) - curl_formfree(form->more); - - if(!(form->flags & HTTPPOST_PTRNAME) && form->name) - free(form->name); /* free the name */ - if(!(form->flags & - (HTTPPOST_PTRCONTENTS|HTTPPOST_BUFFER|HTTPPOST_CALLBACK)) && - form->contents) - free(form->contents); /* free the contents */ - if(form->contenttype) - free(form->contenttype); /* free the content type */ - if(form->showfilename) - free(form->showfilename); /* free the faked file name */ - free(form); /* free the struct */ - - } while((form = next) != NULL); /* continue */ -} - -#ifndef HAVE_BASENAME -/* - (Quote from The Open Group Base Specifications Issue 6 IEEE Std 1003.1, 2004 - Edition) - - The basename() function shall take the pathname pointed to by path and - return a pointer to the final component of the pathname, deleting any - trailing '/' characters. - - If the string pointed to by path consists entirely of the '/' character, - basename() shall return a pointer to the string "/". If the string pointed - to by path is exactly "//", it is implementation-defined whether '/' or "//" - is returned. - - If path is a null pointer or points to an empty string, basename() shall - return a pointer to the string ".". - - The basename() function may modify the string pointed to by path, and may - return a pointer to static storage that may then be overwritten by a - subsequent call to basename(). - - The basename() function need not be reentrant. A function that is not - required to be reentrant is not required to be thread-safe. - -*/ -static char *Curl_basename(char *path) -{ - /* Ignore all the details above for now and make a quick and simple - implementaion here */ - char *s1; - char *s2; - - s1=strrchr(path, '/'); - s2=strrchr(path, '\\'); - - if(s1 && s2) { - path = (s1 > s2? s1 : s2)+1; - } - else if(s1) - path = s1 + 1; - else if(s2) - path = s2 + 1; - - return path; -} -#endif - -static char *strippath(const char *fullfile) -{ - char *filename; - char *base; - filename = strdup(fullfile); /* duplicate since basename() may ruin the - buffer it works on */ - if(!filename) - return NULL; - base = strdup(basename(filename)); - - free(filename); /* free temporary buffer */ - - return base; /* returns an allocated string or NULL ! */ -} - -/* - * Curl_getformdata() converts a linked list of "meta data" into a complete - * (possibly huge) multipart formdata. The input list is in 'post', while the - * output resulting linked lists gets stored in '*finalform'. *sizep will get - * the total size of the whole POST. - * A multipart/form_data content-type is built, unless a custom content-type - * is passed in 'custom_content_type'. - * - * This function will not do a failf() for the potential memory failures but - * should for all other errors it spots. Just note that this function MAY get - * a NULL pointer in the 'data' argument. - */ - -CURLcode Curl_getformdata(struct SessionHandle *data, - struct FormData **finalform, - struct curl_httppost *post, - const char *custom_content_type, - curl_off_t *sizep) -{ - struct FormData *form = NULL; - struct FormData *firstform; - struct curl_httppost *file; - CURLcode result = CURLE_OK; - - curl_off_t size = 0; /* support potentially ENORMOUS formposts */ - char *boundary; - char *fileboundary = NULL; - struct curl_slist* curList; - - *finalform = NULL; /* default form is empty */ - - if(!post) - return result; /* no input => no output! */ - - boundary = Curl_FormBoundary(); - if(!boundary) - return CURLE_OUT_OF_MEMORY; - - /* Make the first line of the output */ - result = AddFormDataf(&form, NULL, - "%s; boundary=%s\r\n", - custom_content_type?custom_content_type: - "Content-Type: multipart/form-data", - boundary); - - if(result) { - Curl_safefree(boundary); - return result; - } - /* we DO NOT include that line in the total size of the POST, since it'll be - part of the header! */ - - firstform = form; - - do { - - if(size) { - result = AddFormDataf(&form, &size, "\r\n"); - if(result) - break; - } - - /* boundary */ - result = AddFormDataf(&form, &size, "--%s\r\n", boundary); - if(result) - break; - - /* Maybe later this should be disabled when a custom_content_type is - passed, since Content-Disposition is not meaningful for all multipart - types. - */ - result = AddFormDataf(&form, &size, - "Content-Disposition: form-data; name=\""); - if(result) - break; - - result = AddFormData(&form, FORM_DATA, post->name, post->namelength, - &size); - if(result) - break; - - result = AddFormDataf(&form, &size, "\""); - if(result) - break; - - if(post->more) { - /* If used, this is a link to more file names, we must then do - the magic to include several files with the same field name */ - - Curl_safefree(fileboundary); - fileboundary = Curl_FormBoundary(); - if(!fileboundary) { - result = CURLE_OUT_OF_MEMORY; - break; - } - - result = AddFormDataf(&form, &size, - "\r\nContent-Type: multipart/mixed," - " boundary=%s\r\n", - fileboundary); - if(result) - break; - } - - file = post; - - do { - - /* If 'showfilename' is set, that is a faked name passed on to us - to use to in the formpost. If that is not set, the actually used - local file name should be added. */ - - if(post->more) { - /* if multiple-file */ - char *filebasename = NULL; - if(!file->showfilename) { - filebasename = strippath(file->contents); - if(!filebasename) { - result = CURLE_OUT_OF_MEMORY; - break; - } - } - - result = AddFormDataf(&form, &size, - "\r\n--%s\r\nContent-Disposition: " - "attachment; filename=\"%s\"", - fileboundary, - (file->showfilename?file->showfilename: - filebasename)); - Curl_safefree(filebasename); - if(result) - break; - } - else if(post->flags & (HTTPPOST_FILENAME|HTTPPOST_BUFFER| - HTTPPOST_CALLBACK)) { - /* it should be noted that for the HTTPPOST_FILENAME and - HTTPPOST_CALLBACK cases the ->showfilename struct member is always - assigned at this point */ - if(post->showfilename || (post->flags & HTTPPOST_FILENAME)) { - char *filebasename= - (!post->showfilename)?strippath(post->contents):NULL; - - result = AddFormDataf(&form, &size, - "; filename=\"%s\"", - (post->showfilename?post->showfilename: - filebasename)); - Curl_safefree(filebasename); - } - - if(result) - break; - } - - if(file->contenttype) { - /* we have a specified type */ - result = AddFormDataf(&form, &size, - "\r\nContent-Type: %s", - file->contenttype); - if(result) - break; - } - - curList = file->contentheader; - while(curList) { - /* Process the additional headers specified for this form */ - result = AddFormDataf( &form, &size, "\r\n%s", curList->data ); - if(result) - break; - curList = curList->next; - } - if(result) - break; - - result = AddFormDataf(&form, &size, "\r\n\r\n"); - if(result) - break; - - if((post->flags & HTTPPOST_FILENAME) || - (post->flags & HTTPPOST_READFILE)) { - /* we should include the contents from the specified file */ - FILE *fileread; - - fileread = strequal("-", file->contents)? - stdin:fopen(file->contents, "rb"); /* binary read for win32 */ - - /* - * VMS: This only allows for stream files on VMS. Stream files are - * OK, as are FIXED & VAR files WITHOUT implied CC For implied CC, - * every record needs to have a \n appended & 1 added to SIZE - */ - - if(fileread) { - if(fileread != stdin) { - /* close the file */ - fclose(fileread); - /* add the file name only - for later reading from this */ - result = AddFormData(&form, FORM_FILE, file->contents, 0, &size); - } - else { - /* When uploading from stdin, we can't know the size of the file, - * thus must read the full file as before. We *could* use chunked - * transfer-encoding, but that only works for HTTP 1.1 and we - * can't be sure we work with such a server. - */ - size_t nread; - char buffer[512]; - while((nread = fread(buffer, 1, sizeof(buffer), fileread)) != 0) { - result = AddFormData(&form, FORM_CONTENT, buffer, nread, &size); - if(result) - break; - } - } - } - else { - if(data) - failf(data, "couldn't open file \"%s\"", file->contents); - *finalform = NULL; - result = CURLE_READ_ERROR; - } - } - else if(post->flags & HTTPPOST_BUFFER) - /* include contents of buffer */ - result = AddFormData(&form, FORM_CONTENT, post->buffer, - post->bufferlength, &size); - else if(post->flags & HTTPPOST_CALLBACK) - /* the contents should be read with the callback and the size - is set with the contentslength */ - result = AddFormData(&form, FORM_CALLBACK, post->userp, - post->contentslength, &size); - else - /* include the contents we got */ - result = AddFormData(&form, FORM_CONTENT, post->contents, - post->contentslength, &size); - - file = file->more; - } while(file && !result); /* for each specified file for this field */ - - if(result) - break; - - if(post->more) { - /* this was a multiple-file inclusion, make a termination file - boundary: */ - result = AddFormDataf(&form, &size, - "\r\n--%s--", - fileboundary); - if(result) - break; - } - - } while((post = post->next) != NULL); /* for each field */ - - /* end-boundary for everything */ - if(CURLE_OK == result) - result = AddFormDataf(&form, &size, - "\r\n--%s--\r\n", - boundary); - - if(result) { - Curl_formclean(&firstform); - Curl_safefree(fileboundary); - Curl_safefree(boundary); - return result; - } - - *sizep = size; - - Curl_safefree(fileboundary); - Curl_safefree(boundary); - - *finalform = firstform; - - return result; -} - -/* - * Curl_FormInit() inits the struct 'form' points to with the 'formdata' - * and resets the 'sent' counter. - */ -int Curl_FormInit(struct Form *form, struct FormData *formdata ) -{ - if(!formdata) - return 1; /* error */ - - form->data = formdata; - form->sent = 0; - form->fp = NULL; - form->fread_func = ZERO_NULL; - - return 0; -} - -/* - * readfromfile() - * - * The read callback that this function may use can return a value larger than - * 'size' (which then this function returns) that indicates a problem and it - * must be properly dealt with - */ -static size_t readfromfile(struct Form *form, char *buffer, - size_t size) -{ - size_t nread; - bool callback = (form->data->type == FORM_CALLBACK)?TRUE:FALSE; - - if(callback) { - if(form->fread_func == ZERO_NULL) - return 0; - else - nread = form->fread_func(buffer, 1, size, form->data->line); - } - else { - if(!form->fp) { - /* this file hasn't yet been opened */ - form->fp = fopen(form->data->line, "rb"); /* b is for binary */ - if(!form->fp) - return (size_t)-1; /* failure */ - } - nread = fread(buffer, 1, size, form->fp); - } - if(!nread) { - /* this is the last chunk from the file, move on */ - if(form->fp) { - fclose(form->fp); - form->fp = NULL; - } - form->data = form->data->next; - } - - return nread; -} - -/* - * Curl_FormReader() is the fread() emulation function that will be used to - * deliver the formdata to the transfer loop and then sent away to the peer. - */ -size_t Curl_FormReader(char *buffer, - size_t size, - size_t nitems, - FILE *mydata) -{ - struct Form *form; - size_t wantedsize; - size_t gotsize = 0; - - form=(struct Form *)mydata; - - wantedsize = size * nitems; - - if(!form->data) - return 0; /* nothing, error, empty */ - - if((form->data->type == FORM_FILE) || - (form->data->type == FORM_CALLBACK)) { - gotsize = readfromfile(form, buffer, wantedsize); - - if(gotsize) - /* If positive or -1, return. If zero, continue! */ - return gotsize; - } - do { - - if((form->data->length - form->sent ) > wantedsize - gotsize) { - - memcpy(buffer + gotsize , form->data->line + form->sent, - wantedsize - gotsize); - - form->sent += wantedsize-gotsize; - - return wantedsize; - } - - memcpy(buffer+gotsize, - form->data->line + form->sent, - (form->data->length - form->sent) ); - gotsize += form->data->length - form->sent; - - form->sent = 0; - - form->data = form->data->next; /* advance */ - - } while(form->data && (form->data->type < FORM_CALLBACK)); - /* If we got an empty line and we have more data, we proceed to the next - line immediately to avoid returning zero before we've reached the end. */ - - return gotsize; -} - -/* - * Curl_formpostheader() returns the first line of the formpost, the - * request-header part (which is not part of the request-body like the rest of - * the post). - */ -char *Curl_formpostheader(void *formp, size_t *len) -{ - char *header; - struct Form *form=(struct Form *)formp; - - if(!form->data) - return 0; /* nothing, ERROR! */ - - header = form->data->line; - *len = form->data->length; - - form->data = form->data->next; /* advance */ - - return header; -} - -#else /* CURL_DISABLE_HTTP */ -CURLFORMcode curl_formadd(struct curl_httppost **httppost, - struct curl_httppost **last_post, - ...) -{ - (void)httppost; - (void)last_post; - return CURL_FORMADD_DISABLED; -} - -int curl_formget(struct curl_httppost *form, void *arg, - curl_formget_callback append) -{ - (void) form; - (void) arg; - (void) append; - return CURL_FORMADD_DISABLED; -} - -void curl_formfree(struct curl_httppost *form) -{ - (void)form; - /* does nothing HTTP is disabled */ -} - -#endif /* CURL_DISABLE_HTTP */ - -#if !defined(CURL_DISABLE_HTTP) || defined(USE_SSLEAY) - -/* - * Curl_FormBoundary() creates a suitable boundary string and returns an - * allocated one. This is also used by SSL-code so it must be present even - * if HTTP is disabled! - */ -char *Curl_FormBoundary(void) -{ - char *retstring; - size_t i; - - static const char table16[]="0123456789abcdef"; - - retstring = malloc(BOUNDARY_LENGTH+1); - - if(!retstring) - return NULL; /* failed */ - - strcpy(retstring, "----------------------------"); - - for(i=strlen(retstring); i, et al. - * - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_FTP - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_UTSNAME_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -#include -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_if2ip.h" -#include "curl_hostip.h" -#include "curl_progress.h" -#include "curl_transfer.h" -#include "curl_escape.h" -#include "curl_http.h" /* for HTTP proxy tunnel stuff */ -#include "curl_socks.h" -#include "curl_ftp.h" -#include "curl_fileinfo.h" -#include "curl_ftplistparser.h" - -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) -#include "curl_krb4.h" -#endif - -#include "curl_strtoofft.h" -#include "curl_strequal.h" -#include "curl_sslgen.h" -#include "curl_connect.h" -#include "curl_strerror.h" -#include "curl_inet_ntop.h" -#include "curl_inet_pton.h" -#include "curl_select.h" -#include "curl_parsedate.h" /* for the week day and month names */ -#include "curl_sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "curl_multiif.h" -#include "curl_url.h" -#include "curl_rawstr.h" -#include "curl_speedcheck.h" -#include "curl_warnless.h" -#include "curl_http_proxy.h" -#include "curl_non_ascii.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifndef NI_MAXHOST -#define NI_MAXHOST 1025 -#endif -#ifndef INET_ADDRSTRLEN -#define INET_ADDRSTRLEN 16 -#endif - -#ifdef CURL_DISABLE_VERBOSE_STRINGS -#define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt -#endif - -/* Local API functions */ -static void state(struct connectdata *conn, - ftpstate newstate); -static CURLcode ftp_sendquote(struct connectdata *conn, - struct curl_slist *quote); -static CURLcode ftp_quit(struct connectdata *conn); -static CURLcode ftp_parse_url_path(struct connectdata *conn); -static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done); -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void ftp_pasv_verbose(struct connectdata *conn, - Curl_addrinfo *ai, - char *newhost, /* ascii version */ - int port); -#endif -static CURLcode ftp_state_post_rest(struct connectdata *conn); -static CURLcode ftp_state_post_cwd(struct connectdata *conn); -static CURLcode ftp_state_quote(struct connectdata *conn, - bool init, ftpstate instate); -static CURLcode ftp_nb_type(struct connectdata *conn, - bool ascii, ftpstate newstate); -static int ftp_need_type(struct connectdata *conn, - bool ascii); -static CURLcode ftp_do(struct connectdata *conn, bool *done); -static CURLcode ftp_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode ftp_connect(struct connectdata *conn, bool *done); -static CURLcode ftp_disconnect(struct connectdata *conn, bool dead_connection); -static CURLcode ftp_do_more(struct connectdata *conn, bool *completed); -static CURLcode ftp_multi_statemach(struct connectdata *conn, bool *done); -static int ftp_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static CURLcode ftp_doing(struct connectdata *conn, - bool *dophase_done); -static CURLcode ftp_setup_connection(struct connectdata * conn); - -static CURLcode init_wc_data(struct connectdata *conn); -static CURLcode wc_statemach(struct connectdata *conn); - -static void wc_data_dtor(void *ptr); - -static CURLcode ftp_state_post_retr_size(struct connectdata *conn, - curl_off_t filesize); - -static CURLcode ftp_readresp(curl_socket_t sockfd, - struct pingpong *pp, - int *ftpcode, - size_t *size); - -/* easy-to-use macro: */ -#define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z)) != CURLE_OK) \ - return result -#define PPSENDF(x,y,z) if((result = Curl_pp_sendf(x,y,z)) != CURLE_OK) \ - return result - - -/* - * FTP protocol handler. - */ - -const struct Curl_handler Curl_handler_ftp = { - "FTP", /* scheme */ - ftp_setup_connection, /* setup_connection */ - ftp_do, /* do_it */ - ftp_done, /* done */ - ftp_do_more, /* do_more */ - ftp_connect, /* connect_it */ - ftp_multi_statemach, /* connecting */ - ftp_doing, /* doing */ - ftp_getsock, /* proto_getsock */ - ftp_getsock, /* doing_getsock */ - ftp_domore_getsock, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_FTP, /* defport */ - CURLPROTO_FTP, /* protocol */ - PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD - | PROTOPT_NOURLQUERY /* flags */ -}; - - -#ifdef USE_SSL -/* - * FTPS protocol handler. - */ - -const struct Curl_handler Curl_handler_ftps = { - "FTPS", /* scheme */ - ftp_setup_connection, /* setup_connection */ - ftp_do, /* do_it */ - ftp_done, /* done */ - ftp_do_more, /* do_more */ - ftp_connect, /* connect_it */ - ftp_multi_statemach, /* connecting */ - ftp_doing, /* doing */ - ftp_getsock, /* proto_getsock */ - ftp_getsock, /* doing_getsock */ - ftp_domore_getsock, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_FTPS, /* defport */ - CURLPROTO_FTP | CURLPROTO_FTPS, /* protocol */ - PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION | - PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY /* flags */ -}; -#endif - -#ifndef CURL_DISABLE_HTTP -/* - * HTTP-proxyed FTP protocol handler. - */ - -static const struct Curl_handler Curl_handler_ftp_proxy = { - "FTP", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_FTP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - - -#ifdef USE_SSL -/* - * HTTP-proxyed FTPS protocol handler. - */ - -static const struct Curl_handler Curl_handler_ftps_proxy = { - "FTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_FTPS, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; -#endif -#endif - - -/* - * NOTE: back in the old days, we added code in the FTP code that made NOBODY - * requests on files respond with headers passed to the client/stdout that - * looked like HTTP ones. - * - * This approach is not very elegant, it causes confusion and is error-prone. - * It is subject for removal at the next (or at least a future) soname bump. - * Until then you can test the effects of the removal by undefining the - * following define named CURL_FTP_HTTPSTYLE_HEAD. - */ -#define CURL_FTP_HTTPSTYLE_HEAD 1 - -static void freedirs(struct ftp_conn *ftpc) -{ - int i; - if(ftpc->dirs) { - for(i=0; i < ftpc->dirdepth; i++) { - if(ftpc->dirs[i]) { - free(ftpc->dirs[i]); - ftpc->dirs[i]=NULL; - } - } - free(ftpc->dirs); - ftpc->dirs = NULL; - ftpc->dirdepth = 0; - } - if(ftpc->file) { - free(ftpc->file); - ftpc->file = NULL; - } -} - -/* Returns non-zero if the given string contains CR (\r) or LF (\n), - which are not allowed within RFC 959 . - Note: The input string is in the client's encoding which might - not be ASCII, so escape sequences \r & \n must be used instead - of hex values 0x0d & 0x0a. -*/ -static bool isBadFtpString(const char *string) -{ - return ((NULL != strchr(string, '\r')) || - (NULL != strchr(string, '\n'))) ? TRUE : FALSE; -} - -/*********************************************************************** - * - * AcceptServerConnect() - * - * After connection request is received from the server this function is - * called to accept the connection and close the listening socket - * - */ -static CURLcode AcceptServerConnect(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - curl_socket_t sock = conn->sock[SECONDARYSOCKET]; - curl_socket_t s = CURL_SOCKET_BAD; -#ifdef ENABLE_IPV6 - struct Curl_sockaddr_storage add; -#else - struct sockaddr_in add; -#endif - curl_socklen_t size = (curl_socklen_t) sizeof(add); - - if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { - size = sizeof(add); - - s=accept(sock, (struct sockaddr *) &add, &size); - } - Curl_closesocket(conn, sock); /* close the first socket */ - - if(CURL_SOCKET_BAD == s) { - failf(data, "Error accept()ing server connect"); - return CURLE_FTP_PORT_FAILED; - } - infof(data, "Connection accepted from server\n"); - - conn->sock[SECONDARYSOCKET] = s; - curlx_nonblock(s, TRUE); /* enable non-blocking */ - conn->sock_accepted[SECONDARYSOCKET] = TRUE; - - if(data->set.fsockopt) { - int error = 0; - - /* activate callback for setting socket options */ - error = data->set.fsockopt(data->set.sockopt_client, - s, - CURLSOCKTYPE_ACCEPT); - - if(error) { - Curl_closesocket(conn, s); /* close the socket and bail out */ - conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; - return CURLE_ABORTED_BY_CALLBACK; - } - } - - return CURLE_OK; - -} - -/* - * ftp_timeleft_accept() returns the amount of milliseconds left allowed for - * waiting server to connect. If the value is negative, the timeout time has - * already elapsed. - * - * The start time is stored in progress.t_acceptdata - as set with - * Curl_pgrsTime(..., TIMER_STARTACCEPT); - * - */ -static long ftp_timeleft_accept(struct SessionHandle *data) -{ - long timeout_ms = DEFAULT_ACCEPT_TIMEOUT; - long other; - struct timeval now; - - if(data->set.accepttimeout > 0) - timeout_ms = data->set.accepttimeout; - - now = Curl_tvnow(); - - /* check if the generic timeout possibly is set shorter */ - other = Curl_timeleft(data, &now, FALSE); - if(other && (other < timeout_ms)) - /* note that this also works fine for when other happens to be negative - due to it already having elapsed */ - timeout_ms = other; - else { - /* subtract elapsed time */ - timeout_ms -= Curl_tvdiff(now, data->progress.t_acceptdata); - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; - } - - return timeout_ms; -} - - -/*********************************************************************** - * - * ReceivedServerConnect() - * - * After allowing server to connect to us from data port, this function - * checks both data connection for connection establishment and ctrl - * connection for a negative response regarding a failure in connecting - * - */ -static CURLcode ReceivedServerConnect(struct connectdata* conn, bool* received) -{ - struct SessionHandle *data = conn->data; - curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; - curl_socket_t data_sock = conn->sock[SECONDARYSOCKET]; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - int result; - long timeout_ms; - ssize_t nread; - int ftpcode; - - *received = FALSE; - - timeout_ms = ftp_timeleft_accept(data); - infof(data, "Checking for server connect\n"); - if(timeout_ms < 0) { - /* if a timeout was already reached, bail out */ - failf(data, "Accept timeout occurred while waiting server connect"); - return CURLE_FTP_ACCEPT_TIMEOUT; - } - - /* First check whether there is a cached response from server */ - if(pp->cache_size && pp->cache && pp->cache[0] > '3') { - /* Data connection could not be established, let's return */ - infof(data, "There is negative response in cache while serv connect\n"); - Curl_GetFTPResponse(&nread, conn, &ftpcode); - return CURLE_FTP_ACCEPT_FAILED; - } - - result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); - - /* see if the connection request is already here */ - switch (result) { - case -1: /* error */ - /* let's die here */ - failf(data, "Error while waiting for server connect"); - return CURLE_FTP_ACCEPT_FAILED; - case 0: /* Server connect is not received yet */ - break; /* loop */ - default: - - if(result & CURL_CSELECT_IN2) { - infof(data, "Ready to accept data connection from server\n"); - *received = TRUE; - } - else if(result & CURL_CSELECT_IN) { - infof(data, "Ctrl conn has data while waiting for data conn\n"); - Curl_GetFTPResponse(&nread, conn, &ftpcode); - - if(ftpcode/100 > 3) - return CURLE_FTP_ACCEPT_FAILED; - - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - - break; - } /* switch() */ - - return CURLE_OK; -} - - -/*********************************************************************** - * - * InitiateTransfer() - * - * After connection from server is accepted this function is called to - * setup transfer parameters and initiate the data transfer. - * - */ -static CURLcode InitiateTransfer(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - struct FTP *ftp = data->state.proto.ftp; - CURLcode result = CURLE_OK; - - if(conn->ssl[SECONDARYSOCKET].use) { - /* since we only have a plaintext TCP connection here, we must now - * do the TLS stuff */ - infof(data, "Doing the SSL/TLS handshake on the data stream\n"); - result = Curl_ssl_connect(conn, SECONDARYSOCKET); - if(result) - return result; - } - - if(conn->proto.ftpc.state_saved == FTP_STOR) { - *(ftp->bytecountp)=0; - - /* When we know we're uploading a specified file, we can get the file - size prior to the actual upload. */ - - Curl_pgrsSetUploadSize(data, data->set.infilesize); - - /* set the SO_SNDBUF for the secondary socket for those who need it */ - Curl_sndbufset(conn->sock[SECONDARYSOCKET]); - - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ - SECONDARYSOCKET, ftp->bytecountp); - } - else { - /* FTP download: */ - Curl_setup_transfer(conn, SECONDARYSOCKET, - conn->proto.ftpc.retr_size_saved, FALSE, - ftp->bytecountp, -1, NULL); /* no upload here */ - } - - conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ - state(conn, FTP_STOP); - - return CURLE_OK; -} - -/*********************************************************************** - * - * AllowServerConnect() - * - * When we've issue the PORT command, we have told the server to connect - * to us. This function - * - will sit and wait here until the server has connected for easy interface - * - will check whether data connection is established if so it is accepted - * for multi interface - * - */ -static CURLcode AllowServerConnect(struct connectdata *conn, bool *connected) -{ - struct SessionHandle *data = conn->data; - long timeout_ms; - long interval_ms; - CURLcode ret = CURLE_OK; - - *connected = FALSE; - infof(data, "Preparing for accepting server on data port\n"); - - /* Save the time we start accepting server connect */ - Curl_pgrsTime(data, TIMER_STARTACCEPT); - - for(;;) { - timeout_ms = ftp_timeleft_accept(data); - if(timeout_ms < 0) { - /* if a timeout was already reached, bail out */ - failf(data, "Accept timeout occurred while waiting server connect"); - return CURLE_FTP_ACCEPT_TIMEOUT; - } - - /* see if the connection request is already here */ - ret = ReceivedServerConnect(conn, connected); - if(ret) - return ret; - - if(*connected) { - ret = AcceptServerConnect(conn); - if(ret) - return ret; - - ret = InitiateTransfer(conn); - if(ret) - return ret; - - break; /* connection is accepted, break the loop */ - } - else { - if(data->state.used_interface == Curl_if_easy) { - interval_ms = 1000; - if(timeout_ms < interval_ms) - interval_ms = timeout_ms; - - /* sleep for 1 second and then continue */ - Curl_socket_ready(CURL_SOCKET_BAD, CURL_SOCKET_BAD, interval_ms); - } - else { - /* Add timeout to multi handle and break out of the loop */ - if(ret == CURLE_OK && *connected == FALSE) { - if(data->set.accepttimeout > 0) - Curl_expire(data, data->set.accepttimeout); - else - Curl_expire(data, DEFAULT_ACCEPT_TIMEOUT); - } - - break; /* connection was not accepted immediately */ - } - } - } - - return ret; -} - -/* macro to check for a three-digit ftp status code at the start of the - given string */ -#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ - ISDIGIT(line[2])) - -/* macro to check for the last line in an FTP server response */ -#define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3])) - -static int ftp_endofresp(struct pingpong *pp, - int *code) -{ - char *line = pp->linestart_resp; - size_t len = pp->nread_resp; - - if((len > 3) && LASTLINE(line)) { - *code = curlx_sltosi(strtol(line, NULL, 10)); - return 1; - } - return 0; -} - -static CURLcode ftp_readresp(curl_socket_t sockfd, - struct pingpong *pp, - int *ftpcode, /* return the ftp-code if done */ - size_t *size) /* size of the response */ -{ - struct connectdata *conn = pp->conn; - struct SessionHandle *data = conn->data; -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - char * const buf = data->state.buffer; -#endif - CURLcode result = CURLE_OK; - int code; - - result = Curl_pp_readresp(sockfd, pp, &code, size); - -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - /* handle the security-oriented responses 6xx ***/ - /* FIXME: some errorchecking perhaps... ***/ - switch(code) { - case 631: - code = Curl_sec_read_msg(conn, buf, PROT_SAFE); - break; - case 632: - code = Curl_sec_read_msg(conn, buf, PROT_PRIVATE); - break; - case 633: - code = Curl_sec_read_msg(conn, buf, PROT_CONFIDENTIAL); - break; - default: - /* normal ftp stuff we pass through! */ - break; - } -#endif - - /* store the latest code for later retrieval */ - data->info.httpcode=code; - - if(ftpcode) - *ftpcode = code; - - if(421 == code) { - /* 421 means "Service not available, closing control connection." and FTP - * servers use it to signal that idle session timeout has been exceeded. - * If we ignored the response, it could end up hanging in some cases. - * - * This response code can come at any point so having it treated - * generically is a good idea. - */ - infof(data, "We got a 421 - timeout!\n"); - state(conn, FTP_STOP); - return CURLE_OPERATION_TIMEDOUT; - } - - return result; -} - -/* --- parse FTP server responses --- */ - -/* - * Curl_GetFTPResponse() is a BLOCKING function to read the full response - * from a server after a command. - * - */ - -CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ - struct connectdata *conn, - int *ftpcode) /* return the ftp-code */ -{ - /* - * We cannot read just one byte per read() and then go back to select() as - * the OpenSSL read() doesn't grok that properly. - * - * Alas, read as much as possible, split up into lines, use the ending - * line in a response or continue reading. */ - - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - long timeout; /* timeout in milliseconds */ - long interval_ms; - struct SessionHandle *data = conn->data; - CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - size_t nread; - int cache_skip=0; - int value_to_be_ignored=0; - - if(ftpcode) - *ftpcode = 0; /* 0 for errors */ - else - /* make the pointer point to something for the rest of this function */ - ftpcode = &value_to_be_ignored; - - *nreadp=0; - - while(!*ftpcode && !result) { - /* check and reset timeout value every lap */ - timeout = Curl_pp_state_timeout(pp); - - if(timeout <=0 ) { - failf(data, "FTP response timeout"); - return CURLE_OPERATION_TIMEDOUT; /* already too little time */ - } - - interval_ms = 1000; /* use 1 second timeout intervals */ - if(timeout < interval_ms) - interval_ms = timeout; - - /* - * Since this function is blocking, we need to wait here for input on the - * connection and only then we call the response reading function. We do - * timeout at least every second to make the timeout check run. - * - * A caution here is that the ftp_readresp() function has a cache that may - * contain pieces of a response from the previous invoke and we need to - * make sure we don't just wait for input while there is unhandled data in - * that cache. But also, if the cache is there, we call ftp_readresp() and - * the cache wasn't good enough to continue we must not just busy-loop - * around this function. - * - */ - - if(pp->cache && (cache_skip < 2)) { - /* - * There's a cache left since before. We then skipping the wait for - * socket action, unless this is the same cache like the previous round - * as then the cache was deemed not enough to act on and we then need to - * wait for more data anyway. - */ - } - else { - switch (Curl_socket_ready(sockfd, CURL_SOCKET_BAD, interval_ms)) { - case -1: /* select() error, stop reading */ - failf(data, "FTP response aborted due to select/poll error: %d", - SOCKERRNO); - return CURLE_RECV_ERROR; - - case 0: /* timeout */ - if(Curl_pgrsUpdate(conn)) - return CURLE_ABORTED_BY_CALLBACK; - continue; /* just continue in our loop for the timeout duration */ - - default: /* for clarity */ - break; - } - } - result = ftp_readresp(sockfd, pp, ftpcode, &nread); - if(result) - break; - - if(!nread && pp->cache) - /* bump cache skip counter as on repeated skips we must wait for more - data */ - cache_skip++; - else - /* when we got data or there is no cache left, we reset the cache skip - counter */ - cache_skip=0; - - *nreadp += nread; - - } /* while there's buffer left and loop is requested */ - - pp->pending_resp = FALSE; - - return result; -} - -/* This is the ONLY way to change FTP state! */ -static void state(struct connectdata *conn, - ftpstate newstate) -{ -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ - static const char * const names[]={ - "STOP", - "WAIT220", - "AUTH", - "USER", - "PASS", - "ACCT", - "PBSZ", - "PROT", - "CCC", - "PWD", - "SYST", - "NAMEFMT", - "QUOTE", - "RETR_PREQUOTE", - "STOR_PREQUOTE", - "POSTQUOTE", - "CWD", - "MKD", - "MDTM", - "TYPE", - "LIST_TYPE", - "RETR_TYPE", - "STOR_TYPE", - "SIZE", - "RETR_SIZE", - "STOR_SIZE", - "REST", - "RETR_REST", - "PORT", - "PRET", - "PASV", - "LIST", - "RETR", - "STOR", - "QUIT" - }; -#endif - struct ftp_conn *ftpc = &conn->proto.ftpc; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(ftpc->state != newstate) - infof(conn->data, "FTP %p state change from %s to %s\n", - ftpc, names[ftpc->state], names[newstate]); -#endif - ftpc->state = newstate; -} - -static CURLcode ftp_state_user(struct connectdata *conn) -{ - CURLcode result; - struct FTP *ftp = conn->data->state.proto.ftp; - /* send USER */ - PPSENDF(&conn->proto.ftpc.pp, "USER %s", ftp->user?ftp->user:""); - - state(conn, FTP_USER); - conn->data->state.ftp_trying_alternative = FALSE; - - return CURLE_OK; -} - -static CURLcode ftp_state_pwd(struct connectdata *conn) -{ - CURLcode result; - - /* send PWD to discover our entry point */ - PPSENDF(&conn->proto.ftpc.pp, "PWD", NULL); - state(conn, FTP_PWD); - - return CURLE_OK; -} - -/* For the FTP "protocol connect" and "doing" phases only */ -static int ftp_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks); -} - -/* For the FTP "DO_MORE" phase only */ -static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(!numsocks) - return GETSOCK_BLANK; - - /* When in DO_MORE state, we could be either waiting for us to connect to a - remote site, or we could wait for that site to connect to us. Or just - handle ordinary commands. - - When waiting for a connect, we will be in FTP_STOP state and then we wait - for the secondary socket to become writeable. If we're in another state, - we're still handling commands on the control (primary) connection. - - */ - - switch(ftpc->state) { - case FTP_STOP: - break; - default: - return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks); - } - - socks[0] = conn->sock[SECONDARYSOCKET]; - if(ftpc->wait_data_conn) { - socks[1] = conn->sock[FIRSTSOCKET]; - return GETSOCK_READSOCK(0) | GETSOCK_READSOCK(1); - } - - return GETSOCK_READSOCK(0); -} - -/* This is called after the FTP_QUOTE state is passed. - - ftp_state_cwd() sends the range of CWD commands to the server to change to - the correct directory. It may also need to send MKD commands to create - missing ones, if that option is enabled. -*/ -static CURLcode ftp_state_cwd(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(ftpc->cwddone) - /* already done and fine */ - result = ftp_state_post_cwd(conn); - else { - ftpc->count2 = 0; /* count2 counts failed CWDs */ - - /* count3 is set to allow a MKD to fail once. In the case when first CWD - fails and then MKD fails (due to another session raced it to create the - dir) this then allows for a second try to CWD to it */ - ftpc->count3 = (conn->data->set.ftp_create_missing_dirs==2)?1:0; - - if(conn->bits.reuse && ftpc->entrypath) { - /* This is a re-used connection. Since we change directory to where the - transfer is taking place, we must first get back to the original dir - where we ended up after login: */ - ftpc->count1 = 0; /* we count this as the first path, then we add one - for all upcoming ones in the ftp->dirs[] array */ - PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->entrypath); - state(conn, FTP_CWD); - } - else { - if(ftpc->dirdepth) { - ftpc->count1 = 1; - /* issue the first CWD, the rest is sent when the CWD responses are - received... */ - PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->dirs[ftpc->count1 -1]); - state(conn, FTP_CWD); - } - else { - /* No CWD necessary */ - result = ftp_state_post_cwd(conn); - } - } - } - return result; -} - -typedef enum { - EPRT, - PORT, - DONE -} ftpport; - -static CURLcode ftp_state_use_port(struct connectdata *conn, - ftpport fcmd) /* start with this */ - -{ - CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct SessionHandle *data=conn->data; - curl_socket_t portsock= CURL_SOCKET_BAD; - char myhost[256] = ""; - - struct Curl_sockaddr_storage ss; - Curl_addrinfo *res, *ai; - curl_socklen_t sslen; - char hbuf[NI_MAXHOST]; - struct sockaddr *sa=(struct sockaddr *)&ss; - struct sockaddr_in * const sa4 = (void *)sa; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 * const sa6 = (void *)sa; -#endif - char tmp[1024]; - static const char mode[][5] = { "EPRT", "PORT" }; - int rc; - int error; - char *host = NULL; - char *string_ftpport = data->set.str[STRING_FTPPORT]; - struct Curl_dns_entry *h=NULL; - unsigned short port_min = 0; - unsigned short port_max = 0; - unsigned short port; - bool possibly_non_local = TRUE; - - char *addr = NULL; - - /* Step 1, figure out what is requested, - * accepted format : - * (ipv4|ipv6|domain|interface)?(:port(-range)?)? - */ - - if(data->set.str[STRING_FTPPORT] && - (strlen(data->set.str[STRING_FTPPORT]) > 1)) { - -#ifdef ENABLE_IPV6 - size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ? - INET6_ADDRSTRLEN : strlen(string_ftpport); -#else - size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ? - INET_ADDRSTRLEN : strlen(string_ftpport); -#endif - char *ip_start = string_ftpport; - char *ip_end = NULL; - char *port_start = NULL; - char *port_sep = NULL; - - addr = calloc(addrlen+1, 1); - if(!addr) - return CURLE_OUT_OF_MEMORY; - -#ifdef ENABLE_IPV6 - if(*string_ftpport == '[') { - /* [ipv6]:port(-range) */ - ip_start = string_ftpport + 1; - if((ip_end = strchr(string_ftpport, ']')) != NULL ) - strncpy(addr, ip_start, ip_end - ip_start); - } - else -#endif - if(*string_ftpport == ':') { - /* :port */ - ip_end = string_ftpport; - } - else if((ip_end = strchr(string_ftpport, ':')) != NULL) { - /* either ipv6 or (ipv4|domain|interface):port(-range) */ -#ifdef ENABLE_IPV6 - if(Curl_inet_pton(AF_INET6, string_ftpport, sa6) == 1) { - /* ipv6 */ - port_min = port_max = 0; - strcpy(addr, string_ftpport); - ip_end = NULL; /* this got no port ! */ - } - else -#endif - /* (ipv4|domain|interface):port(-range) */ - strncpy(addr, string_ftpport, ip_end - ip_start ); - } - else - /* ipv4|interface */ - strcpy(addr, string_ftpport); - - /* parse the port */ - if(ip_end != NULL) { - if((port_start = strchr(ip_end, ':')) != NULL) { - port_min = curlx_ultous(strtoul(port_start+1, NULL, 10)); - if((port_sep = strchr(port_start, '-')) != NULL) { - port_max = curlx_ultous(strtoul(port_sep + 1, NULL, 10)); - } - else - port_max = port_min; - } - } - - /* correct errors like: - * :1234-1230 - * :-4711 , in this case port_min is (unsigned)-1, - * therefore port_min > port_max for all cases - * but port_max = (unsigned)-1 - */ - if(port_min > port_max ) - port_min = port_max = 0; - - - if(*addr != '\0') { - /* attempt to get the address of the given interface name */ - if(!Curl_if2ip(conn->ip_addr->ai_family, addr, - hbuf, sizeof(hbuf))) - /* not an interface, use the given string as host name instead */ - host = addr; - else - host = hbuf; /* use the hbuf for host name */ - } - else - /* there was only a port(-range) given, default the host */ - host = NULL; - } /* data->set.ftpport */ - - if(!host) { - /* not an interface and not a host name, get default by extracting - the IP from the control connection */ - - sslen = sizeof(ss); - if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(conn, SOCKERRNO) ); - Curl_safefree(addr); - return CURLE_FTP_PORT_FAILED; - } - switch(sa->sa_family) { -#ifdef ENABLE_IPV6 - case AF_INET6: - Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); - break; -#endif - default: - Curl_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, sizeof(hbuf)); - break; - } - host = hbuf; /* use this host name */ - possibly_non_local = FALSE; /* we know it is local now */ - } - - /* resolv ip/host to ip */ - rc = Curl_resolv(conn, host, 0, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(conn, &h); - if(h) { - res = h->addr; - /* when we return from this function, we can forget about this entry - to we can unlock it now already */ - Curl_resolv_unlock(data, h); - } /* (h) */ - else - res = NULL; /* failure! */ - - if(res == NULL) { - failf(data, "failed to resolve the address provided to PORT: %s", host); - Curl_safefree(addr); - return CURLE_FTP_PORT_FAILED; - } - - Curl_safefree(addr); - host = NULL; - - /* step 2, create a socket for the requested address */ - - portsock = CURL_SOCKET_BAD; - error = 0; - for(ai = res; ai; ai = ai->ai_next) { - result = Curl_socket(conn, ai, NULL, &portsock); - if(result) { - error = SOCKERRNO; - continue; - } - break; - } - if(!ai) { - failf(data, "socket failure: %s", Curl_strerror(conn, error)); - return CURLE_FTP_PORT_FAILED; - } - - /* step 3, bind to a suitable local address */ - - memcpy(sa, ai->ai_addr, ai->ai_addrlen); - sslen = ai->ai_addrlen; - - for(port = port_min; port <= port_max;) { - if(sa->sa_family == AF_INET) - sa4->sin_port = htons(port); -#ifdef ENABLE_IPV6 - else - sa6->sin6_port = htons(port); -#endif - /* Try binding the given address. */ - if(bind(portsock, sa, sslen) ) { - /* It failed. */ - error = SOCKERRNO; - if(possibly_non_local && (error == EADDRNOTAVAIL)) { - /* The requested bind address is not local. Use the address used for - * the control connection instead and restart the port loop - */ - - infof(data, "bind(port=%hu) on non-local address failed: %s\n", port, - Curl_strerror(conn, error) ); - - sslen = sizeof(ss); - if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(conn, SOCKERRNO) ); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - port = port_min; - possibly_non_local = FALSE; /* don't try this again */ - continue; - } - else if(error != EADDRINUSE && error != EACCES) { - failf(data, "bind(port=%hu) failed: %s", port, - Curl_strerror(conn, error) ); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - } - else - break; - - port++; - } - - /* maybe all ports were in use already*/ - if(port > port_max) { - failf(data, "bind() failed, we ran out of ports!"); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - - /* get the name again after the bind() so that we can extract the - port number it uses now */ - sslen = sizeof(ss); - if(getsockname(portsock, (struct sockaddr *)sa, &sslen)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(conn, SOCKERRNO) ); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - - /* step 4, listen on the socket */ - - if(listen(portsock, 1)) { - failf(data, "socket failure: %s", Curl_strerror(conn, SOCKERRNO)); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - - /* step 5, send the proper FTP command */ - - /* get a plain printable version of the numerical address to work with - below */ - Curl_printable_address(ai, myhost, sizeof(myhost)); - -#ifdef ENABLE_IPV6 - if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) - /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the - request and enable EPRT again! */ - conn->bits.ftp_use_eprt = TRUE; -#endif - - for(; fcmd != DONE; fcmd++) { - - if(!conn->bits.ftp_use_eprt && (EPRT == fcmd)) - /* if disabled, goto next */ - continue; - - if((PORT == fcmd) && sa->sa_family != AF_INET) - /* PORT is ipv4 only */ - continue; - - switch (sa->sa_family) { - case AF_INET: - port = ntohs(sa4->sin_port); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - port = ntohs(sa6->sin6_port); - break; -#endif - default: - continue; /* might as well skip this */ - } - - if(EPRT == fcmd) { - /* - * Two fine examples from RFC2428; - * - * EPRT |1|132.235.1.2|6275| - * - * EPRT |2|1080::8:800:200C:417A|5282| - */ - - result = Curl_pp_sendf(&ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd], - sa->sa_family == AF_INET?1:2, - myhost, port); - if(result) { - failf(data, "Failure sending EPRT command: %s", - curl_easy_strerror(result)); - Curl_closesocket(conn, portsock); - /* don't retry using PORT */ - ftpc->count1 = PORT; - /* bail out */ - state(conn, FTP_STOP); - return result; - } - break; - } - else if(PORT == fcmd) { - char *source = myhost; - char *dest = tmp; - - /* translate x.x.x.x to x,x,x,x */ - while(source && *source) { - if(*source == '.') - *dest=','; - else - *dest = *source; - dest++; - source++; - } - *dest = 0; - snprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff)); - - result = Curl_pp_sendf(&ftpc->pp, "%s %s", mode[fcmd], tmp); - if(result) { - failf(data, "Failure sending PORT command: %s", - curl_easy_strerror(result)); - Curl_closesocket(conn, portsock); - /* bail out */ - state(conn, FTP_STOP); - return result; - } - break; - } - } - - /* store which command was sent */ - ftpc->count1 = fcmd; - - /* we set the secondary socket variable to this for now, it is only so that - the cleanup function will close it in case we fail before the true - secondary stuff is made */ - if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) - Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); - conn->sock[SECONDARYSOCKET] = portsock; - - /* this tcpconnect assignment below is a hackish work-around to make the - multi interface with active FTP work - as it will not wait for a - (passive) connect in Curl_is_connected(). - - The *proper* fix is to make sure that the active connection from the - server is done in a non-blocking way. Currently, it is still BLOCKING. - */ - conn->bits.tcpconnect[SECONDARYSOCKET] = TRUE; - - state(conn, FTP_PORT); - return result; -} - -static CURLcode ftp_state_use_pasv(struct connectdata *conn) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result = CURLE_OK; - /* - Here's the excecutive summary on what to do: - - PASV is RFC959, expect: - 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) - - LPSV is RFC1639, expect: - 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) - - EPSV is RFC2428, expect: - 229 Entering Extended Passive Mode (|||port|) - - */ - - static const char mode[][5] = { "EPSV", "PASV" }; - int modeoff; - -#ifdef PF_INET6 - if(!conn->bits.ftp_use_epsv && conn->bits.ipv6) - /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the - request and enable EPSV again! */ - conn->bits.ftp_use_epsv = TRUE; -#endif - - modeoff = conn->bits.ftp_use_epsv?0:1; - - PPSENDF(&ftpc->pp, "%s", mode[modeoff]); - - ftpc->count1 = modeoff; - state(conn, FTP_PASV); - infof(conn->data, "Connect data stream passively\n"); - - return result; -} - -/* REST is the last command in the chain of commands when a "head"-like - request is made. Thus, if an actual transfer is to be made this is where - we take off for real. */ -static CURLcode ftp_state_post_rest(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->state.proto.ftp; - struct SessionHandle *data = conn->data; - - if(ftp->transfer != FTPTRANSFER_BODY) { - /* doesn't transfer any data */ - - /* still possibly do PRE QUOTE jobs */ - state(conn, FTP_RETR_PREQUOTE); - result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE); - } - else if(data->set.ftp_use_port) { - /* We have chosen to use the PORT (or similar) command */ - result = ftp_state_use_port(conn, EPRT); - } - else { - /* We have chosen (this is default) to use the PASV (or similar) command */ - if(data->set.ftp_use_pret) { - /* The user has requested that we send a PRET command - to prepare the server for the upcoming PASV */ - if(!conn->proto.ftpc.file) { - PPSENDF(&conn->proto.ftpc.pp, "PRET %s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->set.ftp_list_only?"NLST":"LIST")); - } - else if(data->set.upload) { - PPSENDF(&conn->proto.ftpc.pp, "PRET STOR %s", conn->proto.ftpc.file); - } - else { - PPSENDF(&conn->proto.ftpc.pp, "PRET RETR %s", conn->proto.ftpc.file); - } - state(conn, FTP_PRET); - } - else { - result = ftp_state_use_pasv(conn); - } - } - return result; -} - -static CURLcode ftp_state_post_size(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if((ftp->transfer != FTPTRANSFER_BODY) && ftpc->file) { - /* if a "head"-like request is being made (on a file) */ - - /* Determine if server can respond to REST command and therefore - whether it supports range */ - PPSENDF(&conn->proto.ftpc.pp, "REST %d", 0); - - state(conn, FTP_REST); - } - else - result = ftp_state_post_rest(conn); - - return result; -} - -static CURLcode ftp_state_post_type(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if((ftp->transfer == FTPTRANSFER_INFO) && ftpc->file) { - /* if a "head"-like request is being made (on a file) */ - - /* we know ftpc->file is a valid pointer to a file name */ - PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); - - state(conn, FTP_SIZE); - } - else - result = ftp_state_post_size(conn); - - return result; -} - -static CURLcode ftp_state_post_listtype(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - /* If this output is to be machine-parsed, the NLST command might be better - to use, since the LIST command output is not specified or standard in any - way. It has turned out that the NLST list output is not the same on all - servers either... */ - - /* - if FTPFILE_NOCWD was specified, we are currently in - the user's home directory, so we should add the path - as argument for the LIST / NLST / or custom command. - Whether the server will support this, is uncertain. - - The other ftp_filemethods will CWD into dir/dir/ first and - then just do LIST (in that case: nothing to do here) - */ - char *cmd,*lstArg,*slashPos; - - lstArg = NULL; - if((data->set.ftp_filemethod == FTPFILE_NOCWD) && - data->state.path && - data->state.path[0] && - strchr(data->state.path,'/')) { - - lstArg = strdup(data->state.path); - if(!lstArg) - return CURLE_OUT_OF_MEMORY; - - /* Check if path does not end with /, as then we cut off the file part */ - if(lstArg[strlen(lstArg) - 1] != '/') { - - /* chop off the file part if format is dir/dir/file */ - slashPos = strrchr(lstArg,'/'); - if(slashPos) - *(slashPos+1) = '\0'; - } - } - - cmd = aprintf( "%s%s%s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->set.ftp_list_only?"NLST":"LIST"), - lstArg? " ": "", - lstArg? lstArg: "" ); - - if(!cmd) { - if(lstArg) - free(lstArg); - return CURLE_OUT_OF_MEMORY; - } - - result = Curl_pp_sendf(&conn->proto.ftpc.pp, "%s", cmd); - - if(lstArg) - free(lstArg); - - free(cmd); - - if(result != CURLE_OK) - return result; - - state(conn, FTP_LIST); - - return result; -} - -static CURLcode ftp_state_post_retrtype(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - - /* We've sent the TYPE, now we must send the list of prequote strings */ - - result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE); - - return result; -} - -static CURLcode ftp_state_post_stortype(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - - /* We've sent the TYPE, now we must send the list of prequote strings */ - - result = ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE); - - return result; -} - -static CURLcode ftp_state_post_mdtm(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->state.proto.ftp; - struct SessionHandle *data = conn->data; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - /* If we have selected NOBODY and HEADER, it means that we only want file - information. Which in FTP can't be much more than the file size and - date. */ - if(data->set.opt_no_body && ftpc->file && - ftp_need_type(conn, data->set.prefer_ascii)) { - /* The SIZE command is _not_ RFC 959 specified, and therefor many servers - may not support it! It is however the only way we have to get a file's - size! */ - - ftp->transfer = FTPTRANSFER_INFO; - /* this means no actual transfer will be made */ - - /* Some servers return different sizes for different modes, and thus we - must set the proper type before we check the size */ - result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_TYPE); - if(result) - return result; - } - else - result = ftp_state_post_type(conn); - - return result; -} - -/* This is called after the CWD commands have been done in the beginning of - the DO phase */ -static CURLcode ftp_state_post_cwd(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - /* Requested time of file or time-depended transfer? */ - if((data->set.get_filetime || data->set.timecondition) && ftpc->file) { - - /* we have requested to get the modified-time of the file, this is a white - spot as the MDTM is not mentioned in RFC959 */ - PPSENDF(&ftpc->pp, "MDTM %s", ftpc->file); - - state(conn, FTP_MDTM); - } - else - result = ftp_state_post_mdtm(conn); - - return result; -} - - -/* This is called after the TYPE and possible quote commands have been sent */ -static CURLcode ftp_state_ul_setup(struct connectdata *conn, - bool sizechecked) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->state.proto.ftp; - struct SessionHandle *data = conn->data; - struct ftp_conn *ftpc = &conn->proto.ftpc; - int seekerr = CURL_SEEKFUNC_OK; - - if((data->state.resume_from && !sizechecked) || - ((data->state.resume_from > 0) && sizechecked)) { - /* we're about to continue the uploading of a file */ - /* 1. get already existing file's size. We use the SIZE command for this - which may not exist in the server! The SIZE command is not in - RFC959. */ - - /* 2. This used to set REST. But since we can do append, we - don't another ftp command. We just skip the source file - offset and then we APPEND the rest on the file instead */ - - /* 3. pass file-size number of bytes in the source file */ - /* 4. lower the infilesize counter */ - /* => transfer as usual */ - - if(data->state.resume_from < 0 ) { - /* Got no given size to start from, figure it out */ - PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); - state(conn, FTP_STOR_SIZE); - return result; - } - - /* enable append */ - data->set.ftp_append = TRUE; - - /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_FTP_COULDNT_USE_REST; - } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - else { - curl_off_t passed=0; - do { - size_t readthisamountnow = - (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? - BUFSIZE : curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - conn->fread_func(data->state.buffer, 1, readthisamountnow, - conn->fread_in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Failed to read data"); - return CURLE_FTP_COULDNT_USE_REST; - } - } while(passed < data->state.resume_from); - } - } - /* now, decrease the size of the read */ - if(data->set.infilesize>0) { - data->set.infilesize -= data->state.resume_from; - - if(data->set.infilesize <= 0) { - infof(data, "File already completely uploaded\n"); - - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - - /* Set ->transfer so that we won't get any error in - * ftp_done() because we didn't transfer anything! */ - ftp->transfer = FTPTRANSFER_NONE; - - state(conn, FTP_STOP); - return CURLE_OK; - } - } - /* we've passed, proceed as normal */ - } /* resume_from */ - - PPSENDF(&ftpc->pp, data->set.ftp_append?"APPE %s":"STOR %s", - ftpc->file); - - state(conn, FTP_STOR); - - return result; -} - -static CURLcode ftp_state_quote(struct connectdata *conn, - bool init, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct FTP *ftp = data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - bool quote=FALSE; - struct curl_slist *item; - - switch(instate) { - case FTP_QUOTE: - default: - item = data->set.quote; - break; - case FTP_RETR_PREQUOTE: - case FTP_STOR_PREQUOTE: - item = data->set.prequote; - break; - case FTP_POSTQUOTE: - item = data->set.postquote; - break; - } - - /* - * This state uses: - * 'count1' to iterate over the commands to send - * 'count2' to store wether to allow commands to fail - */ - - if(init) - ftpc->count1 = 0; - else - ftpc->count1++; - - if(item) { - int i = 0; - - /* Skip count1 items in the linked list */ - while((i< ftpc->count1) && item) { - item = item->next; - i++; - } - if(item) { - char *cmd = item->data; - if(cmd[0] == '*') { - cmd++; - ftpc->count2 = 1; /* the sent command is allowed to fail */ - } - else - ftpc->count2 = 0; /* failure means cancel operation */ - - PPSENDF(&ftpc->pp, "%s", cmd); - state(conn, instate); - quote = TRUE; - } - } - - if(!quote) { - /* No more quote to send, continue to ... */ - switch(instate) { - case FTP_QUOTE: - default: - result = ftp_state_cwd(conn); - break; - case FTP_RETR_PREQUOTE: - if(ftp->transfer != FTPTRANSFER_BODY) - state(conn, FTP_STOP); - else { - if(ftpc->known_filesize != -1) { - Curl_pgrsSetDownloadSize(data, ftpc->known_filesize); - result = ftp_state_post_retr_size(conn, ftpc->known_filesize); - } - else { - PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); - state(conn, FTP_RETR_SIZE); - } - } - break; - case FTP_STOR_PREQUOTE: - result = ftp_state_ul_setup(conn, FALSE); - break; - case FTP_POSTQUOTE: - break; - } - } - - return result; -} - -/* called from ftp_state_pasv_resp to switch to PASV in case of EPSV - problems */ -static CURLcode ftp_epsv_disable(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - infof(conn->data, "got positive EPSV response, but can't connect. " - "Disabling EPSV\n"); - /* disable it for next transfer */ - conn->bits.ftp_use_epsv = FALSE; - conn->data->state.errorbuf = FALSE; /* allow error message to get - rewritten */ - PPSENDF(&conn->proto.ftpc.pp, "PASV", NULL); - conn->proto.ftpc.count1++; - /* remain in the FTP_PASV state */ - return result; -} - -static CURLcode ftp_state_pasv_resp(struct connectdata *conn, - int ftpcode) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result; - struct SessionHandle *data=conn->data; - Curl_addrinfo *conninfo; - struct Curl_dns_entry *addr=NULL; - int rc; - unsigned short connectport; /* the local port connect() should use! */ - unsigned short newport=0; /* remote port */ - bool connected; - - /* newhost must be able to hold a full IP-style address in ASCII, which - in the IPv6 case means 5*8-1 = 39 letters */ -#define NEWHOST_BUFSIZE 48 - char newhost[NEWHOST_BUFSIZE]; - char *str=&data->state.buffer[4]; /* start on the first letter */ - - if((ftpc->count1 == 0) && - (ftpcode == 229)) { - /* positive EPSV response */ - char *ptr = strchr(str, '('); - if(ptr) { - unsigned int num; - char separator[4]; - ptr++; - if(5 == sscanf(ptr, "%c%c%c%u%c", - &separator[0], - &separator[1], - &separator[2], - &num, - &separator[3])) { - const char sep1 = separator[0]; - int i; - - /* The four separators should be identical, or else this is an oddly - formatted reply and we bail out immediately. */ - for(i=1; i<4; i++) { - if(separator[i] != sep1) { - ptr=NULL; /* set to NULL to signal error */ - break; - } - } - if(ptr) { - newport = (unsigned short)(num & 0xffff); - - if(conn->bits.tunnel_proxy || - conn->proxytype == CURLPROXY_SOCKS5 || - conn->proxytype == CURLPROXY_SOCKS5_HOSTNAME || - conn->proxytype == CURLPROXY_SOCKS4 || - conn->proxytype == CURLPROXY_SOCKS4A) - /* proxy tunnel -> use other host info because ip_addr_str is the - proxy address not the ftp host */ - snprintf(newhost, sizeof(newhost), "%s", conn->host.name); - else - /* use the same IP we are already connected to */ - snprintf(newhost, NEWHOST_BUFSIZE, "%s", conn->ip_addr_str); - } - } - else - ptr=NULL; - } - if(!ptr) { - failf(data, "Weirdly formatted EPSV reply"); - return CURLE_FTP_WEIRD_PASV_REPLY; - } - } - else if((ftpc->count1 == 1) && - (ftpcode == 227)) { - /* positive PASV response */ - int ip[4]; - int port[2]; - - /* - * Scan for a sequence of six comma-separated numbers and use them as - * IP+port indicators. - * - * Found reply-strings include: - * "227 Entering Passive Mode (127,0,0,1,4,51)" - * "227 Data transfer will passively listen to 127,0,0,1,4,51" - * "227 Entering passive mode. 127,0,0,1,4,51" - */ - while(*str) { - if(6 == sscanf(str, "%d,%d,%d,%d,%d,%d", - &ip[0], &ip[1], &ip[2], &ip[3], - &port[0], &port[1])) - break; - str++; - } - - if(!*str) { - failf(data, "Couldn't interpret the 227-response"); - return CURLE_FTP_WEIRD_227_FORMAT; - } - - /* we got OK from server */ - if(data->set.ftp_skip_ip) { - /* told to ignore the remotely given IP but instead use the one we used - for the control connection */ - infof(data, "Skips %d.%d.%d.%d for data connection, uses %s instead\n", - ip[0], ip[1], ip[2], ip[3], - conn->ip_addr_str); - if(conn->bits.tunnel_proxy || - conn->proxytype == CURLPROXY_SOCKS5 || - conn->proxytype == CURLPROXY_SOCKS5_HOSTNAME || - conn->proxytype == CURLPROXY_SOCKS4 || - conn->proxytype == CURLPROXY_SOCKS4A) - /* proxy tunnel -> use other host info because ip_addr_str is the - proxy address not the ftp host */ - snprintf(newhost, sizeof(newhost), "%s", conn->host.name); - else - snprintf(newhost, sizeof(newhost), "%s", conn->ip_addr_str); - } - else - snprintf(newhost, sizeof(newhost), - "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - newport = (unsigned short)(((port[0]<<8) + port[1]) & 0xffff); - } - else if(ftpc->count1 == 0) { - /* EPSV failed, move on to PASV */ - - /* disable it for next transfer */ - conn->bits.ftp_use_epsv = FALSE; - infof(data, "disabling EPSV usage\n"); - - PPSENDF(&ftpc->pp, "PASV", NULL); - ftpc->count1++; - /* remain in the FTP_PASV state */ - return result; - } - else { - failf(data, "Bad PASV/EPSV response: %03d", ftpcode); - return CURLE_FTP_WEIRD_PASV_REPLY; - } - - if(data->set.str[STRING_PROXY] && *data->set.str[STRING_PROXY]) { - /* - * This is a tunnel through a http proxy and we need to connect to the - * proxy again here. - * - * We don't want to rely on a former host lookup that might've expired - * now, instead we remake the lookup here and now! - */ - rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING, ignores the return code but 'addr' will be NULL in - case of failure */ - (void)Curl_resolver_wait_resolv(conn, &addr); - - connectport = - (unsigned short)conn->port; /* we connect to the proxy's port */ - - if(!addr) { - failf(data, "Can't resolve proxy host %s:%hu", - conn->proxy.name, connectport); - return CURLE_FTP_CANT_GET_HOST; - } - } - else { - /* normal, direct, ftp connection */ - rc = Curl_resolv(conn, newhost, newport, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING */ - (void)Curl_resolver_wait_resolv(conn, &addr); - - connectport = newport; /* we connect to the remote port */ - - if(!addr) { - failf(data, "Can't resolve new host %s:%hu", newhost, connectport); - return CURLE_FTP_CANT_GET_HOST; - } - } - - result = Curl_connecthost(conn, - addr, - &conn->sock[SECONDARYSOCKET], - &conninfo, - &connected); - - Curl_resolv_unlock(data, addr); /* we're done using this address */ - - if(result) { - if(ftpc->count1 == 0 && ftpcode == 229) - return ftp_epsv_disable(conn); - - return result; - } - - conn->bits.tcpconnect[SECONDARYSOCKET] = connected; - - /* - * When this is used from the multi interface, this might've returned with - * the 'connected' set to FALSE and thus we are now awaiting a non-blocking - * connect to connect and we should not be "hanging" here waiting. - */ - - if(data->set.verbose) - /* this just dumps information about this second connection */ - ftp_pasv_verbose(conn, conninfo, newhost, connectport); - - switch(conn->proxytype) { - /* FIX: this MUST wait for a proper connect first if 'connected' is - * FALSE */ - case CURLPROXY_SOCKS5: - case CURLPROXY_SOCKS5_HOSTNAME: - result = Curl_SOCKS5(conn->proxyuser, conn->proxypasswd, newhost, newport, - SECONDARYSOCKET, conn); - break; - case CURLPROXY_SOCKS4: - result = Curl_SOCKS4(conn->proxyuser, newhost, newport, - SECONDARYSOCKET, conn, FALSE); - break; - case CURLPROXY_SOCKS4A: - result = Curl_SOCKS4(conn->proxyuser, newhost, newport, - SECONDARYSOCKET, conn, TRUE); - break; - case CURLPROXY_HTTP: - case CURLPROXY_HTTP_1_0: - /* do nothing here. handled later. */ - break; - default: - failf(data, "unknown proxytype option given"); - result = CURLE_COULDNT_CONNECT; - break; - } - - if(result) { - if(ftpc->count1 == 0 && ftpcode == 229) - return ftp_epsv_disable(conn); - return result; - } - - if(conn->bits.tunnel_proxy && conn->bits.httpproxy) { - /* FIX: this MUST wait for a proper connect first if 'connected' is - * FALSE */ - - /* BLOCKING */ - /* We want "seamless" FTP operations through HTTP proxy tunnel */ - - /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the member - * conn->proto.http; we want FTP through HTTP and we have to change the - * member temporarily for connecting to the HTTP proxy. After - * Curl_proxyCONNECT we have to set back the member to the original struct - * FTP pointer - */ - struct HTTP http_proxy; - struct FTP *ftp_save = data->state.proto.ftp; - memset(&http_proxy, 0, sizeof(http_proxy)); - data->state.proto.http = &http_proxy; - - result = Curl_proxyCONNECT(conn, SECONDARYSOCKET, newhost, newport); - - data->state.proto.ftp = ftp_save; - - if(result) - return result; - } - - conn->bits.tcpconnect[SECONDARYSOCKET] = TRUE; - - state(conn, FTP_STOP); /* this phase is completed */ - - return result; -} - -static CURLcode ftp_state_port_resp(struct connectdata *conn, - int ftpcode) -{ - struct SessionHandle *data = conn->data; - struct ftp_conn *ftpc = &conn->proto.ftpc; - ftpport fcmd = (ftpport)ftpc->count1; - CURLcode result = CURLE_OK; - - if(ftpcode != 200) { - /* the command failed */ - - if(EPRT == fcmd) { - infof(data, "disabling EPRT usage\n"); - conn->bits.ftp_use_eprt = FALSE; - } - fcmd++; - - if(fcmd == DONE) { - failf(data, "Failed to do PORT"); - result = CURLE_FTP_PORT_FAILED; - } - else - /* try next */ - result = ftp_state_use_port(conn, fcmd); - } - else { - infof(data, "Connect data stream actively\n"); - state(conn, FTP_STOP); /* end of DO phase */ - } - - return result; -} - -static CURLcode ftp_state_mdtm_resp(struct connectdata *conn, - int ftpcode) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data=conn->data; - struct FTP *ftp = data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - switch(ftpcode) { - case 213: - { - /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the - last .sss part is optional and means fractions of a second */ - int year, month, day, hour, minute, second; - char *buf = data->state.buffer; - if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d", - &year, &month, &day, &hour, &minute, &second)) { - /* we have a time, reformat it */ - time_t secs=time(NULL); - /* using the good old yacc/bison yuck */ - snprintf(buf, sizeof(conn->data->state.buffer), - "%04d%02d%02d %02d:%02d:%02d GMT", - year, month, day, hour, minute, second); - /* now, convert this into a time() value: */ - data->info.filetime = (long)curl_getdate(buf, &secs); - } - -#ifdef CURL_FTP_HTTPSTYLE_HEAD - /* If we asked for a time of the file and we actually got one as well, - we "emulate" a HTTP-style header in our output. */ - - if(data->set.opt_no_body && - ftpc->file && - data->set.get_filetime && - (data->info.filetime>=0) ) { - time_t filetime = (time_t)data->info.filetime; - struct tm buffer; - const struct tm *tm = &buffer; - - result = Curl_gmtime(filetime, &buffer); - if(result) - return result; - - /* format: "Tue, 15 Nov 1994 12:45:26" */ - snprintf(buf, BUFSIZE-1, - "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); - if(result) - return result; - } /* end of a ridiculous amount of conditionals */ -#endif - } - break; - default: - infof(data, "unsupported MDTM reply format\n"); - break; - case 550: /* "No such file or directory" */ - failf(data, "Given file does not exist"); - result = CURLE_FTP_COULDNT_RETR_FILE; - break; - } - - if(data->set.timecondition) { - if((data->info.filetime > 0) && (data->set.timevalue > 0)) { - switch(data->set.timecondition) { - case CURL_TIMECOND_IFMODSINCE: - default: - if(data->info.filetime <= data->set.timevalue) { - infof(data, "The requested document is not new enough\n"); - ftp->transfer = FTPTRANSFER_NONE; /* mark to not transfer data */ - data->info.timecond = TRUE; - state(conn, FTP_STOP); - return CURLE_OK; - } - break; - case CURL_TIMECOND_IFUNMODSINCE: - if(data->info.filetime > data->set.timevalue) { - infof(data, "The requested document is not old enough\n"); - ftp->transfer = FTPTRANSFER_NONE; /* mark to not transfer data */ - data->info.timecond = TRUE; - state(conn, FTP_STOP); - return CURLE_OK; - } - break; - } /* switch */ - } - else { - infof(data, "Skipping time comparison\n"); - } - } - - if(!result) - result = ftp_state_post_mdtm(conn); - - return result; -} - -static CURLcode ftp_state_type_resp(struct connectdata *conn, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data=conn->data; - - if(ftpcode/100 != 2) { - /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a - successful 'TYPE I'. While that is not as RFC959 says, it is still a - positive response code and we allow that. */ - failf(data, "Couldn't set desired mode"); - return CURLE_FTP_COULDNT_SET_TYPE; - } - if(ftpcode != 200) - infof(data, "Got a %03d response code instead of the assumed 200\n", - ftpcode); - - if(instate == FTP_TYPE) - result = ftp_state_post_type(conn); - else if(instate == FTP_LIST_TYPE) - result = ftp_state_post_listtype(conn); - else if(instate == FTP_RETR_TYPE) - result = ftp_state_post_retrtype(conn); - else if(instate == FTP_STOR_TYPE) - result = ftp_state_post_stortype(conn); - - return result; -} - -static CURLcode ftp_state_post_retr_size(struct connectdata *conn, - curl_off_t filesize) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data=conn->data; - struct FTP *ftp = data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(data->set.max_filesize && (filesize > data->set.max_filesize)) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - ftp->downloadsize = filesize; - - if(data->state.resume_from) { - /* We always (attempt to) get the size of downloads, so it is done before - this even when not doing resumes. */ - if(filesize == -1) { - infof(data, "ftp server doesn't support SIZE\n"); - /* We couldn't get the size and therefore we can't know if there really - is a part of the file left to get, although the server will just - close the connection when we start the connection so it won't cause - us any harm, just not make us exit as nicely. */ - } - else { - /* We got a file size report, so we check that there actually is a - part of the file left to get, or else we go home. */ - if(data->state.resume_from< 0) { - /* We're supposed to download the last abs(from) bytes */ - if(filesize < -data->state.resume_from) { - failf(data, "Offset (%" FORMAT_OFF_T - ") was beyond file size (%" FORMAT_OFF_T ")", - data->state.resume_from, filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* convert to size to download */ - ftp->downloadsize = -data->state.resume_from; - /* download from where? */ - data->state.resume_from = filesize - ftp->downloadsize; - } - else { - if(filesize < data->state.resume_from) { - failf(data, "Offset (%" FORMAT_OFF_T - ") was beyond file size (%" FORMAT_OFF_T ")", - data->state.resume_from, filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* Now store the number of bytes we are expected to download */ - ftp->downloadsize = filesize-data->state.resume_from; - } - } - - if(ftp->downloadsize == 0) { - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - infof(data, "File already completely downloaded\n"); - - /* Set ->transfer so that we won't get any error in ftp_done() - * because we didn't transfer the any file */ - ftp->transfer = FTPTRANSFER_NONE; - state(conn, FTP_STOP); - return CURLE_OK; - } - - /* Set resume file transfer offset */ - infof(data, "Instructs server to resume from offset %" FORMAT_OFF_T - "\n", data->state.resume_from); - - PPSENDF(&ftpc->pp, "REST %" FORMAT_OFF_T, data->state.resume_from); - - state(conn, FTP_RETR_REST); - - } - else { - /* no resume */ - PPSENDF(&ftpc->pp, "RETR %s", ftpc->file); - state(conn, FTP_RETR); - } - - return result; -} - -static CURLcode ftp_state_size_resp(struct connectdata *conn, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data=conn->data; - curl_off_t filesize; - char *buf = data->state.buffer; - - /* get the size from the ascii string: */ - filesize = (ftpcode == 213)?curlx_strtoofft(buf+4, NULL, 0):-1; - - if(instate == FTP_SIZE) { -#ifdef CURL_FTP_HTTPSTYLE_HEAD - if(-1 != filesize) { - snprintf(buf, sizeof(data->state.buffer), - "Content-Length: %" FORMAT_OFF_T "\r\n", filesize); - result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); - if(result) - return result; - } -#endif - Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_post_size(conn); - } - else if(instate == FTP_RETR_SIZE) { - Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_post_retr_size(conn, filesize); - } - else if(instate == FTP_STOR_SIZE) { - data->state.resume_from = filesize; - result = ftp_state_ul_setup(conn, TRUE); - } - - return result; -} - -static CURLcode ftp_state_rest_resp(struct connectdata *conn, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - switch(instate) { - case FTP_REST: - default: -#ifdef CURL_FTP_HTTPSTYLE_HEAD - if(ftpcode == 350) { - char buffer[24]= { "Accept-ranges: bytes\r\n" }; - result = Curl_client_write(conn, CLIENTWRITE_BOTH, buffer, 0); - if(result) - return result; - } -#endif - result = ftp_state_post_rest(conn); - break; - - case FTP_RETR_REST: - if(ftpcode != 350) { - failf(conn->data, "Couldn't use REST"); - result = CURLE_FTP_COULDNT_USE_REST; - } - else { - PPSENDF(&ftpc->pp, "RETR %s", ftpc->file); - state(conn, FTP_RETR); - } - break; - } - - return result; -} - -static CURLcode ftp_state_stor_resp(struct connectdata *conn, - int ftpcode, ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - if(ftpcode>=400) { - failf(data, "Failed FTP upload: %0d", ftpcode); - state(conn, FTP_STOP); - /* oops, we never close the sockets! */ - return CURLE_UPLOAD_FAILED; - } - - conn->proto.ftpc.state_saved = instate; - - /* PORT means we are now awaiting the server to connect to us. */ - if(data->set.ftp_use_port) { - bool connected; - - result = AllowServerConnect(conn, &connected); - if(result) - return result; - - if(!connected) { - struct ftp_conn *ftpc = &conn->proto.ftpc; - infof(data, "Data conn was not available immediately\n"); - ftpc->wait_data_conn = TRUE; - } - - return CURLE_OK; - } - else - return InitiateTransfer(conn); -} - -/* for LIST and RETR responses */ -static CURLcode ftp_state_get_resp(struct connectdata *conn, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct FTP *ftp = data->state.proto.ftp; - char *buf = data->state.buffer; - - if((ftpcode == 150) || (ftpcode == 125)) { - - /* - A; - 150 Opening BINARY mode data connection for /etc/passwd (2241 - bytes). (ok, the file is being transferred) - - B: - 150 Opening ASCII mode data connection for /bin/ls - - C: - 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes). - - D: - 150 Opening ASCII mode data connection for [file] (0.0.0.0,0) (545 bytes) - - E: - 125 Data connection already open; Transfer starting. */ - - curl_off_t size=-1; /* default unknown size */ - - - /* - * It appears that there are FTP-servers that return size 0 for files when - * SIZE is used on the file while being in BINARY mode. To work around - * that (stupid) behavior, we attempt to parse the RETR response even if - * the SIZE returned size zero. - * - * Debugging help from Salvatore Sorrentino on February 26, 2003. - */ - - if((instate != FTP_LIST) && - !data->set.prefer_ascii && - (ftp->downloadsize < 1)) { - /* - * It seems directory listings either don't show the size or very - * often uses size 0 anyway. ASCII transfers may very well turn out - * that the transferred amount of data is not the same as this line - * tells, why using this number in those cases only confuses us. - * - * Example D above makes this parsing a little tricky */ - char *bytes; - bytes=strstr(buf, " bytes"); - if(bytes--) { - long in=(long)(bytes-buf); - /* this is a hint there is size information in there! ;-) */ - while(--in) { - /* scan for the left parenthesis and break there */ - if('(' == *bytes) - break; - /* skip only digits */ - if(!ISDIGIT(*bytes)) { - bytes=NULL; - break; - } - /* one more estep backwards */ - bytes--; - } - /* if we have nothing but digits: */ - if(bytes++) { - /* get the number! */ - size = curlx_strtoofft(bytes, NULL, 0); - } - } - } - else if(ftp->downloadsize > -1) - size = ftp->downloadsize; - - if(size > data->req.maxdownload && data->req.maxdownload > 0) - size = data->req.size = data->req.maxdownload; - else if((instate != FTP_LIST) && (data->set.prefer_ascii)) - size = -1; /* kludge for servers that understate ASCII mode file size */ - - infof(data, "Maxdownload = %" FORMAT_OFF_T "\n", data->req.maxdownload); - - if(instate != FTP_LIST) - infof(data, "Getting file with size: %" FORMAT_OFF_T "\n", size); - - /* FTP download: */ - conn->proto.ftpc.state_saved = instate; - conn->proto.ftpc.retr_size_saved = size; - - if(data->set.ftp_use_port) { - bool connected; - - result = AllowServerConnect(conn, &connected); - if(result) - return result; - - if(!connected) { - struct ftp_conn *ftpc = &conn->proto.ftpc; - infof(data, "Data conn was not available immediately\n"); - state(conn, FTP_STOP); - ftpc->wait_data_conn = TRUE; - } - } - else - return InitiateTransfer(conn); - } - else { - if((instate == FTP_LIST) && (ftpcode == 450)) { - /* simply no matching files in the dir listing */ - ftp->transfer = FTPTRANSFER_NONE; /* don't download anything */ - state(conn, FTP_STOP); /* this phase is over */ - } - else { - failf(data, "RETR response: %03d", ftpcode); - return instate == FTP_RETR && ftpcode == 550? - CURLE_REMOTE_FILE_NOT_FOUND: - CURLE_FTP_COULDNT_RETR_FILE; - } - } - - return result; -} - -/* after USER, PASS and ACCT */ -static CURLcode ftp_state_loggedin(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - -#ifdef HAVE_KRB4 - 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 - */ - if(conn->passwd && *conn->passwd) { - /* BLOCKING */ - result = Curl_krb_kauth(conn); - if(result) - return result; - } - } -#endif - if(conn->ssl[FIRSTSOCKET].use) { - /* PBSZ = PROTECTION BUFFER SIZE. - - The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: - - Specifically, the PROT command MUST be preceded by a PBSZ - command and a PBSZ command MUST be preceded by a successful - security data exchange (the TLS negotiation in this case) - - ... (and on page 8): - - Thus the PBSZ command must still be issued, but must have a - parameter of '0' to indicate that no buffering is taking place - and the data connection should not be encapsulated. - */ - PPSENDF(&conn->proto.ftpc.pp, "PBSZ %d", 0); - state(conn, FTP_PBSZ); - } - else { - result = ftp_state_pwd(conn); - } - return result; -} - -/* for USER and PASS responses */ -static CURLcode ftp_state_user_resp(struct connectdata *conn, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct FTP *ftp = data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - (void)instate; /* no use for this yet */ - - /* some need password anyway, and others just return 2xx ignored */ - if((ftpcode == 331) && (ftpc->state == FTP_USER)) { - /* 331 Password required for ... - (the server requires to send the user's password too) */ - PPSENDF(&ftpc->pp, "PASS %s", ftp->passwd?ftp->passwd:""); - state(conn, FTP_PASS); - } - else if(ftpcode/100 == 2) { - /* 230 User ... logged in. - (the user logged in with or without password) */ - result = ftp_state_loggedin(conn); - } - else if(ftpcode == 332) { - if(data->set.str[STRING_FTP_ACCOUNT]) { - PPSENDF(&ftpc->pp, "ACCT %s", data->set.str[STRING_FTP_ACCOUNT]); - state(conn, FTP_ACCT); - } - else { - failf(data, "ACCT requested but none available"); - result = CURLE_LOGIN_DENIED; - } - } - else { - /* All other response codes, like: - - 530 User ... access denied - (the server denies to log the specified user) */ - - if(conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] && - !conn->data->state.ftp_trying_alternative) { - /* Ok, USER failed. Let's try the supplied command. */ - PPSENDF(&conn->proto.ftpc.pp, "%s", - conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); - conn->data->state.ftp_trying_alternative = TRUE; - state(conn, FTP_USER); - result = CURLE_OK; - } - else { - failf(data, "Access denied: %03d", ftpcode); - result = CURLE_LOGIN_DENIED; - } - } - return result; -} - -/* for ACCT response */ -static CURLcode ftp_state_acct_resp(struct connectdata *conn, - int ftpcode) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - if(ftpcode != 230) { - failf(data, "ACCT rejected by server: %03d", ftpcode); - result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ - } - else - result = ftp_state_loggedin(conn); - - return result; -} - - -static CURLcode ftp_statemach_act(struct connectdata *conn) -{ - CURLcode result; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - struct SessionHandle *data=conn->data; - int ftpcode; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - static const char ftpauth[][4] = { "SSL", "TLS" }; - size_t nread = 0; - - if(pp->sendleft) - return Curl_pp_flushsend(pp); - - result = ftp_readresp(sock, pp, &ftpcode, &nread); - if(result) - return result; - - if(ftpcode) { - /* we have now received a full FTP server response */ - switch(ftpc->state) { - case FTP_WAIT220: - if(ftpcode != 220) { - failf(data, "Got a %03d ftp-server response when 220 was expected", - ftpcode); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - - /* We have received a 220 response fine, now we proceed. */ -#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.str[STRING_KRB_LEVEL]); - - if(Curl_sec_login(conn) != CURLE_OK) - infof(data, "Logging in with password in cleartext!\n"); - else - infof(data, "Authentication successful\n"); - } -#endif - - if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* We don't have a SSL/TLS connection yet, but FTPS is - requested. Try a FTPS connection now */ - - ftpc->count3=0; - switch(data->set.ftpsslauth) { - case CURLFTPAUTH_DEFAULT: - case CURLFTPAUTH_SSL: - ftpc->count2 = 1; /* add one to get next */ - ftpc->count1 = 0; - break; - case CURLFTPAUTH_TLS: - ftpc->count2 = -1; /* subtract one to get next */ - ftpc->count1 = 1; - break; - default: - failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", - (int)data->set.ftpsslauth); - return CURLE_UNKNOWN_OPTION; /* we don't know what to do */ - } - PPSENDF(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); - state(conn, FTP_AUTH); - } - else { - result = ftp_state_user(conn); - if(result) - return result; - } - - break; - - case FTP_AUTH: - /* we have gotten the response to a previous AUTH command */ - - /* RFC2228 (page 5) says: - * - * If the server is willing to accept the named security mechanism, - * and does not require any security data, it must respond with - * reply code 234/334. - */ - - if((ftpcode == 234) || (ftpcode == 334)) { - /* Curl_ssl_connect is BLOCKING */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(CURLE_OK == result) { - conn->ssl[SECONDARYSOCKET].use = FALSE; /* clear-text data */ - result = ftp_state_user(conn); - } - } - else if(ftpc->count3 < 1) { - ftpc->count3++; - ftpc->count1 += ftpc->count2; /* get next attempt */ - result = Curl_pp_sendf(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); - /* remain in this same state */ - } - else { - if(data->set.use_ssl > CURLUSESSL_TRY) - /* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */ - result = CURLE_USE_SSL_FAILED; - else - /* ignore the failure and continue */ - result = ftp_state_user(conn); - } - - if(result) - return result; - break; - - case FTP_USER: - case FTP_PASS: - result = ftp_state_user_resp(conn, ftpcode, ftpc->state); - break; - - case FTP_ACCT: - result = ftp_state_acct_resp(conn, ftpcode); - break; - - case FTP_PBSZ: - PPSENDF(&ftpc->pp, "PROT %c", - data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); - state(conn, FTP_PROT); - - break; - - case FTP_PROT: - if(ftpcode/100 == 2) - /* We have enabled SSL for the data connection! */ - conn->ssl[SECONDARYSOCKET].use = - (data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE; - /* FTP servers typically responds with 500 if they decide to reject - our 'P' request */ - else if(data->set.use_ssl > CURLUSESSL_CONTROL) - /* we failed and bails out */ - return CURLE_USE_SSL_FAILED; - - if(data->set.ftp_ccc) { - /* CCC - Clear Command Channel - */ - PPSENDF(&ftpc->pp, "CCC", NULL); - state(conn, FTP_CCC); - } - else { - result = ftp_state_pwd(conn); - if(result) - return result; - } - break; - - case FTP_CCC: - if(ftpcode < 500) { - /* First shut down the SSL layer (note: this call will block) */ - result = Curl_ssl_shutdown(conn, FIRSTSOCKET); - - if(result) { - failf(conn->data, "Failed to clear the command channel (CCC)"); - return result; - } - } - - /* Then continue as normal */ - result = ftp_state_pwd(conn); - if(result) - return result; - break; - - case FTP_PWD: - if(ftpcode == 257) { - char *ptr=&data->state.buffer[4]; /* start on the first letter */ - char *dir; - char *store; - - dir = malloc(nread + 1); - if(!dir) - return CURLE_OUT_OF_MEMORY; - - /* Reply format is like - 257"" and the RFC959 - says - - The directory name can contain any character; embedded - double-quotes should be escaped by double-quotes (the - "quote-doubling" convention). - */ - if('\"' == *ptr) { - /* it started good */ - ptr++; - for(store = dir; *ptr;) { - if('\"' == *ptr) { - if('\"' == ptr[1]) { - /* "quote-doubling" */ - *store = ptr[1]; - ptr++; - } - else { - /* end of path */ - *store = '\0'; /* zero terminate */ - break; /* get out of this loop */ - } - } - else - *store = *ptr; - store++; - ptr++; - } - - /* If the path name does not look like an absolute path (i.e.: it - does not start with a '/'), we probably need some server-dependent - adjustments. For example, this is the case when connecting to - an OS400 FTP server: this server supports two name syntaxes, - the default one being incompatible with standard pathes. In - addition, this server switches automatically to the regular path - syntax when one is encountered in a command: this results in - having an entrypath in the wrong syntax when later used in CWD. - The method used here is to check the server OS: we do it only - if the path name looks strange to minimize overhead on other - systems. */ - - if(!ftpc->server_os && dir[0] != '/') { - - result = Curl_pp_sendf(&ftpc->pp, "SYST", NULL); - if(result != CURLE_OK) { - free(dir); - return result; - } - Curl_safefree(ftpc->entrypath); - ftpc->entrypath = dir; /* remember this */ - infof(data, "Entry path is '%s'\n", ftpc->entrypath); - /* also save it where getinfo can access it: */ - data->state.most_recent_ftp_entrypath = ftpc->entrypath; - state(conn, FTP_SYST); - break; - } - - Curl_safefree(ftpc->entrypath); - ftpc->entrypath = dir; /* remember this */ - infof(data, "Entry path is '%s'\n", ftpc->entrypath); - /* also save it where getinfo can access it: */ - data->state.most_recent_ftp_entrypath = ftpc->entrypath; - } - else { - /* couldn't get the path */ - free(dir); - infof(data, "Failed to figure out path\n"); - } - } - state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE\n")); - break; - - case FTP_SYST: - if(ftpcode == 215) { - char *ptr=&data->state.buffer[4]; /* start on the first letter */ - char *os; - char *store; - - os = malloc(nread + 1); - if(!os) - return CURLE_OUT_OF_MEMORY; - - /* Reply format is like - 215 - */ - while(*ptr == ' ') - ptr++; - for(store = os; *ptr && *ptr != ' ';) - *store++ = *ptr++; - *store = '\0'; /* zero terminate */ - - /* Check for special servers here. */ - - if(strequal(os, "OS/400")) { - /* Force OS400 name format 1. */ - result = Curl_pp_sendf(&ftpc->pp, "SITE NAMEFMT 1", NULL); - if(result != CURLE_OK) { - free(os); - return result; - } - /* remember target server OS */ - Curl_safefree(ftpc->server_os); - ftpc->server_os = os; - state(conn, FTP_NAMEFMT); - break; - } - else { - /* Nothing special for the target server. */ - /* remember target server OS */ - Curl_safefree(ftpc->server_os); - ftpc->server_os = os; - } - } - else { - /* Cannot identify server OS. Continue anyway and cross fingers. */ - } - - state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE\n")); - break; - - case FTP_NAMEFMT: - if(ftpcode == 250) { - /* Name format change successful: reload initial path. */ - ftp_state_pwd(conn); - break; - } - - state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE\n")); - break; - - case FTP_QUOTE: - case FTP_POSTQUOTE: - case FTP_RETR_PREQUOTE: - case FTP_STOR_PREQUOTE: - if((ftpcode >= 400) && !ftpc->count2) { - /* failure response code, and not allowed to fail */ - failf(conn->data, "QUOT command failed with %03d", ftpcode); - return CURLE_QUOTE_ERROR; - } - result = ftp_state_quote(conn, FALSE, ftpc->state); - if(result) - return result; - - break; - - case FTP_CWD: - if(ftpcode/100 != 2) { - /* failure to CWD there */ - if(conn->data->set.ftp_create_missing_dirs && - ftpc->count1 && !ftpc->count2) { - /* try making it */ - ftpc->count2++; /* counter to prevent CWD-MKD loops */ - PPSENDF(&ftpc->pp, "MKD %s", ftpc->dirs[ftpc->count1 - 1]); - state(conn, FTP_MKD); - } - else { - /* return failure */ - failf(data, "Server denied you to change to the given directory"); - ftpc->cwdfail = TRUE; /* don't remember this path as we failed - to enter it */ - return CURLE_REMOTE_ACCESS_DENIED; - } - } - else { - /* success */ - ftpc->count2=0; - if(++ftpc->count1 <= ftpc->dirdepth) { - /* send next CWD */ - PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]); - } - else { - result = ftp_state_post_cwd(conn); - if(result) - return result; - } - } - break; - - case FTP_MKD: - if((ftpcode/100 != 2) && !ftpc->count3--) { - /* failure to MKD the dir */ - failf(data, "Failed to MKD dir: %03d", ftpcode); - return CURLE_REMOTE_ACCESS_DENIED; - } - state(conn, FTP_CWD); - /* send CWD */ - PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]); - break; - - case FTP_MDTM: - result = ftp_state_mdtm_resp(conn, ftpcode); - break; - - case FTP_TYPE: - case FTP_LIST_TYPE: - case FTP_RETR_TYPE: - case FTP_STOR_TYPE: - result = ftp_state_type_resp(conn, ftpcode, ftpc->state); - break; - - case FTP_SIZE: - case FTP_RETR_SIZE: - case FTP_STOR_SIZE: - result = ftp_state_size_resp(conn, ftpcode, ftpc->state); - break; - - case FTP_REST: - case FTP_RETR_REST: - result = ftp_state_rest_resp(conn, ftpcode, ftpc->state); - break; - - case FTP_PRET: - if(ftpcode != 200) { - /* there only is this one standard OK return code. */ - failf(data, "PRET command not accepted: %03d", ftpcode); - return CURLE_FTP_PRET_FAILED; - } - result = ftp_state_use_pasv(conn); - break; - - case FTP_PASV: - result = ftp_state_pasv_resp(conn, ftpcode); - break; - - case FTP_PORT: - result = ftp_state_port_resp(conn, ftpcode); - break; - - case FTP_LIST: - case FTP_RETR: - result = ftp_state_get_resp(conn, ftpcode, ftpc->state); - break; - - case FTP_STOR: - result = ftp_state_stor_resp(conn, ftpcode, ftpc->state); - break; - - case FTP_QUIT: - /* fallthrough, just stop! */ - default: - /* internal error */ - state(conn, FTP_STOP); - break; - } - } /* if(ftpcode) */ - - return result; -} - - -/* called repeatedly until done from curl_multi.c */ -static CURLcode ftp_multi_statemach(struct connectdata *conn, - bool *done) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result = Curl_pp_multi_statemach(&ftpc->pp); - - /* Check for the state outside of the Curl_socket_ready() return code checks - since at times we are in fact already in this state when this function - gets called. */ - *done = (ftpc->state == FTP_STOP) ? TRUE : FALSE; - - return result; -} - -static CURLcode ftp_easy_statemach(struct connectdata *conn) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - CURLcode result = CURLE_OK; - - while(ftpc->state != FTP_STOP) { - result = Curl_pp_easy_statemach(pp); - if(result) - break; - } - - return result; -} - -/* - * Allocate and initialize the struct FTP for the current SessionHandle. If - * need be. - */ - -#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \ - defined(__OPTIMIZE__) && defined(__unix__) && defined(__i386__) - /* workaround icc 9.1 optimizer issue */ -#pragma optimize("", off) -#endif - -static CURLcode ftp_init(struct connectdata *conn) -{ - struct FTP *ftp; - - if(NULL == conn->data->state.proto.ftp) { - conn->data->state.proto.ftp = malloc(sizeof(struct FTP)); - if(NULL == conn->data->state.proto.ftp) - return CURLE_OUT_OF_MEMORY; - } - - ftp = conn->data->state.proto.ftp; - - /* get some initial data into the ftp struct */ - ftp->bytecountp = &conn->data->req.bytecount; - ftp->transfer = FTPTRANSFER_BODY; - ftp->downloadsize = 0; - - /* No need to duplicate user+password, the connectdata struct won't change - during a session, but we re-init them here since on subsequent inits - since the conn struct may have changed or been replaced. - */ - ftp->user = conn->user; - ftp->passwd = conn->passwd; - if(isBadFtpString(ftp->user)) - return CURLE_URL_MALFORMAT; - if(isBadFtpString(ftp->passwd)) - return CURLE_URL_MALFORMAT; - - conn->proto.ftpc.known_filesize = -1; /* unknown size for now */ - - return CURLE_OK; -} - -#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER == 910) && \ - defined(__OPTIMIZE__) && defined(__unix__) && defined(__i386__) - /* workaround icc 9.1 optimizer issue */ -#pragma optimize("", on) -#endif - -/* - * ftp_connect() should do everything that is to be considered a part of - * the connection phase. - * - * The variable 'done' points to will be TRUE if the protocol-layer connect - * phase is done when this function returns, or FALSE is not. When called as - * a part of the easy interface, it will always be TRUE. - */ -static CURLcode ftp_connect(struct connectdata *conn, - bool *done) /* see description above */ -{ - CURLcode result; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct SessionHandle *data=conn->data; - struct pingpong *pp = &ftpc->pp; - - *done = FALSE; /* default to not done yet */ - - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - result = ftp_init(conn); - if(CURLE_OK != result) - return result; - - /* We always support persistent connections on ftp */ - conn->bits.close = FALSE; - - pp->response_time = RESP_TIMEOUT; /* set default response time-out */ - pp->statemach_act = ftp_statemach_act; - pp->endofresp = ftp_endofresp; - pp->conn = conn; - - if(conn->handler->flags & PROTOPT_SSL) { - /* BLOCKING */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(result) - return result; - } - - Curl_pp_init(pp); /* init the generic pingpong data */ - - /* When we connect, we start in the state where we await the 220 - response */ - state(conn, FTP_WAIT220); - - if(data->state.used_interface == Curl_if_multi) - result = ftp_multi_statemach(conn, done); - else { - result = ftp_easy_statemach(conn); - if(!result) - *done = TRUE; - } - - return result; -} - -/*********************************************************************** - * - * ftp_done() - * - * The DONE function. This does what needs to be done after a single DO has - * performed. - * - * Input argument is already checked for validity. - */ -static CURLcode ftp_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - struct SessionHandle *data = conn->data; - struct FTP *ftp = data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - ssize_t nread; - int ftpcode; - CURLcode result = CURLE_OK; - bool was_ctl_valid = ftpc->ctl_valid; - char *path; - const char *path_to_use = data->state.path; - - if(!ftp) - /* When the easy handle is removed from the multi while libcurl is still - * trying to resolve the host name, it seems that the ftp struct is not - * yet initialized, but the removal action calls Curl_done() which calls - * this function. So we simply return success if no ftp pointer is set. - */ - return CURLE_OK; - - switch(status) { - case CURLE_BAD_DOWNLOAD_RESUME: - case CURLE_FTP_WEIRD_PASV_REPLY: - case CURLE_FTP_PORT_FAILED: - case CURLE_FTP_ACCEPT_FAILED: - case CURLE_FTP_ACCEPT_TIMEOUT: - case CURLE_FTP_COULDNT_SET_TYPE: - case CURLE_FTP_COULDNT_RETR_FILE: - case CURLE_PARTIAL_FILE: - case CURLE_UPLOAD_FAILED: - case CURLE_REMOTE_ACCESS_DENIED: - case CURLE_FILESIZE_EXCEEDED: - case CURLE_REMOTE_FILE_NOT_FOUND: - case CURLE_WRITE_ERROR: - /* the connection stays alive fine even though this happened */ - /* fall-through */ - case CURLE_OK: /* doesn't affect the control connection's status */ - if(!premature) { - ftpc->ctl_valid = was_ctl_valid; - break; - } - /* until we cope better with prematurely ended requests, let them - * fallback as if in complete failure */ - default: /* by default, an error means the control connection is - wedged and should not be used anymore */ - ftpc->ctl_valid = FALSE; - ftpc->cwdfail = TRUE; /* set this TRUE to prevent us to remember the - current path, as this connection is going */ - conn->bits.close = TRUE; /* marked for closure */ - result = status; /* use the already set error code */ - break; - } - - /* now store a copy of the directory we are in */ - if(ftpc->prevpath) - free(ftpc->prevpath); - - if(data->set.wildcardmatch) { - if(data->set.chunk_end && ftpc->file) { - data->set.chunk_end(data->wildcard.customptr); - } - ftpc->known_filesize = -1; - } - - /* get the "raw" path */ - path = curl_easy_unescape(data, path_to_use, 0, NULL); - if(!path) { - /* out of memory, but we can limp along anyway (and should try to - * since we may already be in the out of memory cleanup path) */ - if(!result) - result = CURLE_OUT_OF_MEMORY; - ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - conn->bits.close = TRUE; /* mark for connection closure */ - ftpc->prevpath = NULL; /* no path remembering */ - } - else { - size_t flen = ftpc->file?strlen(ftpc->file):0; /* file is "raw" already */ - size_t dlen = strlen(path)-flen; - if(!ftpc->cwdfail) { - if(dlen && (data->set.ftp_filemethod != FTPFILE_NOCWD)) { - ftpc->prevpath = path; - if(flen) - /* if 'path' is not the whole string */ - ftpc->prevpath[dlen]=0; /* terminate */ - } - else { - /* we never changed dir */ - ftpc->prevpath=strdup(""); - free(path); - } - if(ftpc->prevpath) - infof(data, "Remembering we are in dir \"%s\"\n", ftpc->prevpath); - } - else { - ftpc->prevpath = NULL; /* no path */ - free(path); - } - } - /* free the dir tree and file parts */ - freedirs(ftpc); - - /* shut down the socket to inform the server we're done */ - -#ifdef _WIN32_WCE - shutdown(conn->sock[SECONDARYSOCKET],2); /* SD_BOTH */ -#endif - - if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { - if(!result && ftpc->dont_check && data->req.maxdownload > 0) - /* partial download completed */ - result = Curl_pp_sendf(pp, "ABOR"); - if(result) { - failf(data, "Failure sending ABOR command: %s", - curl_easy_strerror(result)); - ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - conn->bits.close = TRUE; /* mark for connection closure */ - } - - if(conn->ssl[SECONDARYSOCKET].use) { - /* The secondary socket is using SSL so we must close down that part - first before we close the socket for real */ - Curl_ssl_close(conn, SECONDARYSOCKET); - - /* Note that we keep "use" set to TRUE since that (next) connection is - still requested to use SSL */ - } - if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) { - Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); - conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; - conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE; - } - } - - if(!result && (ftp->transfer == FTPTRANSFER_BODY) && ftpc->ctl_valid && - pp->pending_resp && !premature) { - /* - * Let's see what the server says about the transfer we just performed, - * but lower the timeout as sometimes this connection has died while the - * data has been transferred. This happens when doing through NATs etc that - * abandon old silent connections. - */ - long old_time = pp->response_time; - - pp->response_time = 60*1000; /* give it only a minute for now */ - pp->response = Curl_tvnow(); /* timeout relative now */ - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - - pp->response_time = old_time; /* set this back to previous value */ - - if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) { - failf(data, "control connection looks dead"); - ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - conn->bits.close = TRUE; /* mark for closure */ - } - - if(result) - return result; - - if(ftpc->dont_check && data->req.maxdownload > 0) { - /* we have just sent ABOR and there is no reliable way to check if it was - * successful or not; we have to close the connection now */ - infof(data, "partial download completed, closing connection\n"); - conn->bits.close = TRUE; /* mark for closure */ - return result; - } - - if(!ftpc->dont_check) { - /* 226 Transfer complete, 250 Requested file action okay, completed. */ - if((ftpcode != 226) && (ftpcode != 250)) { - failf(data, "server did not report OK, got %d", ftpcode); - result = CURLE_PARTIAL_FILE; - } - } - } - - if(result || premature) - /* the response code from the transfer showed an error already so no - use checking further */ - ; - else if(data->set.upload) { - if((-1 != data->set.infilesize) && - (data->set.infilesize != *ftp->bytecountp) && - !data->set.crlf && - (ftp->transfer == FTPTRANSFER_BODY)) { - failf(data, "Uploaded unaligned file size (%" FORMAT_OFF_T - " out of %" FORMAT_OFF_T " bytes)", - *ftp->bytecountp, data->set.infilesize); - result = CURLE_PARTIAL_FILE; - } - } - else { - if((-1 != data->req.size) && - (data->req.size != *ftp->bytecountp) && -#ifdef CURL_DO_LINEEND_CONV - /* Most FTP servers don't adjust their file SIZE response for CRLFs, so - * we'll check to see if the discrepancy can be explained by the number - * of CRLFs we've changed to LFs. - */ - ((data->req.size + data->state.crlf_conversions) != - *ftp->bytecountp) && -#endif /* CURL_DO_LINEEND_CONV */ - (data->req.maxdownload != *ftp->bytecountp)) { - failf(data, "Received only partial file: %" FORMAT_OFF_T " bytes", - *ftp->bytecountp); - result = CURLE_PARTIAL_FILE; - } - else if(!ftpc->dont_check && - !*ftp->bytecountp && - (data->req.size>0)) { - failf(data, "No data was received!"); - result = CURLE_FTP_COULDNT_RETR_FILE; - } - } - - /* clear these for next connection */ - ftp->transfer = FTPTRANSFER_BODY; - ftpc->dont_check = FALSE; - - /* Send any post-transfer QUOTE strings? */ - if(!status && !result && !premature && data->set.postquote) - result = ftp_sendquote(conn, data->set.postquote); - - return result; -} - -/*********************************************************************** - * - * ftp_sendquote() - * - * Where a 'quote' means a list of custom commands to send to the server. - * The quote list is passed as an argument. - * - * BLOCKING - */ - -static -CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote) -{ - struct curl_slist *item; - ssize_t nread; - int ftpcode; - CURLcode result; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - - item = quote; - while(item) { - if(item->data) { - char *cmd = item->data; - bool acceptfail = FALSE; - - /* if a command starts with an asterisk, which a legal FTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ - - if(cmd[0] == '*') { - cmd++; - acceptfail = TRUE; - } - - FTPSENDF(conn, "%s", cmd); - - pp->response = Curl_tvnow(); /* timeout relative now */ - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); - if(result) - return result; - - if(!acceptfail && (ftpcode >= 400)) { - failf(conn->data, "QUOT string not accepted: %s", cmd); - return CURLE_QUOTE_ERROR; - } - } - - item = item->next; - } - - return CURLE_OK; -} - -/*********************************************************************** - * - * ftp_need_type() - * - * Returns TRUE if we in the current situation should send TYPE - */ -static int ftp_need_type(struct connectdata *conn, - bool ascii_wanted) -{ - return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I'); -} - -/*********************************************************************** - * - * ftp_nb_type() - * - * Set TYPE. We only deal with ASCII or BINARY so this function - * sets one of them. - * If the transfer type is not sent, simulate on OK response in newstate - */ -static CURLcode ftp_nb_type(struct connectdata *conn, - bool ascii, ftpstate newstate) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result; - char want = (char)(ascii?'A':'I'); - - if(ftpc->transfertype == want) { - state(conn, newstate); - return ftp_state_type_resp(conn, 200, newstate); - } - - PPSENDF(&ftpc->pp, "TYPE %c", want); - state(conn, newstate); - - /* keep track of our current transfer type */ - ftpc->transfertype = want; - return CURLE_OK; -} - -/*************************************************************************** - * - * ftp_pasv_verbose() - * - * This function only outputs some informationals about this second connection - * when we've issued a PASV command before and thus we have connected to a - * possibly new IP address. - * - */ -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void -ftp_pasv_verbose(struct connectdata *conn, - Curl_addrinfo *ai, - char *newhost, /* ascii version */ - int port) -{ - char buf[256]; - Curl_printable_address(ai, buf, sizeof(buf)); - infof(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port); -} -#endif - -/* - Check if this is a range download, and if so, set the internal variables - properly. - */ - -static CURLcode ftp_range(struct connectdata *conn) -{ - curl_off_t from, to; - char *ptr; - char *ptr2; - struct SessionHandle *data = conn->data; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(data->state.use_range && data->state.range) { - from=curlx_strtoofft(data->state.range, &ptr, 0); - while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) - ptr++; - to=curlx_strtoofft(ptr, &ptr2, 0); - if(ptr == ptr2) { - /* we didn't get any digit */ - to=-1; - } - if((-1 == to) && (from>=0)) { - /* X - */ - data->state.resume_from = from; - DEBUGF(infof(conn->data, "FTP RANGE %" FORMAT_OFF_T " to end of file\n", - from)); - } - else if(from < 0) { - /* -Y */ - data->req.maxdownload = -from; - data->state.resume_from = from; - DEBUGF(infof(conn->data, "FTP RANGE the last %" FORMAT_OFF_T " bytes\n", - -from)); - } - else { - /* X-Y */ - data->req.maxdownload = (to-from)+1; /* include last byte */ - data->state.resume_from = from; - DEBUGF(infof(conn->data, "FTP RANGE from %" FORMAT_OFF_T - " getting %" FORMAT_OFF_T " bytes\n", - from, data->req.maxdownload)); - } - DEBUGF(infof(conn->data, "range-download from %" FORMAT_OFF_T - " to %" FORMAT_OFF_T ", totally %" FORMAT_OFF_T " bytes\n", - from, to, data->req.maxdownload)); - ftpc->dont_check = TRUE; /* dont check for successful transfer */ - } - else - data->req.maxdownload = -1; - return CURLE_OK; -} - - -/* - * ftp_do_more() - * - * This function shall be called when the second FTP (data) connection is - * connected. - */ - -static CURLcode ftp_do_more(struct connectdata *conn, bool *complete) -{ - struct SessionHandle *data=conn->data; - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result = CURLE_OK; - bool connected = FALSE; - - /* the ftp struct is inited in ftp_connect() */ - struct FTP *ftp = data->state.proto.ftp; - - *complete = FALSE; - - /* if the second connection isn't done yet, wait for it */ - if(!conn->bits.tcpconnect[SECONDARYSOCKET]) { - result = Curl_is_connected(conn, SECONDARYSOCKET, &connected); - - /* Ready to do more? */ - if(connected) { - DEBUGF(infof(data, "DO-MORE connected phase starts\n")); - } - else - return result; - } - - if((data->state.used_interface == Curl_if_multi) && - ftpc->state) { - /* multi interface and already in a state so skip the intial commands. - They are only done to kickstart the do_more state */ - result = ftp_multi_statemach(conn, complete); - - /* if we got an error or if we don't wait for a data connection return - immediately */ - if(result || (ftpc->wait_data_conn != TRUE)) - return result; - } - - if(ftp->transfer <= FTPTRANSFER_INFO) { - /* a transfer is about to take place, or if not a file name was given - so we'll do a SIZE on it later and then we need the right TYPE first */ - - if(ftpc->wait_data_conn == TRUE) { - bool serv_conned; - - result = ReceivedServerConnect(conn, &serv_conned); - if(result) - return result; /* Failed to accept data connection */ - - if(serv_conned) { - /* It looks data connection is established */ - result = AcceptServerConnect(conn); - ftpc->wait_data_conn = FALSE; - if(!result) - result = InitiateTransfer(conn); - - if(result) - return result; - } - } - else if(data->set.upload) { - result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_STOR_TYPE); - if(result) - return result; - } - else { - /* download */ - ftp->downloadsize = -1; /* unknown as of yet */ - - result = ftp_range(conn); - if(result) - ; - else if(data->set.ftp_list_only || !ftpc->file) { - /* The specified path ends with a slash, and therefore we think this - is a directory that is requested, use LIST. But before that we - need to set ASCII transfer mode. */ - - /* But only if a body transfer was requested. */ - if(ftp->transfer == FTPTRANSFER_BODY) { - result = ftp_nb_type(conn, TRUE, FTP_LIST_TYPE); - if(result) - return result; - } - /* otherwise just fall through */ - } - else { - result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_RETR_TYPE); - if(result) - return result; - } - } - if(data->state.used_interface == Curl_if_multi) { - result = ftp_multi_statemach(conn, complete); - - return result; - } - else - result = ftp_easy_statemach(conn); - } - - if((result == CURLE_OK) && (ftp->transfer != FTPTRANSFER_BODY)) - /* no data to transfer. FIX: it feels like a kludge to have this here - too! */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - - if(!ftpc->wait_data_conn) { - /* no waiting for the data connection so this is now complete */ - *complete = TRUE; - DEBUGF(infof(data, "DO-MORE phase ends with %d\n", (int)result)); - } - - return result; -} - - - -/*********************************************************************** - * - * ftp_perform() - * - * This is the actual DO function for FTP. Get a file/directory according to - * the options previously setup. - */ - -static -CURLcode ftp_perform(struct connectdata *conn, - bool *connected, /* connect status after PASV / PORT */ - bool *dophase_done) -{ - /* this is FTP and no proxy */ - CURLcode result=CURLE_OK; - - DEBUGF(infof(conn->data, "DO phase starts\n")); - - if(conn->data->set.opt_no_body) { - /* requested no body means no transfer... */ - struct FTP *ftp = conn->data->state.proto.ftp; - ftp->transfer = FTPTRANSFER_INFO; - } - - - *dophase_done = FALSE; /* not done yet */ - - /* start the first command in the DO phase */ - result = ftp_state_quote(conn, TRUE, FTP_QUOTE); - if(result) - return result; - - /* run the state-machine */ - if(conn->data->state.used_interface == Curl_if_multi) - result = ftp_multi_statemach(conn, dophase_done); - else { - result = ftp_easy_statemach(conn); - *dophase_done = TRUE; /* with the easy interface we are done here */ - } - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); - - return result; -} - -static void wc_data_dtor(void *ptr) -{ - struct ftp_wc_tmpdata *tmp = ptr; - if(tmp) - Curl_ftp_parselist_data_free(&tmp->parser); - Curl_safefree(tmp); -} - -static CURLcode init_wc_data(struct connectdata *conn) -{ - char *last_slash; - char *path = conn->data->state.path; - struct WildcardData *wildcard = &(conn->data->wildcard); - CURLcode ret = CURLE_OK; - struct ftp_wc_tmpdata *ftp_tmp; - - last_slash = strrchr(conn->data->state.path, '/'); - if(last_slash) { - last_slash++; - if(last_slash[0] == '\0') { - wildcard->state = CURLWC_CLEAN; - ret = ftp_parse_url_path(conn); - return ret; - } - else { - wildcard->pattern = strdup(last_slash); - if(!wildcard->pattern) - return CURLE_OUT_OF_MEMORY; - last_slash[0] = '\0'; /* cut file from path */ - } - } - else { /* there is only 'wildcard pattern' or nothing */ - if(path[0]) { - wildcard->pattern = strdup(path); - if(!wildcard->pattern) - return CURLE_OUT_OF_MEMORY; - path[0] = '\0'; - } - else { /* only list */ - wildcard->state = CURLWC_CLEAN; - ret = ftp_parse_url_path(conn); - return ret; - } - } - - /* program continues only if URL is not ending with slash, allocate needed - resources for wildcard transfer */ - - /* allocate ftp protocol specific temporary wildcard data */ - ftp_tmp = calloc(1, sizeof(struct ftp_wc_tmpdata)); - if(!ftp_tmp) { - Curl_safefree(wildcard->pattern); - return CURLE_OUT_OF_MEMORY; - } - - /* INITIALIZE parselist structure */ - ftp_tmp->parser = Curl_ftp_parselist_data_alloc(); - if(!ftp_tmp->parser) { - Curl_safefree(wildcard->pattern); - Curl_safefree(ftp_tmp); - return CURLE_OUT_OF_MEMORY; - } - - wildcard->tmp = ftp_tmp; /* put it to the WildcardData tmp pointer */ - wildcard->tmp_dtor = wc_data_dtor; - - /* wildcard does not support NOCWD option (assert it?) */ - if(conn->data->set.ftp_filemethod == FTPFILE_NOCWD) - conn->data->set.ftp_filemethod = FTPFILE_MULTICWD; - - /* try to parse ftp url */ - ret = ftp_parse_url_path(conn); - if(ret) { - Curl_safefree(wildcard->pattern); - wildcard->tmp_dtor(wildcard->tmp); - wildcard->tmp_dtor = ZERO_NULL; - wildcard->tmp = NULL; - return ret; - } - - wildcard->path = strdup(conn->data->state.path); - if(!wildcard->path) { - Curl_safefree(wildcard->pattern); - wildcard->tmp_dtor(wildcard->tmp); - wildcard->tmp_dtor = ZERO_NULL; - wildcard->tmp = NULL; - return CURLE_OUT_OF_MEMORY; - } - - /* backup old write_function */ - ftp_tmp->backup.write_function = conn->data->set.fwrite_func; - /* parsing write function */ - conn->data->set.fwrite_func = Curl_ftp_parselist; - /* backup old file descriptor */ - ftp_tmp->backup.file_descriptor = conn->data->set.out; - /* let the writefunc callback know what curl pointer is working with */ - conn->data->set.out = conn; - - infof(conn->data, "Wildcard - Parsing started\n"); - return CURLE_OK; -} - -/* This is called recursively */ -static CURLcode wc_statemach(struct connectdata *conn) -{ - struct WildcardData * const wildcard = &(conn->data->wildcard); - CURLcode ret = CURLE_OK; - - switch (wildcard->state) { - case CURLWC_INIT: - ret = init_wc_data(conn); - if(wildcard->state == CURLWC_CLEAN) - /* only listing! */ - break; - else - wildcard->state = ret ? CURLWC_ERROR : CURLWC_MATCHING; - break; - - case CURLWC_MATCHING: { - /* In this state is LIST response successfully parsed, so lets restore - previous WRITEFUNCTION callback and WRITEDATA pointer */ - struct ftp_wc_tmpdata *ftp_tmp = wildcard->tmp; - conn->data->set.fwrite_func = ftp_tmp->backup.write_function; - conn->data->set.out = ftp_tmp->backup.file_descriptor; - ftp_tmp->backup.write_function = ZERO_NULL; - ftp_tmp->backup.file_descriptor = NULL; - wildcard->state = CURLWC_DOWNLOADING; - - if(Curl_ftp_parselist_geterror(ftp_tmp->parser)) { - /* error found in LIST parsing */ - wildcard->state = CURLWC_CLEAN; - return wc_statemach(conn); - } - else if(wildcard->filelist->size == 0) { - /* no corresponding file */ - wildcard->state = CURLWC_CLEAN; - return CURLE_REMOTE_FILE_NOT_FOUND; - } - return wc_statemach(conn); - } - - case CURLWC_DOWNLOADING: { - /* filelist has at least one file, lets get first one */ - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct curl_fileinfo *finfo = wildcard->filelist->head->ptr; - char *tmp_path = malloc(strlen(conn->data->state.path) + - strlen(finfo->filename) + 1); - if(!tmp_path) { - return CURLE_OUT_OF_MEMORY; - } - - tmp_path[0] = 0; - /* make full path to matched file */ - strcat(tmp_path, wildcard->path); - strcat(tmp_path, finfo->filename); - /* switch default "state.pathbuffer" and tmp_path, good to see - ftp_parse_url_path function to understand this trick */ - Curl_safefree(conn->data->state.pathbuffer); - conn->data->state.pathbuffer = tmp_path; - conn->data->state.path = tmp_path; - - infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename); - if(conn->data->set.chunk_bgn) { - long userresponse = conn->data->set.chunk_bgn( - finfo, wildcard->customptr, (int)wildcard->filelist->size); - switch(userresponse) { - case CURL_CHUNK_BGN_FUNC_SKIP: - infof(conn->data, "Wildcard - \"%s\" skipped by user\n", - finfo->filename); - wildcard->state = CURLWC_SKIP; - return wc_statemach(conn); - case CURL_CHUNK_BGN_FUNC_FAIL: - return CURLE_CHUNK_FAILED; - } - } - - if(finfo->filetype != CURLFILETYPE_FILE) { - wildcard->state = CURLWC_SKIP; - return wc_statemach(conn); - } - - if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) - ftpc->known_filesize = finfo->size; - - ret = ftp_parse_url_path(conn); - if(ret) { - return ret; - } - - /* we don't need the Curl_fileinfo of first file anymore */ - Curl_llist_remove(wildcard->filelist, wildcard->filelist->head, NULL); - - if(wildcard->filelist->size == 0) { /* remains only one file to down. */ - wildcard->state = CURLWC_CLEAN; - /* after that will be ftp_do called once again and no transfer - will be done because of CURLWC_CLEAN state */ - return CURLE_OK; - } - } break; - - case CURLWC_SKIP: { - if(conn->data->set.chunk_end) - conn->data->set.chunk_end(conn->data->wildcard.customptr); - Curl_llist_remove(wildcard->filelist, wildcard->filelist->head, NULL); - wildcard->state = (wildcard->filelist->size == 0) ? - CURLWC_CLEAN : CURLWC_DOWNLOADING; - return wc_statemach(conn); - } - - case CURLWC_CLEAN: { - struct ftp_wc_tmpdata *ftp_tmp = wildcard->tmp; - ret = CURLE_OK; - if(ftp_tmp) { - ret = Curl_ftp_parselist_geterror(ftp_tmp->parser); - } - wildcard->state = ret ? CURLWC_ERROR : CURLWC_DONE; - } break; - - case CURLWC_DONE: - case CURLWC_ERROR: - break; - } - - return ret; -} - -/*********************************************************************** - * - * ftp_do() - * - * This function is registered as 'curl_do' function. It decodes the path - * parts etc as a wrapper to the actual DO function (ftp_perform). - * - * The input argument is already checked for validity. - */ -static CURLcode ftp_do(struct connectdata *conn, bool *done) -{ - CURLcode retcode = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - *done = FALSE; /* default to false */ - ftpc->wait_data_conn = FALSE; /* default to no such wait */ - - /* - Since connections can be re-used between SessionHandles, this might be a - connection already existing but on a fresh SessionHandle struct so we must - make sure we have a good 'struct FTP' to play with. For new connections, - the struct FTP is allocated and setup in the ftp_connect() function. - */ - Curl_reset_reqproto(conn); - retcode = ftp_init(conn); - if(retcode) - return retcode; - - if(conn->data->set.wildcardmatch) { - retcode = wc_statemach(conn); - if(conn->data->wildcard.state == CURLWC_SKIP || - conn->data->wildcard.state == CURLWC_DONE) { - /* do not call ftp_regular_transfer */ - return CURLE_OK; - } - if(retcode) /* error, loop or skipping the file */ - return retcode; - } - else { /* no wildcard FSM needed */ - retcode = ftp_parse_url_path(conn); - if(retcode) - return retcode; - } - - retcode = ftp_regular_transfer(conn, done); - - return retcode; -} - - -CURLcode Curl_ftpsendf(struct connectdata *conn, - const char *fmt, ...) -{ - ssize_t bytes_written; -#define SBUF_SIZE 1024 - 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, SBUF_SIZE-3, fmt, ap); - va_end(ap); - - strcat(s, "\r\n"); /* append a trailing CRLF */ - - bytes_written=0; - write_len = strlen(s); - - res = Curl_convert_to_network(conn->data, s, write_len); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(res) - return(res); - - for(;;) { -#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) - DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); - conn->data_prot = data_sec; -#endif - - if(CURLE_OK != res) - break; - - if(conn->data->set.verbose) - Curl_debug(conn->data, CURLINFO_HEADER_OUT, - sptr, (size_t)bytes_written, conn); - - if(bytes_written != (ssize_t)write_len) { - write_len -= bytes_written; - sptr += bytes_written; - } - else - break; - } - - return res; -} - -/*********************************************************************** - * - * ftp_quit() - * - * This should be called before calling sclose() on an ftp control connection - * (not data connections). We should then wait for the response from the - * server before returning. The calling code should then try to close the - * connection. - * - */ -static CURLcode ftp_quit(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - - if(conn->proto.ftpc.ctl_valid) { - result = Curl_pp_sendf(&conn->proto.ftpc.pp, "QUIT", NULL); - if(result) { - failf(conn->data, "Failure sending QUIT command: %s", - curl_easy_strerror(result)); - conn->proto.ftpc.ctl_valid = FALSE; /* mark control connection as bad */ - conn->bits.close = TRUE; /* mark for connection closure */ - state(conn, FTP_STOP); - return result; - } - - state(conn, FTP_QUIT); - - result = ftp_easy_statemach(conn); - } - - return result; -} - -/*********************************************************************** - * - * ftp_disconnect() - * - * Disconnect from an FTP server. Cleanup protocol-specific per-connection - * resources. BLOCKING. - */ -static CURLcode ftp_disconnect(struct connectdata *conn, bool dead_connection) -{ - struct ftp_conn *ftpc= &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to. - - ftp_quit() will check the state of ftp->ctl_valid. If it's ok it - will try to send the QUIT command, otherwise it will just return. - */ - if(dead_connection) - ftpc->ctl_valid = FALSE; - - /* The FTP session may or may not have been allocated/setup at this point! */ - (void)ftp_quit(conn); /* ignore errors on the QUIT */ - - if(ftpc->entrypath) { - struct SessionHandle *data = conn->data; - if(data->state.most_recent_ftp_entrypath == ftpc->entrypath) { - data->state.most_recent_ftp_entrypath = NULL; - } - free(ftpc->entrypath); - ftpc->entrypath = NULL; - } - - freedirs(ftpc); - if(ftpc->prevpath) { - free(ftpc->prevpath); - ftpc->prevpath = NULL; - } - if(ftpc->server_os) { - free(ftpc->server_os); - ftpc->server_os = NULL; - } - - Curl_pp_disconnect(pp); - -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - Curl_sec_end(conn); -#endif - - return CURLE_OK; -} - -/*********************************************************************** - * - * ftp_parse_url_path() - * - * Parse the URL path into separate path components. - * - */ -static -CURLcode ftp_parse_url_path(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - /* the ftp struct is already inited in ftp_connect() */ - struct FTP *ftp = data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - const char *slash_pos; /* position of the first '/' char in curpos */ - const char *path_to_use = data->state.path; - const char *cur_pos; - const char *filename = NULL; - - cur_pos = path_to_use; /* current position in path. point at the begin - of next path component */ - - ftpc->ctl_valid = FALSE; - ftpc->cwdfail = FALSE; - - switch(data->set.ftp_filemethod) { - case FTPFILE_NOCWD: - /* fastest, but less standard-compliant */ - - /* - The best time to check whether the path is a file or directory is right - here. so: - - the first condition in the if() right here, is there just in case - someone decides to set path to NULL one day - */ - if(data->state.path && - data->state.path[0] && - (data->state.path[strlen(data->state.path) - 1] != '/') ) - filename = data->state.path; /* this is a full file path */ - /* - ftpc->file is not used anywhere other than for operations on a file. - In other words, never for directory operations. - So we can safely leave filename as NULL here and use it as a - argument in dir/file decisions. - */ - break; - - case FTPFILE_SINGLECWD: - /* get the last slash */ - if(!path_to_use[0]) { - /* no dir, no file */ - ftpc->dirdepth = 0; - break; - } - slash_pos=strrchr(cur_pos, '/'); - if(slash_pos || !*cur_pos) { - ftpc->dirs = calloc(1, sizeof(ftpc->dirs[0])); - if(!ftpc->dirs) - return CURLE_OUT_OF_MEMORY; - - ftpc->dirs[0] = curl_easy_unescape(conn->data, slash_pos ? cur_pos : "/", - slash_pos ? - curlx_sztosi(slash_pos-cur_pos) : 1, - NULL); - if(!ftpc->dirs[0]) { - freedirs(ftpc); - return CURLE_OUT_OF_MEMORY; - } - ftpc->dirdepth = 1; /* we consider it to be a single dir */ - filename = slash_pos ? slash_pos+1 : cur_pos; /* rest is file name */ - } - else - filename = cur_pos; /* this is a file name only */ - break; - - default: /* allow pretty much anything */ - case FTPFILE_MULTICWD: - ftpc->dirdepth = 0; - ftpc->diralloc = 5; /* default dir depth to allocate */ - ftpc->dirs = calloc(ftpc->diralloc, sizeof(ftpc->dirs[0])); - if(!ftpc->dirs) - return CURLE_OUT_OF_MEMORY; - - /* we have a special case for listing the root dir only */ - if(strequal(path_to_use, "/")) { - cur_pos++; /* make it point to the zero byte */ - ftpc->dirs[0] = strdup("/"); - ftpc->dirdepth++; - } - else { - /* parse the URL path into separate path components */ - while((slash_pos = strchr(cur_pos, '/')) != NULL) { - /* 1 or 0 pointer offset to indicate absolute directory */ - ssize_t absolute_dir = ((cur_pos - data->state.path > 0) && - (ftpc->dirdepth == 0))?1:0; - - /* seek out the next path component */ - if(slash_pos-cur_pos) { - /* we skip empty path components, like "x//y" since the FTP command - CWD requires a parameter and a non-existent parameter a) doesn't - work on many servers and b) has no effect on the others. */ - int len = curlx_sztosi(slash_pos - cur_pos + absolute_dir); - ftpc->dirs[ftpc->dirdepth] = - curl_easy_unescape(conn->data, cur_pos - absolute_dir, len, NULL); - if(!ftpc->dirs[ftpc->dirdepth]) { /* run out of memory ... */ - failf(data, "no memory"); - freedirs(ftpc); - return CURLE_OUT_OF_MEMORY; - } - if(isBadFtpString(ftpc->dirs[ftpc->dirdepth])) { - free(ftpc->dirs[ftpc->dirdepth]); - freedirs(ftpc); - return CURLE_URL_MALFORMAT; - } - } - else { - cur_pos = slash_pos + 1; /* jump to the rest of the string */ - continue; - } - - cur_pos = slash_pos + 1; /* jump to the rest of the string */ - if(++ftpc->dirdepth >= ftpc->diralloc) { - /* enlarge array */ - char **bigger; - ftpc->diralloc *= 2; /* double the size each time */ - bigger = realloc(ftpc->dirs, ftpc->diralloc * sizeof(ftpc->dirs[0])); - if(!bigger) { - freedirs(ftpc); - return CURLE_OUT_OF_MEMORY; - } - ftpc->dirs = bigger; - } - } - } - filename = cur_pos; /* the rest is the file name */ - break; - } /* switch */ - - if(filename && *filename) { - ftpc->file = curl_easy_unescape(conn->data, filename, 0, NULL); - if(NULL == ftpc->file) { - freedirs(ftpc); - failf(data, "no memory"); - return CURLE_OUT_OF_MEMORY; - } - if(isBadFtpString(ftpc->file)) { - freedirs(ftpc); - return CURLE_URL_MALFORMAT; - } - } - else - ftpc->file=NULL; /* instead of point to a zero byte, we make it a NULL - pointer */ - - if(data->set.upload && !ftpc->file && (ftp->transfer == FTPTRANSFER_BODY)) { - /* We need a file name when uploading. Return error! */ - failf(data, "Uploading to a URL without a file name!"); - return CURLE_URL_MALFORMAT; - } - - ftpc->cwddone = FALSE; /* default to not done */ - - if(ftpc->prevpath) { - /* prevpath is "raw" so we convert the input path before we compare the - strings */ - int dlen; - char *path = curl_easy_unescape(conn->data, data->state.path, 0, &dlen); - if(!path) { - freedirs(ftpc); - return CURLE_OUT_OF_MEMORY; - } - - dlen -= ftpc->file?curlx_uztosi(strlen(ftpc->file)):0; - if((dlen == curlx_uztosi(strlen(ftpc->prevpath))) && - strnequal(path, ftpc->prevpath, dlen)) { - infof(data, "Request has same path as previous transfer\n"); - ftpc->cwddone = TRUE; - } - free(path); - } - - return CURLE_OK; -} - -/* call this when the DO phase has completed */ -static CURLcode ftp_dophase_done(struct connectdata *conn, - bool connected) -{ - struct FTP *ftp = conn->data->state.proto.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(connected) { - bool completed; - CURLcode result = ftp_do_more(conn, &completed); - - if(result) { - if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { - /* close the second socket if it was created already */ - Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); - conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; - } - return result; - } - } - - if(ftp->transfer != FTPTRANSFER_BODY) - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - else if(!connected) - /* since we didn't connect now, we want do_more to get called */ - conn->bits.do_more = TRUE; - - ftpc->ctl_valid = TRUE; /* seems good */ - - return CURLE_OK; -} - -/* called from curl_multi.c while DOing */ -static CURLcode ftp_doing(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result = ftp_multi_statemach(conn, dophase_done); - - if(result) - DEBUGF(infof(conn->data, "DO phase failed\n")); - else if(*dophase_done) { - result = ftp_dophase_done(conn, FALSE /* not connected */); - - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - return result; -} - -/*********************************************************************** - * - * ftp_regular_transfer() - * - * The input argument is already checked for validity. - * - * Performs all commands done before a regular transfer between a local and a - * remote host. - * - * ftp->ctl_valid starts out as FALSE, and gets set to TRUE if we reach the - * ftp_done() function without finding any major problem. - */ -static -CURLcode ftp_regular_transfer(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result=CURLE_OK; - bool connected=FALSE; - struct SessionHandle *data = conn->data; - struct ftp_conn *ftpc = &conn->proto.ftpc; - data->req.size = -1; /* make sure this is unknown at this point */ - - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - Curl_pgrsSetUploadSize(data, 0); - Curl_pgrsSetDownloadSize(data, 0); - - ftpc->ctl_valid = TRUE; /* starts good */ - - result = ftp_perform(conn, - &connected, /* have we connected after PASV/PORT */ - dophase_done); /* all commands in the DO-phase done? */ - - if(CURLE_OK == result) { - - if(!*dophase_done) - /* the DO phase has not completed yet */ - return CURLE_OK; - - result = ftp_dophase_done(conn, connected); - if(result) - return result; - } - else - freedirs(ftpc); - - return result; -} - -static CURLcode ftp_setup_connection(struct connectdata * conn) -{ - struct SessionHandle *data = conn->data; - char * type; - char command; - - if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel ftp operations through the proxy, we - switch and use HTTP operations only */ -#ifndef CURL_DISABLE_HTTP - if(conn->handler == &Curl_handler_ftp) - conn->handler = &Curl_handler_ftp_proxy; - else { -#ifdef USE_SSL - conn->handler = &Curl_handler_ftps_proxy; -#else - failf(data, "FTPS not supported!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - /* - * We explicitly mark this connection as persistent here as we're doing - * FTP over HTTP and thus we accidentally avoid setting this value - * otherwise. - */ - conn->bits.close = FALSE; -#else - failf(data, "FTP over http proxy requires HTTP support built-in!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - data->state.path++; /* don't include the initial slash */ - data->state.slash_removed = TRUE; /* we've skipped the slash */ - - /* FTP URLs support an extension like ";type=" that - * we'll try to get now! */ - type = strstr(data->state.path, ";type="); - - if(!type) - type = strstr(conn->host.rawalloc, ";type="); - - if(type) { - *type = 0; /* it was in the middle of the hostname */ - command = Curl_raw_toupper(type[6]); - conn->bits.type_set = TRUE; - - switch (command) { - case 'A': /* ASCII mode */ - data->set.prefer_ascii = TRUE; - break; - - case 'D': /* directory mode */ - data->set.ftp_list_only = TRUE; - break; - - case 'I': /* binary mode */ - default: - /* switch off ASCII */ - data->set.prefer_ascii = FALSE; - break; - } - } - - return CURLE_OK; -} - -#endif /* CURL_DISABLE_FTP */ diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c deleted file mode 100644 index a1a7d5187..000000000 --- a/lib/ftplistparser.c +++ /dev/null @@ -1,1050 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -/** - * Now implemented: - * - * 1) UNIX version 1 - * drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog - * 2) UNIX version 2 - * drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog - * 3) UNIX version 3 - * drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog - * 4) UNIX symlink - * lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000 - * 5) DOS style - * 01-29-97 11:32PM prog - */ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_FTP - -#include - -#include "curl_urldata.h" -#include "curl_fileinfo.h" -#include "curl_llist.h" -#include "curl_strtoofft.h" -#include "curl_rawstr.h" -#include "curl_ftp.h" -#include "curl_ftplistparser.h" -#include "curl_fnmatch.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* allocs buffer which will contain one line of LIST command response */ -#define FTP_BUFFER_ALLOCSIZE 160 - -typedef enum { - PL_UNIX_TOTALSIZE = 0, - PL_UNIX_FILETYPE, - PL_UNIX_PERMISSION, - PL_UNIX_HLINKS, - PL_UNIX_USER, - PL_UNIX_GROUP, - PL_UNIX_SIZE, - PL_UNIX_TIME, - PL_UNIX_FILENAME, - PL_UNIX_SYMLINK -} pl_unix_mainstate; - -typedef union { - enum { - PL_UNIX_TOTALSIZE_INIT = 0, - PL_UNIX_TOTALSIZE_READING - } total_dirsize; - - enum { - PL_UNIX_HLINKS_PRESPACE = 0, - PL_UNIX_HLINKS_NUMBER - } hlinks; - - enum { - PL_UNIX_USER_PRESPACE = 0, - PL_UNIX_USER_PARSING - } user; - - enum { - PL_UNIX_GROUP_PRESPACE = 0, - PL_UNIX_GROUP_NAME - } group; - - enum { - PL_UNIX_SIZE_PRESPACE = 0, - PL_UNIX_SIZE_NUMBER - } size; - - enum { - PL_UNIX_TIME_PREPART1 = 0, - PL_UNIX_TIME_PART1, - PL_UNIX_TIME_PREPART2, - PL_UNIX_TIME_PART2, - PL_UNIX_TIME_PREPART3, - PL_UNIX_TIME_PART3 - } time; - - enum { - PL_UNIX_FILENAME_PRESPACE = 0, - PL_UNIX_FILENAME_NAME, - PL_UNIX_FILENAME_WINDOWSEOL - } filename; - - enum { - PL_UNIX_SYMLINK_PRESPACE = 0, - PL_UNIX_SYMLINK_NAME, - PL_UNIX_SYMLINK_PRETARGET1, - PL_UNIX_SYMLINK_PRETARGET2, - PL_UNIX_SYMLINK_PRETARGET3, - PL_UNIX_SYMLINK_PRETARGET4, - PL_UNIX_SYMLINK_TARGET, - PL_UNIX_SYMLINK_WINDOWSEOL - } symlink; -} pl_unix_substate; - -typedef enum { - PL_WINNT_DATE = 0, - PL_WINNT_TIME, - PL_WINNT_DIRORSIZE, - PL_WINNT_FILENAME -} pl_winNT_mainstate; - -typedef union { - enum { - PL_WINNT_TIME_PRESPACE = 0, - PL_WINNT_TIME_TIME - } time; - enum { - PL_WINNT_DIRORSIZE_PRESPACE = 0, - PL_WINNT_DIRORSIZE_CONTENT - } dirorsize; - enum { - PL_WINNT_FILENAME_PRESPACE = 0, - PL_WINNT_FILENAME_CONTENT, - PL_WINNT_FILENAME_WINEOL - } filename; -} pl_winNT_substate; - -/* This struct is used in wildcard downloading - for parsing LIST response */ -struct ftp_parselist_data { - enum { - OS_TYPE_UNKNOWN = 0, - OS_TYPE_UNIX, - OS_TYPE_WIN_NT - } os_type; - - union { - struct { - pl_unix_mainstate main; - pl_unix_substate sub; - } UNIX; - - struct { - pl_winNT_mainstate main; - pl_winNT_substate sub; - } NT; - } state; - - CURLcode error; - struct curl_fileinfo *file_data; - unsigned int item_length; - size_t item_offset; - struct { - size_t filename; - size_t user; - size_t group; - size_t time; - size_t perm; - size_t symlink_target; - } offsets; -}; - -struct ftp_parselist_data *Curl_ftp_parselist_data_alloc(void) -{ - return calloc(1, sizeof(struct ftp_parselist_data)); -} - - -void Curl_ftp_parselist_data_free(struct ftp_parselist_data **pl_data) -{ - if(*pl_data) - free(*pl_data); - *pl_data = NULL; -} - - -CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data) -{ - return pl_data->error; -} - - -#define FTP_LP_MALFORMATED_PERM 0x01000000 - -static int ftp_pl_get_permission(const char *str) -{ - int permissions = 0; - /* USER */ - if(str[0] == 'r') - permissions |= 1 << 8; - else if(str[0] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - if(str[1] == 'w') - permissions |= 1 << 7; - else if(str[1] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - - if(str[2] == 'x') - permissions |= 1 << 6; - else if(str[2] == 's') { - permissions |= 1 << 6; - permissions |= 1 << 11; - } - else if(str[2] == 'S') - permissions |= 1 << 11; - else if(str[2] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - /* GROUP */ - if(str[3] == 'r') - permissions |= 1 << 5; - else if(str[3] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - if(str[4] == 'w') - permissions |= 1 << 4; - else if(str[4] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - if(str[5] == 'x') - permissions |= 1 << 3; - else if(str[5] == 's') { - permissions |= 1 << 3; - permissions |= 1 << 10; - } - else if(str[5] == 'S') - permissions |= 1 << 10; - else if(str[5] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - /* others */ - if(str[6] == 'r') - permissions |= 1 << 2; - else if(str[6] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - if(str[7] == 'w') - permissions |= 1 << 1; - else if(str[7] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - if(str[8] == 'x') - permissions |= 1; - else if(str[8] == 't') { - permissions |= 1; - permissions |= 1 << 9; - } - else if(str[8] == 'T') - permissions |= 1 << 9; - else if(str[8] != '-') - permissions |= FTP_LP_MALFORMATED_PERM; - - return permissions; -} - -static void PL_ERROR(struct connectdata *conn, CURLcode err) -{ - struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp; - struct ftp_parselist_data *parser = tmpdata->parser; - if(parser->file_data) - Curl_fileinfo_dtor(NULL, parser->file_data); - parser->file_data = NULL; - parser->error = err; -} - -static bool ftp_pl_gettime(struct ftp_parselist_data *parser, char *string) -{ - (void)parser; - (void)string; - /* TODO - * There could be possible parse timestamp from server. Leaving unimplemented - * for now. - * If you want implement this, please add CURLFINFOFLAG_KNOWN_TIME flag to - * parser->file_data->flags - * - * Ftp servers are giving usually these formats: - * Apr 11 1998 (unknown time.. set it to 00:00:00?) - * Apr 11 12:21 (unknown year -> set it to NOW() time?) - * 08-05-09 02:49PM (ms-dos format) - * 20100421092538 -> for MLST/MLSD response - */ - - return FALSE; -} - -static CURLcode ftp_pl_insert_finfo(struct connectdata *conn, - struct curl_fileinfo *finfo) -{ - curl_fnmatch_callback compare; - struct WildcardData *wc = &conn->data->wildcard; - struct ftp_wc_tmpdata *tmpdata = wc->tmp; - struct curl_llist *llist = wc->filelist; - struct ftp_parselist_data *parser = tmpdata->parser; - bool add = TRUE; - - /* move finfo pointers to b_data */ - char *str = finfo->b_data; - finfo->filename = str + parser->offsets.filename; - finfo->strings.group = parser->offsets.group ? - str + parser->offsets.group : NULL; - finfo->strings.perm = parser->offsets.perm ? - str + parser->offsets.perm : NULL; - finfo->strings.target = parser->offsets.symlink_target ? - str + parser->offsets.symlink_target : NULL; - finfo->strings.time = str + parser->offsets.time; - finfo->strings.user = parser->offsets.user ? - str + parser->offsets.user : NULL; - - /* get correct fnmatch callback */ - compare = conn->data->set.fnmatch; - if(!compare) - compare = Curl_fnmatch; - - /* filter pattern-corresponding filenames */ - if(compare(conn->data->set.fnmatch_data, wc->pattern, - finfo->filename) == 0) { - /* discard symlink which is containing multiple " -> " */ - if((finfo->filetype == CURLFILETYPE_SYMLINK) && finfo->strings.target && - (strstr(finfo->strings.target, " -> "))) { - add = FALSE; - } - } - else { - add = FALSE; - } - - if(add) { - if(!Curl_llist_insert_next(llist, llist->tail, finfo)) { - Curl_fileinfo_dtor(NULL, finfo); - tmpdata->parser->file_data = NULL; - return CURLE_OUT_OF_MEMORY; - } - } - else { - Curl_fileinfo_dtor(NULL, finfo); - } - - tmpdata->parser->file_data = NULL; - return CURLE_OK; -} - -size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, - void *connptr) -{ - size_t bufflen = size*nmemb; - struct connectdata *conn = (struct connectdata *)connptr; - struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp; - struct ftp_parselist_data *parser = tmpdata->parser; - struct curl_fileinfo *finfo; - unsigned long i = 0; - CURLcode rc; - - if(parser->error) { /* error in previous call */ - /* scenario: - * 1. call => OK.. - * 2. call => OUT_OF_MEMORY (or other error) - * 3. (last) call => is skipped RIGHT HERE and the error is hadled later - * in wc_statemach() - */ - return bufflen; - } - - if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) { - /* considering info about FILE response format */ - parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ? - OS_TYPE_WIN_NT : OS_TYPE_UNIX; - } - - while(i < bufflen) { /* FSM */ - - char c = buffer[i]; - if(!parser->file_data) { /* tmp file data is not allocated yet */ - parser->file_data = Curl_fileinfo_alloc(); - if(!parser->file_data) { - parser->error = CURLE_OUT_OF_MEMORY; - return bufflen; - } - parser->file_data->b_data = malloc(FTP_BUFFER_ALLOCSIZE); - if(!parser->file_data->b_data) { - PL_ERROR(conn, CURLE_OUT_OF_MEMORY); - return bufflen; - } - parser->file_data->b_size = FTP_BUFFER_ALLOCSIZE; - parser->item_offset = 0; - parser->item_length = 0; - } - - finfo = parser->file_data; - finfo->b_data[finfo->b_used++] = c; - - if(finfo->b_used >= finfo->b_size - 1) { - /* if it is important, extend buffer space for file data */ - char *tmp = realloc(finfo->b_data, - finfo->b_size + FTP_BUFFER_ALLOCSIZE); - if(tmp) { - finfo->b_size += FTP_BUFFER_ALLOCSIZE; - finfo->b_data = tmp; - } - else { - Curl_fileinfo_dtor(NULL, parser->file_data); - parser->file_data = NULL; - parser->error = CURLE_OUT_OF_MEMORY; - PL_ERROR(conn, CURLE_OUT_OF_MEMORY); - return bufflen; - } - } - - switch (parser->os_type) { - case OS_TYPE_UNIX: - switch (parser->state.UNIX.main) { - case PL_UNIX_TOTALSIZE: - switch(parser->state.UNIX.sub.total_dirsize) { - case PL_UNIX_TOTALSIZE_INIT: - if(c == 't') { - parser->state.UNIX.sub.total_dirsize = PL_UNIX_TOTALSIZE_READING; - parser->item_length++; - } - else { - parser->state.UNIX.main = PL_UNIX_FILETYPE; - /* start FSM again not considering size of directory */ - finfo->b_used = 0; - i--; - } - break; - case PL_UNIX_TOTALSIZE_READING: - parser->item_length++; - if(c == '\r') { - parser->item_length--; - finfo->b_used--; - } - else if(c == '\n') { - finfo->b_data[parser->item_length - 1] = 0; - if(strncmp("total ", finfo->b_data, 6) == 0) { - char *endptr = finfo->b_data+6; - /* here we can deal with directory size */ - while(ISSPACE(*endptr)) - endptr++; - if(*endptr != 0) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - else { - parser->state.UNIX.main = PL_UNIX_FILETYPE; - finfo->b_used = 0; - } - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - break; - } - break; - case PL_UNIX_FILETYPE: - switch (c) { - case '-': - finfo->filetype = CURLFILETYPE_FILE; - break; - case 'd': - finfo->filetype = CURLFILETYPE_DIRECTORY; - break; - case 'l': - finfo->filetype = CURLFILETYPE_SYMLINK; - break; - case 'p': - finfo->filetype = CURLFILETYPE_NAMEDPIPE; - break; - case 's': - finfo->filetype = CURLFILETYPE_SOCKET; - break; - case 'c': - finfo->filetype = CURLFILETYPE_DEVICE_CHAR; - break; - case 'b': - finfo->filetype = CURLFILETYPE_DEVICE_BLOCK; - break; - case 'D': - finfo->filetype = CURLFILETYPE_DOOR; - break; - default: - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - parser->state.UNIX.main = PL_UNIX_PERMISSION; - parser->item_length = 0; - parser->item_offset = 1; - break; - case PL_UNIX_PERMISSION: - parser->item_length++; - if(parser->item_length <= 9) { - if(!strchr("rwx-tTsS", c)) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - else if(parser->item_length == 10) { - unsigned int perm; - if(c != ' ') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - finfo->b_data[10] = 0; /* terminate permissions */ - perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset); - if(perm & FTP_LP_MALFORMATED_PERM) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_PERM; - parser->file_data->perm = perm; - parser->offsets.perm = parser->item_offset; - - parser->item_length = 0; - parser->state.UNIX.main = PL_UNIX_HLINKS; - parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_PRESPACE; - } - break; - case PL_UNIX_HLINKS: - switch(parser->state.UNIX.sub.hlinks) { - case PL_UNIX_HLINKS_PRESPACE: - if(c != ' ') { - if(c >= '0' && c <= '9') { - parser->item_offset = finfo->b_used - 1; - parser->item_length = 1; - parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - break; - case PL_UNIX_HLINKS_NUMBER: - parser->item_length ++; - if(c == ' ') { - char *p; - long int hlinks; - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10); - if(p[0] == '\0' && hlinks != LONG_MAX && hlinks != LONG_MIN) { - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT; - parser->file_data->hardlinks = hlinks; - } - parser->item_length = 0; - parser->item_offset = 0; - parser->state.UNIX.main = PL_UNIX_USER; - parser->state.UNIX.sub.user = PL_UNIX_USER_PRESPACE; - } - else if(c < '0' || c > '9') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - } - break; - case PL_UNIX_USER: - switch(parser->state.UNIX.sub.user) { - case PL_UNIX_USER_PRESPACE: - if(c != ' ') { - parser->item_offset = finfo->b_used - 1; - parser->item_length = 1; - parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING; - } - break; - case PL_UNIX_USER_PARSING: - parser->item_length++; - if(c == ' ') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.user = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_GROUP; - parser->state.UNIX.sub.group = PL_UNIX_GROUP_PRESPACE; - parser->item_offset = 0; - parser->item_length = 0; - } - break; - } - break; - case PL_UNIX_GROUP: - switch(parser->state.UNIX.sub.group) { - case PL_UNIX_GROUP_PRESPACE: - if(c != ' ') { - parser->item_offset = finfo->b_used - 1; - parser->item_length = 1; - parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME; - } - break; - case PL_UNIX_GROUP_NAME: - parser->item_length++; - if(c == ' ') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.group = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_SIZE; - parser->state.UNIX.sub.size = PL_UNIX_SIZE_PRESPACE; - parser->item_offset = 0; - parser->item_length = 0; - } - break; - } - break; - case PL_UNIX_SIZE: - switch(parser->state.UNIX.sub.size) { - case PL_UNIX_SIZE_PRESPACE: - if(c != ' ') { - if(c >= '0' && c <= '9') { - parser->item_offset = finfo->b_used - 1; - parser->item_length = 1; - parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - break; - case PL_UNIX_SIZE_NUMBER: - parser->item_length++; - if(c == ' ') { - char *p; - curl_off_t fsize; - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - fsize = curlx_strtoofft(finfo->b_data+parser->item_offset, &p, 10); - if(p[0] == '\0' && fsize != CURL_OFF_T_MAX && - fsize != CURL_OFF_T_MIN) { - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE; - parser->file_data->size = fsize; - } - parser->item_length = 0; - parser->item_offset = 0; - parser->state.UNIX.main = PL_UNIX_TIME; - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART1; - } - else if(!ISDIGIT(c)) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - } - break; - case PL_UNIX_TIME: - switch(parser->state.UNIX.sub.time) { - case PL_UNIX_TIME_PREPART1: - if(c != ' ') { - if(ISALNUM(c)) { - parser->item_offset = finfo->b_used -1; - parser->item_length = 1; - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - break; - case PL_UNIX_TIME_PART1: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2; - } - else if(!ISALNUM(c) && c != '.') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - case PL_UNIX_TIME_PREPART2: - parser->item_length++; - if(c != ' ') { - if(ISALNUM(c)) { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - break; - case PL_UNIX_TIME_PART2: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3; - } - else if(!ISALNUM(c) && c != '.') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - case PL_UNIX_TIME_PREPART3: - parser->item_length++; - if(c != ' ') { - if(ISALNUM(c)) { - parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - break; - case PL_UNIX_TIME_PART3: - parser->item_length++; - if(c == ' ') { - finfo->b_data[parser->item_offset + parser->item_length -1] = 0; - parser->offsets.time = parser->item_offset; - if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) { - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME; - } - if(finfo->filetype == CURLFILETYPE_SYMLINK) { - parser->state.UNIX.main = PL_UNIX_SYMLINK; - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRESPACE; - } - else { - parser->state.UNIX.main = PL_UNIX_FILENAME; - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_PRESPACE; - } - } - else if(!ISALNUM(c) && c != '.' && c != ':') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - } - break; - case PL_UNIX_FILENAME: - switch(parser->state.UNIX.sub.filename) { - case PL_UNIX_FILENAME_PRESPACE: - if(c != ' ') { - parser->item_offset = finfo->b_used - 1; - parser->item_length = 1; - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME; - } - break; - case PL_UNIX_FILENAME_NAME: - parser->item_length++; - if(c == '\r') { - parser->item_length--; - parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL; - } - else if(c == '\n') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.filename = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_FILETYPE; - rc = ftp_pl_insert_finfo(conn, finfo); - if(rc) { - PL_ERROR(conn, rc); - return bufflen; - } - } - break; - case PL_UNIX_FILENAME_WINDOWSEOL: - if(c == '\n') { - finfo->b_data[parser->item_offset + parser->item_length] = 0; - parser->offsets.filename = parser->item_offset; - parser->state.UNIX.main = PL_UNIX_FILETYPE; - rc = ftp_pl_insert_finfo(conn, finfo); - if(rc) { - PL_ERROR(conn, rc); - return bufflen; - } - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - } - break; - case PL_UNIX_SYMLINK: - switch(parser->state.UNIX.sub.symlink) { - case PL_UNIX_SYMLINK_PRESPACE: - if(c != ' ') { - parser->item_offset = finfo->b_used - 1; - parser->item_length = 1; - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_NAME: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1; - } - else if(c == '\r' || c == '\n') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - case PL_UNIX_SYMLINK_PRETARGET1: - parser->item_length++; - if(c == '-') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2; - } - else if(c == '\r' || c == '\n') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET2: - parser->item_length++; - if(c == '>') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3; - } - else if(c == '\r' || c == '\n') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET3: - parser->item_length++; - if(c == ' ') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4; - /* now place where is symlink following */ - finfo->b_data[parser->item_offset + parser->item_length - 4] = 0; - parser->offsets.filename = parser->item_offset; - parser->item_length = 0; - parser->item_offset = 0; - } - else if(c == '\r' || c == '\n') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - else { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; - } - break; - case PL_UNIX_SYMLINK_PRETARGET4: - if(c != '\r' && c != '\n') { - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET; - parser->item_offset = finfo->b_used - 1; - parser->item_length = 1; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - case PL_UNIX_SYMLINK_TARGET: - parser->item_length ++; - if(c == '\r') { - parser->item_length --; - parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL; - } - else if(c == '\n') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.symlink_target = parser->item_offset; - rc = ftp_pl_insert_finfo(conn, finfo); - if(rc) { - PL_ERROR(conn, rc); - return bufflen; - } - parser->state.UNIX.main = PL_UNIX_FILETYPE; - } - break; - case PL_UNIX_SYMLINK_WINDOWSEOL: - if(c == '\n') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - parser->offsets.symlink_target = parser->item_offset; - rc = ftp_pl_insert_finfo(conn, finfo); - if(rc) { - PL_ERROR(conn, rc); - return bufflen; - } - parser->state.UNIX.main = PL_UNIX_FILETYPE; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - } - break; - } - break; - case OS_TYPE_WIN_NT: - switch(parser->state.NT.main) { - case PL_WINNT_DATE: - parser->item_length++; - if(parser->item_length < 9) { - if(!strchr("0123456789-", c)) { /* only simple control */ - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - else if(parser->item_length == 9) { - if(c == ' ') { - parser->state.NT.main = PL_WINNT_TIME; - parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - case PL_WINNT_TIME: - parser->item_length++; - switch(parser->state.NT.sub.time) { - case PL_WINNT_TIME_PRESPACE: - if(!ISSPACE(c)) { - parser->state.NT.sub.time = PL_WINNT_TIME_TIME; - } - break; - case PL_WINNT_TIME_TIME: - if(c == ' ') { - parser->offsets.time = parser->item_offset; - finfo->b_data[parser->item_offset + parser->item_length -1] = 0; - parser->state.NT.main = PL_WINNT_DIRORSIZE; - parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_PRESPACE; - parser->item_length = 0; - } - else if(!strchr("APM0123456789:", c)) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - } - break; - case PL_WINNT_DIRORSIZE: - switch(parser->state.NT.sub.dirorsize) { - case PL_WINNT_DIRORSIZE_PRESPACE: - if(c == ' ') { - - } - else { - parser->item_offset = finfo->b_used - 1; - parser->item_length = 1; - parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT; - } - break; - case PL_WINNT_DIRORSIZE_CONTENT: - parser->item_length ++; - if(c == ' ') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - if(strcmp("", finfo->b_data + parser->item_offset) == 0) { - finfo->filetype = CURLFILETYPE_DIRECTORY; - finfo->size = 0; - } - else { - char *endptr; - finfo->size = curlx_strtoofft(finfo->b_data + - parser->item_offset, - &endptr, 10); - if(!*endptr) { - if(finfo->size == CURL_OFF_T_MAX || - finfo->size == CURL_OFF_T_MIN) { - if(errno == ERANGE) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - /* correct file type */ - parser->file_data->filetype = CURLFILETYPE_FILE; - } - - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE; - parser->item_length = 0; - parser->state.NT.main = PL_WINNT_FILENAME; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - break; - } - break; - case PL_WINNT_FILENAME: - switch (parser->state.NT.sub.filename) { - case PL_WINNT_FILENAME_PRESPACE: - if(c != ' ') { - parser->item_offset = finfo->b_used -1; - parser->item_length = 1; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT; - } - break; - case PL_WINNT_FILENAME_CONTENT: - parser->item_length++; - if(c == '\r') { - parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL; - finfo->b_data[finfo->b_used - 1] = 0; - } - else if(c == '\n') { - parser->offsets.filename = parser->item_offset; - finfo->b_data[finfo->b_used - 1] = 0; - parser->offsets.filename = parser->item_offset; - rc = ftp_pl_insert_finfo(conn, finfo); - if(rc) { - PL_ERROR(conn, rc); - return bufflen; - } - parser->state.NT.main = PL_WINNT_DATE; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - break; - case PL_WINNT_FILENAME_WINEOL: - if(c == '\n') { - parser->offsets.filename = parser->item_offset; - rc = ftp_pl_insert_finfo(conn, finfo); - if(rc) { - PL_ERROR(conn, rc); - return bufflen; - } - parser->state.NT.main = PL_WINNT_DATE; - parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - break; - } - break; - } - break; - default: - return bufflen+1; - } - - i++; - } - - return bufflen; -} - -#endif /* CURL_DISABLE_FTP */ diff --git a/lib/getenv.c b/lib/getenv.c deleted file mode 100644 index cf8b03619..000000000 --- a/lib/getenv.c +++ /dev/null @@ -1,61 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef __VMS -#include -#endif - -#include -#include "curl_memory.h" - -#include "curl_memdebug.h" - -static -char *GetEnv(const char *variable) -{ -#ifdef _WIN32_WCE - return NULL; -#else -#ifdef WIN32 - char env[MAX_PATH]; /* MAX_PATH is from windef.h */ - char *temp = getenv(variable); - env[0] = '\0'; - if(temp != NULL) - ExpandEnvironmentStringsA(temp, env, sizeof(env)); - return (env[0] != '\0')?strdup(env):NULL; -#else - char *env = getenv(variable); -#ifdef __VMS - if(env && strcmp("HOME",variable) == 0) - env = decc_translate_vms(env); -#endif - return (env && env[0])?strdup(env):NULL; -#endif -#endif -} - -char *curl_getenv(const char *v) -{ - return GetEnv(v); -} diff --git a/lib/getinfo.c b/lib/getinfo.c deleted file mode 100644 index 0404c28ea..000000000 --- a/lib/getinfo.c +++ /dev/null @@ -1,330 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include - -#include "curl_urldata.h" -#include "curl_getinfo.h" - -#include "curl_memory.h" -#include "curl_sslgen.h" -#include "curl_connect.h" /* Curl_getconnectinfo() */ -#include "curl_progress.h" - -/* Make this the last #include */ -#include "curl_memdebug.h" - -/* - * This is supposed to be called in the beginning of a perform() session - * and should reset all session-info variables - */ -CURLcode Curl_initinfo(struct SessionHandle *data) -{ - struct Progress *pro = &data->progress; - struct PureInfo *info =&data->info; - - pro->t_nslookup = 0; - pro->t_connect = 0; - pro->t_appconnect = 0; - pro->t_pretransfer = 0; - pro->t_starttransfer = 0; - pro->timespent = 0; - pro->t_redirect = 0; - - info->httpcode = 0; - info->httpversion=0; - info->filetime=-1; /* -1 is an illegal time and thus means unknown */ - - if(info->contenttype) - free(info->contenttype); - info->contenttype = NULL; - - info->header_size = 0; - info->request_size = 0; - info->numconnects = 0; - - info->conn_primary_ip[0] = '\0'; - info->conn_local_ip[0] = '\0'; - info->conn_primary_port = 0; - info->conn_local_port = 0; - - return CURLE_OK; -} - -static CURLcode getinfo_char(struct SessionHandle *data, CURLINFO info, - char **param_charp) -{ - switch(info) { - case CURLINFO_EFFECTIVE_URL: - *param_charp = data->change.url?data->change.url:(char *)""; - break; - case CURLINFO_CONTENT_TYPE: - *param_charp = data->info.contenttype; - break; - case CURLINFO_PRIVATE: - *param_charp = (char *) data->set.private_data; - break; - case CURLINFO_FTP_ENTRY_PATH: - /* Return the entrypath string from the most recent connection. - This pointer was copied from the connectdata structure by FTP. - The actual string may be free()ed by subsequent libcurl calls so - it must be copied to a safer area before the next libcurl call. - Callers must never free it themselves. */ - *param_charp = data->state.most_recent_ftp_entrypath; - break; - case CURLINFO_REDIRECT_URL: - /* Return the URL this request would have been redirected to if that - option had been enabled! */ - *param_charp = data->info.wouldredirect; - break; - case CURLINFO_PRIMARY_IP: - /* Return the ip address of the most recent (primary) connection */ - *param_charp = data->info.conn_primary_ip; - break; - case CURLINFO_LOCAL_IP: - /* Return the source/local ip address of the most recent (primary) - connection */ - *param_charp = data->info.conn_local_ip; - break; - case CURLINFO_RTSP_SESSION_ID: - *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; - break; - - default: - return CURLE_BAD_FUNCTION_ARGUMENT; - } - return CURLE_OK; -} - -static CURLcode getinfo_long(struct SessionHandle *data, CURLINFO info, - long *param_longp) -{ - curl_socket_t sockfd; - - union { - unsigned long *to_ulong; - long *to_long; - } lptr; - - switch(info) { - case CURLINFO_RESPONSE_CODE: - *param_longp = data->info.httpcode; - break; - case CURLINFO_HTTP_CONNECTCODE: - *param_longp = data->info.httpproxycode; - break; - case CURLINFO_FILETIME: - *param_longp = data->info.filetime; - break; - case CURLINFO_HEADER_SIZE: - *param_longp = data->info.header_size; - break; - case CURLINFO_REQUEST_SIZE: - *param_longp = data->info.request_size; - break; - case CURLINFO_SSL_VERIFYRESULT: - *param_longp = data->set.ssl.certverifyresult; - break; - case CURLINFO_REDIRECT_COUNT: - *param_longp = data->set.followlocation; - break; - case CURLINFO_HTTPAUTH_AVAIL: - lptr.to_long = param_longp; - *lptr.to_ulong = data->info.httpauthavail; - break; - case CURLINFO_PROXYAUTH_AVAIL: - lptr.to_long = param_longp; - *lptr.to_ulong = data->info.proxyauthavail; - break; - case CURLINFO_OS_ERRNO: - *param_longp = data->state.os_errno; - break; - case CURLINFO_NUM_CONNECTS: - *param_longp = data->info.numconnects; - break; - case CURLINFO_LASTSOCKET: - sockfd = Curl_getconnectinfo(data, NULL); - - /* note: this is not a good conversion for systems with 64 bit sockets and - 32 bit longs */ - if(sockfd != CURL_SOCKET_BAD) - *param_longp = (long)sockfd; - else - /* this interface is documented to return -1 in case of badness, which - may not be the same as the CURL_SOCKET_BAD value */ - *param_longp = -1; - break; - case CURLINFO_PRIMARY_PORT: - /* Return the (remote) port of the most recent (primary) connection */ - *param_longp = data->info.conn_primary_port; - break; - case CURLINFO_LOCAL_PORT: - /* Return the local port of the most recent (primary) connection */ - *param_longp = data->info.conn_local_port; - break; - case CURLINFO_CONDITION_UNMET: - /* return if the condition prevented the document to get transferred */ - *param_longp = data->info.timecond; - break; - case CURLINFO_RTSP_CLIENT_CSEQ: - *param_longp = data->state.rtsp_next_client_CSeq; - break; - case CURLINFO_RTSP_SERVER_CSEQ: - *param_longp = data->state.rtsp_next_server_CSeq; - break; - case CURLINFO_RTSP_CSEQ_RECV: - *param_longp = data->state.rtsp_CSeq_recv; - break; - - default: - return CURLE_BAD_FUNCTION_ARGUMENT; - } - return CURLE_OK; -} - -static CURLcode getinfo_double(struct SessionHandle *data, CURLINFO info, - double *param_doublep) -{ - switch(info) { - case CURLINFO_TOTAL_TIME: - *param_doublep = data->progress.timespent; - break; - case CURLINFO_NAMELOOKUP_TIME: - *param_doublep = data->progress.t_nslookup; - break; - case CURLINFO_CONNECT_TIME: - *param_doublep = data->progress.t_connect; - break; - case CURLINFO_APPCONNECT_TIME: - *param_doublep = data->progress.t_appconnect; - break; - case CURLINFO_PRETRANSFER_TIME: - *param_doublep = data->progress.t_pretransfer; - break; - case CURLINFO_STARTTRANSFER_TIME: - *param_doublep = data->progress.t_starttransfer; - break; - case CURLINFO_SIZE_UPLOAD: - *param_doublep = (double)data->progress.uploaded; - break; - case CURLINFO_SIZE_DOWNLOAD: - *param_doublep = (double)data->progress.downloaded; - break; - case CURLINFO_SPEED_DOWNLOAD: - *param_doublep = (double)data->progress.dlspeed; - break; - case CURLINFO_SPEED_UPLOAD: - *param_doublep = (double)data->progress.ulspeed; - break; - case CURLINFO_CONTENT_LENGTH_DOWNLOAD: - *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? - (double)data->progress.size_dl:-1; - break; - case CURLINFO_CONTENT_LENGTH_UPLOAD: - *param_doublep = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? - (double)data->progress.size_ul:-1; - break; - case CURLINFO_REDIRECT_TIME: - *param_doublep = data->progress.t_redirect; - break; - - default: - return CURLE_BAD_FUNCTION_ARGUMENT; - } - return CURLE_OK; -} - -static CURLcode getinfo_slist(struct SessionHandle *data, CURLINFO info, - struct curl_slist **param_slistp) -{ - union { - struct curl_certinfo * to_certinfo; - struct curl_slist * to_slist; - } ptr; - - switch(info) { - case CURLINFO_SSL_ENGINES: - *param_slistp = Curl_ssl_engines_list(data); - break; - case CURLINFO_COOKIELIST: - *param_slistp = Curl_cookie_list(data); - break; - case CURLINFO_CERTINFO: - /* Return the a pointer to the certinfo struct. Not really an slist - pointer but we can pretend it is here */ - ptr.to_certinfo = &data->info.certs; - *param_slistp = ptr.to_slist; - break; - - default: - return CURLE_BAD_FUNCTION_ARGUMENT; - } - return CURLE_OK; -} - -CURLcode Curl_getinfo(struct SessionHandle *data, CURLINFO info, ...) -{ - va_list arg; - long *param_longp=NULL; - double *param_doublep=NULL; - char **param_charp=NULL; - struct curl_slist **param_slistp=NULL; - int type; - /* default return code is to error out! */ - CURLcode ret = CURLE_BAD_FUNCTION_ARGUMENT; - - if(!data) - return ret; - - va_start(arg, info); - - type = CURLINFO_TYPEMASK & (int)info; - switch(type) { - case CURLINFO_STRING: - param_charp = va_arg(arg, char **); - if(NULL != param_charp) - ret = getinfo_char(data, info, param_charp); - break; - case CURLINFO_LONG: - param_longp = va_arg(arg, long *); - if(NULL != param_longp) - ret = getinfo_long(data, info, param_longp); - break; - case CURLINFO_DOUBLE: - param_doublep = va_arg(arg, double *); - if(NULL != param_doublep) - ret = getinfo_double(data, info, param_doublep); - break; - case CURLINFO_SLIST: - param_slistp = va_arg(arg, struct curl_slist **); - if(NULL != param_slistp) - ret = getinfo_slist(data, info, param_slistp); - break; - default: - break; - } - - va_end(arg); - return ret; -} diff --git a/lib/gopher.c b/lib/gopher.c deleted file mode 100644 index 80fc18e8e..000000000 --- a/lib/gopher.c +++ /dev/null @@ -1,169 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_GOPHER - -#include "curl_urldata.h" -#include -#include "curl_transfer.h" -#include "curl_sendf.h" - -#include "curl_progress.h" -#include "curl_strequal.h" -#include "curl_gopher.h" -#include "curl_rawstr.h" -#include "curl_select.h" -#include "curl_url.h" -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - * Forward declarations. - */ - -static CURLcode gopher_do(struct connectdata *conn, bool *done); - -/* - * Gopher protocol handler. - * This is also a nice simple template to build off for simple - * connect-command-download protocols. - */ - -const struct Curl_handler Curl_handler_gopher = { - "GOPHER", /* scheme */ - ZERO_NULL, /* setup_connection */ - gopher_do, /* do_it */ - ZERO_NULL, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_GOPHER, /* defport */ - CURLPROTO_GOPHER, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -static CURLcode gopher_do(struct connectdata *conn, bool *done) -{ - CURLcode result=CURLE_OK; - struct SessionHandle *data=conn->data; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - - curl_off_t *bytecount = &data->req.bytecount; - char *path = data->state.path; - char *sel; - char *sel_org = NULL; - ssize_t amount, k; - - *done = TRUE; /* unconditionally */ - - /* Create selector. Degenerate cases: / and /1 => convert to "" */ - if(strlen(path) <= 2) - sel = (char *)""; - else { - char *newp; - size_t j, i; - int len; - - /* Otherwise, drop / and the first character (i.e., item type) ... */ - newp = path; - newp+=2; - - /* ... then turn ? into TAB for search servers, Veronica, etc. ... */ - j = strlen(newp); - for(i=0; i, et al. - * - * 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. - * - ***************************************************************************/ - -/* - * Source file for all GnuTLS-specific code for the TLS/SSL layer. No code - * but curl_sslgen.c should ever call or use these functions. - * - * Note: don't use the GnuTLS' *_t variable type names in this source code, - * since they were not present in 1.0.X. - */ - -#include "curl_setup.h" - -#ifdef USE_GNUTLS - -#include -#include - -#ifdef USE_GNUTLS_NETTLE -#include -#include -#else -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_inet_pton.h" -#include "curl_gtls.h" -#include "curl_sslgen.h" -#include "curl_parsedate.h" -#include "curl_connect.h" /* for the connect timeout */ -#include "curl_select.h" -#include "curl_rawstr.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - Some hackish cast macros based on: - http://library.gnome.org/devel/glib/unstable/glib-Type-Conversion-Macros.html -*/ -#ifndef GNUTLS_POINTER_TO_INT_CAST -#define GNUTLS_POINTER_TO_INT_CAST(p) ((int) (long) (p)) -#endif -#ifndef GNUTLS_INT_TO_POINTER_CAST -#define GNUTLS_INT_TO_POINTER_CAST(i) ((void*) (long) (i)) -#endif - -/* Enable GnuTLS debugging by defining GTLSDEBUG */ -/*#define GTLSDEBUG */ - -#ifdef GTLSDEBUG -static void tls_log_func(int level, const char *str) -{ - fprintf(stderr, "|<%d>| %s", level, str); -} -#endif -static bool gtls_inited = FALSE; - -#if defined(GNUTLS_VERSION_NUMBER) -# if (GNUTLS_VERSION_NUMBER >= 0x020c00) -# undef gnutls_transport_set_lowat -# define gnutls_transport_set_lowat(A,B) Curl_nop_stmt -# define USE_GNUTLS_PRIORITY_SET_DIRECT 1 -# endif -# if (GNUTLS_VERSION_NUMBER >= 0x020c03) -# define GNUTLS_MAPS_WINSOCK_ERRORS 1 -# endif -#endif - -/* - * Custom push and pull callback functions used by GNU TLS to read and write - * to the socket. These functions are simple wrappers to send() and recv() - * (although here using sread/swrite macros as defined by curl_setup_once.h). - * We use custom functions rather than the GNU TLS defaults because it allows - * us to get specific about the fourth "flags" argument, and to use arbitrary - * private data with gnutls_transport_set_ptr if we wish. - * - * When these custom push and pull callbacks fail, GNU TLS checks its own - * session-specific error variable, and when not set also its own global - * errno variable, in order to take appropriate action. GNU TLS does not - * require that the transport is actually a socket. This implies that for - * Windows builds these callbacks should ideally set the session-specific - * error variable using function gnutls_transport_set_errno or as a last - * resort global errno variable using gnutls_transport_set_global_errno, - * with a transport agnostic error value. This implies that some winsock - * error translation must take place in these callbacks. - * - * Paragraph above applies to GNU TLS versions older than 2.12.3, since - * this version GNU TLS does its own internal winsock error translation - * using system_errno() function. - */ - -#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) -# define gtls_EINTR 4 -# define gtls_EIO 5 -# define gtls_EAGAIN 11 -static int gtls_mapped_sockerrno(void) -{ - switch(SOCKERRNO) { - case WSAEWOULDBLOCK: - return gtls_EAGAIN; - case WSAEINTR: - return gtls_EINTR; - default: - break; - } - return gtls_EIO; -} -#endif - -static ssize_t Curl_gtls_push(void *s, const void *buf, size_t len) -{ - ssize_t ret = swrite(GNUTLS_POINTER_TO_INT_CAST(s), buf, len); -#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) - if(ret < 0) - gnutls_transport_set_global_errno(gtls_mapped_sockerrno()); -#endif - return ret; -} - -static ssize_t Curl_gtls_pull(void *s, void *buf, size_t len) -{ - ssize_t ret = sread(GNUTLS_POINTER_TO_INT_CAST(s), buf, len); -#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) - if(ret < 0) - gnutls_transport_set_global_errno(gtls_mapped_sockerrno()); -#endif - return ret; -} - -/* Curl_gtls_init() - * - * Global GnuTLS init, called from Curl_ssl_init(). This calls functions that - * are not thread-safe and thus this function itself is not thread-safe and - * must only be called from within curl_global_init() to keep the thread - * situation under control! - */ -int Curl_gtls_init(void) -{ - int ret = 1; - if(!gtls_inited) { - ret = gnutls_global_init()?0:1; -#ifdef GTLSDEBUG - gnutls_global_set_log_function(tls_log_func); - gnutls_global_set_log_level(2); -#endif - gtls_inited = TRUE; - } - return ret; -} - -int Curl_gtls_cleanup(void) -{ - if(gtls_inited) { - gnutls_global_deinit(); - gtls_inited = FALSE; - } - return 1; -} - -static void showtime(struct SessionHandle *data, - const char *text, - time_t stamp) -{ - struct tm buffer; - const struct tm *tm = &buffer; - CURLcode result = Curl_gmtime(stamp, &buffer); - if(result) - return; - - snprintf(data->state.buffer, - BUFSIZE, - "\t %s: %s, %02d %s %4d %02d:%02d:%02d GMT\n", - text, - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - infof(data, "%s\n", data->state.buffer); -} - -static gnutls_datum load_file (const char *file) -{ - FILE *f; - gnutls_datum loaded_file = { NULL, 0 }; - long filelen; - void *ptr; - - if(!(f = fopen(file, "r"))) - return loaded_file; - if(fseek(f, 0, SEEK_END) != 0 - || (filelen = ftell(f)) < 0 - || fseek(f, 0, SEEK_SET) != 0 - || !(ptr = malloc((size_t)filelen))) - goto out; - if(fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) { - free(ptr); - goto out; - } - - loaded_file.data = ptr; - loaded_file.size = (unsigned int)filelen; -out: - fclose(f); - return loaded_file; -} - -static void unload_file(gnutls_datum data) { - free(data.data); -} - - -/* this function does a SSL/TLS (re-)handshake */ -static CURLcode handshake(struct connectdata *conn, - int sockindex, - bool duringconnect, - bool nonblocking) -{ - struct SessionHandle *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - gnutls_session session = conn->ssl[sockindex].session; - curl_socket_t sockfd = conn->sock[sockindex]; - long timeout_ms; - int rc; - int what; - - for(;;) { - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, duringconnect); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing== - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading== - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_ready(readfd, writefd, - nonblocking?0: - timeout_ms?timeout_ms:1000); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) - return CURLE_OK; - else if(timeout_ms) { - /* timeout */ - failf(data, "SSL connection timeout at %ld", timeout_ms); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - rc = gnutls_handshake(session); - - if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { - connssl->connecting_state = - gnutls_record_get_direction(session)? - ssl_connect_2_writing:ssl_connect_2_reading; - continue; - if(nonblocking) - return CURLE_OK; - } - else if((rc < 0) && !gnutls_error_is_fatal(rc)) { - const char *strerr = NULL; - - if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); - strerr = gnutls_alert_get_name(alert); - } - - if(strerr == NULL) - strerr = gnutls_strerror(rc); - - failf(data, "gnutls_handshake() warning: %s", strerr); - } - else if(rc < 0) { - const char *strerr = NULL; - - if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); - strerr = gnutls_alert_get_name(alert); - } - - if(strerr == NULL) - strerr = gnutls_strerror(rc); - - failf(data, "gnutls_handshake() failed: %s", strerr); - return CURLE_SSL_CONNECT_ERROR; - } - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - return CURLE_OK; - } -} - -static gnutls_x509_crt_fmt do_file_type(const char *type) -{ - if(!type || !type[0]) - return GNUTLS_X509_FMT_PEM; - if(Curl_raw_equal(type, "PEM")) - return GNUTLS_X509_FMT_PEM; - if(Curl_raw_equal(type, "DER")) - return GNUTLS_X509_FMT_DER; - return -1; -} - -static CURLcode -gtls_connect_step1(struct connectdata *conn, - int sockindex) -{ -#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT - static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; -#endif - struct SessionHandle *data = conn->data; - gnutls_session session; - int rc; - void *ssl_sessionid; - size_t ssl_idsize; - bool sni = TRUE; /* default is SNI enabled */ -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - - if(conn->ssl[sockindex].state == ssl_connection_complete) - /* to make us tolerant against being called more than once for the - same connection */ - return CURLE_OK; - - if(!gtls_inited) - Curl_gtls_init(); - - /* GnuTLS only supports SSLv3 and TLSv1 */ - if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) { - failf(data, "GnuTLS does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - else if(data->set.ssl.version == CURL_SSLVERSION_SSLv3) - sni = FALSE; /* SSLv3 has no SNI */ - - /* allocate a cred struct */ - rc = gnutls_certificate_allocate_credentials(&conn->ssl[sockindex].cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef USE_TLS_SRP - if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { - infof(data, "Using TLS-SRP username: %s\n", data->set.ssl.username); - - rc = gnutls_srp_allocate_client_credentials( - &conn->ssl[sockindex].srp_client_cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_allocate_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_OUT_OF_MEMORY; - } - - rc = gnutls_srp_set_client_credentials(conn->ssl[sockindex]. - srp_client_cred, - data->set.ssl.username, - data->set.ssl.password); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_set_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - } -#endif - - if(data->set.ssl.CAfile) { - /* set the trusted CA cert bundle file */ - gnutls_certificate_set_verify_flags(conn->ssl[sockindex].cred, - GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); - - rc = gnutls_certificate_set_x509_trust_file(conn->ssl[sockindex].cred, - data->set.ssl.CAfile, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)\n", - data->set.ssl.CAfile, gnutls_strerror(rc)); - if(data->set.ssl.verifypeer) - return CURLE_SSL_CACERT_BADFILE; - } - else - infof(data, "found %d certificates in %s\n", - rc, data->set.ssl.CAfile); - } - - if(data->set.ssl.CRLfile) { - /* set the CRL list file */ - rc = gnutls_certificate_set_x509_crl_file(conn->ssl[sockindex].cred, - data->set.ssl.CRLfile, - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - failf(data, "error reading crl file %s (%s)", - data->set.ssl.CRLfile, gnutls_strerror(rc)); - return CURLE_SSL_CRL_BADFILE; - } - else - infof(data, "found %d CRL in %s\n", - rc, data->set.ssl.CRLfile); - } - - /* Initialize TLS session as a client */ - rc = gnutls_init(&conn->ssl[sockindex].session, GNUTLS_CLIENT); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_init() failed: %d", rc); - return CURLE_SSL_CONNECT_ERROR; - } - - /* convenient assign */ - session = conn->ssl[sockindex].session; - - if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) && -#endif - sni && - (gnutls_server_name_set(session, GNUTLS_NAME_DNS, conn->host.name, - strlen(conn->host.name)) < 0)) - infof(data, "WARNING: failed to configure server name indication (SNI) " - "TLS extension\n"); - - /* Use default priorities */ - rc = gnutls_set_default_priority(session); - if(rc != GNUTLS_E_SUCCESS) - return CURLE_SSL_CONNECT_ERROR; - - if(data->set.ssl.version == CURL_SSLVERSION_SSLv3) { -#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT - static const int protocol_priority[] = { GNUTLS_SSL3, 0 }; - rc = gnutls_protocol_set_priority(session, protocol_priority); -#else - const char *err; - /* the combination of the cipher ARCFOUR with SSL 3.0 and TLS 1.0 is not - vulnerable to attacks such as the BEAST, why this code now explicitly - asks for that - */ - rc = gnutls_priority_set_direct(session, - "NORMAL:-VERS-TLS-ALL:+VERS-SSL3.0:" - "-CIPHER-ALL:+ARCFOUR-128", - &err); -#endif - if(rc != GNUTLS_E_SUCCESS) - return CURLE_SSL_CONNECT_ERROR; - } - -#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT - /* Sets the priority on the certificate types supported by gnutls. Priority - is higher for types specified before others. After specifying the types - you want, you must append a 0. */ - rc = gnutls_certificate_type_set_priority(session, cert_type_priority); - if(rc != GNUTLS_E_SUCCESS) - return CURLE_SSL_CONNECT_ERROR; -#endif - - if(data->set.str[STRING_CERT]) { - if(gnutls_certificate_set_x509_key_file( - conn->ssl[sockindex].cred, - data->set.str[STRING_CERT], - data->set.str[STRING_KEY] ? - data->set.str[STRING_KEY] : data->set.str[STRING_CERT], - do_file_type(data->set.str[STRING_CERT_TYPE]) ) != - GNUTLS_E_SUCCESS) { - failf(data, "error reading X.509 key or certificate file"); - return CURLE_SSL_CONNECT_ERROR; - } - } - -#ifdef USE_TLS_SRP - /* put the credentials to the current session */ - if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { - rc = gnutls_credentials_set(session, GNUTLS_CRD_SRP, - conn->ssl[sockindex].srp_client_cred); - if(rc != GNUTLS_E_SUCCESS) - failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); - } - else -#endif - rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, - conn->ssl[sockindex].cred); - - /* set the connection handle (file descriptor for the socket) */ - gnutls_transport_set_ptr(session, - GNUTLS_INT_TO_POINTER_CAST(conn->sock[sockindex])); - - /* register callback functions to send and receive data. */ - gnutls_transport_set_push_function(session, Curl_gtls_push); - gnutls_transport_set_pull_function(session, Curl_gtls_pull); - - /* lowat must be set to zero when using custom push and pull functions. */ - gnutls_transport_set_lowat(session, 0); - - /* This might be a reconnect, so we check for a session ID in the cache - to speed up things */ - - if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, &ssl_idsize)) { - /* we got a session id, use it! */ - gnutls_session_set_data(session, ssl_sessionid, ssl_idsize); - - /* Informational message */ - infof (data, "SSL re-using session ID\n"); - } - - return CURLE_OK; -} - -static Curl_recv gtls_recv; -static Curl_send gtls_send; - -static CURLcode -gtls_connect_step3(struct connectdata *conn, - int sockindex) -{ - unsigned int cert_list_size; - const gnutls_datum *chainp; - unsigned int verify_status; - gnutls_x509_crt x509_cert,x509_issuer; - gnutls_datum issuerp; - char certbuf[256]; /* big enough? */ - size_t size; - unsigned int algo; - unsigned int bits; - time_t certclock; - const char *ptr; - struct SessionHandle *data = conn->data; - gnutls_session session = conn->ssl[sockindex].session; - int rc; - int incache; - void *ssl_sessionid; - CURLcode result = CURLE_OK; - - /* This function will return the peer's raw certificate (chain) as sent by - the peer. These certificates are in raw format (DER encoded for - X.509). In case of a X.509 then a certificate list may be present. The - first certificate in the list is the peer's certificate, following the - issuer's certificate, then the issuer's issuer etc. */ - - chainp = gnutls_certificate_get_peers(session, &cert_list_size); - if(!chainp) { - if(data->set.ssl.verifypeer || - data->set.ssl.verifyhost || - data->set.ssl.issuercert) { -#ifdef USE_TLS_SRP - if(data->set.ssl.authtype == CURL_TLSAUTH_SRP - && data->set.ssl.username != NULL - && !data->set.ssl.verifypeer - && gnutls_cipher_get(session)) { - /* no peer cert, but auth is ok if we have SRP user and cipher and no - peer verify */ - } - else { -#endif - failf(data, "failed to get server cert"); - return CURLE_PEER_FAILED_VERIFICATION; -#ifdef USE_TLS_SRP - } -#endif - } - infof(data, "\t common name: WARNING couldn't obtain\n"); - } - - if(data->set.ssl.verifypeer) { - /* This function will try to verify the peer's certificate and return its - status (trusted, invalid etc.). The value of status should be one or - more of the gnutls_certificate_status_t enumerated elements bitwise - or'd. To avoid denial of service attacks some default upper limits - regarding the certificate key size and chain size are set. To override - them use gnutls_certificate_set_verify_limits(). */ - - rc = gnutls_certificate_verify_peers2(session, &verify_status); - if(rc < 0) { - failf(data, "server cert verify failed: %d", rc); - return CURLE_SSL_CONNECT_ERROR; - } - - /* verify_status is a bitmask of gnutls_certificate_status bits */ - if(verify_status & GNUTLS_CERT_INVALID) { - if(data->set.ssl.verifypeer) { - failf(data, "server certificate verification failed. CAfile: %s " - "CRLfile: %s", data->set.ssl.CAfile?data->set.ssl.CAfile:"none", - data->set.ssl.CRLfile?data->set.ssl.CRLfile:"none"); - return CURLE_SSL_CACERT; - } - else - infof(data, "\t server certificate verification FAILED\n"); - } - else - infof(data, "\t server certificate verification OK\n"); - } - else { - infof(data, "\t server certificate verification SKIPPED\n"); - goto after_server_cert_verification; - } - - /* initialize an X.509 certificate structure. */ - gnutls_x509_crt_init(&x509_cert); - - /* convert the given DER or PEM encoded Certificate to the native - gnutls_x509_crt_t format */ - gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); - - if(data->set.ssl.issuercert) { - gnutls_x509_crt_init(&x509_issuer); - issuerp = load_file(data->set.ssl.issuercert); - gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); - rc = gnutls_x509_crt_check_issuer(x509_cert,x509_issuer); - unload_file(issuerp); - if(rc <= 0) { - failf(data, "server certificate issuer check failed (IssuerCert: %s)", - data->set.ssl.issuercert?data->set.ssl.issuercert:"none"); - return CURLE_SSL_ISSUER_ERROR; - } - infof(data,"\t server certificate issuer check OK (Issuer Cert: %s)\n", - data->set.ssl.issuercert?data->set.ssl.issuercert:"none"); - } - - size=sizeof(certbuf); - rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME, - 0, /* the first and only one */ - FALSE, - certbuf, - &size); - if(rc) { - infof(data, "error fetching CN from cert:%s\n", - gnutls_strerror(rc)); - } - - /* This function will check if the given certificate's subject matches the - given hostname. This is a basic implementation of the matching described - in RFC2818 (HTTPS), which takes into account wildcards, and the subject - alternative name PKIX extension. Returns non zero on success, and zero on - failure. */ - rc = gnutls_x509_crt_check_hostname(x509_cert, conn->host.name); - - if(!rc) { - if(data->set.ssl.verifyhost) { - failf(data, "SSL: certificate subject name (%s) does not match " - "target host name '%s'", certbuf, conn->host.dispname); - gnutls_x509_crt_deinit(x509_cert); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, "\t common name: %s (does not match '%s')\n", - certbuf, conn->host.dispname); - } - else - infof(data, "\t common name: %s (matched)\n", certbuf); - - /* Check for time-based validity */ - certclock = gnutls_x509_crt_get_expiration_time(x509_cert); - - if(certclock == (time_t)-1) { - failf(data, "server cert expiration date verify failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - if(certclock < time(NULL)) { - if(data->set.ssl.verifypeer) { - failf(data, "server certificate expiration date has passed."); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, "\t server certificate expiration date FAILED\n"); - } - else - infof(data, "\t server certificate expiration date OK\n"); - - certclock = gnutls_x509_crt_get_activation_time(x509_cert); - - if(certclock == (time_t)-1) { - failf(data, "server cert activation date verify failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - if(certclock > time(NULL)) { - if(data->set.ssl.verifypeer) { - failf(data, "server certificate not activated yet."); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, "\t server certificate activation date FAILED\n"); - } - else - infof(data, "\t server certificate activation date OK\n"); - - /* Show: - - - ciphers used - - subject - - start date - - expire date - - common name - - issuer - - */ - - /* public key algorithm's parameters */ - algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits); - infof(data, "\t certificate public key: %s\n", - gnutls_pk_algorithm_get_name(algo)); - - /* version of the X.509 certificate. */ - infof(data, "\t certificate version: #%d\n", - gnutls_x509_crt_get_version(x509_cert)); - - - size = sizeof(certbuf); - gnutls_x509_crt_get_dn(x509_cert, certbuf, &size); - infof(data, "\t subject: %s\n", certbuf); - - certclock = gnutls_x509_crt_get_activation_time(x509_cert); - showtime(data, "start date", certclock); - - certclock = gnutls_x509_crt_get_expiration_time(x509_cert); - showtime(data, "expire date", certclock); - - size = sizeof(certbuf); - gnutls_x509_crt_get_issuer_dn(x509_cert, certbuf, &size); - infof(data, "\t issuer: %s\n", certbuf); - - gnutls_x509_crt_deinit(x509_cert); - -after_server_cert_verification: - - /* compression algorithm (if any) */ - ptr = gnutls_compression_get_name(gnutls_compression_get(session)); - /* the *_get_name() says "NULL" if GNUTLS_COMP_NULL is returned */ - infof(data, "\t compression: %s\n", ptr); - - /* the name of the cipher used. ie 3DES. */ - ptr = gnutls_cipher_get_name(gnutls_cipher_get(session)); - infof(data, "\t cipher: %s\n", ptr); - - /* the MAC algorithms name. ie SHA1 */ - ptr = gnutls_mac_get_name(gnutls_mac_get(session)); - infof(data, "\t MAC: %s\n", ptr); - - conn->ssl[sockindex].state = ssl_connection_complete; - conn->recv[sockindex] = gtls_recv; - conn->send[sockindex] = gtls_send; - - { - /* we always unconditionally get the session id here, as even if we - already got it from the cache and asked to use it in the connection, it - might've been rejected and then a new one is in use now and we need to - detect that. */ - void *connect_sessionid; - size_t connect_idsize; - - /* get the session ID data size */ - gnutls_session_get_data(session, NULL, &connect_idsize); - connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ - - if(connect_sessionid) { - /* extract session ID to the allocated buffer */ - gnutls_session_get_data(session, connect_sessionid, &connect_idsize); - - incache = !(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)); - if(incache) { - /* there was one before in the cache, so instead of risking that the - previous one was rejected, we just kill that and store the new */ - Curl_ssl_delsessionid(conn, ssl_sessionid); - } - - /* store this session id */ - result = Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize); - if(result) { - free(connect_sessionid); - result = CURLE_OUT_OF_MEMORY; - } - } - else - result = CURLE_OUT_OF_MEMORY; - } - - return result; -} - - -/* - * This function is called after the TCP connect has completed. Setup the TLS - * layer and do all necessary magic. - */ -/* We use connssl->connecting_state to keep track of the connection status; - there are three states: 'ssl_connect_1' (not started yet or complete), - 'ssl_connect_2_reading' (waiting for data from server), and - 'ssl_connect_2_writing' (waiting to be able to write). - */ -static CURLcode -gtls_connect_common(struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - int rc; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - /* Initiate the connection, if not already done */ - if(ssl_connect_1==connssl->connecting_state) { - rc = gtls_connect_step1 (conn, sockindex); - if(rc) - return rc; - } - - rc = handshake(conn, sockindex, TRUE, nonblocking); - if(rc) - /* handshake() sets its own error message with failf() */ - return rc; - - /* Finish connecting once the handshake is done */ - if(ssl_connect_1==connssl->connecting_state) { - rc = gtls_connect_step3(conn, sockindex); - if(rc) - return rc; - } - - *done = ssl_connect_1==connssl->connecting_state; - - return CURLE_OK; -} - -CURLcode -Curl_gtls_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) -{ - return gtls_connect_common(conn, sockindex, TRUE, done); -} - -CURLcode -Curl_gtls_connect(struct connectdata *conn, - int sockindex) - -{ - CURLcode retcode; - bool done = FALSE; - - retcode = gtls_connect_common(conn, sockindex, FALSE, &done); - if(retcode) - return retcode; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static ssize_t gtls_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - ssize_t rc = gnutls_record_send(conn->ssl[sockindex].session, mem, len); - - if(rc < 0 ) { - *curlcode = (rc == GNUTLS_E_AGAIN) - ? CURLE_AGAIN - : CURLE_SEND_ERROR; - - rc = -1; - } - - return rc; -} - -void Curl_gtls_close_all(struct SessionHandle *data) -{ - /* FIX: make the OpenSSL code more generic and use parts of it here */ - (void)data; -} - -static void close_one(struct connectdata *conn, - int idx) -{ - if(conn->ssl[idx].session) { - gnutls_bye(conn->ssl[idx].session, GNUTLS_SHUT_RDWR); - gnutls_deinit(conn->ssl[idx].session); - conn->ssl[idx].session = NULL; - } - if(conn->ssl[idx].cred) { - gnutls_certificate_free_credentials(conn->ssl[idx].cred); - conn->ssl[idx].cred = NULL; - } -#ifdef USE_TLS_SRP - if(conn->ssl[idx].srp_client_cred) { - gnutls_srp_free_client_credentials(conn->ssl[idx].srp_client_cred); - conn->ssl[idx].srp_client_cred = NULL; - } -#endif -} - -void Curl_gtls_close(struct connectdata *conn, int sockindex) -{ - close_one(conn, sockindex); -} - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -int Curl_gtls_shutdown(struct connectdata *conn, int sockindex) -{ - ssize_t result; - int retval = 0; - struct SessionHandle *data = conn->data; - int done = 0; - char buf[120]; - - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - gnutls_bye(conn->ssl[sockindex].session, GNUTLS_SHUT_WR); - - if(conn->ssl[sockindex].session) { - while(!done) { - int what = Curl_socket_ready(conn->sock[sockindex], - CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - result = gnutls_record_recv(conn->ssl[sockindex].session, - buf, sizeof(buf)); - switch(result) { - case 0: - /* This is the expected response. There was no data but only - the close notify alert */ - done = 1; - break; - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED\n"); - break; - default: - retval = -1; - done = 1; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = 1; - break; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = 1; - } - } - gnutls_deinit(conn->ssl[sockindex].session); - } - gnutls_certificate_free_credentials(conn->ssl[sockindex].cred); - -#ifdef USE_TLS_SRP - if(data->set.ssl.authtype == CURL_TLSAUTH_SRP - && data->set.ssl.username != NULL) - gnutls_srp_free_client_credentials(conn->ssl[sockindex].srp_client_cred); -#endif - - conn->ssl[sockindex].cred = NULL; - conn->ssl[sockindex].session = NULL; - - return retval; -} - -static ssize_t gtls_recv(struct connectdata *conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *curlcode) -{ - ssize_t ret; - - ret = gnutls_record_recv(conn->ssl[num].session, buf, buffersize); - if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { - *curlcode = CURLE_AGAIN; - return -1; - } - - if(ret == GNUTLS_E_REHANDSHAKE) { - /* BLOCKING call, this is bad but a work-around for now. Fixing this "the - proper way" takes a whole lot of work. */ - CURLcode rc = handshake(conn, num, FALSE, FALSE); - if(rc) - /* handshake() writes error message on its own */ - *curlcode = rc; - else - *curlcode = CURLE_AGAIN; /* then return as if this was a wouldblock */ - return -1; - } - - if(ret < 0) { - failf(conn->data, "GnuTLS recv error (%d): %s", - (int)ret, gnutls_strerror((int)ret)); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - - return ret; -} - -void Curl_gtls_session_free(void *ptr) -{ - free(ptr); -} - -size_t Curl_gtls_version(char *buffer, size_t size) -{ - return snprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); -} - -int Curl_gtls_seed(struct SessionHandle *data) -{ - /* we have the "SSL is seeded" boolean static to prevent multiple - time-consuming seedings in vain */ - static bool ssl_seeded = FALSE; - - /* Quickly add a bit of entropy */ -#ifndef USE_GNUTLS_NETTLE - gcry_fast_random_poll(); -#endif - - if(!ssl_seeded || data->set.str[STRING_SSL_RANDOM_FILE] || - data->set.str[STRING_SSL_EGDSOCKET]) { - - /* TODO: to a good job seeding the RNG - This may involve the gcry_control function and these options: - GCRYCTL_SET_RANDOM_SEED_FILE - GCRYCTL_SET_RNDEGD_SOCKET - */ - ssl_seeded = TRUE; - } - return 0; -} - -void Curl_gtls_random(struct SessionHandle *data, - unsigned char *entropy, - size_t length) -{ -#if defined(USE_GNUTLS_NETTLE) - (void)data; - gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length); -#elif defined(USE_GNUTLS) - Curl_gtls_seed(data); /* Initiate the seed if not already done */ - gcry_randomize(entropy, length, GCRY_STRONG_RANDOM); -#endif -} - -void Curl_gtls_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len) -{ -#if defined(USE_GNUTLS_NETTLE) - struct md5_ctx MD5pw; - md5_init(&MD5pw); - md5_update(&MD5pw, tmplen, tmp); - md5_digest(&MD5pw, md5len, md5sum); -#elif defined(USE_GNUTLS) - gcry_md_hd_t MD5pw; - gcry_md_open(&MD5pw, GCRY_MD_MD5, 0); - gcry_md_write(MD5pw, tmp, tmplen); - memcpy(md5sum, gcry_md_read (MD5pw, 0), md5len); - gcry_md_close(MD5pw); -#endif -} - -#endif /* USE_GNUTLS */ diff --git a/lib/hash.c b/lib/hash.c deleted file mode 100644 index 732dbcf73..000000000 --- a/lib/hash.c +++ /dev/null @@ -1,400 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_hash.h" -#include "curl_llist.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -static void -hash_element_dtor(void *user, void *element) -{ - struct curl_hash *h = (struct curl_hash *) user; - struct curl_hash_element *e = (struct curl_hash_element *) element; - - Curl_safefree(e->key); - - if(e->ptr) { - h->dtor(e->ptr); - e->ptr = NULL; - } - - e->key_len = 0; - - free(e); -} - -/* return 1 on error, 0 is fine */ -int -Curl_hash_init(struct curl_hash *h, - int slots, - hash_function hfunc, - comp_function comparator, - curl_hash_dtor dtor) -{ - int i; - - if(!slots || !hfunc || !comparator ||!dtor) { - return 1; /* failure */ - } - - h->hash_func = hfunc; - h->comp_func = comparator; - h->dtor = dtor; - h->size = 0; - h->slots = slots; - - h->table = malloc(slots * sizeof(struct curl_llist *)); - if(h->table) { - for(i = 0; i < slots; ++i) { - h->table[i] = Curl_llist_alloc((curl_llist_dtor) hash_element_dtor); - if(!h->table[i]) { - while(i--) { - Curl_llist_destroy(h->table[i], NULL); - h->table[i] = NULL; - } - free(h->table); - h->table = NULL; - h->slots = 0; - return 1; /* failure */ - } - } - return 0; /* fine */ - } - else { - h->slots = 0; - return 1; /* failure */ - } -} - -struct curl_hash * -Curl_hash_alloc(int slots, - hash_function hfunc, - comp_function comparator, - curl_hash_dtor dtor) -{ - struct curl_hash *h; - - if(!slots || !hfunc || !comparator ||!dtor) { - return NULL; /* failure */ - } - - h = malloc(sizeof(struct curl_hash)); - if(h) { - if(Curl_hash_init(h, slots, hfunc, comparator, dtor)) { - /* failure */ - free(h); - h = NULL; - } - } - - return h; -} - - - -static struct curl_hash_element * -mk_hash_element(const void *key, size_t key_len, const void *p) -{ - struct curl_hash_element *he = malloc(sizeof(struct curl_hash_element)); - - if(he) { - void *dupkey = malloc(key_len); - if(dupkey) { - /* copy the key */ - memcpy(dupkey, key, key_len); - - he->key = dupkey; - he->key_len = key_len; - he->ptr = (void *) p; - } - else { - /* failed to duplicate the key, free memory and fail */ - free(he); - he = NULL; - } - } - return he; -} - -#define FETCH_LIST(x,y,z) x->table[x->hash_func(y, z, x->slots)] - -/* Insert the data in the hash. If there already was a match in the hash, - * that data is replaced. - * - * @unittest: 1305 - */ -void * -Curl_hash_add(struct curl_hash *h, void *key, size_t key_len, void *p) -{ - struct curl_hash_element *he; - struct curl_llist_element *le; - struct curl_llist *l = FETCH_LIST (h, key, key_len); - - for(le = l->head; le; le = le->next) { - he = (struct curl_hash_element *) le->ptr; - if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_llist_remove(l, le, (void *)h); - --h->size; - break; - } - } - - he = mk_hash_element(key, key_len, p); - if(he) { - if(Curl_llist_insert_next(l, l->tail, he)) { - ++h->size; - return p; /* return the new entry */ - } - /* - * Couldn't insert it, destroy the 'he' element and the key again. We - * don't call hash_element_dtor() since that would also call the - * "destructor" for the actual data 'p'. When we fail, we shall not touch - * that data. - */ - free(he->key); - free(he); - } - - return NULL; /* failure */ -} - -/* remove the identified hash entry, returns non-zero on failure */ -int Curl_hash_delete(struct curl_hash *h, void *key, size_t key_len) -{ - struct curl_llist_element *le; - struct curl_hash_element *he; - struct curl_llist *l = FETCH_LIST(h, key, key_len); - - for(le = l->head; le; le = le->next) { - he = le->ptr; - if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_llist_remove(l, le, (void *) h); - --h->size; - return 0; - } - } - return 1; -} - -void * -Curl_hash_pick(struct curl_hash *h, void *key, size_t key_len) -{ - struct curl_llist_element *le; - struct curl_hash_element *he; - struct curl_llist *l; - - if(h) { - l = FETCH_LIST(h, key, key_len); - for(le = l->head; le; le = le->next) { - he = le->ptr; - if(h->comp_func(he->key, he->key_len, key, key_len)) { - return he->ptr; - } - } - } - - return NULL; -} - -#if defined(DEBUGBUILD) && defined(AGGRESIVE_TEST) -void -Curl_hash_apply(curl_hash *h, void *user, - void (*cb)(void *user, void *ptr)) -{ - struct curl_llist_element *le; - int i; - - for(i = 0; i < h->slots; ++i) { - for(le = (h->table[i])->head; - le; - le = le->next) { - curl_hash_element *el = le->ptr; - cb(user, el->ptr); - } - } -} -#endif - -void -Curl_hash_clean(struct curl_hash *h) -{ - int i; - - for(i = 0; i < h->slots; ++i) { - Curl_llist_destroy(h->table[i], (void *) h); - h->table[i] = NULL; - } - - Curl_safefree(h->table); - h->size = 0; - h->slots = 0; -} - -void -Curl_hash_clean_with_criterium(struct curl_hash *h, void *user, - int (*comp)(void *, void *)) -{ - struct curl_llist_element *le; - struct curl_llist_element *lnext; - struct curl_llist *list; - int i; - - if(!h) - return; - - for(i = 0; i < h->slots; ++i) { - list = h->table[i]; - le = list->head; /* get first list entry */ - while(le) { - struct curl_hash_element *he = le->ptr; - lnext = le->next; - /* ask the callback function if we shall remove this entry or not */ - if(comp(user, he->ptr)) { - Curl_llist_remove(list, le, (void *) h); - --h->size; /* one less entry in the hash now */ - } - le = lnext; - } - } -} - -void -Curl_hash_destroy(struct curl_hash *h) -{ - if(!h) - return; - - Curl_hash_clean(h); - - free(h); -} - -size_t Curl_hash_str(void* key, size_t key_length, size_t slots_num) -{ - const char* key_str = (const char *) key; - const char *end = key_str + key_length; - unsigned long h = 5381; - - while(key_str < end) { - h += h << 5; - h ^= (unsigned long) *key_str++; - } - - return (h % slots_num); -} - -size_t Curl_str_key_compare(void*k1, size_t key1_len, void*k2, size_t key2_len) -{ - char *key1 = (char *)k1; - char *key2 = (char *)k2; - - if(key1_len == key2_len && - *key1 == *key2 && - memcmp(key1, key2, key1_len) == 0) { - return 1; - } - - return 0; -} - -void Curl_hash_start_iterate(struct curl_hash *hash, - struct curl_hash_iterator *iter) -{ - iter->hash = hash; - iter->slot_index = 0; - iter->current_element = NULL; -} - -struct curl_hash_element * -Curl_hash_next_element(struct curl_hash_iterator *iter) -{ - int i; - struct curl_hash *h = iter->hash; - - /* Get the next element in the current list, if any */ - if(iter->current_element) - iter->current_element = iter->current_element->next; - - /* If we have reached the end of the list, find the next one */ - if(!iter->current_element) { - for(i = iter->slot_index;i < h->slots;i++) { - if(h->table[i]->head) { - iter->current_element = h->table[i]->head; - iter->slot_index = i+1; - break; - } - } - } - - if(iter->current_element) { - struct curl_hash_element *he = iter->current_element->ptr; - return he; - } - else { - iter->current_element = NULL; - return NULL; - } -} - -#if 0 /* useful function for debugging hashes and their contents */ -void Curl_hash_print(struct curl_hash *h, - void (*func)(void *)) -{ - struct curl_hash_iterator iter; - struct curl_hash_element *he; - int last_index = -1; - - if(!h) - return; - - fprintf(stderr, "=Hash dump=\n"); - - Curl_hash_start_iterate(h, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - if(iter.slot_index != last_index) { - fprintf(stderr, "index %d:", iter.slot_index); - if(last_index >= 0) { - fprintf(stderr, "\n"); - } - last_index = iter.slot_index; - } - - if(func) - func(he->ptr); - else - fprintf(stderr, " [%p]", he->ptr); - - he = Curl_hash_next_element(&iter); - } - fprintf(stderr, "\n"); -} -#endif diff --git a/lib/hmac.c b/lib/hmac.c deleted file mode 100644 index 692d27940..000000000 --- a/lib/hmac.c +++ /dev/null @@ -1,133 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - * RFC2104 Keyed-Hashing for Message Authentication - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_CRYPTO_AUTH - -#include "curl_hmac.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - * Generic HMAC algorithm. - * - * This module computes HMAC digests based on any hash function. Parameters - * and computing procedures are set-up dynamically at HMAC computation - * context initialisation. - */ - -static const unsigned char hmac_ipad = 0x36; -static const unsigned char hmac_opad = 0x5C; - - - -HMAC_context * -Curl_HMAC_init(const HMAC_params * hashparams, - const unsigned char * key, - unsigned int keylen) -{ - size_t i; - HMAC_context * ctxt; - unsigned char * hkey; - unsigned char b; - - /* Create HMAC context. */ - i = sizeof *ctxt + 2 * hashparams->hmac_ctxtsize + - hashparams->hmac_resultlen; - ctxt = malloc(i); - - if(!ctxt) - return ctxt; - - ctxt->hmac_hash = hashparams; - ctxt->hmac_hashctxt1 = (void *) (ctxt + 1); - ctxt->hmac_hashctxt2 = (void *) ((char *) ctxt->hmac_hashctxt1 + - hashparams->hmac_ctxtsize); - - /* If the key is too long, replace it by its hash digest. */ - if(keylen > hashparams->hmac_maxkeylen) { - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, key, keylen); - hkey = (unsigned char *) ctxt->hmac_hashctxt2 + hashparams->hmac_ctxtsize; - (*hashparams->hmac_hfinal)(hkey, ctxt->hmac_hashctxt1); - key = hkey; - keylen = hashparams->hmac_resultlen; - } - - /* Prime the two hash contexts with the modified key. */ - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt1); - (*hashparams->hmac_hinit)(ctxt->hmac_hashctxt2); - - for(i = 0; i < keylen; i++) { - b = (unsigned char)(*key ^ hmac_ipad); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &b, 1); - b = (unsigned char)(*key++ ^ hmac_opad); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &b, 1); - } - - for(; i < hashparams->hmac_maxkeylen; i++) { - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt1, &hmac_ipad, 1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, &hmac_opad, 1); - } - - /* Done, return pointer to HMAC context. */ - return ctxt; -} - -int Curl_HMAC_update(HMAC_context * ctxt, - const unsigned char * data, - unsigned int len) -{ - /* Update first hash calculation. */ - (*ctxt->hmac_hash->hmac_hupdate)(ctxt->hmac_hashctxt1, data, len); - return 0; -} - - -int Curl_HMAC_final(HMAC_context * ctxt, unsigned char * result) -{ - const HMAC_params * hashparams = ctxt->hmac_hash; - - /* Do not get result if called with a null parameter: only release - storage. */ - - if(!result) - result = (unsigned char *) ctxt->hmac_hashctxt2 + - ctxt->hmac_hash->hmac_ctxtsize; - - (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt1); - (*hashparams->hmac_hupdate)(ctxt->hmac_hashctxt2, - result, hashparams->hmac_resultlen); - (*hashparams->hmac_hfinal)(result, ctxt->hmac_hashctxt2); - free((char *) ctxt); - return 0; -} - -#endif /* CURL_DISABLE_CRYPTO_AUTH */ diff --git a/lib/hostasyn.c b/lib/hostasyn.c deleted file mode 100644 index 0097b6cfd..000000000 --- a/lib/hostasyn.c +++ /dev/null @@ -1,157 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#ifdef HAVE_PROCESS_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_hostip.h" -#include "curl_hash.h" -#include "curl_share.h" -#include "curl_strerror.h" -#include "curl_url.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/*********************************************************************** - * Only for builds using asynchronous name resolves - **********************************************************************/ -#ifdef CURLRES_ASYNCH - -/* - * Curl_addrinfo_callback() gets called by ares, gethostbyname_thread() - * or getaddrinfo_thread() when we got the name resolved (or not!). - * - * If the status argument is CURL_ASYNC_SUCCESS, this function takes - * ownership of the Curl_addrinfo passed, storing the resolved data - * in the DNS cache. - * - * The storage operation locks and unlocks the DNS cache. - */ -CURLcode Curl_addrinfo_callback(struct connectdata *conn, - int status, - struct Curl_addrinfo *ai) -{ - struct Curl_dns_entry *dns = NULL; - CURLcode rc = CURLE_OK; - - conn->async.status = status; - - if(CURL_ASYNC_SUCCESS == status) { - if(ai) { - struct SessionHandle *data = conn->data; - - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns = Curl_cache_addr(data, ai, - conn->async.hostname, - conn->async.port); - if(!dns) { - /* failed to store, cleanup and return error */ - Curl_freeaddrinfo(ai); - rc = CURLE_OUT_OF_MEMORY; - } - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - } - else { - rc = CURLE_OUT_OF_MEMORY; - } - } - - conn->async.dns = dns; - - /* Set async.done TRUE last in this function since it may be used multi- - threaded and once this is TRUE the other thread may read fields from the - async struct */ - conn->async.done = TRUE; - - /* ipv4: The input hostent struct will be freed by ares when we return from - this function */ - return rc; -} - -/* Call this function after Curl_connect() has returned async=TRUE and - then a successful name resolve has been received. - - Note: this function disconnects and frees the conn data in case of - resolve failure */ -CURLcode Curl_async_resolved(struct connectdata *conn, - bool *protocol_done) -{ - CURLcode code; - - if(conn->async.dns) { - conn->dns_entry = conn->async.dns; - conn->async.dns = NULL; - } - - code = Curl_setup_conn(conn, protocol_done); - - if(code) - /* We're not allowed to return failure with memory left allocated - in the connectdata struct, free those here */ - Curl_disconnect(conn, FALSE); /* close the connection */ - - return code; -} - -/* - * Curl_getaddrinfo() is the generic low-level name resolve API within this - * source file. There are several versions of this function - for different - * name resolve layers (selected at build-time). They all take this same set - * of arguments - */ -Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) -{ - return Curl_resolver_getaddrinfo(conn, hostname, port, waitp); -} - -#endif /* CURLRES_ASYNCH */ diff --git a/lib/hostcheck.c b/lib/hostcheck.c deleted file mode 100644 index a5bf8b02f..000000000 --- a/lib/hostcheck.c +++ /dev/null @@ -1,96 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(USE_SSLEAY) || defined(USE_AXTLS) -/* these two backends use functions from this file */ - -#include "curl_hostcheck.h" -#include "curl_rawstr.h" - -/* - * Match a hostname against a wildcard pattern. - * E.g. - * "foo.host.com" matches "*.host.com". - * - * We use the matching rule described in RFC6125, section 6.4.3. - * http://tools.ietf.org/html/rfc6125#section-6.4.3 - */ - -static int hostmatch(const char *hostname, const char *pattern) -{ - const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; - int wildcard_enabled; - size_t prefixlen, suffixlen; - pattern_wildcard = strchr(pattern, '*'); - if(pattern_wildcard == NULL) - return Curl_raw_equal(pattern, hostname) ? - CURL_HOST_MATCH : CURL_HOST_NOMATCH; - - /* We require at least 2 dots in pattern to avoid too wide wildcard - match. */ - wildcard_enabled = 1; - pattern_label_end = strchr(pattern, '.'); - if(pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL || - pattern_wildcard > pattern_label_end || - Curl_raw_nequal(pattern, "xn--", 4)) { - wildcard_enabled = 0; - } - if(!wildcard_enabled) - return Curl_raw_equal(pattern, hostname) ? - CURL_HOST_MATCH : CURL_HOST_NOMATCH; - - hostname_label_end = strchr(hostname, '.'); - if(hostname_label_end == NULL || - !Curl_raw_equal(pattern_label_end, hostname_label_end)) - return CURL_HOST_NOMATCH; - - /* The wildcard must match at least one character, so the left-most - label of the hostname is at least as large as the left-most label - of the pattern. */ - if(hostname_label_end - hostname < pattern_label_end - pattern) - return CURL_HOST_NOMATCH; - - prefixlen = pattern_wildcard - pattern; - suffixlen = pattern_label_end - (pattern_wildcard+1); - return Curl_raw_nequal(pattern, hostname, prefixlen) && - Curl_raw_nequal(pattern_wildcard+1, hostname_label_end - suffixlen, - suffixlen) ? - CURL_HOST_MATCH : CURL_HOST_NOMATCH; -} - -int Curl_cert_hostcheck(const char *match_pattern, const char *hostname) -{ - if(!match_pattern || !*match_pattern || - !hostname || !*hostname) /* sanity check */ - return 0; - - if(Curl_raw_equal(hostname, match_pattern)) /* trivial case */ - return 1; - - if(hostmatch(hostname,match_pattern) == CURL_HOST_MATCH) - return 1; - return 0; -} - -#endif /* SSLEAY or AXTLS */ diff --git a/lib/hostip.c b/lib/hostip.c deleted file mode 100644 index 7cc51f899..000000000 --- a/lib/hostip.c +++ /dev/null @@ -1,820 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#ifdef HAVE_SETJMP_H -#include -#endif -#ifdef HAVE_SIGNAL_H -#include -#endif - -#ifdef HAVE_PROCESS_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_hostip.h" -#include "curl_hash.h" -#include "curl_share.h" -#include "curl_strerror.h" -#include "curl_url.h" -#include "curl_inet_ntop.h" -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#if defined(CURLRES_SYNCH) && \ - defined(HAVE_ALARM) && defined(SIGALRM) && defined(HAVE_SIGSETJMP) -/* alarm-based timeouts can only be used with all the dependencies satisfied */ -#define USE_ALARM_TIMEOUT -#endif - -/* - * curl_hostip.c explained - * ======================= - * - * The main COMPILE-TIME DEFINES to keep in mind when reading the curl_host*.c - * source file are these: - * - * CURLRES_IPV6 - this host has getaddrinfo() and family, and thus we use - * that. The host may not be able to resolve IPv6, but we don't really have to - * take that into account. Hosts that aren't IPv6-enabled have CURLRES_IPV4 - * defined. - * - * CURLRES_ARES - is defined if libcurl is built to use c-ares for - * asynchronous name resolves. This can be Windows or *nix. - * - * CURLRES_THREADED - is defined if libcurl is built to run under (native) - * Windows, and then the name resolve will be done in a new thread, and the - * supported API will be the same as for ares-builds. - * - * If any of the two previous are defined, CURLRES_ASYNCH is defined too. If - * libcurl is not built to use an asynchronous resolver, CURLRES_SYNCH is - * defined. - * - * The curl_host*.c sources files are split up like this: - * - * curl_hostip.c - method-independent resolver and utility functions - * curl_hostasyn.c - functions for asynchronous name resolves - * curl_hostsyn.c - functions for synchronous name resolves - * curl_hostip4.c - ipv4-specific functions - * curl_hostip6.c - ipv6-specific functions - * - * The two asynchronous name resolver backends are implemented in: - * curl_asyn_ares.c - functions for ares-using name resolves - * curl_asyn_thread.c - functions for threaded name resolves - - * The curl_hostip.h is the united header file for all this. It defines the - * CURLRES_* defines based on the config*.h and curl_setup.h defines. - */ - -/* These two symbols are for the global DNS cache */ -static struct curl_hash hostname_cache; -static int host_cache_initialized; - -static void freednsentry(void *freethis); - -/* - * Curl_global_host_cache_init() initializes and sets up a global DNS cache. - * Global DNS cache is general badness. Do not use. This will be removed in - * a future version. Use the share interface instead! - * - * Returns a struct curl_hash pointer on success, NULL on failure. - */ -struct curl_hash *Curl_global_host_cache_init(void) -{ - int rc = 0; - if(!host_cache_initialized) { - rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str, - Curl_str_key_compare, freednsentry); - if(!rc) - host_cache_initialized = 1; - } - return rc?NULL:&hostname_cache; -} - -/* - * Destroy and cleanup the global DNS cache - */ -void Curl_global_host_cache_dtor(void) -{ - if(host_cache_initialized) { - Curl_hash_clean(&hostname_cache); - host_cache_initialized = 0; - } -} - -/* - * Return # of adresses in a Curl_addrinfo struct - */ -int Curl_num_addresses(const Curl_addrinfo *addr) -{ - int i = 0; - while(addr) { - addr = addr->ai_next; - i++; - } - return i; -} - -/* - * Curl_printable_address() returns a printable version of the 1st address - * given in the 'ai' argument. The result will be stored in the buf that is - * bufsize bytes big. - * - * If the conversion fails, it returns NULL. - */ -const char * -Curl_printable_address(const Curl_addrinfo *ai, char *buf, size_t bufsize) -{ - const struct sockaddr_in *sa4; - const struct in_addr *ipaddr4; -#ifdef ENABLE_IPV6 - const struct sockaddr_in6 *sa6; - const struct in6_addr *ipaddr6; -#endif - - switch (ai->ai_family) { - case AF_INET: - sa4 = (const void *)ai->ai_addr; - ipaddr4 = &sa4->sin_addr; - return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, - bufsize); -#ifdef ENABLE_IPV6 - case AF_INET6: - sa6 = (const void *)ai->ai_addr; - ipaddr6 = &sa6->sin6_addr; - return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf, - bufsize); -#endif - default: - break; - } - return NULL; -} - -/* - * Return a hostcache id string for the provided host + port, to be used by - * the DNS caching. - */ -static char * -create_hostcache_id(const char *name, int port) -{ - /* create and return the new allocated entry */ - char *id = aprintf("%s:%d", name, port); - char *ptr = id; - if(ptr) { - /* lower case the name part */ - while(*ptr && (*ptr != ':')) { - *ptr = (char)TOLOWER(*ptr); - ptr++; - } - } - return id; -} - -struct hostcache_prune_data { - long cache_timeout; - time_t now; -}; - -/* - * This function is set as a callback to be called for every entry in the DNS - * cache when we want to prune old unused entries. - * - * Returning non-zero means remove the entry, return 0 to keep it in the - * cache. - */ -static int -hostcache_timestamp_remove(void *datap, void *hc) -{ - struct hostcache_prune_data *data = - (struct hostcache_prune_data *) datap; - struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; - - return (data->now - c->timestamp >= data->cache_timeout); -} - -/* - * Prune the DNS cache. This assumes that a lock has already been taken. - */ -static void -hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now) -{ - struct hostcache_prune_data user; - - user.cache_timeout = cache_timeout; - user.now = now; - - Curl_hash_clean_with_criterium(hostcache, - (void *) &user, - hostcache_timestamp_remove); -} - -/* - * Library-wide function for pruning the DNS cache. This function takes and - * returns the appropriate locks. - */ -void Curl_hostcache_prune(struct SessionHandle *data) -{ - time_t now; - - if((data->set.dns_cache_timeout == -1) || !data->dns.hostcache) - /* cache forever means never prune, and NULL hostcache means - we can't do it */ - return; - - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - time(&now); - - /* Remove outdated and unused entries from the hostcache */ - hostcache_prune(data->dns.hostcache, - data->set.dns_cache_timeout, - now); - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); -} - -/* - * Check if the entry should be pruned. Assumes a locked cache. - */ -static int -remove_entry_if_stale(struct SessionHandle *data, struct Curl_dns_entry *dns) -{ - struct hostcache_prune_data user; - - if(!dns || (data->set.dns_cache_timeout == -1) || !data->dns.hostcache) - /* cache forever means never prune, and NULL hostcache means - we can't do it */ - return 0; - - time(&user.now); - user.cache_timeout = data->set.dns_cache_timeout; - - if(!hostcache_timestamp_remove(&user,dns) ) - return 0; - - Curl_hash_clean_with_criterium(data->dns.hostcache, - (void *) &user, - hostcache_timestamp_remove); - - return 1; -} - - -#ifdef HAVE_SIGSETJMP -/* Beware this is a global and unique instance. This is used to store the - return address that we can jump back to from inside a signal handler. This - is not thread-safe stuff. */ -sigjmp_buf curl_jmpenv; -#endif - - -/* - * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. - * - * When calling Curl_resolv() has resulted in a response with a returned - * address, we call this function to store the information in the dns - * cache etc - * - * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. - */ -struct Curl_dns_entry * -Curl_cache_addr(struct SessionHandle *data, - Curl_addrinfo *addr, - const char *hostname, - int port) -{ - char *entry_id; - size_t entry_len; - struct Curl_dns_entry *dns; - struct Curl_dns_entry *dns2; - - /* Create an entry id, based upon the hostname and port */ - entry_id = create_hostcache_id(hostname, port); - /* If we can't create the entry id, fail */ - if(!entry_id) - return NULL; - entry_len = strlen(entry_id); - - /* Create a new cache entry */ - dns = calloc(1, sizeof(struct Curl_dns_entry)); - if(!dns) { - free(entry_id); - return NULL; - } - - dns->inuse = 0; /* init to not used */ - dns->addr = addr; /* this is the address(es) */ - time(&dns->timestamp); - if(dns->timestamp == 0) - dns->timestamp = 1; /* zero indicates that entry isn't in hash table */ - - /* Store the resolved data in our DNS cache. */ - dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len+1, - (void *)dns); - if(!dns2) { - free(dns); - free(entry_id); - return NULL; - } - - dns = dns2; - dns->inuse++; /* mark entry as in-use */ - - /* free the allocated entry_id */ - free(entry_id); - - return dns; -} - -/* - * Curl_resolv() is the main name resolve function within libcurl. It resolves - * a name and returns a pointer to the entry in the 'entry' argument (if one - * is provided). This function might return immediately if we're using asynch - * resolves. See the return codes. - * - * The cache entry we return will get its 'inuse' counter increased when this - * function is used. You MUST call Curl_resolv_unlock() later (when you're - * done using this struct) to decrease the counter again. - * - * In debug mode, we specifically test for an interface name "LocalHost" - * and resolve "localhost" instead as a means to permit test cases - * to connect to a local test server with any host name. - * - * Return codes: - * - * CURLRESOLV_ERROR (-1) = error, no pointer - * CURLRESOLV_RESOLVED (0) = OK, pointer provided - * CURLRESOLV_PENDING (1) = waiting for response, no pointer - */ - -int Curl_resolv(struct connectdata *conn, - const char *hostname, - int port, - struct Curl_dns_entry **entry) -{ - char *entry_id = NULL; - struct Curl_dns_entry *dns = NULL; - size_t entry_len; - struct SessionHandle *data = conn->data; - CURLcode result; - int rc = CURLRESOLV_ERROR; /* default to failure */ - - *entry = NULL; - - /* Create an entry id, based upon the hostname and port */ - entry_id = create_hostcache_id(hostname, port); - /* If we can't create the entry id, fail */ - if(!entry_id) - return rc; - - entry_len = strlen(entry_id); - - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - /* See if its already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1); - - /* free the allocated entry_id again */ - free(entry_id); - - /* See whether the returned entry is stale. Done before we release lock */ - if(remove_entry_if_stale(data, dns)) - dns = NULL; /* the memory deallocation is being handled by the hash */ - - if(dns) { - dns->inuse++; /* we use it! */ - rc = CURLRESOLV_RESOLVED; - } - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) { - /* The entry was not in the cache. Resolve it to IP address */ - - Curl_addrinfo *addr; - int respwait; - - /* Check what IP specifics the app has requested and if we can provide it. - * If not, bail out. */ - if(!Curl_ipvalid(conn)) - return CURLRESOLV_ERROR; - - /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a - non-zero value indicating that we need to wait for the response to the - resolve call */ - addr = Curl_getaddrinfo(conn, -#ifdef DEBUGBUILD - (data->set.str[STRING_DEVICE] - && !strcmp(data->set.str[STRING_DEVICE], - "LocalHost"))?"localhost": -#endif - hostname, port, &respwait); - - if(!addr) { - if(respwait) { - /* the response to our resolve call will come asynchronously at - a later time, good or bad */ - /* First, check that we haven't received the info by now */ - result = Curl_resolver_is_resolved(conn, &dns); - if(result) /* error detected */ - return CURLRESOLV_ERROR; - if(dns) - rc = CURLRESOLV_RESOLVED; /* pointer provided */ - else - rc = CURLRESOLV_PENDING; /* no info yet */ - } - } - else { - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - /* we got a response, store it in the cache */ - dns = Curl_cache_addr(data, addr, hostname, port); - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) - /* returned failure, bail out nicely */ - Curl_freeaddrinfo(addr); - else - rc = CURLRESOLV_RESOLVED; - } - } - - *entry = dns; - - return rc; -} - -#ifdef USE_ALARM_TIMEOUT -/* - * This signal handler jumps back into the main libcurl code and continues - * execution. This effectively causes the remainder of the application to run - * within a signal handler which is nonportable and could lead to problems. - */ -static -RETSIGTYPE alarmfunc(int sig) -{ - /* this is for "-ansi -Wall -pedantic" to stop complaining! (rabe) */ - (void)sig; - siglongjmp(curl_jmpenv, 1); - return; -} -#endif /* USE_ALARM_TIMEOUT */ - -/* - * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a - * timeout. This function might return immediately if we're using asynch - * resolves. See the return codes. - * - * The cache entry we return will get its 'inuse' counter increased when this - * function is used. You MUST call Curl_resolv_unlock() later (when you're - * done using this struct) to decrease the counter again. - * - * If built with a synchronous resolver and use of signals is not - * disabled by the application, then a nonzero timeout will cause a - * timeout after the specified number of milliseconds. Otherwise, timeout - * is ignored. - * - * Return codes: - * - * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired - * CURLRESOLV_ERROR (-1) = error, no pointer - * CURLRESOLV_RESOLVED (0) = OK, pointer provided - * CURLRESOLV_PENDING (1) = waiting for response, no pointer - */ - -int Curl_resolv_timeout(struct connectdata *conn, - const char *hostname, - int port, - struct Curl_dns_entry **entry, - long timeoutms) -{ -#ifdef USE_ALARM_TIMEOUT -#ifdef HAVE_SIGACTION - struct sigaction keep_sigact; /* store the old struct here */ - volatile bool keep_copysig = FALSE; /* wether old sigact has been saved */ - struct sigaction sigact; -#else -#ifdef HAVE_SIGNAL - void (*keep_sigact)(int); /* store the old handler here */ -#endif /* HAVE_SIGNAL */ -#endif /* HAVE_SIGACTION */ - volatile long timeout; - volatile unsigned int prev_alarm = 0; - struct SessionHandle *data = conn->data; -#endif /* USE_ALARM_TIMEOUT */ - int rc; - - *entry = NULL; - - if(timeoutms < 0) - /* got an already expired timeout */ - return CURLRESOLV_TIMEDOUT; - -#ifdef USE_ALARM_TIMEOUT - if(data->set.no_signal) - /* Ignore the timeout when signals are disabled */ - timeout = 0; - else - timeout = timeoutms; - - if(!timeout) - /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */ - return Curl_resolv(conn, hostname, port, entry); - - if(timeout < 1000) - /* The alarm() function only provides integer second resolution, so if - we want to wait less than one second we must bail out already now. */ - return CURLRESOLV_TIMEDOUT; - - /************************************************************* - * Set signal handler to catch SIGALRM - * Store the old value to be able to set it back later! - *************************************************************/ -#ifdef HAVE_SIGACTION - sigaction(SIGALRM, NULL, &sigact); - keep_sigact = sigact; - keep_copysig = TRUE; /* yes, we have a copy */ - sigact.sa_handler = alarmfunc; -#ifdef SA_RESTART - /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */ - sigact.sa_flags &= ~SA_RESTART; -#endif - /* now set the new struct */ - sigaction(SIGALRM, &sigact, NULL); -#else /* HAVE_SIGACTION */ - /* no sigaction(), revert to the much lamer signal() */ -#ifdef HAVE_SIGNAL - keep_sigact = signal(SIGALRM, alarmfunc); -#endif -#endif /* HAVE_SIGACTION */ - - /* alarm() makes a signal get sent when the timeout fires off, and that - will abort system calls */ - prev_alarm = alarm(curlx_sltoui(timeout/1000L)); - - /* This allows us to time-out from the name resolver, as the timeout - will generate a signal and we will siglongjmp() from that here. - This technique has problems (see alarmfunc). - This should be the last thing we do before calling Curl_resolv(), - as otherwise we'd have to worry about variables that get modified - before we invoke Curl_resolv() (and thus use "volatile"). */ - if(sigsetjmp(curl_jmpenv, 1)) { - /* this is coming from a siglongjmp() after an alarm signal */ - failf(data, "name lookup timed out"); - rc = CURLRESOLV_ERROR; - goto clean_up; - } - -#else -#ifndef CURLRES_ASYNCH - if(timeoutms) - infof(conn->data, "timeout on name lookup is not supported\n"); -#else - (void)timeoutms; /* timeoutms not used with an async resolver */ -#endif -#endif /* USE_ALARM_TIMEOUT */ - - /* Perform the actual name resolution. This might be interrupted by an - * alarm if it takes too long. - */ - rc = Curl_resolv(conn, hostname, port, entry); - -#ifdef USE_ALARM_TIMEOUT -clean_up: - - if(!prev_alarm) - /* deactivate a possibly active alarm before uninstalling the handler */ - alarm(0); - -#ifdef HAVE_SIGACTION - if(keep_copysig) { - /* we got a struct as it looked before, now put that one back nice - and clean */ - sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */ - } -#else -#ifdef HAVE_SIGNAL - /* restore the previous SIGALRM handler */ - signal(SIGALRM, keep_sigact); -#endif -#endif /* HAVE_SIGACTION */ - - /* switch back the alarm() to either zero or to what it was before minus - the time we spent until now! */ - if(prev_alarm) { - /* there was an alarm() set before us, now put it back */ - unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created); - - /* the alarm period is counted in even number of seconds */ - unsigned long alarm_set = prev_alarm - elapsed_ms/1000; - - if(!alarm_set || - ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) { - /* if the alarm time-left reached zero or turned "negative" (counted - with unsigned values), we should fire off a SIGALRM here, but we - won't, and zero would be to switch it off so we never set it to - less than 1! */ - alarm(1); - rc = CURLRESOLV_TIMEDOUT; - failf(data, "Previous alarm fired off!"); - } - else - alarm((unsigned int)alarm_set); - } -#endif /* USE_ALARM_TIMEOUT */ - - return rc; -} - -/* - * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been - * made, the struct may be destroyed due to pruning. It is important that only - * one unlock is made for each Curl_resolv() call. - */ -void Curl_resolv_unlock(struct SessionHandle *data, struct Curl_dns_entry *dns) -{ - DEBUGASSERT(dns && (dns->inuse>0)); - - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - dns->inuse--; - /* only free if nobody is using AND it is not in hostcache (timestamp == - 0) */ - if(dns->inuse == 0 && dns->timestamp == 0) { - Curl_freeaddrinfo(dns->addr); - free(dns); - } - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); -} - -/* - * File-internal: free a cache dns entry. - */ -static void freednsentry(void *freethis) -{ - struct Curl_dns_entry *p = (struct Curl_dns_entry *) freethis; - - /* mark the entry as not in hostcache */ - p->timestamp = 0; - if(p->inuse == 0) { - Curl_freeaddrinfo(p->addr); - free(p); - } -} - -/* - * Curl_mk_dnscache() creates a new DNS cache and returns the handle for it. - */ -struct curl_hash *Curl_mk_dnscache(void) -{ - return Curl_hash_alloc(7, Curl_hash_str, Curl_str_key_compare, freednsentry); -} - -static int hostcache_inuse(void *data, void *hc) -{ - struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; - - if(c->inuse == 1) - Curl_resolv_unlock(data, c); - - return 1; /* free all entries */ -} - -void Curl_hostcache_clean(struct SessionHandle *data) -{ - /* Entries added to the hostcache with the CURLOPT_RESOLVE function are - * still present in the cache with the inuse counter set to 1. Detect them - * and cleanup! - */ - Curl_hash_clean_with_criterium(data->dns.hostcache, data, hostcache_inuse); -} - -void Curl_hostcache_destroy(struct SessionHandle *data) -{ - Curl_hostcache_clean(data); - Curl_hash_destroy(data->dns.hostcache); - data->dns.hostcachetype = HCACHE_NONE; - data->dns.hostcache = NULL; -} - -CURLcode Curl_loadhostpairs(struct SessionHandle *data) -{ - struct curl_slist *hostp; - char hostname[256]; - char address[256]; - int port; - - for(hostp = data->change.resolve; hostp; hostp = hostp->next ) { - if(!hostp->data) - continue; - if(hostp->data[0] == '-') { - /* TODO: mark an entry for removal */ - } - else if(3 == sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port, - address)) { - struct Curl_dns_entry *dns; - Curl_addrinfo *addr; - char *entry_id; - size_t entry_len; - - addr = Curl_str2addr(address, port); - if(!addr) { - infof(data, "Resolve %s found illegal!\n", hostp->data); - continue; - } - - /* Create an entry id, based upon the hostname and port */ - entry_id = create_hostcache_id(hostname, port); - /* If we can't create the entry id, fail */ - if(!entry_id) { - Curl_freeaddrinfo(addr); - return CURLE_OUT_OF_MEMORY; - } - - entry_len = strlen(entry_id); - - if(data->share) - Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - - /* See if its already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1); - - /* free the allocated entry_id again */ - free(entry_id); - - if(!dns) - /* if not in the cache already, put this host in the cache */ - dns = Curl_cache_addr(data, addr, hostname, port); - else - /* this is a duplicate, free it again */ - Curl_freeaddrinfo(addr); - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - if(!dns) { - Curl_freeaddrinfo(addr); - return CURLE_OUT_OF_MEMORY; - } - infof(data, "Added %s:%d:%s to DNS cache\n", - hostname, port, address); - } - } - data->change.resolve = NULL; /* dealt with now */ - - return CURLE_OK; -} diff --git a/lib/hostip4.c b/lib/hostip4.c deleted file mode 100644 index 5b64b4673..000000000 --- a/lib/hostip4.c +++ /dev/null @@ -1,310 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#ifdef HAVE_PROCESS_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_hostip.h" -#include "curl_hash.h" -#include "curl_share.h" -#include "curl_strerror.h" -#include "curl_url.h" -#include "curl_inet_pton.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/*********************************************************************** - * Only for plain-ipv4 builds - **********************************************************************/ -#ifdef CURLRES_IPV4 /* plain ipv4 code coming up */ -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct connectdata *conn) -{ - if(conn->ip_version == CURL_IPRESOLVE_V6) - /* an ipv6 address was requested and we can't get/use one */ - return FALSE; - - return TRUE; /* OK, proceed */ -} - -#ifdef CURLRES_SYNCH - -/* - * Curl_getaddrinfo() - the ipv4 synchronous version. - * - * The original code to this function was from the Dancer source code, written - * by Bjorn Reese, it has since been patched and modified considerably. - * - * gethostbyname_r() is the thread-safe version of the gethostbyname() - * function. When we build for plain IPv4, we attempt to use this - * function. There are _three_ different gethostbyname_r() versions, and we - * detect which one this platform supports in the configure script and set up - * the HAVE_GETHOSTBYNAME_R_3, HAVE_GETHOSTBYNAME_R_5 or - * HAVE_GETHOSTBYNAME_R_6 defines accordingly. Note that HAVE_GETADDRBYNAME - * has the corresponding rules. This is primarily on *nix. Note that some unix - * flavours have thread-safe versions of the plain gethostbyname() etc. - * - */ -Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) -{ - Curl_addrinfo *ai = NULL; - -#ifdef CURL_DISABLE_VERBOSE_STRINGS - (void)conn; -#endif - - *waitp = 0; /* synchronous response only */ - - ai = Curl_ipv4_resolve_r(hostname, port); - if(!ai) - infof(conn->data, "Curl_ipv4_resolve_r failed for %s\n", hostname); - - return ai; -} -#endif /* CURLRES_SYNCH */ -#endif /* CURLRES_IPV4 */ - -#if defined(CURLRES_IPV4) && !defined(CURLRES_ARES) - -/* - * Curl_ipv4_resolve_r() - ipv4 threadsafe resolver function. - * - * This is used for both synchronous and asynchronous resolver builds, - * implying that only threadsafe code and function calls may be used. - * - */ -Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, - int port) -{ -#if !defined(HAVE_GETADDRINFO_THREADSAFE) && defined(HAVE_GETHOSTBYNAME_R_3) - int res; -#endif - Curl_addrinfo *ai = NULL; - struct hostent *h = NULL; - struct in_addr in; - struct hostent *buf = NULL; - - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - return Curl_ip2addr(AF_INET, &in, hostname, port); - -#if defined(HAVE_GETADDRINFO_THREADSAFE) - else { - struct addrinfo hints; - char sbuf[NI_MAXSERV]; - char *sbufptr = NULL; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = PF_INET; - hints.ai_socktype = SOCK_STREAM; - if(port) { - snprintf(sbuf, sizeof(sbuf), "%d", port); - sbufptr = sbuf; - } - - (void)Curl_getaddrinfo_ex(hostname, sbufptr, &hints, &ai); - -#elif defined(HAVE_GETHOSTBYNAME_R) - /* - * gethostbyname_r() is the preferred resolve function for many platforms. - * Since there are three different versions of it, the following code is - * somewhat #ifdef-ridden. - */ - else { - int h_errnop; - - buf = calloc(1, CURL_HOSTENT_SIZE); - if(!buf) - return NULL; /* major failure */ - /* - * The clearing of the buffer is a workaround for a gethostbyname_r bug in - * qnx nto and it is also _required_ for some of these functions on some - * platforms. - */ - -#if defined(HAVE_GETHOSTBYNAME_R_5) - /* Solaris, IRIX and more */ - h = gethostbyname_r(hostname, - (struct hostent *)buf, - (char *)buf + sizeof(struct hostent), - CURL_HOSTENT_SIZE - sizeof(struct hostent), - &h_errnop); - - /* If the buffer is too small, it returns NULL and sets errno to - * ERANGE. The errno is thread safe if this is compiled with - * -D_REENTRANT as then the 'errno' variable is a macro defined to get - * used properly for threads. - */ - - if(h) { - ; - } - else -#elif defined(HAVE_GETHOSTBYNAME_R_6) - /* Linux */ - - (void)gethostbyname_r(hostname, - (struct hostent *)buf, - (char *)buf + sizeof(struct hostent), - CURL_HOSTENT_SIZE - sizeof(struct hostent), - &h, /* DIFFERENCE */ - &h_errnop); - /* Redhat 8, using glibc 2.2.93 changed the behavior. Now all of a - * sudden this function returns EAGAIN if the given buffer size is too - * small. Previous versions are known to return ERANGE for the same - * problem. - * - * This wouldn't be such a big problem if older versions wouldn't - * sometimes return EAGAIN on a common failure case. Alas, we can't - * assume that EAGAIN *or* ERANGE means ERANGE for any given version of - * glibc. - * - * For now, we do that and thus we may call the function repeatedly and - * fail for older glibc versions that return EAGAIN, until we run out of - * buffer size (step_size grows beyond CURL_HOSTENT_SIZE). - * - * If anyone has a better fix, please tell us! - * - * ------------------------------------------------------------------- - * - * On October 23rd 2003, Dan C dug up more details on the mysteries of - * gethostbyname_r() in glibc: - * - * In glibc 2.2.5 the interface is different (this has also been - * discovered in glibc 2.1.1-6 as shipped by Redhat 6). What I can't - * explain, is that tests performed on glibc 2.2.4-34 and 2.2.4-32 - * (shipped/upgraded by Redhat 7.2) don't show this behavior! - * - * In this "buggy" version, the return code is -1 on error and 'errno' - * is set to the ERANGE or EAGAIN code. Note that 'errno' is not a - * thread-safe variable. - */ - - if(!h) /* failure */ -#elif defined(HAVE_GETHOSTBYNAME_R_3) - /* AIX, Digital Unix/Tru64, HPUX 10, more? */ - - /* For AIX 4.3 or later, we don't use gethostbyname_r() at all, because of - * the plain fact that it does not return unique full buffers on each - * call, but instead several of the pointers in the hostent structs will - * point to the same actual data! This have the unfortunate down-side that - * our caching system breaks down horribly. Luckily for us though, AIX 4.3 - * and more recent versions have a "completely thread-safe"[*] libc where - * all the data is stored in thread-specific memory areas making calls to - * the plain old gethostbyname() work fine even for multi-threaded - * programs. - * - * This AIX 4.3 or later detection is all made in the configure script. - * - * Troels Walsted Hansen helped us work this out on March 3rd, 2003. - * - * [*] = much later we've found out that it isn't at all "completely - * thread-safe", but at least the gethostbyname() function is. - */ - - if(CURL_HOSTENT_SIZE >= - (sizeof(struct hostent)+sizeof(struct hostent_data))) { - - /* August 22nd, 2000: Albert Chin-A-Young brought an updated version - * that should work! September 20: Richard Prescott worked on the buffer - * size dilemma. - */ - - res = gethostbyname_r(hostname, - (struct hostent *)buf, - (struct hostent_data *)((char *)buf + - sizeof(struct hostent))); - h_errnop = SOCKERRNO; /* we don't deal with this, but set it anyway */ - } - else - res = -1; /* failure, too smallish buffer size */ - - if(!res) { /* success */ - - h = buf; /* result expected in h */ - - /* This is the worst kind of the different gethostbyname_r() interfaces. - * Since we don't know how big buffer this particular lookup required, - * we can't realloc down the huge alloc without doing closer analysis of - * the returned data. Thus, we always use CURL_HOSTENT_SIZE for every - * name lookup. Fixing this would require an extra malloc() and then - * calling Curl_addrinfo_copy() that subsequent realloc()s down the new - * memory area to the actually used amount. - */ - } - else -#endif /* HAVE_...BYNAME_R_5 || HAVE_...BYNAME_R_6 || HAVE_...BYNAME_R_3 */ - { - h = NULL; /* set return code to NULL */ - free(buf); - } -#else /* HAVE_GETADDRINFO_THREADSAFE || HAVE_GETHOSTBYNAME_R */ - /* - * Here is code for platforms that don't have a thread safe - * getaddrinfo() nor gethostbyname_r() function or for which - * gethostbyname() is the preferred one. - */ - else { - h = gethostbyname((void*)hostname); -#endif /* HAVE_GETADDRINFO_THREADSAFE || HAVE_GETHOSTBYNAME_R */ - } - - if(h) { - ai = Curl_he2ai(h, port); - - if(buf) /* used a *_r() function */ - free(buf); - } - - return ai; -} -#endif /* defined(CURLRES_IPV4) && !defined(CURLRES_ARES) */ diff --git a/lib/hostip6.c b/lib/hostip6.c deleted file mode 100644 index cfd6081c1..000000000 --- a/lib/hostip6.c +++ /dev/null @@ -1,224 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#ifdef HAVE_PROCESS_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_hostip.h" -#include "curl_hash.h" -#include "curl_share.h" -#include "curl_strerror.h" -#include "curl_url.h" -#include "curl_inet_pton.h" -#include "curl_connect.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/*********************************************************************** - * Only for ipv6-enabled builds - **********************************************************************/ -#ifdef CURLRES_IPV6 - - -#if defined(CURLDEBUG) && defined(HAVE_GETNAMEINFO) -/* These are strictly for memory tracing and are using the same style as the - * family otherwise present in curl_memdebug.c. I put these ones here since - * they require a bunch of structs I didn't want to include there. - */ - -/* - * For CURLRES_ARS, this should be written using ares_gethostbyaddr() - * (ignoring the fact c-ares doesn't return 'serv'). - */ - -int curl_dogetnameinfo(GETNAMEINFO_QUAL_ARG1 GETNAMEINFO_TYPE_ARG1 sa, - GETNAMEINFO_TYPE_ARG2 salen, - char *host, GETNAMEINFO_TYPE_ARG46 hostlen, - char *serv, GETNAMEINFO_TYPE_ARG46 servlen, - GETNAMEINFO_TYPE_ARG7 flags, - int line, const char *source) -{ - int res = (getnameinfo)(sa, salen, - host, hostlen, - serv, servlen, - flags); - if(0 == res) - /* success */ - curl_memlog("GETNAME %s:%d getnameinfo()\n", - source, line); - else - curl_memlog("GETNAME %s:%d getnameinfo() failed = %d\n", - source, line, res); - return res; -} -#endif /* defined(CURLDEBUG) && defined(HAVE_GETNAMEINFO) */ - -/* - * Curl_ipv6works() returns TRUE if ipv6 seems to work. - */ -bool Curl_ipv6works(void) -{ - /* the nature of most system is that IPv6 status doesn't come and go - during a program's lifetime so we only probe the first time and then we - have the info kept for fast re-use */ - static int ipv6_works = -1; - if(-1 == ipv6_works) { - /* probe to see if we have a working IPv6 stack */ - curl_socket_t s = socket(PF_INET6, SOCK_DGRAM, 0); - if(s == CURL_SOCKET_BAD) - /* an ipv6 address was requested but we can't get/use one */ - ipv6_works = 0; - else { - ipv6_works = 1; - Curl_closesocket(NULL, s); - } - } - return (ipv6_works>0)?TRUE:FALSE; -} - -/* - * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've - * been set and returns TRUE if they are OK. - */ -bool Curl_ipvalid(struct connectdata *conn) -{ - if(conn->ip_version == CURL_IPRESOLVE_V6) - return Curl_ipv6works(); - return TRUE; -} - -#if defined(CURLRES_SYNCH) - -#ifdef DEBUG_ADDRINFO -static void dump_addrinfo(struct connectdata *conn, const Curl_addrinfo *ai) -{ - printf("dump_addrinfo:\n"); - for(; ai; ai = ai->ai_next) { - char buf[INET6_ADDRSTRLEN]; - - printf(" fam %2d, CNAME %s, ", - ai->ai_family, ai->ai_canonname ? ai->ai_canonname : ""); - if(Curl_printable_address(ai, buf, sizeof(buf))) - printf("%s\n", buf); - else - printf("failed; %s\n", Curl_strerror(conn, SOCKERRNO)); - } -} -#else -#define dump_addrinfo(x,y) Curl_nop_stmt -#endif - -/* - * Curl_getaddrinfo() when built ipv6-enabled (non-threading and - * non-ares version). - * - * Returns name information about the given hostname and port number. If - * successful, the 'addrinfo' is returned and the forth argument will point to - * memory we need to free after use. That memory *MUST* be freed with - * Curl_freeaddrinfo(), nothing else. - */ -Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) -{ - struct addrinfo hints; - Curl_addrinfo *res; - int error; - char sbuf[NI_MAXSERV]; - char *sbufptr = NULL; - char addrbuf[128]; - int pf; - struct SessionHandle *data = conn->data; - - *waitp = 0; /* synchronous response only */ - - /* - * Check if a limited name resolve has been requested. - */ - switch(conn->ip_version) { - case CURL_IPRESOLVE_V4: - pf = PF_INET; - break; - case CURL_IPRESOLVE_V6: - pf = PF_INET6; - break; - default: - pf = PF_UNSPEC; - break; - } - - if((pf != PF_INET) && !Curl_ipv6works()) - /* the stack seems to be a non-ipv6 one */ - pf = PF_INET; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = pf; - hints.ai_socktype = conn->socktype; - - if((1 == Curl_inet_pton(AF_INET, hostname, addrbuf)) || - (1 == Curl_inet_pton(AF_INET6, hostname, addrbuf))) { - /* the given address is numerical only, prevent a reverse lookup */ - hints.ai_flags = AI_NUMERICHOST; - } - - if(port) { - snprintf(sbuf, sizeof(sbuf), "%d", port); - sbufptr=sbuf; - } - error = Curl_getaddrinfo_ex(hostname, sbufptr, &hints, &res); - if(error) { - infof(data, "getaddrinfo(3) failed for %s:%d\n", hostname, port); - return NULL; - } - - dump_addrinfo(conn, res); - - return res; -} -#endif /* CURLRES_SYNCH */ -#endif /* CURLRES_IPV6 */ - diff --git a/lib/hostsyn.c b/lib/hostsyn.c deleted file mode 100644 index 9a26f8d44..000000000 --- a/lib/hostsyn.c +++ /dev/null @@ -1,75 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#ifdef HAVE_PROCESS_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_hostip.h" -#include "curl_hash.h" -#include "curl_share.h" -#include "curl_strerror.h" -#include "curl_url.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/*********************************************************************** - * Only for builds using synchronous name resolves - **********************************************************************/ -#ifdef CURLRES_SYNCH - -/* - * Function provided by the resolver backend to set DNS servers to use. - */ -CURLcode Curl_set_dns_servers(struct SessionHandle *data, - char *servers) -{ - (void)data; - (void)servers; - return CURLE_NOT_BUILT_IN; - -} - -#endif /* truly sync */ diff --git a/lib/http.c b/lib/http.c deleted file mode 100644 index 420361c76..000000000 --- a/lib/http.c +++ /dev/null @@ -1,3506 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_HTTP - -#ifdef HAVE_NETINET_IN_H -#include -#endif - -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#include "curl_urldata.h" -#include -#include "curl_transfer.h" -#include "curl_sendf.h" -#include "curl_formdata.h" -#include "curl_progress.h" -#include "curl_base64.h" -#include "curl_cookie.h" -#include "curl_strequal.h" -#include "curl_sslgen.h" -#include "curl_http_digest.h" -#include "curl_ntlm.h" -#include "curl_ntlm_wb.h" -#include "curl_http_negotiate.h" -#include "curl_url.h" -#include "curl_share.h" -#include "curl_hostip.h" -#include "curl_http.h" -#include "curl_memory.h" -#include "curl_select.h" -#include "curl_parsedate.h" /* for the week day and month names */ -#include "curl_strtoofft.h" -#include "curl_multiif.h" -#include "curl_rawstr.h" -#include "curl_content_encoding.h" -#include "curl_http_proxy.h" -#include "curl_warnless.h" -#include "curl_non_ascii.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - * Forward declarations. - */ - -static int http_getsock_do(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -static int http_should_fail(struct connectdata *conn); - -#ifdef USE_SSL -static CURLcode https_connecting(struct connectdata *conn, bool *done); -static int https_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -#else -#define https_connecting(x,y) CURLE_COULDNT_CONNECT -#endif - -/* - * HTTP handler interface. - */ -const struct Curl_handler Curl_handler_http = { - "HTTP", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -#ifdef USE_SSL -/* - * HTTPS handler interface. - */ -const struct Curl_handler Curl_handler_https = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - https_connecting, /* connecting */ - ZERO_NULL, /* doing */ - https_getsock, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_HTTPS, /* defport */ - CURLPROTO_HTTP | CURLPROTO_HTTPS, /* protocol */ - PROTOPT_SSL /* flags */ -}; -#endif - - -/* - * checkheaders() checks the linked list of custom HTTP headers for a - * particular header (prefix). - * - * Returns a pointer to the first matching header or NULL if none matched. - */ -char *Curl_checkheaders(struct SessionHandle *data, const char *thisheader) -{ - struct curl_slist *head; - size_t thislen = strlen(thisheader); - - for(head = data->set.headers; head; head=head->next) { - if(Curl_raw_nequal(head->data, thisheader, thislen)) - return head->data; - } - return NULL; -} - -/* - * Strip off leading and trailing whitespace from the value in the - * given HTTP header line and return a strdupped copy. Returns NULL in - * case of allocation failure. Returns an empty string if the header value - * consists entirely of whitespace. - */ -static char *copy_header_value(const char *h) -{ - const char *start; - const char *end; - char *value; - size_t len; - - DEBUGASSERT(h); - - /* Find the end of the header name */ - while(*h && (*h != ':')) - ++h; - - if(*h) - /* Skip over colon */ - ++h; - - /* Find the first non-space letter */ - start = h; - while(*start && ISSPACE(*start)) - start++; - - /* data is in the host encoding so - use '\r' and '\n' instead of 0x0d and 0x0a */ - end = strchr(start, '\r'); - if(!end) - end = strchr(start, '\n'); - if(!end) - end = strchr(start, '\0'); - if(!end) - return NULL; - - /* skip all trailing space letters */ - while((end > start) && ISSPACE(*end)) - end--; - - /* get length of the type */ - len = end-start+1; - - value = malloc(len + 1); - if(!value) - return NULL; - - memcpy(value, start, len); - value[len] = 0; /* zero terminate */ - - return value; -} - -/* - * http_output_basic() sets up an Authorization: header (or the proxy version) - * for HTTP Basic authentication. - * - * Returns CURLcode. - */ -static CURLcode http_output_basic(struct connectdata *conn, bool proxy) -{ - size_t size = 0; - char *authorization = NULL; - struct SessionHandle *data = conn->data; - char **userp; - const char *user; - const char *pwd; - CURLcode error; - - if(proxy) { - userp = &conn->allocptr.proxyuserpwd; - user = conn->proxyuser; - pwd = conn->proxypasswd; - } - else { - userp = &conn->allocptr.userpwd; - user = conn->user; - pwd = conn->passwd; - } - - snprintf(data->state.buffer, sizeof(data->state.buffer), "%s:%s", user, pwd); - - error = Curl_base64_encode(data, - data->state.buffer, strlen(data->state.buffer), - &authorization, &size); - if(error) - return error; - - if(!authorization) - return CURLE_REMOTE_ACCESS_DENIED; - - Curl_safefree(*userp); - *userp = aprintf("%sAuthorization: Basic %s\r\n", - proxy?"Proxy-":"", - authorization); - free(authorization); - if(!*userp) - return CURLE_OUT_OF_MEMORY; - - return CURLE_OK; -} - -/* pickoneauth() selects the most favourable authentication method from the - * ones available and the ones we want. - * - * return TRUE if one was picked - */ -static bool pickoneauth(struct auth *pick) -{ - bool picked; - /* only deal with authentication we want */ - unsigned long avail = pick->avail & pick->want; - picked = TRUE; - - /* The order of these checks is highly relevant, as this will be the order - of preference in case of the existence of multiple accepted types. */ - if(avail & CURLAUTH_GSSNEGOTIATE) - pick->picked = CURLAUTH_GSSNEGOTIATE; - else if(avail & CURLAUTH_DIGEST) - pick->picked = CURLAUTH_DIGEST; - else if(avail & CURLAUTH_NTLM) - pick->picked = CURLAUTH_NTLM; - else if(avail & CURLAUTH_NTLM_WB) - pick->picked = CURLAUTH_NTLM_WB; - else if(avail & CURLAUTH_BASIC) - pick->picked = CURLAUTH_BASIC; - else { - pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */ - picked = FALSE; - } - pick->avail = CURLAUTH_NONE; /* clear it here */ - - return picked; -} - -/* - * Curl_http_perhapsrewind() - * - * If we are doing POST or PUT { - * If we have more data to send { - * If we are doing NTLM { - * Keep sending since we must not disconnect - * } - * else { - * If there is more than just a little data left to send, close - * the current connection by force. - * } - * } - * If we have sent any data { - * If we don't have track of all the data { - * call app to tell it to rewind - * } - * else { - * rewind internally so that the operation can restart fine - * } - * } - * } - */ -static CURLcode http_perhapsrewind(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - struct HTTP *http = data->state.proto.http; - curl_off_t bytessent; - curl_off_t expectsend = -1; /* default is unknown */ - - if(!http) - /* If this is still NULL, we have not reach very far and we can safely - skip this rewinding stuff */ - return CURLE_OK; - - switch(data->set.httpreq) { - case HTTPREQ_GET: - case HTTPREQ_HEAD: - return CURLE_OK; - default: - break; - } - - bytessent = http->writebytecount; - - if(conn->bits.authneg) - /* This is a state where we are known to be negotiating and we don't send - any data then. */ - expectsend = 0; - else { - /* figure out how much data we are expected to send */ - switch(data->set.httpreq) { - case HTTPREQ_POST: - if(data->set.postfieldsize != -1) - expectsend = data->set.postfieldsize; - else if(data->set.postfields) - expectsend = (curl_off_t)strlen(data->set.postfields); - break; - case HTTPREQ_PUT: - if(data->set.infilesize != -1) - expectsend = data->set.infilesize; - break; - case HTTPREQ_POST_FORM: - expectsend = http->postsize; - break; - default: - break; - } - } - - conn->bits.rewindaftersend = FALSE; /* default */ - - if((expectsend == -1) || (expectsend > bytessent)) { - /* There is still data left to send */ - if((data->state.authproxy.picked == CURLAUTH_NTLM) || - (data->state.authhost.picked == CURLAUTH_NTLM) || - (data->state.authproxy.picked == CURLAUTH_NTLM_WB) || - (data->state.authhost.picked == CURLAUTH_NTLM_WB)) { - if(((expectsend - bytessent) < 2000) || - (conn->ntlm.state != NTLMSTATE_NONE) || - (conn->proxyntlm.state != NTLMSTATE_NONE)) { - /* The NTLM-negotiation has started *OR* there is just a little (<2K) - data left to send, keep on sending. */ - - /* rewind data when completely done sending! */ - if(!conn->bits.authneg) { - conn->bits.rewindaftersend = TRUE; - infof(data, "Rewind stream after send\n"); - } - - return CURLE_OK; - } - if(conn->bits.close) - /* this is already marked to get closed */ - return CURLE_OK; - - infof(data, "NTLM send, close instead of sending %" FORMAT_OFF_T - " bytes\n", (curl_off_t)(expectsend - bytessent)); - } - - /* This is not NTLM or many bytes left to send: close - */ - conn->bits.close = TRUE; - data->req.size = 0; /* don't download any more than 0 bytes */ - - /* There still is data left to send, but this connection is marked for - closure so we can safely do the rewind right now */ - } - - if(bytessent) - /* we rewind now at once since if we already sent something */ - return Curl_readrewind(conn); - - return CURLE_OK; -} - -/* - * Curl_http_auth_act() gets called when all HTTP headers have been received - * and it checks what authentication methods that are available and decides - * which one (if any) to use. It will set 'newurl' if an auth method was - * picked. - */ - -CURLcode Curl_http_auth_act(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - bool pickhost = FALSE; - bool pickproxy = FALSE; - CURLcode code = CURLE_OK; - - if(100 <= data->req.httpcode && 199 >= data->req.httpcode) - /* this is a transient response code, ignore */ - return CURLE_OK; - - if(data->state.authproblem) - return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK; - - if(conn->bits.user_passwd && - ((data->req.httpcode == 401) || - (conn->bits.authneg && data->req.httpcode < 300))) { - pickhost = pickoneauth(&data->state.authhost); - if(!pickhost) - data->state.authproblem = TRUE; - } - if(conn->bits.proxy_user_passwd && - ((data->req.httpcode == 407) || - (conn->bits.authneg && data->req.httpcode < 300))) { - pickproxy = pickoneauth(&data->state.authproxy); - if(!pickproxy) - data->state.authproblem = TRUE; - } - - if(pickhost || pickproxy) { - /* In case this is GSS auth, the newurl field is already allocated so - we must make sure to free it before allocating a new one. As figured - out in bug #2284386 */ - Curl_safefree(data->req.newurl); - data->req.newurl = strdup(data->change.url); /* clone URL */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - - if((data->set.httpreq != HTTPREQ_GET) && - (data->set.httpreq != HTTPREQ_HEAD) && - !conn->bits.rewindaftersend) { - code = http_perhapsrewind(conn); - if(code) - return code; - } - } - - else if((data->req.httpcode < 300) && - (!data->state.authhost.done) && - conn->bits.authneg) { - /* no (known) authentication available, - authentication is not "done" yet and - no authentication seems to be required and - we didn't try HEAD or GET */ - if((data->set.httpreq != HTTPREQ_GET) && - (data->set.httpreq != HTTPREQ_HEAD)) { - data->req.newurl = strdup(data->change.url); /* clone URL */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - data->state.authhost.done = TRUE; - } - } - if(http_should_fail(conn)) { - failf (data, "The requested URL returned error: %d", - data->req.httpcode); - code = CURLE_HTTP_RETURNED_ERROR; - } - - return code; -} - - -/* - * Output the correct authentication header depending on the auth type - * and whether or not it is to a proxy. - */ -static CURLcode -output_auth_headers(struct connectdata *conn, - struct auth *authstatus, - const char *request, - const char *path, - bool proxy) -{ - struct SessionHandle *data = conn->data; - const char *auth=NULL; - CURLcode result = CURLE_OK; -#ifdef USE_HTTP_NEGOTIATE - struct negotiatedata *negdata = proxy? - &data->state.proxyneg:&data->state.negotiate; -#endif - -#ifdef CURL_DISABLE_CRYPTO_AUTH - (void)request; - (void)path; -#endif - -#ifdef USE_HTTP_NEGOTIATE - negdata->state = GSS_AUTHNONE; - if((authstatus->picked == CURLAUTH_GSSNEGOTIATE) && - negdata->context && !GSS_ERROR(negdata->status)) { - auth="GSS-Negotiate"; - result = Curl_output_negotiate(conn, proxy); - if(result) - return result; - authstatus->done = TRUE; - negdata->state = GSS_AUTHSENT; - } - else -#endif -#ifdef USE_NTLM - if(authstatus->picked == CURLAUTH_NTLM) { - auth="NTLM"; - result = Curl_output_ntlm(conn, proxy); - if(result) - return result; - } - else -#endif -#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) - if(authstatus->picked == CURLAUTH_NTLM_WB) { - auth="NTLM_WB"; - result = Curl_output_ntlm_wb(conn, proxy); - if(result) - return result; - } - else -#endif -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(authstatus->picked == CURLAUTH_DIGEST) { - auth="Digest"; - result = Curl_output_digest(conn, - proxy, - (const unsigned char *)request, - (const unsigned char *)path); - if(result) - return result; - } - else -#endif - if(authstatus->picked == CURLAUTH_BASIC) { - /* Basic */ - if((proxy && conn->bits.proxy_user_passwd && - !Curl_checkheaders(data, "Proxy-authorization:")) || - (!proxy && conn->bits.user_passwd && - !Curl_checkheaders(data, "Authorization:"))) { - auth="Basic"; - result = http_output_basic(conn, proxy); - if(result) - return result; - } - /* NOTE: this function should set 'done' TRUE, as the other auth - functions work that way */ - authstatus->done = TRUE; - } - - if(auth) { - infof(data, "%s auth using %s with user '%s'\n", - proxy?"Proxy":"Server", auth, - proxy?(conn->proxyuser?conn->proxyuser:""): - (conn->user?conn->user:"")); - authstatus->multi = (!authstatus->done) ? TRUE : FALSE; - } - else - authstatus->multi = FALSE; - - return CURLE_OK; -} - -/** - * Curl_http_output_auth() setups the authentication headers for the - * host/proxy and the correct authentication - * method. conn->data->state.authdone is set to TRUE when authentication is - * done. - * - * @param conn all information about the current connection - * @param request pointer to the request keyword - * @param path pointer to the requested path - * @param proxytunnel boolean if this is the request setting up a "proxy - * tunnel" - * - * @returns CURLcode - */ -CURLcode -Curl_http_output_auth(struct connectdata *conn, - const char *request, - const char *path, - bool proxytunnel) /* TRUE if this is the request setting - up the proxy tunnel */ -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct auth *authhost; - struct auth *authproxy; - - DEBUGASSERT(data); - - authhost = &data->state.authhost; - authproxy = &data->state.authproxy; - - if((conn->bits.httpproxy && conn->bits.proxy_user_passwd) || - conn->bits.user_passwd) - /* continue please */ ; - else { - authhost->done = TRUE; - authproxy->done = TRUE; - return CURLE_OK; /* no authentication with no user or password */ - } - - if(authhost->want && !authhost->picked) - /* The app has selected one or more methods, but none has been picked - so far by a server round-trip. Then we set the picked one to the - want one, and if this is one single bit it'll be used instantly. */ - authhost->picked = authhost->want; - - if(authproxy->want && !authproxy->picked) - /* The app has selected one or more methods, but none has been picked so - far by a proxy round-trip. Then we set the picked one to the want one, - and if this is one single bit it'll be used instantly. */ - authproxy->picked = authproxy->want; - -#ifndef CURL_DISABLE_PROXY - /* Send proxy authentication header if needed */ - if(conn->bits.httpproxy && - (conn->bits.tunnel_proxy == proxytunnel)) { - result = output_auth_headers(conn, authproxy, request, path, TRUE); - if(result) - return result; - } - else -#else - (void)proxytunnel; -#endif /* CURL_DISABLE_PROXY */ - /* we have no proxy so let's pretend we're done authenticating - with it */ - authproxy->done = TRUE; - - /* To prevent the user+password to get sent to other than the original - host due to a location-follow, we do some weirdo checks here */ - if(!data->state.this_is_a_follow || - conn->bits.netrc || - !data->state.first_host || - data->set.http_disable_hostname_check_before_authentication || - Curl_raw_equal(data->state.first_host, conn->host.name)) { - result = output_auth_headers(conn, authhost, request, path, FALSE); - } - else - authhost->done = TRUE; - - return result; -} - - -/* - * Curl_http_input_auth() deals with Proxy-Authenticate: and WWW-Authenticate: - * headers. They are dealt with both in the curl_transfer.c main loop and in - * the proxy CONNECT loop. - */ - -CURLcode Curl_http_input_auth(struct connectdata *conn, - int httpcode, - const char *header) /* the first non-space */ -{ - /* - * This resource requires authentication - */ - struct SessionHandle *data = conn->data; - - unsigned long *availp; - const char *start; - struct auth *authp; - - if(httpcode == 407) { - start = header+strlen("Proxy-authenticate:"); - availp = &data->info.proxyauthavail; - authp = &data->state.authproxy; - } - else { - start = header+strlen("WWW-Authenticate:"); - availp = &data->info.httpauthavail; - authp = &data->state.authhost; - } - - /* pass all white spaces */ - while(*start && ISSPACE(*start)) - start++; - - /* - * Here we check if we want the specific single authentication (using ==) and - * if we do, we initiate usage of it. - * - * If the provided authentication is wanted as one out of several accepted - * types (using &), we OR this authentication type to the authavail - * variable. - * - * Note: - * - * ->picked is first set to the 'want' value (one or more bits) before the - * request is sent, and then it is again set _after_ all response 401/407 - * headers have been received but then only to a single preferred method - * (bit). - * - */ - - while(*start) { -#ifdef USE_HTTP_NEGOTIATE - if(checkprefix("GSS-Negotiate", start) || - checkprefix("Negotiate", start)) { - int neg; - *availp |= CURLAUTH_GSSNEGOTIATE; - authp->avail |= CURLAUTH_GSSNEGOTIATE; - - if(authp->picked == CURLAUTH_GSSNEGOTIATE) { - if(data->state.negotiate.state == GSS_AUTHSENT) { - /* if we sent GSS authentication in the outgoing request and we get - this back, we're in trouble */ - infof(data, "Authentication problem. Ignoring this.\n"); - data->state.authproblem = TRUE; - } - else { - neg = Curl_input_negotiate(conn, (bool)(httpcode == 407), start); - if(neg == 0) { - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->change.url); - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - data->state.authproblem = FALSE; - /* we received GSS auth info and we dealt with it fine */ - data->state.negotiate.state = GSS_AUTHRECV; - } - else - data->state.authproblem = TRUE; - } - } - } - else -#endif -#ifdef USE_NTLM - /* NTLM support requires the SSL crypto libs */ - if(checkprefix("NTLM", start)) { - *availp |= CURLAUTH_NTLM; - authp->avail |= CURLAUTH_NTLM; - if(authp->picked == CURLAUTH_NTLM || - authp->picked == CURLAUTH_NTLM_WB) { - /* NTLM authentication is picked and activated */ - CURLcode ntlm = - Curl_input_ntlm(conn, (httpcode == 407)?TRUE:FALSE, start); - if(CURLE_OK == ntlm) { - data->state.authproblem = FALSE; -#ifdef NTLM_WB_ENABLED - if(authp->picked == CURLAUTH_NTLM_WB) { - *availp &= ~CURLAUTH_NTLM; - authp->avail &= ~CURLAUTH_NTLM; - *availp |= CURLAUTH_NTLM_WB; - authp->avail |= CURLAUTH_NTLM_WB; - - /* Get the challenge-message which will be passed to - * ntlm_auth for generating the type 3 message later */ - while(*start && ISSPACE(*start)) - start++; - if(checkprefix("NTLM", start)) { - start += strlen("NTLM"); - while(*start && ISSPACE(*start)) - start++; - if(*start) - if((conn->challenge_header = strdup(start)) == NULL) - return CURLE_OUT_OF_MEMORY; - } - } -#endif - } - else { - infof(data, "Authentication problem. Ignoring this.\n"); - data->state.authproblem = TRUE; - } - } - } - else -#endif -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(checkprefix("Digest", start)) { - if((authp->avail & CURLAUTH_DIGEST) != 0) { - infof(data, "Ignoring duplicate digest auth header.\n"); - } - else { - CURLdigest dig; - *availp |= CURLAUTH_DIGEST; - authp->avail |= CURLAUTH_DIGEST; - - /* We call this function on input Digest headers even if Digest - * authentication isn't activated yet, as we need to store the - * incoming data from this header in case we are gonna use - * Digest. */ - dig = Curl_input_digest(conn, (httpcode == 407)?TRUE:FALSE, start); - - if(CURLDIGEST_FINE != dig) { - infof(data, "Authentication problem. Ignoring this.\n"); - data->state.authproblem = TRUE; - } - } - } - else -#endif - if(checkprefix("Basic", start)) { - *availp |= CURLAUTH_BASIC; - authp->avail |= CURLAUTH_BASIC; - if(authp->picked == CURLAUTH_BASIC) { - /* We asked for Basic authentication but got a 40X back - anyway, which basically means our name+password isn't - valid. */ - authp->avail = CURLAUTH_NONE; - infof(data, "Authentication problem. Ignoring this.\n"); - data->state.authproblem = TRUE; - } - } - - /* there may be multiple methods on one line, so keep reading */ - while(*start && *start != ',') /* read up to the next comma */ - start++; - if(*start == ',') /* if we're on a comma, skip it */ - start++; - while(*start && ISSPACE(*start)) - start++; - } - return CURLE_OK; -} - -/** - * http_should_fail() determines whether an HTTP response has gotten us - * into an error state or not. - * - * @param conn all information about the current connection - * - * @retval 0 communications should continue - * - * @retval 1 communications should not continue - */ -static int http_should_fail(struct connectdata *conn) -{ - struct SessionHandle *data; - int httpcode; - - DEBUGASSERT(conn); - data = conn->data; - DEBUGASSERT(data); - - httpcode = data->req.httpcode; - - /* - ** If we haven't been asked to fail on error, - ** don't fail. - */ - if(!data->set.http_fail_on_error) - return 0; - - /* - ** Any code < 400 is never terminal. - */ - if(httpcode < 400) - return 0; - - if(data->state.resume_from && - (data->set.httpreq==HTTPREQ_GET) && - (httpcode == 416)) { - /* "Requested Range Not Satisfiable", just proceed and - pretend this is no error */ - return 0; - } - - /* - ** Any code >= 400 that's not 401 or 407 is always - ** a terminal error - */ - if((httpcode != 401) && - (httpcode != 407)) - return 1; - - /* - ** All we have left to deal with is 401 and 407 - */ - DEBUGASSERT((httpcode == 401) || (httpcode == 407)); - - /* - ** Examine the current authentication state to see if this - ** is an error. The idea is for this function to get - ** called after processing all the headers in a response - ** message. So, if we've been to asked to authenticate a - ** particular stage, and we've done it, we're OK. But, if - ** we're already completely authenticated, it's not OK to - ** get another 401 or 407. - ** - ** It is possible for authentication to go stale such that - ** the client needs to reauthenticate. Once that info is - ** available, use it here. - */ - - /* - ** Either we're not authenticating, or we're supposed to - ** be authenticating something else. This is an error. - */ - if((httpcode == 401) && !conn->bits.user_passwd) - return TRUE; - if((httpcode == 407) && !conn->bits.proxy_user_passwd) - return TRUE; - - return data->state.authproblem; -} - -/* - * readmoredata() is a "fread() emulation" to provide POST and/or request - * data. It is used when a huge POST is to be made and the entire chunk wasn't - * sent in the first send(). This function will then be called from the - * curl_transfer.c loop when more data is to be sent to the peer. - * - * Returns the amount of bytes it filled the buffer with. - */ -static size_t readmoredata(char *buffer, - size_t size, - size_t nitems, - void *userp) -{ - struct connectdata *conn = (struct connectdata *)userp; - struct HTTP *http = conn->data->state.proto.http; - size_t fullsize = size * nitems; - - if(0 == http->postsize) - /* nothing to return */ - return 0; - - /* make sure that a HTTP request is never sent away chunked! */ - conn->data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; - - if(http->postsize <= (curl_off_t)fullsize) { - memcpy(buffer, http->postdata, (size_t)http->postsize); - fullsize = (size_t)http->postsize; - - if(http->backup.postsize) { - /* move backup data into focus and continue on that */ - http->postdata = http->backup.postdata; - http->postsize = http->backup.postsize; - conn->fread_func = http->backup.fread_func; - conn->fread_in = http->backup.fread_in; - - http->sending++; /* move one step up */ - - http->backup.postsize=0; - } - else - http->postsize = 0; - - return fullsize; - } - - memcpy(buffer, http->postdata, fullsize); - http->postdata += fullsize; - http->postsize -= fullsize; - - return fullsize; -} - -/* ------------------------------------------------------------------------- */ -/* add_buffer functions */ - -/* - * Curl_add_buffer_init() sets up and returns a fine buffer struct - */ -Curl_send_buffer *Curl_add_buffer_init(void) -{ - return calloc(1, sizeof(Curl_send_buffer)); -} - -/* - * Curl_add_buffer_send() sends a header buffer and frees all associated - * memory. Body data may be appended to the header data if desired. - * - * Returns CURLcode - */ -CURLcode Curl_add_buffer_send(Curl_send_buffer *in, - struct connectdata *conn, - - /* add the number of sent bytes to this - counter */ - long *bytes_written, - - /* how much of the buffer contains body data */ - size_t included_body_bytes, - int socketindex) - -{ - ssize_t amount; - CURLcode res; - char *ptr; - size_t size; - struct HTTP *http = conn->data->state.proto.http; - size_t sendsize; - curl_socket_t sockfd; - size_t headersize; - - DEBUGASSERT(socketindex <= SECONDARYSOCKET); - - sockfd = conn->sock[socketindex]; - - /* The looping below is required since we use non-blocking sockets, but due - to the circumstances we will just loop and try again and again etc */ - - ptr = in->buffer; - size = in->size_used; - - headersize = size - included_body_bytes; /* the initial part that isn't body - is header */ - - DEBUGASSERT(size > included_body_bytes); - - res = Curl_convert_to_network(conn->data, ptr, headersize); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(res) { - /* conversion failed, free memory and return to the caller */ - if(in->buffer) - free(in->buffer); - free(in); - return res; - } - - if(conn->handler->flags & PROTOPT_SSL) { - /* We never send more than CURL_MAX_WRITE_SIZE bytes in one single chunk - when we speak HTTPS, as if only a fraction of it is sent now, this data - needs to fit into the normal read-callback buffer later on and that - buffer is using this size. - */ - - sendsize= (size > CURL_MAX_WRITE_SIZE)?CURL_MAX_WRITE_SIZE:size; - - /* OpenSSL is very picky and we must send the SAME buffer pointer to the - library when we attempt to re-send this buffer. Sending the same data - is not enough, we must use the exact same address. For this reason, we - must copy the data to the uploadbuffer first, since that is the buffer - we will be using if this send is retried later. - */ - memcpy(conn->data->state.uploadbuffer, ptr, sendsize); - ptr = conn->data->state.uploadbuffer; - } - else - sendsize = size; - - res = Curl_write(conn, sockfd, ptr, sendsize, &amount); - - if(CURLE_OK == res) { - /* - * Note that we may not send the entire chunk at once, and we have a set - * number of data bytes at the end of the big buffer (out of which we may - * only send away a part). - */ - /* how much of the header that was sent */ - size_t headlen = (size_t)amount>headersize?headersize:(size_t)amount; - size_t bodylen = amount - headlen; - - if(conn->data->set.verbose) { - /* this data _may_ contain binary stuff */ - Curl_debug(conn->data, CURLINFO_HEADER_OUT, ptr, headlen, conn); - if(bodylen) { - /* there was body data sent beyond the initial header part, pass that - on to the debug callback too */ - Curl_debug(conn->data, CURLINFO_DATA_OUT, - ptr+headlen, bodylen, conn); - } - } - if(bodylen) - /* since we sent a piece of the body here, up the byte counter for it - accordingly */ - http->writebytecount += bodylen; - - /* 'amount' can never be a very large value here so typecasting it so a - signed 31 bit value should not cause problems even if ssize_t is - 64bit */ - *bytes_written += (long)amount; - - if(http) { - if((size_t)amount != size) { - /* The whole request could not be sent in one system call. We must - queue it up and send it later when we get the chance. We must not - loop here and wait until it might work again. */ - - size -= amount; - - ptr = in->buffer + amount; - - /* backup the currently set pointers */ - http->backup.fread_func = conn->fread_func; - http->backup.fread_in = conn->fread_in; - http->backup.postdata = http->postdata; - http->backup.postsize = http->postsize; - - /* set the new pointers for the request-sending */ - conn->fread_func = (curl_read_callback)readmoredata; - conn->fread_in = (void *)conn; - http->postdata = ptr; - http->postsize = (curl_off_t)size; - - http->send_buffer = in; - http->sending = HTTPSEND_REQUEST; - - return CURLE_OK; - } - http->sending = HTTPSEND_BODY; - /* the full buffer was sent, clean up and return */ - } - else { - if((size_t)amount != size) - /* We have no continue-send mechanism now, fail. This can only happen - when this function is used from the CONNECT sending function. We - currently (stupidly) assume that the whole request is always sent - away in the first single chunk. - - This needs FIXing. - */ - return CURLE_SEND_ERROR; - else - conn->writechannel_inuse = FALSE; - } - } - if(in->buffer) - free(in->buffer); - free(in); - - return res; -} - - -/* - * add_bufferf() add the formatted input to the buffer. - */ -CURLcode Curl_add_bufferf(Curl_send_buffer *in, const char *fmt, ...) -{ - char *s; - va_list ap; - va_start(ap, fmt); - s = vaprintf(fmt, ap); /* this allocs a new string to append */ - va_end(ap); - - if(s) { - CURLcode result = Curl_add_buffer(in, s, strlen(s)); - free(s); - return result; - } - /* If we failed, we cleanup the whole buffer and return error */ - if(in->buffer) - free(in->buffer); - free(in); - return CURLE_OUT_OF_MEMORY; -} - -/* - * add_buffer() appends a memory chunk to the existing buffer - */ -CURLcode Curl_add_buffer(Curl_send_buffer *in, const void *inptr, size_t size) -{ - char *new_rb; - size_t new_size; - - if(~size < in->size_used) { - /* If resulting used size of send buffer would wrap size_t, cleanup - the whole buffer and return error. Otherwise the required buffer - size will fit into a single allocatable memory chunk */ - Curl_safefree(in->buffer); - free(in); - return CURLE_OUT_OF_MEMORY; - } - - if(!in->buffer || - ((in->size_used + size) > (in->size_max - 1))) { - - /* If current buffer size isn't enough to hold the result, use a - buffer size that doubles the required size. If this new size - would wrap size_t, then just use the largest possible one */ - - if((size > (size_t)-1/2) || (in->size_used > (size_t)-1/2) || - (~(size*2) < (in->size_used*2))) - new_size = (size_t)-1; - else - new_size = (in->size_used+size)*2; - - if(in->buffer) - /* we have a buffer, enlarge the existing one */ - new_rb = realloc(in->buffer, new_size); - else - /* create a new buffer */ - new_rb = malloc(new_size); - - if(!new_rb) { - /* If we failed, we cleanup the whole buffer and return error */ - Curl_safefree(in->buffer); - free(in); - return CURLE_OUT_OF_MEMORY; - } - - in->buffer = new_rb; - in->size_max = new_size; - } - memcpy(&in->buffer[in->size_used], inptr, size); - - in->size_used += size; - - return CURLE_OK; -} - -/* end of the add_buffer functions */ -/* ------------------------------------------------------------------------- */ - - - -/* - * Curl_compareheader() - * - * Returns TRUE if 'headerline' contains the 'header' with given 'content'. - * Pass headers WITH the colon. - */ -bool -Curl_compareheader(const char *headerline, /* line to check */ - const char *header, /* header keyword _with_ colon */ - const char *content) /* content string to find */ -{ - /* RFC2616, section 4.2 says: "Each header field consists of a name followed - * by a colon (":") and the field value. Field names are case-insensitive. - * The field value MAY be preceded by any amount of LWS, though a single SP - * is preferred." */ - - size_t hlen = strlen(header); - size_t clen; - size_t len; - const char *start; - const char *end; - - if(!Curl_raw_nequal(headerline, header, hlen)) - return FALSE; /* doesn't start with header */ - - /* pass the header */ - start = &headerline[hlen]; - - /* pass all white spaces */ - while(*start && ISSPACE(*start)) - start++; - - /* find the end of the header line */ - end = strchr(start, '\r'); /* lines end with CRLF */ - if(!end) { - /* in case there's a non-standard compliant line here */ - end = strchr(start, '\n'); - - if(!end) - /* hm, there's no line ending here, use the zero byte! */ - end = strchr(start, '\0'); - } - - len = end-start; /* length of the content part of the input line */ - clen = strlen(content); /* length of the word to find */ - - /* find the content string in the rest of the line */ - for(;len>=clen;len--, start++) { - if(Curl_raw_nequal(start, content, clen)) - return TRUE; /* match! */ - } - - return FALSE; /* no match */ -} - -/* - * Curl_http_connect() performs HTTP stuff to do at connect-time, called from - * the generic Curl_connect(). - */ -CURLcode Curl_http_connect(struct connectdata *conn, bool *done) -{ - struct SessionHandle *data; - CURLcode result; - - data=conn->data; - - /* We default to persistent connections. We set this already in this connect - function to make the re-use checks properly be able to check this bit. */ - conn->bits.close = FALSE; - - if(data->state.used_interface == Curl_if_multi) { - /* when the multi interface is used, the CONNECT procedure might not have - been completed */ - result = Curl_proxy_connect(conn); - if(result) - return result; - } - - if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) - /* nothing else to do except wait right now - we're not done here. */ - return CURLE_OK; - - if(conn->given->flags & PROTOPT_SSL) { - /* perform SSL initialization */ - if(data->state.used_interface == Curl_if_multi) { - result = https_connecting(conn, done); - if(result) - return result; - } - else { - /* BLOCKING */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(result) - return result; - *done = TRUE; - } - } - else { - *done = TRUE; - } - - return CURLE_OK; -} - -/* this returns the socket to wait for in the DO and DOING state for the multi - interface and then we're always _sending_ a request and thus we wait for - the single socket to become writable only */ -static int http_getsock_do(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - /* write mode */ - (void)numsocks; /* unused, we trust it to be at least 1 */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_WRITESOCK(0); -} - -#ifdef USE_SSL -static CURLcode https_connecting(struct connectdata *conn, bool *done) -{ - CURLcode result; - DEBUGASSERT((conn) && (conn->handler->flags & PROTOPT_SSL)); - - /* perform SSL initialization for this socket */ - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done); - if(result) - conn->bits.close = TRUE; /* a failed connection is marked for closure - to prevent (bad) re-use or similar */ - return result; -} -#endif - -#if defined(USE_SSLEAY) || defined(USE_GNUTLS) || defined(USE_SCHANNEL) || \ - defined(USE_DARWINSSL) -/* This function is for OpenSSL, GnuTLS, darwinssl, and schannel only. - It should be made to query the generic SSL layer instead. */ -static int https_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - if(conn->handler->flags & PROTOPT_SSL) { - struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; - - if(!numsocks) - return GETSOCK_BLANK; - - if(connssl->connecting_state == ssl_connect_2_writing) { - /* write mode */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_WRITESOCK(0); - } - else if(connssl->connecting_state == ssl_connect_2_reading) { - /* read mode */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_READSOCK(0); - } - } - return CURLE_OK; -} -#else -#ifdef USE_SSL -static int https_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - (void)conn; - (void)socks; - (void)numsocks; - return GETSOCK_BLANK; -} -#endif /* USE_SSL */ -#endif /* USE_SSLEAY || USE_GNUTLS || USE_SCHANNEL */ - -/* - * Curl_http_done() gets called from Curl_done() after a single HTTP request - * has been performed. - */ - -CURLcode Curl_http_done(struct connectdata *conn, - CURLcode status, bool premature) -{ - struct SessionHandle *data = conn->data; - struct HTTP *http =data->state.proto.http; - - Curl_unencode_cleanup(conn); - - /* set the proper values (possibly modified on POST) */ - conn->fread_func = data->set.fread_func; /* restore */ - conn->fread_in = data->set.in; /* restore */ - conn->seek_func = data->set.seek_func; /* restore */ - conn->seek_client = data->set.seek_client; /* restore */ - - if(http == NULL) - return CURLE_OK; - - if(http->send_buffer) { - Curl_send_buffer *buff = http->send_buffer; - - free(buff->buffer); - free(buff); - http->send_buffer = NULL; /* clear the pointer */ - } - - if(HTTPREQ_POST_FORM == data->set.httpreq) { - data->req.bytecount = http->readbytecount + http->writebytecount; - - Curl_formclean(&http->sendit); /* Now free that whole lot */ - if(http->form.fp) { - /* a file being uploaded was left opened, close it! */ - fclose(http->form.fp); - http->form.fp = NULL; - } - } - else if(HTTPREQ_PUT == data->set.httpreq) - data->req.bytecount = http->readbytecount + http->writebytecount; - - if(status != CURLE_OK) - return (status); - - if(!premature && /* this check is pointless when DONE is called before the - entire operation is complete */ - !conn->bits.retry && - ((http->readbytecount + - data->req.headerbytecount - - data->req.deductheadercount)) <= 0) { - /* If this connection isn't simply closed to be retried, AND nothing was - read from the HTTP server (that counts), this can't be right so we - return an error here */ - failf(data, "Empty reply from server"); - return CURLE_GOT_NOTHING; - } - - return CURLE_OK; -} - - -/* Determine if we should use HTTP 1.1 for this request. Reasons to avoid it - are if the user specifically requested HTTP 1.0, if the server we are - connected to only supports 1.0, or if any server previously contacted to - handle this request only supports 1.0. */ -static bool use_http_1_1(const struct SessionHandle *data, - const struct connectdata *conn) -{ - return ((data->set.httpversion == CURL_HTTP_VERSION_1_1) || - ((data->set.httpversion != CURL_HTTP_VERSION_1_0) && - ((conn->httpversion == 11) || - ((conn->httpversion != 10) && - (data->state.httpversion != 10))))) ? TRUE : FALSE; -} - -/* check and possibly add an Expect: header */ -static CURLcode expect100(struct SessionHandle *data, - struct connectdata *conn, - Curl_send_buffer *req_buffer) -{ - CURLcode result = CURLE_OK; - const char *ptr; - data->state.expect100header = FALSE; /* default to false unless it is set - to TRUE below */ - if(use_http_1_1(data, conn)) { - /* if not doing HTTP 1.0 or disabled explicitly, we add a Expect: - 100-continue to the headers which actually speeds up post operations - (as there is one packet coming back from the web server) */ - ptr = Curl_checkheaders(data, "Expect:"); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, "Expect:", "100-continue"); - } - else { - result = Curl_add_bufferf(req_buffer, - "Expect: 100-continue\r\n"); - if(result == CURLE_OK) - data->state.expect100header = TRUE; - } - } - return result; -} - -CURLcode Curl_add_custom_headers(struct connectdata *conn, - Curl_send_buffer *req_buffer) -{ - char *ptr; - struct curl_slist *headers=conn->data->set.headers; - - while(headers) { - ptr = strchr(headers->data, ':'); - if(ptr) { - /* we require a colon for this to be a true header */ - - ptr++; /* pass the colon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - - if(*ptr) { - /* only send this if the contents was non-blank */ - - if(conn->allocptr.host && - /* a Host: header was sent already, don't pass on any custom Host: - header as that will produce *two* in the same request! */ - checkprefix("Host:", headers->data)) - ; - else if(conn->data->set.httpreq == HTTPREQ_POST_FORM && - /* this header (extended by curl_formdata.c) is sent later */ - checkprefix("Content-Type:", headers->data)) - ; - else if(conn->bits.authneg && - /* while doing auth neg, don't allow the custom length since - we will force length zero then */ - checkprefix("Content-Length", headers->data)) - ; - else if(conn->allocptr.te && - /* when asking for Transfer-Encoding, don't pass on a custom - Connection: */ - checkprefix("Connection", headers->data)) - ; - else { - CURLcode result = Curl_add_bufferf(req_buffer, "%s\r\n", - headers->data); - if(result) - return result; - } - } - } - else { - ptr = strchr(headers->data, ';'); - if(ptr) { - - ptr++; /* pass the semicolon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - - if(*ptr) { - /* this may be used for something else in the future */ - } - else { - if(*(--ptr) == ';') { - CURLcode result; - - /* send no-value custom header if terminated by semicolon */ - *ptr = ':'; - result = Curl_add_bufferf(req_buffer, "%s\r\n", - headers->data); - if(result) - return result; - } - } - } - } - headers = headers->next; - } - return CURLE_OK; -} - -CURLcode Curl_add_timecondition(struct SessionHandle *data, - Curl_send_buffer *req_buffer) -{ - const struct tm *tm; - char *buf = data->state.buffer; - CURLcode result = CURLE_OK; - struct tm keeptime; - - result = Curl_gmtime(data->set.timevalue, &keeptime); - if(result) { - failf(data, "Invalid TIMEVALUE"); - return result; - } - tm = &keeptime; - - /* The If-Modified-Since header family should have their times set in - * GMT as RFC2616 defines: "All HTTP date/time stamps MUST be - * represented in Greenwich Mean Time (GMT), without exception. For the - * purposes of HTTP, GMT is exactly equal to UTC (Coordinated Universal - * Time)." (see page 20 of RFC2616). - */ - - /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ - snprintf(buf, BUFSIZE-1, - "%s, %02d %s %4d %02d:%02d:%02d GMT", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - - switch(data->set.timecondition) { - case CURL_TIMECOND_IFMODSINCE: - default: - result = Curl_add_bufferf(req_buffer, - "If-Modified-Since: %s\r\n", buf); - break; - case CURL_TIMECOND_IFUNMODSINCE: - result = Curl_add_bufferf(req_buffer, - "If-Unmodified-Since: %s\r\n", buf); - break; - case CURL_TIMECOND_LASTMOD: - result = Curl_add_bufferf(req_buffer, - "Last-Modified: %s\r\n", buf); - break; - } - - return result; -} - -/* - * Curl_http() gets called from the generic Curl_do() function when a HTTP - * request is to be performed. This creates and sends a properly constructed - * HTTP request. - */ -CURLcode Curl_http(struct connectdata *conn, bool *done) -{ - struct SessionHandle *data=conn->data; - CURLcode result=CURLE_OK; - struct HTTP *http; - const char *ppath = data->state.path; - bool paste_ftp_userpwd = FALSE; - char ftp_typecode[sizeof("/;type=?")] = ""; - const char *host = conn->host.name; - const char *te = ""; /* transfer-encoding */ - const char *ptr; - const char *request; - Curl_HttpReq httpreq = data->set.httpreq; - char *addcookies = NULL; - curl_off_t included_body = 0; - const char *httpstring; - Curl_send_buffer *req_buffer; - curl_off_t postsize = 0; /* curl_off_t to handle large file sizes */ - int seekerr = CURL_SEEKFUNC_OK; - - /* Always consider the DO phase done after this function call, even if there - may be parts of the request that is not yet sent, since we can deal with - the rest of the request in the PERFORM phase. */ - *done = TRUE; - - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - if(!data->state.proto.http) { - /* Only allocate this struct if we don't already have it! */ - - http = calloc(1, sizeof(struct HTTP)); - if(!http) - return CURLE_OUT_OF_MEMORY; - data->state.proto.http = http; - } - else - http = data->state.proto.http; - - if(!data->state.this_is_a_follow) { - /* this is not a followed location, get the original host name */ - if(data->state.first_host) - /* Free to avoid leaking memory on multiple requests*/ - free(data->state.first_host); - - data->state.first_host = strdup(conn->host.name); - if(!data->state.first_host) - return CURLE_OUT_OF_MEMORY; - } - http->writebytecount = http->readbytecount = 0; - - if((conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_FTP)) && - data->set.upload) { - httpreq = HTTPREQ_PUT; - } - - /* Now set the 'request' pointer to the proper request string */ - if(data->set.str[STRING_CUSTOMREQUEST]) - request = data->set.str[STRING_CUSTOMREQUEST]; - else { - if(data->set.opt_no_body) - request = "HEAD"; - else { - DEBUGASSERT((httpreq > HTTPREQ_NONE) && (httpreq < HTTPREQ_LAST)); - switch(httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - request = "POST"; - break; - case HTTPREQ_PUT: - request = "PUT"; - break; - default: /* this should never happen */ - case HTTPREQ_GET: - request = "GET"; - break; - case HTTPREQ_HEAD: - request = "HEAD"; - break; - } - } - } - - /* The User-Agent string might have been allocated in curl_url.c already, - because it might have been used in the proxy connect, but if we have - got a header with the user-agent string specified, we erase the - previously made string here. */ - if(Curl_checkheaders(data, "User-Agent:") && conn->allocptr.uagent) { - free(conn->allocptr.uagent); - conn->allocptr.uagent=NULL; - } - - /* setup the authentication headers */ - result = Curl_http_output_auth(conn, request, ppath, FALSE); - if(result) - return result; - - if((data->state.authhost.multi || data->state.authproxy.multi) && - (httpreq != HTTPREQ_GET) && - (httpreq != HTTPREQ_HEAD)) { - /* Auth is required and we are not authenticated yet. Make a PUT or POST - with content-length zero as a "probe". */ - conn->bits.authneg = TRUE; - } - else - conn->bits.authneg = FALSE; - - Curl_safefree(conn->allocptr.ref); - if(data->change.referer && !Curl_checkheaders(data, "Referer:")) - conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); - else - conn->allocptr.ref = NULL; - - if(data->set.str[STRING_COOKIE] && !Curl_checkheaders(data, "Cookie:")) - addcookies = data->set.str[STRING_COOKIE]; - - if(!Curl_checkheaders(data, "Accept-Encoding:") && - data->set.str[STRING_ENCODING]) { - Curl_safefree(conn->allocptr.accept_encoding); - conn->allocptr.accept_encoding = - aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); - if(!conn->allocptr.accept_encoding) - return CURLE_OUT_OF_MEMORY; - } - -#ifdef HAVE_LIBZ - /* we only consider transfer-encoding magic if libz support is built-in */ - - if(!Curl_checkheaders(data, "TE:") && data->set.http_transfer_encoding) { - /* When we are to insert a TE: header in the request, we must also insert - TE in a Connection: header, so we need to merge the custom provided - Connection: header and prevent the original to get sent. Note that if - the user has inserted his/hers own TE: header we don't do this magic - but then assume that the user will handle it all! */ - char *cptr = Curl_checkheaders(data, "Connection:"); -#define TE_HEADER "TE: gzip\r\n" - - Curl_safefree(conn->allocptr.te); - - /* Create the (updated) Connection: header */ - conn->allocptr.te = cptr? aprintf("%s, TE\r\n" TE_HEADER, cptr): - strdup("Connection: TE\r\n" TE_HEADER); - - if(!conn->allocptr.te) - return CURLE_OUT_OF_MEMORY; - } -#endif - - ptr = Curl_checkheaders(data, "Transfer-Encoding:"); - if(ptr) { - /* Some kind of TE is requested, check if 'chunked' is chosen */ - data->req.upload_chunky = - Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); - } - else { - if((conn->handler->protocol&CURLPROTO_HTTP) && - data->set.upload && - (data->set.infilesize == -1)) { - if(conn->bits.authneg) - /* don't enable chunked during auth neg */ - ; - else if(use_http_1_1(data, conn)) { - /* HTTP, upload, unknown file size and not HTTP 1.0 */ - data->req.upload_chunky = TRUE; - } - else { - failf(data, "Chunky upload is not supported by HTTP 1.0"); - return CURLE_UPLOAD_FAILED; - } - } - else { - /* else, no chunky upload */ - data->req.upload_chunky = FALSE; - } - - if(data->req.upload_chunky) - te = "Transfer-Encoding: chunked\r\n"; - } - - Curl_safefree(conn->allocptr.host); - - ptr = Curl_checkheaders(data, "Host:"); - if(ptr && (!data->state.this_is_a_follow || - Curl_raw_equal(data->state.first_host, conn->host.name))) { -#if !defined(CURL_DISABLE_COOKIES) - /* If we have a given custom Host: header, we extract the host name in - order to possibly use it for cookie reasons later on. We only allow the - custom Host: header if this is NOT a redirect, as setting Host: in the - redirected request is being out on thin ice. Except if the host name - is the same as the first one! */ - char *cookiehost = copy_header_value(ptr); - if(!cookiehost) - return CURLE_OUT_OF_MEMORY; - if(!*cookiehost) - /* ignore empty data */ - free(cookiehost); - else { - /* If the host begins with '[', we start searching for the port after - the bracket has been closed */ - int startsearch = 0; - if(*cookiehost == '[') { - char *closingbracket; - /* since the 'cookiehost' is an allocated memory area that will be - freed later we cannot simply increment the pointer */ - memmove(cookiehost, cookiehost + 1, strlen(cookiehost) - 1); - closingbracket = strchr(cookiehost, ']'); - if(closingbracket) - *closingbracket = 0; - } - else { - char *colon = strchr(cookiehost + startsearch, ':'); - if(colon) - *colon = 0; /* The host must not include an embedded port number */ - } - Curl_safefree(conn->allocptr.cookiehost); - conn->allocptr.cookiehost = cookiehost; - } -#endif - - conn->allocptr.host = NULL; - } - else { - /* When building Host: headers, we must put the host name within - [brackets] if the host name is a plain IPv6-address. RFC2732-style. */ - - if(((conn->given->protocol&CURLPROTO_HTTPS) && - (conn->remote_port == PORT_HTTPS)) || - ((conn->given->protocol&CURLPROTO_HTTP) && - (conn->remote_port == PORT_HTTP)) ) - /* if(HTTPS on port 443) OR (HTTP on port 80) then don't include - the port number in the host string */ - conn->allocptr.host = aprintf("Host: %s%s%s\r\n", - conn->bits.ipv6_ip?"[":"", - host, - conn->bits.ipv6_ip?"]":""); - else - conn->allocptr.host = aprintf("Host: %s%s%s:%hu\r\n", - conn->bits.ipv6_ip?"[":"", - host, - conn->bits.ipv6_ip?"]":"", - conn->remote_port); - - if(!conn->allocptr.host) - /* without Host: we can't make a nice request */ - return CURLE_OUT_OF_MEMORY; - } - -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* Using a proxy but does not tunnel through it */ - - /* The path sent to the proxy is in fact the entire URL. But if the remote - host is a IDN-name, we must make sure that the request we produce only - uses the encoded host name! */ - if(conn->host.dispname != conn->host.name) { - char *url = data->change.url; - ptr = strstr(url, conn->host.dispname); - if(ptr) { - /* This is where the display name starts in the URL, now replace this - part with the encoded name. TODO: This method of replacing the host - name is rather crude as I believe there's a slight risk that the - user has entered a user name or password that contain the host name - string. */ - size_t currlen = strlen(conn->host.dispname); - size_t newlen = strlen(conn->host.name); - size_t urllen = strlen(url); - - char *newurl; - - newurl = malloc(urllen + newlen - currlen + 1); - if(newurl) { - /* copy the part before the host name */ - memcpy(newurl, url, ptr - url); - /* append the new host name instead of the old */ - memcpy(newurl + (ptr - url), conn->host.name, newlen); - /* append the piece after the host name */ - memcpy(newurl + newlen + (ptr - url), - ptr + currlen, /* copy the trailing zero byte too */ - urllen - (ptr-url) - currlen + 1); - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - data->change.url = newurl; - data->change.url_alloc = TRUE; - } - else - return CURLE_OUT_OF_MEMORY; - } - } - ppath = data->change.url; - if(checkprefix("ftp://", ppath)) { - if(data->set.proxy_transfer_mode) { - /* when doing ftp, append ;type= if not present */ - char *type = strstr(ppath, ";type="); - if(type && type[6] && type[7] == 0) { - switch (Curl_raw_toupper(type[6])) { - case 'A': - case 'D': - case 'I': - break; - default: - type = NULL; - } - } - if(!type) { - char *p = ftp_typecode; - /* avoid sending invalid URLs like ftp://example.com;type=i if the - * user specified ftp://example.com without the slash */ - if(!*data->state.path && ppath[strlen(ppath) - 1] != '/') { - *p++ = '/'; - } - snprintf(p, sizeof(ftp_typecode) - 1, ";type=%c", - data->set.prefer_ascii ? 'a' : 'i'); - } - } - if(conn->bits.user_passwd && !conn->bits.userpwd_in_url) - paste_ftp_userpwd = TRUE; - } - } -#endif /* CURL_DISABLE_PROXY */ - - if(HTTPREQ_POST_FORM == httpreq) { - /* we must build the whole post sequence first, so that we have a size of - the whole transfer before we start to send it */ - result = Curl_getformdata(data, &http->sendit, data->set.httppost, - Curl_checkheaders(data, "Content-Type:"), - &http->postsize); - if(result) - return result; - } - - http->p_accept = Curl_checkheaders(data, "Accept:")?NULL:"Accept: */*\r\n"; - - if(( (HTTPREQ_POST == httpreq) || - (HTTPREQ_POST_FORM == httpreq) || - (HTTPREQ_PUT == httpreq) ) && - data->state.resume_from) { - /********************************************************************** - * Resuming upload in HTTP means that we PUT or POST and that we have - * got a resume_from value set. The resume value has already created - * a Range: header that will be passed along. We need to "fast forward" - * the file the given number of bytes and decrease the assume upload - * file size before we continue this venture in the dark lands of HTTP. - *********************************************************************/ - - if(data->state.resume_from < 0 ) { - /* - * This is meant to get the size of the present remote-file by itself. - * We don't support this now. Bail out! - */ - data->state.resume_from = 0; - } - - if(data->state.resume_from && !data->state.this_is_a_follow) { - /* do we still game? */ - - /* Now, let's read off the proper amount of bytes from the - input. */ - if(conn->seek_func) { - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_READ_ERROR; - } - /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - else { - curl_off_t passed=0; - do { - size_t readthisamountnow = - (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? - BUFSIZE : curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - data->set.fread_func(data->state.buffer, 1, readthisamountnow, - data->set.in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Could only read %" FORMAT_OFF_T - " bytes from the input", - passed); - return CURLE_READ_ERROR; - } - } while(passed < data->state.resume_from); - } - } - - /* now, decrease the size of the read */ - if(data->set.infilesize>0) { - data->set.infilesize -= data->state.resume_from; - - if(data->set.infilesize <= 0) { - failf(data, "File already completely uploaded"); - return CURLE_PARTIAL_FILE; - } - } - /* we've passed, proceed as normal */ - } - } - if(data->state.use_range) { - /* - * A range is selected. We use different headers whether we're downloading - * or uploading and we always let customized headers override our internal - * ones if any such are specified. - */ - if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && - !Curl_checkheaders(data, "Range:")) { - /* if a line like this was already allocated, free the previous one */ - if(conn->allocptr.rangeline) - free(conn->allocptr.rangeline); - conn->allocptr.rangeline = aprintf("Range: bytes=%s\r\n", - data->state.range); - } - else if((httpreq != HTTPREQ_GET) && - !Curl_checkheaders(data, "Content-Range:")) { - - /* if a line like this was already allocated, free the previous one */ - if(conn->allocptr.rangeline) - free(conn->allocptr.rangeline); - - if(data->set.set_resume_from < 0) { - /* Upload resume was asked for, but we don't know the size of the - remote part so we tell the server (and act accordingly) that we - upload the whole file (again) */ - conn->allocptr.rangeline = - aprintf("Content-Range: bytes 0-%" FORMAT_OFF_T - "/%" FORMAT_OFF_T "\r\n", - data->set.infilesize - 1, data->set.infilesize); - - } - else if(data->state.resume_from) { - /* This is because "resume" was selected */ - curl_off_t total_expected_size= - data->state.resume_from + data->set.infilesize; - conn->allocptr.rangeline = - aprintf("Content-Range: bytes %s%" FORMAT_OFF_T - "/%" FORMAT_OFF_T "\r\n", - data->state.range, total_expected_size-1, - total_expected_size); - } - else { - /* Range was selected and then we just pass the incoming range and - append total size */ - conn->allocptr.rangeline = - aprintf("Content-Range: bytes %s/%" FORMAT_OFF_T "\r\n", - data->state.range, data->set.infilesize); - } - if(!conn->allocptr.rangeline) - return CURLE_OUT_OF_MEMORY; - } - } - - /* Use 1.1 unless the user specifically asked for 1.0 or the server only - supports 1.0 */ - httpstring= use_http_1_1(data, conn)?"1.1":"1.0"; - - /* initialize a dynamic send-buffer */ - req_buffer = Curl_add_buffer_init(); - - if(!req_buffer) - return CURLE_OUT_OF_MEMORY; - - /* add the main request stuff */ - /* GET/HEAD/POST/PUT */ - result = Curl_add_bufferf(req_buffer, "%s ", request); - if(result) - return result; - - /* url */ - if(paste_ftp_userpwd) - result = Curl_add_bufferf(req_buffer, "ftp://%s:%s@%s", - conn->user, conn->passwd, - ppath + sizeof("ftp://") - 1); - else - result = Curl_add_buffer(req_buffer, ppath, strlen(ppath)); - if(result) - return result; - - result = - Curl_add_bufferf(req_buffer, - "%s" /* ftp typecode (;type=x) */ - " HTTP/%s\r\n" /* HTTP version */ - "%s" /* proxyuserpwd */ - "%s" /* userpwd */ - "%s" /* range */ - "%s" /* user agent */ - "%s" /* host */ - "%s" /* accept */ - "%s" /* TE: */ - "%s" /* accept-encoding */ - "%s" /* referer */ - "%s" /* Proxy-Connection */ - "%s",/* transfer-encoding */ - - ftp_typecode, - httpstring, - conn->allocptr.proxyuserpwd? - conn->allocptr.proxyuserpwd:"", - conn->allocptr.userpwd?conn->allocptr.userpwd:"", - (data->state.use_range && conn->allocptr.rangeline)? - conn->allocptr.rangeline:"", - (data->set.str[STRING_USERAGENT] && - *data->set.str[STRING_USERAGENT] && - conn->allocptr.uagent)? - conn->allocptr.uagent:"", - (conn->allocptr.host?conn->allocptr.host:""), - http->p_accept?http->p_accept:"", - conn->allocptr.te?conn->allocptr.te:"", - (data->set.str[STRING_ENCODING] && - *data->set.str[STRING_ENCODING] && - conn->allocptr.accept_encoding)? - conn->allocptr.accept_encoding:"", - (data->change.referer && conn->allocptr.ref)? - conn->allocptr.ref:"" /* Referer: */, - (conn->bits.httpproxy && - !conn->bits.tunnel_proxy && - !Curl_checkheaders(data, "Proxy-Connection:"))? - "Proxy-Connection: Keep-Alive\r\n":"", - te - ); - - /* - * Free userpwd now --- cannot reuse this for Negotiate and possibly NTLM - * with basic and digest, it will be freed anyway by the next request - */ - - Curl_safefree (conn->allocptr.userpwd); - conn->allocptr.userpwd = NULL; - - if(result) - return result; - -#if !defined(CURL_DISABLE_COOKIES) - if(data->cookies || addcookies) { - struct Cookie *co=NULL; /* no cookies from start */ - int count=0; - - if(data->cookies) { - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - co = Curl_cookie_getlist(data->cookies, - conn->allocptr.cookiehost? - conn->allocptr.cookiehost:host, - data->state.path, - (conn->handler->protocol&CURLPROTO_HTTPS)? - TRUE:FALSE); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } - if(co) { - struct Cookie *store=co; - /* now loop through all cookies that matched */ - while(co) { - if(co->value) { - if(0 == count) { - result = Curl_add_bufferf(req_buffer, "Cookie: "); - if(result) - break; - } - result = Curl_add_bufferf(req_buffer, - "%s%s=%s", count?"; ":"", - co->name, co->value); - if(result) - break; - count++; - } - co = co->next; /* next cookie please */ - } - Curl_cookie_freelist(store, FALSE); /* free the cookie list */ - } - if(addcookies && (CURLE_OK == result)) { - if(!count) - result = Curl_add_bufferf(req_buffer, "Cookie: "); - if(CURLE_OK == result) { - result = Curl_add_bufferf(req_buffer, "%s%s", - count?"; ":"", - addcookies); - count++; - } - } - if(count && (CURLE_OK == result)) - result = Curl_add_buffer(req_buffer, "\r\n", 2); - - if(result) - return result; - } -#endif - - if(data->set.timecondition) { - result = Curl_add_timecondition(data, req_buffer); - if(result) - return result; - } - - result = Curl_add_custom_headers(conn, req_buffer); - if(result) - return result; - - http->postdata = NULL; /* nothing to post at this point */ - Curl_pgrsSetUploadSize(data, 0); /* upload size is 0 atm */ - - /* If 'authdone' is FALSE, we must not set the write socket index to the - Curl_transfer() call below, as we're not ready to actually upload any - data yet. */ - - switch(httpreq) { - - case HTTPREQ_POST_FORM: - if(!http->sendit || conn->bits.authneg) { - /* nothing to post! */ - result = Curl_add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); - if(result) - return result; - - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* setup variables for the upcoming transfer */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, - -1, NULL); - break; - } - - if(Curl_FormInit(&http->form, http->sendit)) { - failf(data, "Internal HTTP POST error!"); - return CURLE_HTTP_POST_ERROR; - } - - /* Get the currently set callback function pointer and store that in the - form struct since we might want the actual user-provided callback later - on. The conn->fread_func pointer itself will be changed for the - multipart case to the function that returns a multipart formatted - stream. */ - http->form.fread_func = conn->fread_func; - - /* Set the read function to read from the generated form data */ - conn->fread_func = (curl_read_callback)Curl_FormReader; - conn->fread_in = &http->form; - - http->sending = HTTPSEND_BODY; - - if(!data->req.upload_chunky && - !Curl_checkheaders(data, "Content-Length:")) { - /* only add Content-Length if not uploading chunked */ - result = Curl_add_bufferf(req_buffer, - "Content-Length: %" FORMAT_OFF_T "\r\n", - http->postsize); - if(result) - return result; - } - - result = expect100(data, conn, req_buffer); - if(result) - return result; - - { - - /* Get Content-Type: line from Curl_formpostheader. - */ - char *contentType; - size_t linelength=0; - contentType = Curl_formpostheader((void *)&http->form, - &linelength); - if(!contentType) { - failf(data, "Could not get Content-Type header line!"); - return CURLE_HTTP_POST_ERROR; - } - - result = Curl_add_buffer(req_buffer, contentType, linelength); - if(result) - return result; - } - - /* make the request end in a true CRLF */ - result = Curl_add_buffer(req_buffer, "\r\n", 2); - if(result) - return result; - - /* set upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); - - /* fire away the whole request to the server */ - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* setup variables for the upcoming transfer */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, FIRSTSOCKET, - &http->writebytecount); - - if(result) { - Curl_formclean(&http->sendit); /* free that whole lot */ - return result; - } - - /* convert the form data */ - result = Curl_convert_form(data, http->sendit); - if(result) { - Curl_formclean(&http->sendit); /* free that whole lot */ - return result; - } - - break; - - case HTTPREQ_PUT: /* Let's PUT the data to the server! */ - - if(conn->bits.authneg) - postsize = 0; - else - postsize = data->set.infilesize; - - if((postsize != -1) && !data->req.upload_chunky && - !Curl_checkheaders(data, "Content-Length:")) { - /* only add Content-Length if not uploading chunked */ - result = Curl_add_bufferf(req_buffer, - "Content-Length: %" FORMAT_OFF_T "\r\n", - postsize ); - if(result) - return result; - } - - result = expect100(data, conn, req_buffer); - if(result) - return result; - - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers */ - if(result) - return result; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, postsize); - - /* this sends the buffer and frees all the buffer resources */ - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - if(result) - failf(data, "Failed sending PUT request"); - else - /* prepare for transfer */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, postsize?FIRSTSOCKET:-1, - postsize?&http->writebytecount:NULL); - if(result) - return result; - break; - - case HTTPREQ_POST: - /* this is the simple POST, using x-www-form-urlencoded style */ - - if(conn->bits.authneg) - postsize = 0; - else { - /* figure out the size of the postfields */ - postsize = (data->set.postfieldsize != -1)? - data->set.postfieldsize: - (data->set.postfields? (curl_off_t)strlen(data->set.postfields):-1); - } - if(!data->req.upload_chunky) { - /* We only set Content-Length and allow a custom Content-Length if - we don't upload data chunked, as RFC2616 forbids us to set both - kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - - if(conn->bits.authneg || !Curl_checkheaders(data, "Content-Length:")) { - /* we allow replacing this header if not during auth negotiation, - although it isn't very wise to actually set your own */ - result = Curl_add_bufferf(req_buffer, - "Content-Length: %" FORMAT_OFF_T"\r\n", - postsize); - if(result) - return result; - } - } - - if(!Curl_checkheaders(data, "Content-Type:")) { - result = Curl_add_bufferf(req_buffer, - "Content-Type: application/" - "x-www-form-urlencoded\r\n"); - if(result) - return result; - } - - /* For really small posts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, "Expect:"); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, "Expect:", "100-continue"); - } - else if(postsize > TINY_INITIAL_POST_SIZE || postsize < 0) { - result = expect100(data, conn, req_buffer); - if(result) - return result; - } - else - data->state.expect100header = FALSE; - - if(data->set.postfields) { - - if(!data->state.expect100header && - (postsize < MAX_INITIAL_POST_SIZE)) { - /* if we don't use expect: 100 AND - postsize is less than MAX_INITIAL_POST_SIZE - - then append the post data to the HTTP request header. This limit - is no magic limit but only set to prevent really huge POSTs to - get the data duplicated with malloc() and family. */ - - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ - if(result) - return result; - - if(!data->req.upload_chunky) { - /* We're not sending it 'chunked', append it to the request - already now to reduce the number if send() calls */ - result = Curl_add_buffer(req_buffer, data->set.postfields, - (size_t)postsize); - included_body = postsize; - } - else { - if(postsize) { - /* Append the POST data chunky-style */ - result = Curl_add_bufferf(req_buffer, "%x\r\n", (int)postsize); - if(CURLE_OK == result) { - result = Curl_add_buffer(req_buffer, data->set.postfields, - (size_t)postsize); - if(CURLE_OK == result) - result = Curl_add_buffer(req_buffer, "\r\n", 2); - included_body = postsize + 2; - } - } - if(CURLE_OK == result) - result = Curl_add_buffer(req_buffer, - "\x30\x0d\x0a\x0d\x0a", 5); - /* 0 CR LF CR LF */ - included_body += 5; - } - if(result) - return result; - /* Make sure the progress information is accurate */ - Curl_pgrsSetUploadSize(data, postsize); - } - else { - /* A huge POST coming up, do data separate from the request */ - http->postsize = postsize; - http->postdata = data->set.postfields; - - http->sending = HTTPSEND_BODY; - - conn->fread_func = (curl_read_callback)readmoredata; - conn->fread_in = (void *)conn; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); - - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ - if(result) - return result; - } - } - else { - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ - if(result) - return result; - - if(data->req.upload_chunky && conn->bits.authneg) { - /* Chunky upload is selected and we're negotiating auth still, send - end-of-data only */ - result = Curl_add_buffer(req_buffer, - "\x30\x0d\x0a\x0d\x0a", 5); - /* 0 CR LF CR LF */ - if(result) - return result; - } - - else if(data->set.postfieldsize) { - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, postsize?postsize:-1); - - /* set the pointer to mark that we will send the post body using the - read callback, but only if we're not in authenticate - negotiation */ - if(!conn->bits.authneg) { - http->postdata = (char *)&http->postdata; - http->postsize = postsize; - } - } - } - /* issue the request */ - result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, - (size_t)included_body, FIRSTSOCKET); - - if(result) - failf(data, "Failed sending HTTP POST request"); - else - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, http->postdata?FIRSTSOCKET:-1, - http->postdata?&http->writebytecount:NULL); - break; - - default: - result = Curl_add_buffer(req_buffer, "\r\n", 2); - if(result) - return result; - - /* issue the request */ - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - - if(result) - failf(data, "Failed sending HTTP request"); - else - /* HTTP GET/HEAD download: */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, - http->postdata?FIRSTSOCKET:-1, - http->postdata?&http->writebytecount:NULL); - } - if(result) - return result; - - if(http->writebytecount) { - /* if a request-body has been sent off, we make sure this progress is noted - properly */ - Curl_pgrsSetUploadCounter(data, http->writebytecount); - if(Curl_pgrsUpdate(conn)) - result = CURLE_ABORTED_BY_CALLBACK; - - if(http->writebytecount >= postsize) { - /* already sent the entire request body, mark the "upload" as - complete */ - infof(data, "upload completely sent off: %" FORMAT_OFF_T " out of " - "%" FORMAT_OFF_T " bytes\n", - http->writebytecount, postsize); - data->req.upload_done = TRUE; - data->req.keepon &= ~KEEP_SEND; /* we're done writing */ - data->req.exp100 = EXP100_SEND_DATA; /* already sent */ - } - } - - return result; -} - -/* - * checkhttpprefix() - * - * Returns TRUE if member of the list matches prefix of string - */ -static bool -checkhttpprefix(struct SessionHandle *data, - const char *s) -{ - struct curl_slist *head = data->set.http200aliases; - bool rc = FALSE; -#ifdef CURL_DOES_CONVERSIONS - /* convert from the network encoding using a scratch area */ - char *scratch = strdup(s); - if(NULL == scratch) { - failf (data, "Failed to allocate memory for conversion!"); - return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ - } - if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) { - /* Curl_convert_from_network calls failf if unsuccessful */ - free(scratch); - return FALSE; /* can't return CURLE_foobar so return FALSE */ - } - s = scratch; -#endif /* CURL_DOES_CONVERSIONS */ - - while(head) { - if(checkprefix(head->data, s)) { - rc = TRUE; - break; - } - head = head->next; - } - - if(!rc && (checkprefix("HTTP/", s))) - rc = TRUE; - -#ifdef CURL_DOES_CONVERSIONS - free(scratch); -#endif /* CURL_DOES_CONVERSIONS */ - return rc; -} - -#ifndef CURL_DISABLE_RTSP -static bool -checkrtspprefix(struct SessionHandle *data, - const char *s) -{ - -#ifdef CURL_DOES_CONVERSIONS - /* convert from the network encoding using a scratch area */ - char *scratch = strdup(s); - if(NULL == scratch) { - failf (data, "Failed to allocate memory for conversion!"); - return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ - } - if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) { - /* Curl_convert_from_network calls failf if unsuccessful */ - free(scratch); - return FALSE; /* can't return CURLE_foobar so return FALSE */ - } - s = scratch; -#else - (void)data; /* unused */ -#endif /* CURL_DOES_CONVERSIONS */ - if(checkprefix("RTSP/", s)) - return TRUE; - else - return FALSE; -} -#endif /* CURL_DISABLE_RTSP */ - -static bool -checkprotoprefix(struct SessionHandle *data, struct connectdata *conn, - const char *s) -{ -#ifndef CURL_DISABLE_RTSP - if(conn->handler->protocol & CURLPROTO_RTSP) - return checkrtspprefix(data, s); -#else - (void)conn; -#endif /* CURL_DISABLE_RTSP */ - - return checkhttpprefix(data, s); -} - -/* - * header_append() copies a chunk of data to the end of the already received - * header. We make sure that the full string fit in the allocated header - * buffer, or else we enlarge it. - */ -static CURLcode header_append(struct SessionHandle *data, - struct SingleRequest *k, - size_t length) -{ - if(k->hbuflen + length >= data->state.headersize) { - /* We enlarge the header buffer as it is too small */ - char *newbuff; - size_t hbufp_index; - size_t newsize; - - if(k->hbuflen + length > CURL_MAX_HTTP_HEADER) { - /* The reason to have a max limit for this is to avoid the risk of a bad - server feeding libcurl with a never-ending header that will cause - reallocs infinitely */ - failf (data, "Avoided giant realloc for header (max is %d)!", - CURL_MAX_HTTP_HEADER); - return CURLE_OUT_OF_MEMORY; - } - - newsize=CURLMAX((k->hbuflen+ length)*3/2, data->state.headersize*2); - hbufp_index = k->hbufp - data->state.headerbuff; - newbuff = realloc(data->state.headerbuff, newsize); - if(!newbuff) { - failf (data, "Failed to alloc memory for big header!"); - return CURLE_OUT_OF_MEMORY; - } - data->state.headersize=newsize; - data->state.headerbuff = newbuff; - k->hbufp = data->state.headerbuff + hbufp_index; - } - memcpy(k->hbufp, k->str_start, length); - k->hbufp += length; - k->hbuflen += length; - *k->hbufp = 0; - - return CURLE_OK; -} - -static void print_http_error(struct SessionHandle *data) -{ - struct SingleRequest *k = &data->req; - char *beg = k->p; - - /* make sure that data->req.p points to the HTTP status line */ - if(!strncmp(beg, "HTTP", 4)) { - - /* skip to HTTP status code */ - beg = strchr(beg, ' '); - if(beg && *++beg) { - - /* find trailing CR */ - char end_char = '\r'; - char *end = strchr(beg, end_char); - if(!end) { - /* try to find LF (workaround for non-compliant HTTP servers) */ - end_char = '\n'; - end = strchr(beg, end_char); - } - - if(end) { - /* temporarily replace CR or LF by NUL and print the error message */ - *end = '\0'; - failf(data, "The requested URL returned error: %s", beg); - - /* restore the previously replaced CR or LF */ - *end = end_char; - return; - } - } - } - - /* fall-back to printing the HTTP status code only */ - failf(data, "The requested URL returned error: %d", k->httpcode); -} - -/* - * Read any HTTP header lines from the server and pass them to the client app. - */ -CURLcode Curl_http_readwrite_headers(struct SessionHandle *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading) -{ - CURLcode result; - struct SingleRequest *k = &data->req; - - /* header line within buffer loop */ - do { - size_t rest_length; - size_t full_length; - int writetype; - - /* str_start is start of line within buf */ - k->str_start = k->str; - - /* data is in network encoding so use 0x0a instead of '\n' */ - k->end_ptr = memchr(k->str_start, 0x0a, *nread); - - if(!k->end_ptr) { - /* Not a complete header line within buffer, append the data to - the end of the headerbuff. */ - result = header_append(data, k, *nread); - if(result) - return result; - - if(!k->headerline && (k->hbuflen>5)) { - /* make a first check that this looks like a protocol header */ - if(!checkprotoprefix(data, conn, data->state.headerbuff)) { - /* this is not the beginning of a protocol first header line */ - k->header = FALSE; - k->badheader = HEADER_ALLBAD; - break; - } - } - - break; /* read more and try again */ - } - - /* decrease the size of the remaining (supposed) header line */ - rest_length = (k->end_ptr - k->str)+1; - *nread -= (ssize_t)rest_length; - - k->str = k->end_ptr + 1; /* move past new line */ - - full_length = k->str - k->str_start; - - result = header_append(data, k, full_length); - if(result) - return result; - - k->end_ptr = k->hbufp; - k->p = data->state.headerbuff; - - /**** - * We now have a FULL header line that p points to - *****/ - - if(!k->headerline) { - /* the first read header */ - if((k->hbuflen>5) && - !checkprotoprefix(data, conn, data->state.headerbuff)) { - /* this is not the beginning of a protocol first header line */ - k->header = FALSE; - if(*nread) - /* since there's more, this is a partial bad header */ - k->badheader = HEADER_PARTHEADER; - else { - /* this was all we read so it's all a bad header */ - k->badheader = HEADER_ALLBAD; - *nread = (ssize_t)rest_length; - } - break; - } - } - - /* headers are in network encoding so - use 0x0a and 0x0d instead of '\n' and '\r' */ - if((0x0a == *k->p) || (0x0d == *k->p)) { - size_t headerlen; - /* Zero-length header line means end of headers! */ - -#ifdef CURL_DOES_CONVERSIONS - if(0x0d == *k->p) { - *k->p = '\r'; /* replace with CR in host encoding */ - k->p++; /* pass the CR byte */ - } - if(0x0a == *k->p) { - *k->p = '\n'; /* replace with LF in host encoding */ - k->p++; /* pass the LF byte */ - } -#else - if('\r' == *k->p) - k->p++; /* pass the \r byte */ - if('\n' == *k->p) - k->p++; /* pass the \n byte */ -#endif /* CURL_DOES_CONVERSIONS */ - - if(100 <= k->httpcode && 199 >= k->httpcode) { - /* - * We have made a HTTP PUT or POST and this is 1.1-lingo - * that tells us that the server is OK with this and ready - * to receive the data. - * However, we'll get more headers now so we must get - * back into the header-parsing state! - */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* if we did wait for this do enable write now! */ - if(k->exp100) { - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - } - } - else { - k->header = FALSE; /* no more header to parse! */ - - if((k->size == -1) && !k->chunk && !conn->bits.close && - (conn->httpversion >= 11) && - !(conn->handler->protocol & CURLPROTO_RTSP) && - data->set.httpreq != HTTPREQ_HEAD) { - /* On HTTP 1.1, when connection is not to get closed, but no - Content-Length nor Content-Encoding chunked have been - received, according to RFC2616 section 4.4 point 5, we - assume that the server will close the connection to - signal the end of the document. */ - infof(data, "no chunk, no close, no size. Assume close to " - "signal end\n"); - conn->bits.close = TRUE; - } - } - - /* - * When all the headers have been parsed, see if we should give - * up and return an error. - */ - if(http_should_fail(conn)) { - failf (data, "The requested URL returned error: %d", - k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } - - /* now, only output this if the header AND body are requested: - */ - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - - headerlen = k->p - data->state.headerbuff; - - result = Curl_client_write(conn, writetype, - data->state.headerbuff, - headerlen); - if(result) - return result; - - data->info.header_size += (long)headerlen; - data->req.headerbytecount += (long)headerlen; - - data->req.deductheadercount = - (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; - - if(!*stop_reading) { - /* Curl_http_auth_act() checks what authentication methods - * that are available and decides which one (if any) to - * use. It will set 'newurl' if an auth method was picked. */ - result = Curl_http_auth_act(conn); - - if(result) - return result; - - if(k->httpcode >= 300) { - if((!conn->bits.authneg) && !conn->bits.close && - !conn->bits.rewindaftersend) { - /* - * General treatment of errors when about to send data. Including : - * "417 Expectation Failed", while waiting for 100-continue. - * - * The check for close above is done simply because of something - * else has already deemed the connection to get closed then - * something else should've considered the big picture and we - * avoid this check. - * - * rewindaftersend indicates that something has told libcurl to - * continue sending even if it gets discarded - */ - - switch(data->set.httpreq) { - case HTTPREQ_PUT: - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - /* We got an error response. If this happened before the whole - * request body has been sent we stop sending and mark the - * connection for closure after we've read the entire response. - */ - if(!k->upload_done) { - infof(data, "HTTP error before end of send, stop sending\n"); - conn->bits.close = TRUE; /* close after this */ - k->upload_done = TRUE; - k->keepon &= ~KEEP_SEND; /* don't send */ - if(data->state.expect100header) - k->exp100 = EXP100_FAILED; - } - break; - - default: /* default label present to avoid compiler warnings */ - break; - } - } - } - - if(conn->bits.rewindaftersend) { - /* We rewind after a complete send, so thus we continue - sending now */ - infof(data, "Keep sending data to get tossed away!\n"); - k->keepon |= KEEP_SEND; - } - } - - if(!k->header) { - /* - * really end-of-headers. - * - * If we requested a "no body", this is a good time to get - * out and return home. - */ - if(data->set.opt_no_body) - *stop_reading = TRUE; - else { - /* If we know the expected size of this document, we set the - maximum download size to the size of the expected - document or else, we won't know when to stop reading! - - Note that we set the download maximum even if we read a - "Connection: close" header, to make sure that - "Content-Length: 0" still prevents us from attempting to - read the (missing) response-body. - */ - /* According to RFC2616 section 4.4, we MUST ignore - Content-Length: headers if we are now receiving data - using chunked Transfer-Encoding. - */ - if(k->chunk) - k->maxdownload = k->size = -1; - } - if(-1 != k->size) { - /* We do this operation even if no_body is true, since this - data might be retrieved later with curl_easy_getinfo() - and its CURLINFO_CONTENT_LENGTH_DOWNLOAD option. */ - - Curl_pgrsSetDownloadSize(data, k->size); - k->maxdownload = k->size; - } - - /* If max download size is *zero* (nothing) we already - have nothing and can safely return ok now! */ - if(0 == k->maxdownload) - *stop_reading = TRUE; - - if(*stop_reading) { - /* we make sure that this socket isn't read more now */ - k->keepon &= ~KEEP_RECV; - } - - if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, - k->str_start, headerlen, conn); - break; /* exit header line loop */ - } - - /* We continue reading headers, so reset the line-based - header parsing variables hbufp && hbuflen */ - k->hbufp = data->state.headerbuff; - k->hbuflen = 0; - continue; - } - - /* - * Checks for special headers coming up. - */ - - if(!k->headerline++) { - /* This is the first header, it MUST be the error code line - or else we consider this to be the body right away! */ - int httpversion_major; - int rtspversion_major; - int nc = 0; -#ifdef CURL_DOES_CONVERSIONS -#define HEADER1 scratch -#define SCRATCHSIZE 21 - CURLcode res; - char scratch[SCRATCHSIZE+1]; /* "HTTP/major.minor 123" */ - /* We can't really convert this yet because we - don't know if it's the 1st header line or the body. - So we do a partial conversion into a scratch area, - leaving the data at k->p as-is. - */ - strncpy(&scratch[0], k->p, SCRATCHSIZE); - scratch[SCRATCHSIZE] = 0; /* null terminate */ - res = Curl_convert_from_network(data, - &scratch[0], - SCRATCHSIZE); - if(res) - /* Curl_convert_from_network calls failf if unsuccessful */ - return res; -#else -#define HEADER1 k->p /* no conversion needed, just use k->p */ -#endif /* CURL_DOES_CONVERSIONS */ - - if(conn->handler->protocol & CURLPROTO_HTTP) { - nc = sscanf(HEADER1, - " HTTP/%d.%d %3d", - &httpversion_major, - &conn->httpversion, - &k->httpcode); - if(nc==3) { - conn->httpversion += 10 * httpversion_major; - } - else { - /* this is the real world, not a Nirvana - NCSA 1.5.x returns this crap when asked for HTTP/1.1 - */ - nc=sscanf(HEADER1, " HTTP %3d", &k->httpcode); - conn->httpversion = 10; - - /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ - if(!nc) { - if(checkhttpprefix(data, k->p)) { - nc = 1; - k->httpcode = 200; - conn->httpversion = 10; - } - } - } - } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - nc = sscanf(HEADER1, - " RTSP/%d.%d %3d", - &rtspversion_major, - &conn->rtspversion, - &k->httpcode); - if(nc==3) { - conn->rtspversion += 10 * rtspversion_major; - conn->httpversion = 11; /* For us, RTSP acts like HTTP 1.1 */ - } - else { - /* TODO: do we care about the other cases here? */ - nc = 0; - } - } - - if(nc) { - data->info.httpcode = k->httpcode; - - data->info.httpversion = conn->httpversion; - if(!data->state.httpversion || - data->state.httpversion > conn->httpversion) - /* store the lowest server version we encounter */ - data->state.httpversion = conn->httpversion; - - /* - * This code executes as part of processing the header. As a - * result, it's not totally clear how to interpret the - * response code yet as that depends on what other headers may - * be present. 401 and 407 may be errors, but may be OK - * depending on how authentication is working. Other codes - * are definitely errors, so give up here. - */ - if(data->set.http_fail_on_error && (k->httpcode >= 400) && - ((k->httpcode != 401) || !conn->bits.user_passwd) && - ((k->httpcode != 407) || !conn->bits.proxy_user_passwd) ) { - - if(data->state.resume_from && - (data->set.httpreq==HTTPREQ_GET) && - (k->httpcode == 416)) { - /* "Requested Range Not Satisfiable", just proceed and - pretend this is no error */ - } - else { - /* serious error, go home! */ - print_http_error(data); - return CURLE_HTTP_RETURNED_ERROR; - } - } - - if(conn->httpversion == 10) { - /* Default action for HTTP/1.0 must be to close, unless - we get one of those fancy headers that tell us the - server keeps it open for us! */ - infof(data, "HTTP 1.0, assume close after body\n"); - conn->bits.close = TRUE; - } - else if(conn->httpversion >= 11 && - !conn->bits.close) { - - /* If HTTP version is >= 1.1 and connection is persistent - server supports pipelining. */ - DEBUGF(infof(data, - "HTTP 1.1 or later with persistent connection, " - "pipelining supported\n")); - conn->server_supports_pipelining = TRUE; - } - - switch(k->httpcode) { - case 204: - /* (quote from RFC2616, section 10.2.5): The server has - * fulfilled the request but does not need to return an - * entity-body ... The 204 response MUST NOT include a - * message-body, and thus is always terminated by the first - * empty line after the header fields. */ - /* FALLTHROUGH */ - case 304: - /* (quote from RFC2616, section 10.3.5): The 304 response - * MUST NOT contain a message-body, and thus is always - * terminated by the first empty line after the header - * fields. */ - if(data->set.timecondition) - data->info.timecond = TRUE; - k->size=0; - k->maxdownload=0; - k->ignorecl = TRUE; /* ignore Content-Length headers */ - break; - default: - /* nothing */ - break; - } - } - else { - k->header = FALSE; /* this is not a header line */ - break; - } - } - - result = Curl_convert_from_network(data, k->p, strlen(k->p)); - /* Curl_convert_from_network calls failf if unsuccessful */ - if(result) - return result; - - /* Check for Content-Length: header lines to get size */ - if(!k->ignorecl && !data->set.ignorecl && - checkprefix("Content-Length:", k->p)) { - curl_off_t contentlength = curlx_strtoofft(k->p+15, NULL, 10); - if(data->set.max_filesize && - contentlength > data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - if(contentlength >= 0) { - k->size = contentlength; - k->maxdownload = k->size; - /* we set the progress download size already at this point - just to make it easier for apps/callbacks to extract this - info as soon as possible */ - Curl_pgrsSetDownloadSize(data, k->size); - } - else { - /* Negative Content-Length is really odd, and we know it - happens for example when older Apache servers send large - files */ - conn->bits.close = TRUE; - infof(data, "Negative content-length: %" FORMAT_OFF_T - ", closing after transfer\n", contentlength); - } - } - /* check for Content-Type: header lines to get the MIME-type */ - else if(checkprefix("Content-Type:", k->p)) { - char *contenttype = copy_header_value(k->p); - if(!contenttype) - return CURLE_OUT_OF_MEMORY; - if(!*contenttype) - /* ignore empty data */ - free(contenttype); - else { - Curl_safefree(data->info.contenttype); - data->info.contenttype = contenttype; - } - } - else if((conn->httpversion == 10) && - conn->bits.httpproxy && - Curl_compareheader(k->p, - "Proxy-Connection:", "keep-alive")) { - /* - * When a HTTP/1.0 reply comes when using a proxy, the - * 'Proxy-Connection: keep-alive' line tells us the - * connection will be kept alive for our pleasure. - * Default action for 1.0 is to close. - */ - conn->bits.close = FALSE; /* don't close when done */ - infof(data, "HTTP/1.0 proxy connection set to keep alive!\n"); - } - else if((conn->httpversion == 11) && - conn->bits.httpproxy && - Curl_compareheader(k->p, - "Proxy-Connection:", "close")) { - /* - * We get a HTTP/1.1 response from a proxy and it says it'll - * close down after this transfer. - */ - conn->bits.close = TRUE; /* close when done */ - infof(data, "HTTP/1.1 proxy connection set close!\n"); - } - else if((conn->httpversion == 10) && - Curl_compareheader(k->p, "Connection:", "keep-alive")) { - /* - * A HTTP/1.0 reply with the 'Connection: keep-alive' line - * tells us the connection will be kept alive for our - * pleasure. Default action for 1.0 is to close. - * - * [RFC2068, section 19.7.1] */ - conn->bits.close = FALSE; /* don't close when done */ - infof(data, "HTTP/1.0 connection set to keep alive!\n"); - } - else if(Curl_compareheader(k->p, "Connection:", "close")) { - /* - * [RFC 2616, section 8.1.2.1] - * "Connection: close" is HTTP/1.1 language and means that - * the connection will close when this request has been - * served. - */ - conn->bits.close = TRUE; /* close when done */ - } - else if(checkprefix("Transfer-Encoding:", k->p)) { - /* One or more encodings. We check for chunked and/or a compression - algorithm. */ - /* - * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding - * means that the server will send a series of "chunks". Each - * chunk starts with line with info (including size of the - * coming block) (terminated with CRLF), then a block of data - * with the previously mentioned size. There can be any amount - * of chunks, and a chunk-data set to zero signals the - * end-of-chunks. */ - - char *start; - - /* Find the first non-space letter */ - start = k->p + 18; - - for(;;) { - /* skip whitespaces and commas */ - while(*start && (ISSPACE(*start) || (*start == ','))) - start++; - - if(checkprefix("chunked", start)) { - k->chunk = TRUE; /* chunks coming our way */ - - /* init our chunky engine */ - Curl_httpchunk_init(conn); - - start += 7; - } - - if(k->auto_decoding) - /* TODO: we only support the first mentioned compression for now */ - break; - - if(checkprefix("identity", start)) { - k->auto_decoding = IDENTITY; - start += 8; - } - else if(checkprefix("deflate", start)) { - k->auto_decoding = DEFLATE; - start += 7; - } - else if(checkprefix("gzip", start)) { - k->auto_decoding = GZIP; - start += 4; - } - else if(checkprefix("x-gzip", start)) { - k->auto_decoding = GZIP; - start += 6; - } - else if(checkprefix("compress", start)) { - k->auto_decoding = COMPRESS; - start += 8; - } - else if(checkprefix("x-compress", start)) { - k->auto_decoding = COMPRESS; - start += 10; - } - else - /* unknown! */ - break; - - } - - } - else if(checkprefix("Content-Encoding:", k->p) && - data->set.str[STRING_ENCODING]) { - /* - * Process Content-Encoding. Look for the values: identity, - * gzip, deflate, compress, x-gzip and x-compress. x-gzip and - * x-compress are the same as gzip and compress. (Sec 3.5 RFC - * 2616). zlib cannot handle compress. However, errors are - * handled further down when the response body is processed - */ - char *start; - - /* Find the first non-space letter */ - start = k->p + 17; - while(*start && ISSPACE(*start)) - start++; - - /* Record the content-encoding for later use */ - if(checkprefix("identity", start)) - k->auto_decoding = IDENTITY; - else if(checkprefix("deflate", start)) - k->auto_decoding = DEFLATE; - else if(checkprefix("gzip", start) - || checkprefix("x-gzip", start)) - k->auto_decoding = GZIP; - else if(checkprefix("compress", start) - || checkprefix("x-compress", start)) - k->auto_decoding = COMPRESS; - } - else if(checkprefix("Content-Range:", k->p)) { - /* Content-Range: bytes [num]- - Content-Range: bytes: [num]- - Content-Range: [num]- - - The second format was added since Sun's webserver - JavaWebServer/1.1.1 obviously sends the header this way! - The third added since some servers use that! - */ - - char *ptr = k->p + 14; - - /* Move forward until first digit */ - while(*ptr && !ISDIGIT(*ptr)) - ptr++; - - k->offset = curlx_strtoofft(ptr, NULL, 10); - - if(data->state.resume_from == k->offset) - /* we asked for a resume and we got it */ - k->content_range = TRUE; - } -#if !defined(CURL_DISABLE_COOKIES) - else if(data->cookies && - checkprefix("Set-Cookie:", k->p)) { - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, - CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_add(data, - data->cookies, TRUE, k->p+11, - /* If there is a custom-set Host: name, use it - here, or else use real peer host name. */ - conn->allocptr.cookiehost? - conn->allocptr.cookiehost:conn->host.name, - data->state.path); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } -#endif - else if(checkprefix("Last-Modified:", k->p) && - (data->set.timecondition || data->set.get_filetime) ) { - time_t secs=time(NULL); - k->timeofdoc = curl_getdate(k->p+strlen("Last-Modified:"), - &secs); - if(data->set.get_filetime) - data->info.filetime = (long)k->timeofdoc; - } - else if((checkprefix("WWW-Authenticate:", k->p) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", k->p) && - (407 == k->httpcode))) { - result = Curl_http_input_auth(conn, k->httpcode, k->p); - if(result) - return result; - } - else if((k->httpcode >= 300 && k->httpcode < 400) && - checkprefix("Location:", k->p) && - !data->req.location) { - /* this is the URL that the server advises us to use instead */ - char *location = copy_header_value(k->p); - if(!location) - return CURLE_OUT_OF_MEMORY; - if(!*location) - /* ignore empty data */ - free(location); - else { - data->req.location = location; - - if(data->set.http_follow_location) { - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->req.location); /* clone */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - - /* some cases of POST and PUT etc needs to rewind the data - stream at this point */ - result = http_perhapsrewind(conn); - if(result) - return result; - } - } - } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - result = Curl_rtsp_parseheader(conn, k->p); - if(result) - return result; - } - - /* - * End of header-checks. Write them to the client. - */ - - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - - if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, - k->p, (size_t)k->hbuflen, conn); - - result = Curl_client_write(conn, writetype, k->p, k->hbuflen); - if(result) - return result; - - data->info.header_size += (long)k->hbuflen; - data->req.headerbytecount += (long)k->hbuflen; - - /* reset hbufp pointer && hbuflen */ - k->hbufp = data->state.headerbuff; - k->hbuflen = 0; - } - while(!*stop_reading && *k->str); /* header line within buffer */ - - /* We might have reached the end of the header part here, but - there might be a non-header part left in the end of the read - buffer. */ - - return CURLE_OK; -} - -#endif /* CURL_DISABLE_HTTP */ diff --git a/lib/http_chunks.c b/lib/http_chunks.c deleted file mode 100644 index 2112f72ec..000000000 --- a/lib/http_chunks.c +++ /dev/null @@ -1,397 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_HTTP - -#include "curl_urldata.h" /* it includes curl_http_chunks.h */ -#include "curl_sendf.h" /* for the client write stuff */ - -#include "curl_content_encoding.h" -#include "curl_http.h" -#include "curl_memory.h" -#include "curl_non_ascii.h" /* for Curl_convert_to_network prototype */ - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - * Chunk format (simplified): - * - * [ chunk extension ] CRLF - * CRLF - * - * Highlights from RFC2616 section 3.6 say: - - The chunked encoding modifies the body of a message in order to - transfer it as a series of chunks, each with its own size indicator, - followed by an OPTIONAL trailer containing entity-header fields. This - allows dynamically produced content to be transferred along with the - information necessary for the recipient to verify that it has - received the full message. - - Chunked-Body = *chunk - last-chunk - trailer - CRLF - - chunk = chunk-size [ chunk-extension ] CRLF - chunk-data CRLF - chunk-size = 1*HEX - last-chunk = 1*("0") [ chunk-extension ] CRLF - - chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) - chunk-ext-name = token - chunk-ext-val = token | quoted-string - chunk-data = chunk-size(OCTET) - trailer = *(entity-header CRLF) - - The chunk-size field is a string of hex digits indicating the size of - the chunk. The chunked encoding is ended by any chunk whose size is - zero, followed by the trailer, which is terminated by an empty line. - - */ - -/* Check for an ASCII hex digit. - We avoid the use of isxdigit to accommodate non-ASCII hosts. */ -static bool Curl_isxdigit(char digit) -{ - return ( (digit >= 0x30 && digit <= 0x39) /* 0-9 */ - || (digit >= 0x41 && digit <= 0x46) /* A-F */ - || (digit >= 0x61 && digit <= 0x66) /* a-f */ ) ? TRUE : FALSE; -} - -void Curl_httpchunk_init(struct connectdata *conn) -{ - struct Curl_chunker *chunk = &conn->chunk; - chunk->hexindex=0; /* start at 0 */ - chunk->dataleft=0; /* no data left yet! */ - chunk->state = CHUNK_HEX; /* we get hex first! */ -} - -/* - * chunk_read() returns a OK for normal operations, or a positive return code - * for errors. STOP means this sequence of chunks is complete. The 'wrote' - * argument is set to tell the caller how many bytes we actually passed to the - * client (for byte-counting and whatever). - * - * The states and the state-machine is further explained in the header file. - * - * This function always uses ASCII hex values to accommodate non-ASCII hosts. - * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. - */ -CHUNKcode Curl_httpchunk_read(struct connectdata *conn, - char *datap, - ssize_t datalen, - ssize_t *wrotep) -{ - CURLcode result=CURLE_OK; - struct SessionHandle *data = conn->data; - struct Curl_chunker *ch = &conn->chunk; - struct SingleRequest *k = &data->req; - size_t piece; - size_t length = (size_t)datalen; - size_t *wrote = (size_t *)wrotep; - - *wrote = 0; /* nothing's written yet */ - - /* the original data is written to the client, but we go on with the - chunk read process, to properly calculate the content length*/ - if(data->set.http_te_skip && !k->ignorebody) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, datap, datalen); - if(result) - return CHUNKE_WRITE_ERROR; - } - - while(length) { - switch(ch->state) { - case CHUNK_HEX: - if(Curl_isxdigit(*datap)) { - if(ch->hexindex < MAXNUM_SIZE) { - ch->hexbuffer[ch->hexindex] = *datap; - datap++; - length--; - ch->hexindex++; - } - else { - return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */ - } - } - else { - if(0 == ch->hexindex) { - /* This is illegal data, we received junk where we expected - a hexadecimal digit. */ - return CHUNKE_ILLEGAL_HEX; - } - /* length and datap are unmodified */ - ch->hexbuffer[ch->hexindex]=0; - - /* convert to host encoding before calling strtoul */ - result = Curl_convert_from_network(conn->data, ch->hexbuffer, - ch->hexindex); - if(result) { - /* Curl_convert_from_network calls failf if unsuccessful */ - /* Treat it as a bad hex character */ - return(CHUNKE_ILLEGAL_HEX); - } - - ch->datasize=strtoul(ch->hexbuffer, NULL, 16); - ch->state = CHUNK_POSTHEX; - } - break; - - case CHUNK_POSTHEX: - /* In this state, we're waiting for CRLF to arrive. We support - this to allow so called chunk-extensions to show up here - before the CRLF comes. */ - if(*datap == 0x0d) - ch->state = CHUNK_CR; - length--; - datap++; - break; - - case CHUNK_CR: - /* waiting for the LF */ - if(*datap == 0x0a) { - /* we're now expecting data to come, unless size was zero! */ - if(0 == ch->datasize) { - ch->state = CHUNK_TRAILER; /* now check for trailers */ - conn->trlPos=0; - } - else { - ch->state = CHUNK_DATA; - } - } - else - /* previously we got a fake CR, go back to CR waiting! */ - ch->state = CHUNK_CR; - datap++; - length--; - break; - - case CHUNK_DATA: - /* we get pure and fine data - - We expect another 'datasize' of data. We have 'length' right now, - it can be more or less than 'datasize'. Get the smallest piece. - */ - piece = (ch->datasize >= length)?length:ch->datasize; - - /* Write the data portion available */ -#ifdef HAVE_LIBZ - switch (conn->data->set.http_ce_skip? - IDENTITY : data->req.auto_decoding) { - case IDENTITY: -#endif - if(!k->ignorebody) { - if(!data->set.http_te_skip) - result = Curl_client_write(conn, CLIENTWRITE_BODY, datap, - piece); - else - result = CURLE_OK; - } -#ifdef HAVE_LIBZ - break; - - case DEFLATE: - /* update data->req.keep.str to point to the chunk data. */ - data->req.str = datap; - result = Curl_unencode_deflate_write(conn, &data->req, - (ssize_t)piece); - break; - - case GZIP: - /* update data->req.keep.str to point to the chunk data. */ - data->req.str = datap; - result = Curl_unencode_gzip_write(conn, &data->req, - (ssize_t)piece); - break; - - case COMPRESS: - default: - failf (conn->data, - "Unrecognized content encoding type. " - "libcurl understands `identity', `deflate' and `gzip' " - "content encodings."); - return CHUNKE_BAD_ENCODING; - } -#endif - - if(result) - return CHUNKE_WRITE_ERROR; - - *wrote += piece; - - ch->datasize -= piece; /* decrease amount left to expect */ - datap += piece; /* move read pointer forward */ - length -= piece; /* decrease space left in this round */ - - if(0 == ch->datasize) - /* end of data this round, we now expect a trailing CRLF */ - ch->state = CHUNK_POSTCR; - break; - - case CHUNK_POSTCR: - if(*datap == 0x0d) { - ch->state = CHUNK_POSTLF; - datap++; - length--; - } - else - return CHUNKE_BAD_CHUNK; - - break; - - case CHUNK_POSTLF: - if(*datap == 0x0a) { - /* - * The last one before we go back to hex state and start all - * over. - */ - Curl_httpchunk_init(conn); - datap++; - length--; - } - else - return CHUNKE_BAD_CHUNK; - - break; - - case CHUNK_TRAILER: - if(*datap == 0x0d) { - /* this is the end of a trailer, but if the trailer was zero bytes - there was no trailer and we move on */ - - if(conn->trlPos) { - /* we allocate trailer with 3 bytes extra room to fit this */ - conn->trailer[conn->trlPos++]=0x0d; - conn->trailer[conn->trlPos++]=0x0a; - conn->trailer[conn->trlPos]=0; - - /* Convert to host encoding before calling Curl_client_write */ - result = Curl_convert_from_network(conn->data, conn->trailer, - conn->trlPos); - if(result) - /* Curl_convert_from_network calls failf if unsuccessful */ - /* Treat it as a bad chunk */ - return CHUNKE_BAD_CHUNK; - - if(!data->set.http_te_skip) { - result = Curl_client_write(conn, CLIENTWRITE_HEADER, - conn->trailer, conn->trlPos); - if(result) - return CHUNKE_WRITE_ERROR; - } - conn->trlPos=0; - ch->state = CHUNK_TRAILER_CR; - } - else { - /* no trailer, we're on the final CRLF pair */ - ch->state = CHUNK_TRAILER_POSTCR; - break; /* don't advance the pointer */ - } - } - else { - /* conn->trailer is assumed to be freed in curl_url.c on a - connection basis */ - if(conn->trlPos >= conn->trlMax) { - /* we always allocate three extra bytes, just because when the full - header has been received we append CRLF\0 */ - char *ptr; - if(conn->trlMax) { - conn->trlMax *= 2; - ptr = realloc(conn->trailer, conn->trlMax + 3); - } - else { - conn->trlMax=128; - ptr = malloc(conn->trlMax + 3); - } - if(!ptr) - return CHUNKE_OUT_OF_MEMORY; - conn->trailer = ptr; - } - conn->trailer[conn->trlPos++]=*datap; - } - datap++; - length--; - break; - - case CHUNK_TRAILER_CR: - if(*datap == 0x0a) { - ch->state = CHUNK_TRAILER_POSTCR; - datap++; - length--; - } - else - return CHUNKE_BAD_CHUNK; - break; - - case CHUNK_TRAILER_POSTCR: - /* We enter this state when a CR should arrive so we expect to - have to first pass a CR before we wait for LF */ - if(*datap != 0x0d) { - /* not a CR then it must be another header in the trailer */ - ch->state = CHUNK_TRAILER; - break; - } - datap++; - length--; - /* now wait for the final LF */ - ch->state = CHUNK_STOP; - break; - - case CHUNK_STOPCR: - /* Read the final CRLF that ends all chunk bodies */ - - if(*datap == 0x0d) { - ch->state = CHUNK_STOP; - datap++; - length--; - } - else - return CHUNKE_BAD_CHUNK; - break; - - case CHUNK_STOP: - if(*datap == 0x0a) { - length--; - - /* Record the length of any data left in the end of the buffer - even if there's no more chunks to read */ - - ch->dataleft = length; - return CHUNKE_STOP; /* return stop */ - } - else - return CHUNKE_BAD_CHUNK; - - default: - return CHUNKE_STATE_ERROR; - } - } - return CHUNKE_OK; -} -#endif /* CURL_DISABLE_HTTP */ diff --git a/lib/http_digest.c b/lib/http_digest.c deleted file mode 100644 index dae679903..000000000 --- a/lib/http_digest.c +++ /dev/null @@ -1,583 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_rawstr.h" -#include "curl_base64.h" -#include "curl_md5.h" -#include "curl_http_digest.h" -#include "curl_strtok.h" -#include "curl_url.h" -#include "curl_memory.h" -#include "curl_non_ascii.h" /* included for Curl_convert_... prototypes */ -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#define MAX_VALUE_LENGTH 256 -#define MAX_CONTENT_LENGTH 1024 - -static void digest_cleanup_one(struct digestdata *dig); - -/* - * Return 0 on success and then the buffers are filled in fine. - * - * Non-zero means failure to parse. - */ -static int get_pair(const char *str, char *value, char *content, - const char **endptr) -{ - int c; - bool starts_with_quote = FALSE; - bool escape = FALSE; - - for(c=MAX_VALUE_LENGTH-1; (*str && (*str != '=') && c--); ) - *value++ = *str++; - *value=0; - - if('=' != *str++) - /* eek, no match */ - return 1; - - if('\"' == *str) { - /* this starts with a quote so it must end with one as well! */ - str++; - starts_with_quote = TRUE; - } - - for(c=MAX_CONTENT_LENGTH-1; *str && c--; str++) { - switch(*str) { - case '\\': - if(!escape) { - /* possibly the start of an escaped quote */ - escape = TRUE; - *content++ = '\\'; /* even though this is an escape character, we still - store it as-is in the target buffer */ - continue; - } - break; - case ',': - if(!starts_with_quote) { - /* this signals the end of the content if we didn't get a starting - quote and then we do "sloppy" parsing */ - c=0; /* the end */ - continue; - } - break; - case '\r': - case '\n': - /* end of string */ - c=0; - continue; - case '\"': - if(!escape && starts_with_quote) { - /* end of string */ - c=0; - continue; - } - break; - } - escape = FALSE; - *content++ = *str; - } - *content=0; - - *endptr = str; - - return 0; /* all is fine! */ -} - -/* Test example headers: - -WWW-Authenticate: Digest realm="testrealm", nonce="1053604598" -Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" - -*/ - -CURLdigest Curl_input_digest(struct connectdata *conn, - bool proxy, - const char *header) /* rest of the *-authenticate: - header */ -{ - char *token = NULL; - char *tmp = NULL; - bool foundAuth = FALSE; - bool foundAuthInt = FALSE; - struct SessionHandle *data=conn->data; - bool before = FALSE; /* got a nonce before */ - struct digestdata *d; - - if(proxy) { - d = &data->state.proxydigest; - } - else { - d = &data->state.digest; - } - - /* skip initial whitespaces */ - while(*header && ISSPACE(*header)) - header++; - - if(checkprefix("Digest", header)) { - header += strlen("Digest"); - - /* If we already have received a nonce, keep that in mind */ - if(d->nonce) - before = TRUE; - - /* clear off any former leftovers and init to defaults */ - digest_cleanup_one(d); - - for(;;) { - char value[MAX_VALUE_LENGTH]; - char content[MAX_CONTENT_LENGTH]; - - while(*header && ISSPACE(*header)) - header++; - - /* extract a value=content pair */ - if(!get_pair(header, value, content, &header)) { - if(Curl_raw_equal(value, "nonce")) { - d->nonce = strdup(content); - if(!d->nonce) - return CURLDIGEST_NOMEM; - } - else if(Curl_raw_equal(value, "stale")) { - if(Curl_raw_equal(content, "true")) { - d->stale = TRUE; - d->nc = 1; /* we make a new nonce now */ - } - } - else if(Curl_raw_equal(value, "realm")) { - d->realm = strdup(content); - if(!d->realm) - return CURLDIGEST_NOMEM; - } - else if(Curl_raw_equal(value, "opaque")) { - d->opaque = strdup(content); - if(!d->opaque) - return CURLDIGEST_NOMEM; - } - else if(Curl_raw_equal(value, "qop")) { - char *tok_buf; - /* tokenize the list and choose auth if possible, use a temporary - clone of the buffer since strtok_r() ruins it */ - tmp = strdup(content); - if(!tmp) - return CURLDIGEST_NOMEM; - token = strtok_r(tmp, ",", &tok_buf); - while(token != NULL) { - if(Curl_raw_equal(token, "auth")) { - foundAuth = TRUE; - } - else if(Curl_raw_equal(token, "auth-int")) { - foundAuthInt = TRUE; - } - token = strtok_r(NULL, ",", &tok_buf); - } - free(tmp); - /*select only auth o auth-int. Otherwise, ignore*/ - if(foundAuth) { - d->qop = strdup("auth"); - if(!d->qop) - return CURLDIGEST_NOMEM; - } - else if(foundAuthInt) { - d->qop = strdup("auth-int"); - if(!d->qop) - return CURLDIGEST_NOMEM; - } - } - else if(Curl_raw_equal(value, "algorithm")) { - d->algorithm = strdup(content); - if(!d->algorithm) - return CURLDIGEST_NOMEM; - if(Curl_raw_equal(content, "MD5-sess")) - d->algo = CURLDIGESTALGO_MD5SESS; - else if(Curl_raw_equal(content, "MD5")) - d->algo = CURLDIGESTALGO_MD5; - else - return CURLDIGEST_BADALGO; - } - else { - /* unknown specifier, ignore it! */ - } - } - else - break; /* we're done here */ - - /* pass all additional spaces here */ - while(*header && ISSPACE(*header)) - header++; - if(',' == *header) - /* allow the list to be comma-separated */ - header++; - } - /* We had a nonce since before, and we got another one now without - 'stale=true'. This means we provided bad credentials in the previous - request */ - if(before && !d->stale) - return CURLDIGEST_BAD; - - /* We got this header without a nonce, that's a bad Digest line! */ - if(!d->nonce) - return CURLDIGEST_BAD; - } - else - /* else not a digest, get out */ - return CURLDIGEST_NONE; - - return CURLDIGEST_FINE; -} - -/* convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/ -static void md5_to_ascii(unsigned char *source, /* 16 bytes */ - unsigned char *dest) /* 33 bytes */ -{ - int i; - for(i=0; i<16; i++) - snprintf((char *)&dest[i*2], 3, "%02x", source[i]); -} - -CURLcode Curl_output_digest(struct connectdata *conn, - bool proxy, - const unsigned char *request, - const unsigned char *uripath) -{ - /* We have a Digest setup for this, use it! Now, to get all the details for - this sorted out, I must urge you dear friend to read up on the RFC2617 - section 3.2.2, */ - unsigned char md5buf[16]; /* 16 bytes/128 bits */ - unsigned char request_digest[33]; - unsigned char *md5this; - unsigned char *ha1; - unsigned char ha2[33];/* 32 digits and 1 zero byte */ - char cnoncebuf[33]; - char *cnonce = NULL; - size_t cnonce_sz = 0; - char *tmp = NULL; - struct timeval now; - - char **allocuserpwd; - const char *userp; - const char *passwdp; - struct auth *authp; - - struct SessionHandle *data = conn->data; - struct digestdata *d; - CURLcode rc; -/* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines. - It converts digest text to ASCII so the MD5 will be correct for - what ultimately goes over the network. -*/ -#define CURL_OUTPUT_DIGEST_CONV(a, b) \ - rc = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \ - if(rc != CURLE_OK) { \ - free(b); \ - return rc; \ - } - - if(proxy) { - d = &data->state.proxydigest; - allocuserpwd = &conn->allocptr.proxyuserpwd; - userp = conn->proxyuser; - passwdp = conn->proxypasswd; - authp = &data->state.authproxy; - } - else { - d = &data->state.digest; - allocuserpwd = &conn->allocptr.userpwd; - userp = conn->user; - passwdp = conn->passwd; - authp = &data->state.authhost; - } - - if(*allocuserpwd) { - Curl_safefree(*allocuserpwd); - *allocuserpwd = NULL; - } - - /* not set means empty */ - if(!userp) - userp=""; - - if(!passwdp) - passwdp=""; - - if(!d->nonce) { - authp->done = FALSE; - return CURLE_OK; - } - authp->done = TRUE; - - if(!d->nc) - d->nc = 1; - - if(!d->cnonce) { - /* Generate a cnonce */ - now = Curl_tvnow(); - snprintf(cnoncebuf, sizeof(cnoncebuf), "%32ld", - (long)now.tv_sec + now.tv_usec); - - rc = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), - &cnonce, &cnonce_sz); - if(rc) - return rc; - d->cnonce = cnonce; - } - - /* - if the algorithm is "MD5" or unspecified (which then defaults to MD5): - - A1 = unq(username-value) ":" unq(realm-value) ":" passwd - - if the algorithm is "MD5-sess" then: - - A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) - ":" unq(nonce-value) ":" unq(cnonce-value) - */ - - md5this = (unsigned char *) - aprintf("%s:%s:%s", userp, d->realm, passwdp); - if(!md5this) - return CURLE_OUT_OF_MEMORY; - - CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ - Curl_md5it(md5buf, md5this); - free(md5this); /* free this again */ - - ha1 = malloc(33); /* 32 digits and 1 zero byte */ - if(!ha1) - return CURLE_OUT_OF_MEMORY; - - md5_to_ascii(md5buf, ha1); - - if(d->algo == CURLDIGESTALGO_MD5SESS) { - /* nonce and cnonce are OUTSIDE the hash */ - tmp = aprintf("%s:%s:%s", ha1, d->nonce, d->cnonce); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */ - Curl_md5it(md5buf, (unsigned char *)tmp); - free(tmp); /* free this again */ - md5_to_ascii(md5buf, ha1); - } - - /* - If the "qop" directive's value is "auth" or is unspecified, then A2 is: - - A2 = Method ":" digest-uri-value - - If the "qop" value is "auth-int", then A2 is: - - A2 = Method ":" digest-uri-value ":" H(entity-body) - - (The "Method" value is the HTTP request method as specified in section - 5.1.1 of RFC 2616) - */ - - /* So IE browsers < v7 cut off the URI part at the query part when they - evaluate the MD5 and some (IIS?) servers work with them so we may need to - do the Digest IE-style. Note that the different ways cause different MD5 - sums to get sent. - - Apache servers can be set to do the Digest IE-style automatically using - the BrowserMatch feature: - http://httpd.apache.org/docs/2.2/mod/mod_auth_digest.html#msie - - Further details on Digest implementation differences: - http://www.fngtps.com/2006/09/http-authentication - */ - if(authp->iestyle && ((tmp = strchr((char *)uripath, '?')) != NULL)) { - md5this = (unsigned char *)aprintf("%s:%.*s", request, - curlx_sztosi(tmp - (char *)uripath), - uripath); - } - else - md5this = (unsigned char *)aprintf("%s:%s", request, uripath); - - if(!md5this) { - free(ha1); - return CURLE_OUT_OF_MEMORY; - } - - if(d->qop && Curl_raw_equal(d->qop, "auth-int")) { - /* We don't support auth-int at the moment. I can't see a easy way to get - entity-body here */ - /* TODO: Append H(entity-body)*/ - } - CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ - Curl_md5it(md5buf, md5this); - free(md5this); /* free this again */ - md5_to_ascii(md5buf, ha2); - - if(d->qop) { - md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s", - ha1, - d->nonce, - d->nc, - d->cnonce, - d->qop, - ha2); - } - else { - md5this = (unsigned char *)aprintf("%s:%s:%s", - ha1, - d->nonce, - ha2); - } - free(ha1); - if(!md5this) - return CURLE_OUT_OF_MEMORY; - - CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ - Curl_md5it(md5buf, md5this); - free(md5this); /* free this again */ - md5_to_ascii(md5buf, request_digest); - - /* for test case 64 (snooped from a Mozilla 1.3a request) - - Authorization: Digest username="testuser", realm="testrealm", \ - nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca" - */ - - if(d->qop) { - *allocuserpwd = - aprintf( "%sAuthorization: Digest " - "username=\"%s\", " - "realm=\"%s\", " - "nonce=\"%s\", " - "uri=\"%s\", " - "cnonce=\"%s\", " - "nc=%08x, " - "qop=%s, " - "response=\"%s\"", - proxy?"Proxy-":"", - userp, - d->realm, - d->nonce, - uripath, /* this is the PATH part of the URL */ - d->cnonce, - d->nc, - d->qop, - request_digest); - - if(Curl_raw_equal(d->qop, "auth")) - d->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 padded - which tells to the server how many times you are using the - same nonce in the qop=auth mode. */ - } - else { - *allocuserpwd = - aprintf( "%sAuthorization: Digest " - "username=\"%s\", " - "realm=\"%s\", " - "nonce=\"%s\", " - "uri=\"%s\", " - "response=\"%s\"", - proxy?"Proxy-":"", - userp, - d->realm, - d->nonce, - uripath, /* this is the PATH part of the URL */ - request_digest); - } - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - - /* Add optional fields */ - if(d->opaque) { - /* append opaque */ - tmp = aprintf("%s, opaque=\"%s\"", *allocuserpwd, d->opaque); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - free(*allocuserpwd); - *allocuserpwd = tmp; - } - - if(d->algorithm) { - /* append algorithm */ - tmp = aprintf("%s, algorithm=\"%s\"", *allocuserpwd, d->algorithm); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - free(*allocuserpwd); - *allocuserpwd = tmp; - } - - /* append CRLF + zero (3 bytes) to the userpwd header */ - tmp = realloc(*allocuserpwd, strlen(*allocuserpwd) + 3); - if(!tmp) - return CURLE_OUT_OF_MEMORY; - strcat(tmp, "\r\n"); - *allocuserpwd = tmp; - - return CURLE_OK; -} - -static void digest_cleanup_one(struct digestdata *d) -{ - if(d->nonce) - free(d->nonce); - d->nonce = NULL; - - if(d->cnonce) - free(d->cnonce); - d->cnonce = NULL; - - if(d->realm) - free(d->realm); - d->realm = NULL; - - if(d->opaque) - free(d->opaque); - d->opaque = NULL; - - if(d->qop) - free(d->qop); - d->qop = NULL; - - if(d->algorithm) - free(d->algorithm); - d->algorithm = NULL; - - d->nc = 0; - d->algo = CURLDIGESTALGO_MD5; /* default algorithm */ - d->stale = FALSE; /* default means normal, not stale */ -} - - -void Curl_digest_cleanup(struct SessionHandle *data) -{ - digest_cleanup_one(&data->state.digest); - digest_cleanup_one(&data->state.proxydigest); -} - -#endif diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c deleted file mode 100644 index 446c49bb9..000000000 --- a/lib/http_negotiate.c +++ /dev/null @@ -1,373 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_GSSAPI -#ifdef HAVE_OLD_GSSMIT -#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name -#define NCOMPAT 1 -#endif - -#ifndef CURL_DISABLE_HTTP - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_gssapi.h" -#include "curl_rawstr.h" -#include "curl_base64.h" -#include "curl_http_negotiate.h" -#include "curl_memory.h" -#include "curl_url.h" - -#ifdef HAVE_SPNEGO -# include -# ifdef USE_SSLEAY -# ifdef USE_OPENSSL -# include -# else -# include -# endif -# else -# error "Can't compile SPNEGO support without OpenSSL." -# endif -#endif - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -static int -get_gss_name(struct connectdata *conn, bool proxy, gss_name_t *server) -{ - struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: - &conn->data->state.negotiate; - OM_uint32 major_status, minor_status; - gss_buffer_desc token = GSS_C_EMPTY_BUFFER; - char name[2048]; - const char* service; - - /* GSSAPI implementation by Globus (known as GSI) requires the name to be - of form "/" instead of @ (ie. slash instead - of at-sign). Also GSI servers are often identified as 'host' not 'khttp'. - Change following lines if you want to use GSI */ - - /* IIS uses the @ form but uses 'http' as the service name */ - - if(neg_ctx->gss) - service = "KHTTP"; - else - service = "HTTP"; - - token.length = strlen(service) + 1 + strlen(proxy ? conn->proxy.name : - conn->host.name) + 1; - if(token.length + 1 > sizeof(name)) - return EMSGSIZE; - - snprintf(name, sizeof(name), "%s@%s", service, proxy ? conn->proxy.name : - conn->host.name); - - token.value = (void *) name; - major_status = gss_import_name(&minor_status, - &token, - GSS_C_NT_HOSTBASED_SERVICE, - server); - - return GSS_ERROR(major_status) ? -1 : 0; -} - -static void -log_gss_error(struct connectdata *conn, OM_uint32 error_status, - const char *prefix) -{ - OM_uint32 maj_stat, min_stat; - OM_uint32 msg_ctx = 0; - gss_buffer_desc status_string; - char buf[1024]; - size_t len; - - snprintf(buf, sizeof(buf), "%s", prefix); - len = strlen(buf); - do { - maj_stat = gss_display_status(&min_stat, - error_status, - GSS_C_MECH_CODE, - GSS_C_NO_OID, - &msg_ctx, - &status_string); - if(sizeof(buf) > len + status_string.length + 1) { - snprintf(buf + len, sizeof(buf) - len, - ": %s", (char*) status_string.value); - len += status_string.length; - } - gss_release_buffer(&min_stat, &status_string); - } while(!GSS_ERROR(maj_stat) && msg_ctx != 0); - - infof(conn->data, "%s\n", buf); -} - -/* returning zero (0) means success, everything else is treated as "failure" - with no care exactly what the failure was */ -int Curl_input_negotiate(struct connectdata *conn, bool proxy, - const char *header) -{ - struct SessionHandle *data = conn->data; - struct negotiatedata *neg_ctx = proxy?&data->state.proxyneg: - &data->state.negotiate; - OM_uint32 major_status, minor_status, minor_status2; - gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; - gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; - int ret; - size_t len; - size_t rawlen = 0; - bool gss; - const char* protocol; - CURLcode error; - - while(*header && ISSPACE(*header)) - header++; - if(checkprefix("GSS-Negotiate", header)) { - protocol = "GSS-Negotiate"; - gss = TRUE; - } - else if(checkprefix("Negotiate", header)) { - protocol = "Negotiate"; - gss = FALSE; - } - else - return -1; - - if(neg_ctx->context) { - if(neg_ctx->gss != gss) { - return -1; - } - } - else { - neg_ctx->protocol = protocol; - neg_ctx->gss = gss; - } - - if(neg_ctx->context && neg_ctx->status == GSS_S_COMPLETE) { - /* We finished successfully our part of authentication, but server - * rejected it (since we're again here). Exit with an error since we - * can't invent anything better */ - Curl_cleanup_negotiate(data); - return -1; - } - - if(neg_ctx->server_name == NULL && - (ret = get_gss_name(conn, proxy, &neg_ctx->server_name))) - return ret; - - header += strlen(neg_ctx->protocol); - while(*header && ISSPACE(*header)) - header++; - - len = strlen(header); - if(len > 0) { - error = Curl_base64_decode(header, - (unsigned char **)&input_token.value, &rawlen); - if(error || rawlen == 0) - return -1; - input_token.length = rawlen; - -#ifdef HAVE_SPNEGO /* Handle SPNEGO */ - if(checkprefix("Negotiate", header)) { - ASN1_OBJECT * object = NULL; - unsigned char * spnegoToken = NULL; - size_t spnegoTokenLength = 0; - unsigned char * mechToken = NULL; - size_t mechTokenLength = 0; - - if(input_token.value == NULL) - return CURLE_OUT_OF_MEMORY; - - spnegoToken = malloc(input_token.length); - if(spnegoToken == NULL) - return CURLE_OUT_OF_MEMORY; - - spnegoTokenLength = input_token.length; - - object = OBJ_txt2obj ("1.2.840.113554.1.2.2", 1); - if(!parseSpnegoTargetToken(spnegoToken, - spnegoTokenLength, - NULL, - NULL, - &mechToken, - &mechTokenLength, - NULL, - NULL)) { - free(spnegoToken); - spnegoToken = NULL; - infof(data, "Parse SPNEGO Target Token failed\n"); - } - else { - free(input_token.value); - input_token.value = malloc(mechTokenLength); - if(input_token.value == NULL) - return CURLE_OUT_OF_MEMORY; - - memcpy(input_token.value, mechToken,mechTokenLength); - input_token.length = mechTokenLength; - free(mechToken); - mechToken = NULL; - infof(data, "Parse SPNEGO Target Token succeeded\n"); - } - } -#endif - } - - major_status = Curl_gss_init_sec_context(data, - &minor_status, - &neg_ctx->context, - neg_ctx->server_name, - GSS_C_NO_CHANNEL_BINDINGS, - &input_token, - &output_token, - NULL); - if(input_token.length > 0) - gss_release_buffer(&minor_status2, &input_token); - neg_ctx->status = major_status; - if(GSS_ERROR(major_status)) { - /* Curl_cleanup_negotiate(data) ??? */ - log_gss_error(conn, minor_status, - "gss_init_sec_context() failed: "); - return -1; - } - - if(output_token.length == 0) { - return -1; - } - - neg_ctx->output_token = output_token; - /* conn->bits.close = FALSE; */ - - return 0; -} - - -CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy) -{ - struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: - &conn->data->state.negotiate; - char *encoded = NULL; - size_t len = 0; - char *userp; - CURLcode error; - -#ifdef HAVE_SPNEGO /* Handle SPNEGO */ - if(checkprefix("Negotiate", neg_ctx->protocol)) { - ASN1_OBJECT * object = NULL; - unsigned char * spnegoToken = NULL; - size_t spnegoTokenLength = 0; - unsigned char * responseToken = NULL; - size_t responseTokenLength = 0; - - responseToken = malloc(neg_ctx->output_token.length); - if(responseToken == NULL) - return CURLE_OUT_OF_MEMORY; - memcpy(responseToken, neg_ctx->output_token.value, - neg_ctx->output_token.length); - responseTokenLength = neg_ctx->output_token.length; - - object=OBJ_txt2obj ("1.2.840.113554.1.2.2", 1); - if(!makeSpnegoInitialToken (object, - responseToken, - responseTokenLength, - &spnegoToken, - &spnegoTokenLength)) { - free(responseToken); - responseToken = NULL; - infof(conn->data, "Make SPNEGO Initial Token failed\n"); - } - else { - free(responseToken); - responseToken = NULL; - free(neg_ctx->output_token.value); - neg_ctx->output_token.value = malloc(spnegoTokenLength); - if(neg_ctx->output_token.value == NULL) { - free(spnegoToken); - spnegoToken = NULL; - return CURLE_OUT_OF_MEMORY; - } - memcpy(neg_ctx->output_token.value, spnegoToken,spnegoTokenLength); - neg_ctx->output_token.length = spnegoTokenLength; - free(spnegoToken); - spnegoToken = NULL; - infof(conn->data, "Make SPNEGO Initial Token succeeded\n"); - } - } -#endif - error = Curl_base64_encode(conn->data, - neg_ctx->output_token.value, - neg_ctx->output_token.length, - &encoded, &len); - if(error) { - Curl_safefree(neg_ctx->output_token.value); - neg_ctx->output_token.value = NULL; - return error; - } - - if(len == 0) { - Curl_safefree(neg_ctx->output_token.value); - neg_ctx->output_token.value = NULL; - return CURLE_REMOTE_ACCESS_DENIED; - } - - userp = aprintf("%sAuthorization: %s %s\r\n", proxy ? "Proxy-" : "", - neg_ctx->protocol, encoded); - - if(proxy) - conn->allocptr.proxyuserpwd = userp; - else - conn->allocptr.userpwd = userp; - free(encoded); - Curl_cleanup_negotiate (conn->data); - return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK; -} - -static void cleanup(struct negotiatedata *neg_ctx) -{ - OM_uint32 minor_status; - if(neg_ctx->context != GSS_C_NO_CONTEXT) - gss_delete_sec_context(&minor_status, &neg_ctx->context, GSS_C_NO_BUFFER); - - if(neg_ctx->output_token.length != 0) - gss_release_buffer(&minor_status, &neg_ctx->output_token); - - if(neg_ctx->server_name != GSS_C_NO_NAME) - gss_release_name(&minor_status, &neg_ctx->server_name); - - memset(neg_ctx, 0, sizeof(*neg_ctx)); -} - -void Curl_cleanup_negotiate(struct SessionHandle *data) -{ - cleanup(&data->state.negotiate); - cleanup(&data->state.proxyneg); -} - - -#endif -#endif diff --git a/lib/http_negotiate_sspi.c b/lib/http_negotiate_sspi.c deleted file mode 100644 index d82b27110..000000000 --- a/lib/http_negotiate_sspi.c +++ /dev/null @@ -1,308 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef USE_WINDOWS_SSPI - -#ifndef CURL_DISABLE_HTTP - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_rawstr.h" -#include "curl_warnless.h" -#include "curl_base64.h" -#include "curl_http_negotiate.h" -#include "curl_memory.h" -#include "curl_multibyte.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -static int -get_gss_name(struct connectdata *conn, bool proxy, - struct negotiatedata *neg_ctx) -{ - const char* service; - size_t length; - - if(proxy && !conn->proxy.name) - /* proxy auth requested but no given proxy name, error out! */ - return -1; - - /* GSSAPI implementation by Globus (known as GSI) requires the name to be - of form "/" instead of @ (ie. slash instead - of at-sign). Also GSI servers are often identified as 'host' not 'khttp'. - Change following lines if you want to use GSI */ - - /* IIS uses the @ form but uses 'http' as the service name, - and SSPI then generates an NTLM token. When using / a - Kerberos token is generated. */ - - if(neg_ctx->gss) - service = "KHTTP"; - else - service = "HTTP"; - - length = strlen(service) + 1 + strlen(proxy ? conn->proxy.name : - conn->host.name) + 1; - if(length + 1 > sizeof(neg_ctx->server_name)) - return EMSGSIZE; - - snprintf(neg_ctx->server_name, sizeof(neg_ctx->server_name), "%s/%s", - service, proxy ? conn->proxy.name : conn->host.name); - - return 0; -} - -/* returning zero (0) means success, everything else is treated as "failure" - with no care exactly what the failure was */ -int Curl_input_negotiate(struct connectdata *conn, bool proxy, - const char *header) -{ - struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: - &conn->data->state.negotiate; - BYTE *input_token = 0; - SecBufferDesc out_buff_desc; - SecBuffer out_sec_buff; - SecBufferDesc in_buff_desc; - SecBuffer in_sec_buff; - unsigned long context_attributes; - TimeStamp lifetime; - TCHAR *sname; - int ret; - size_t len = 0, input_token_len = 0; - bool gss = FALSE; - const char* protocol; - CURLcode error; - - while(*header && ISSPACE(*header)) - header++; - - if(checkprefix("GSS-Negotiate", header)) { - protocol = "GSS-Negotiate"; - gss = TRUE; - } - else if(checkprefix("Negotiate", header)) { - protocol = "Negotiate"; - gss = FALSE; - } - else - return -1; - - if(neg_ctx->context) { - if(neg_ctx->gss != gss) { - return -1; - } - } - else { - neg_ctx->protocol = protocol; - neg_ctx->gss = gss; - } - - if(neg_ctx->context && neg_ctx->status == SEC_E_OK) { - /* We finished successfully our part of authentication, but server - * rejected it (since we're again here). Exit with an error since we - * can't invent anything better */ - Curl_cleanup_negotiate(conn->data); - return -1; - } - - if(0 == strlen(neg_ctx->server_name)) { - ret = get_gss_name(conn, proxy, neg_ctx); - if(ret) - return ret; - } - - if(!neg_ctx->output_token) { - PSecPkgInfo SecurityPackage; - ret = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT("Negotiate"), - &SecurityPackage); - if(ret != SEC_E_OK) - return -1; - - /* Allocate input and output buffers according to the max token size - as indicated by the security package */ - neg_ctx->max_token_length = SecurityPackage->cbMaxToken; - neg_ctx->output_token = malloc(neg_ctx->max_token_length); - s_pSecFn->FreeContextBuffer(SecurityPackage); - } - - /* Obtain the input token, if any */ - header += strlen(neg_ctx->protocol); - while(*header && ISSPACE(*header)) - header++; - - len = strlen(header); - if(!len) { - /* first call in a new negotation, we have to acquire credentials, - and allocate memory for the context */ - - neg_ctx->credentials = malloc(sizeof(CredHandle)); - neg_ctx->context = malloc(sizeof(CtxtHandle)); - - if(!neg_ctx->credentials || !neg_ctx->context) - return -1; - - neg_ctx->status = - s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT("Negotiate"), - SECPKG_CRED_OUTBOUND, NULL, NULL, - NULL, NULL, neg_ctx->credentials, - &lifetime); - if(neg_ctx->status != SEC_E_OK) - return -1; - } - else { - input_token = malloc(neg_ctx->max_token_length); - if(!input_token) - return -1; - - error = Curl_base64_decode(header, - (unsigned char **)&input_token, - &input_token_len); - if(error || input_token_len == 0) - return -1; - } - - /* prepare the output buffers, and input buffers if present */ - out_buff_desc.ulVersion = 0; - out_buff_desc.cBuffers = 1; - out_buff_desc.pBuffers = &out_sec_buff; - - out_sec_buff.cbBuffer = curlx_uztoul(neg_ctx->max_token_length); - out_sec_buff.BufferType = SECBUFFER_TOKEN; - out_sec_buff.pvBuffer = neg_ctx->output_token; - - - if(input_token) { - in_buff_desc.ulVersion = 0; - in_buff_desc.cBuffers = 1; - in_buff_desc.pBuffers = &in_sec_buff; - - in_sec_buff.cbBuffer = curlx_uztoul(input_token_len); - in_sec_buff.BufferType = SECBUFFER_TOKEN; - in_sec_buff.pvBuffer = input_token; - } - - sname = Curl_convert_UTF8_to_tchar(neg_ctx->server_name); - if(!sname) - return CURLE_OUT_OF_MEMORY; - - neg_ctx->status = s_pSecFn->InitializeSecurityContext( - neg_ctx->credentials, - input_token ? neg_ctx->context : 0, - sname, - ISC_REQ_CONFIDENTIALITY, - 0, - SECURITY_NATIVE_DREP, - input_token ? &in_buff_desc : 0, - 0, - neg_ctx->context, - &out_buff_desc, - &context_attributes, - &lifetime); - - Curl_unicodefree(sname); - - if(GSS_ERROR(neg_ctx->status)) - return -1; - - if(neg_ctx->status == SEC_I_COMPLETE_NEEDED || - neg_ctx->status == SEC_I_COMPLETE_AND_CONTINUE) { - neg_ctx->status = s_pSecFn->CompleteAuthToken(neg_ctx->context, - &out_buff_desc); - if(GSS_ERROR(neg_ctx->status)) - return -1; - } - - neg_ctx->output_token_length = out_sec_buff.cbBuffer; - - return 0; -} - - -CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy) -{ - struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: - &conn->data->state.negotiate; - char *encoded = NULL; - size_t len = 0; - char *userp; - CURLcode error; - - error = Curl_base64_encode(conn->data, - (const char*)neg_ctx->output_token, - neg_ctx->output_token_length, - &encoded, &len); - if(error) - return error; - - if(len == 0) - return CURLE_REMOTE_ACCESS_DENIED; - - userp = aprintf("%sAuthorization: %s %s\r\n", proxy ? "Proxy-" : "", - neg_ctx->protocol, encoded); - - if(proxy) - conn->allocptr.proxyuserpwd = userp; - else - conn->allocptr.userpwd = userp; - free(encoded); - Curl_cleanup_negotiate (conn->data); - return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK; -} - -static void cleanup(struct negotiatedata *neg_ctx) -{ - if(neg_ctx->context) { - s_pSecFn->DeleteSecurityContext(neg_ctx->context); - free(neg_ctx->context); - neg_ctx->context = 0; - } - - if(neg_ctx->credentials) { - s_pSecFn->FreeCredentialsHandle(neg_ctx->credentials); - free(neg_ctx->credentials); - neg_ctx->credentials = 0; - } - - if(neg_ctx->output_token) { - free(neg_ctx->output_token); - neg_ctx->output_token = 0; - } - - neg_ctx->max_token_length = 0; -} - -void Curl_cleanup_negotiate(struct SessionHandle *data) -{ - cleanup(&data->state.negotiate); - cleanup(&data->state.proxyneg); -} - - -#endif -#endif diff --git a/lib/http_proxy.c b/lib/http_proxy.c deleted file mode 100644 index 14ef9dc1e..000000000 --- a/lib/http_proxy.c +++ /dev/null @@ -1,595 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) - -#include "curl_urldata.h" -#include -#include "curl_http_proxy.h" -#include "curl_sendf.h" -#include "curl_http.h" -#include "curl_url.h" -#include "curl_select.h" -#include "curl_rawstr.h" -#include "curl_progress.h" -#include "curl_non_ascii.h" -#include "curl_connect.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curlx.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -CURLcode Curl_proxy_connect(struct connectdata *conn) -{ - if(conn->bits.tunnel_proxy && conn->bits.httpproxy) { -#ifndef CURL_DISABLE_PROXY - /* for [protocol] tunneled through HTTP proxy */ - struct HTTP http_proxy; - void *prot_save; - CURLcode result; - - /* BLOCKING */ - /* We want "seamless" operations through HTTP proxy tunnel */ - - /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the - * member conn->proto.http; we want [protocol] through HTTP and we have - * to change the member temporarily for connecting to the HTTP - * proxy. After Curl_proxyCONNECT we have to set back the member to the - * original pointer - * - * This function might be called several times in the multi interface case - * if the proxy's CONNTECT response is not instant. - */ - prot_save = conn->data->state.proto.generic; - memset(&http_proxy, 0, sizeof(http_proxy)); - conn->data->state.proto.http = &http_proxy; - conn->bits.close = FALSE; - result = Curl_proxyCONNECT(conn, FIRSTSOCKET, - conn->host.name, conn->remote_port); - conn->data->state.proto.generic = prot_save; - if(CURLE_OK != result) - return result; -#else - return CURLE_NOT_BUILT_IN; -#endif - } - /* no HTTP tunnel proxy, just return */ - return CURLE_OK; -} - -/* - * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This - * function will issue the necessary commands to get a seamless tunnel through - * this proxy. After that, the socket can be used just as a normal socket. - * - * This badly needs to be rewritten. CONNECT should be sent and dealt with - * like any ordinary HTTP request, and not specially crafted like this. This - * function only remains here like this for now since the rewrite is a bit too - * much work to do at the moment. - * - * This function is BLOCKING which is nasty for all multi interface using apps. - */ - -CURLcode Curl_proxyCONNECT(struct connectdata *conn, - int sockindex, - const char *hostname, - unsigned short remote_port) -{ - int subversion=0; - struct SessionHandle *data=conn->data; - struct SingleRequest *k = &data->req; - CURLcode result; - long timeout = - data->set.timeout?data->set.timeout:PROXY_TIMEOUT; /* in milliseconds */ - curl_socket_t tunnelsocket = conn->sock[sockindex]; - curl_off_t cl=0; - bool closeConnection = FALSE; - bool chunked_encoding = FALSE; - long check; - -#define SELECT_OK 0 -#define SELECT_ERROR 1 -#define SELECT_TIMEOUT 2 - int error = SELECT_OK; - - if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE) - return CURLE_OK; /* CONNECT is already completed */ - - conn->bits.proxy_connect_closed = FALSE; - - do { - if(TUNNEL_INIT == conn->tunnel_state[sockindex]) { - /* BEGIN CONNECT PHASE */ - char *host_port; - Curl_send_buffer *req_buffer; - - infof(data, "Establish HTTP proxy tunnel to %s:%hu\n", - hostname, remote_port); - - if(data->req.newurl) { - /* This only happens if we've looped here due to authentication - reasons, and we don't really use the newly cloned URL here - then. Just free() it. */ - free(data->req.newurl); - data->req.newurl = NULL; - } - - /* initialize a dynamic send-buffer */ - req_buffer = Curl_add_buffer_init(); - - if(!req_buffer) - return CURLE_OUT_OF_MEMORY; - - host_port = aprintf("%s:%hu", hostname, remote_port); - if(!host_port) { - free(req_buffer); - return CURLE_OUT_OF_MEMORY; - } - - /* Setup the proxy-authorization header, if any */ - result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE); - - free(host_port); - - if(CURLE_OK == result) { - char *host=(char *)""; - const char *proxyconn=""; - const char *useragent=""; - const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ? - "1.0" : "1.1"; - char *hostheader= /* host:port with IPv6 support */ - aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"", - hostname, conn->bits.ipv6_ip?"]":"", - remote_port); - if(!hostheader) { - free(req_buffer); - return CURLE_OUT_OF_MEMORY; - } - - if(!Curl_checkheaders(data, "Host:")) { - host = aprintf("Host: %s\r\n", hostheader); - if(!host) { - free(hostheader); - free(req_buffer); - return CURLE_OUT_OF_MEMORY; - } - } - if(!Curl_checkheaders(data, "Proxy-Connection:")) - proxyconn = "Proxy-Connection: Keep-Alive\r\n"; - - if(!Curl_checkheaders(data, "User-Agent:") && - data->set.str[STRING_USERAGENT]) - useragent = conn->allocptr.uagent; - - result = - Curl_add_bufferf(req_buffer, - "CONNECT %s HTTP/%s\r\n" - "%s" /* Host: */ - "%s" /* Proxy-Authorization */ - "%s" /* User-Agent */ - "%s", /* Proxy-Connection */ - hostheader, - http, - host, - conn->allocptr.proxyuserpwd? - conn->allocptr.proxyuserpwd:"", - useragent, - proxyconn); - - if(host && *host) - free(host); - free(hostheader); - - if(CURLE_OK == result) - result = Curl_add_custom_headers(conn, req_buffer); - - if(CURLE_OK == result) - /* CRLF terminate the request */ - result = Curl_add_bufferf(req_buffer, "\r\n"); - - if(CURLE_OK == result) { - /* Send the connect request to the proxy */ - /* BLOCKING */ - result = - Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, sockindex); - } - req_buffer = NULL; - if(result) - failf(data, "Failed sending CONNECT to proxy"); - } - - Curl_safefree(req_buffer); - if(result) - return result; - - conn->tunnel_state[sockindex] = TUNNEL_CONNECT; - } /* END CONNECT PHASE */ - - /* now we've issued the CONNECT and we're waiting to hear back - - we try not to block here in multi-mode because that might be a LONG - wait if the proxy cannot connect-through to the remote host. */ - - /* if timeout is requested, find out how much remaining time we have */ - check = timeout - /* timeout time */ - Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ - if(check <= 0) { - failf(data, "Proxy CONNECT aborted due to timeout"); - return CURLE_RECV_ERROR; - } - - /* if we're in multi-mode and we would block, return instead for a retry */ - if(Curl_if_multi == data->state.used_interface) { - if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0)) - /* return so we'll be called again polling-style */ - return CURLE_OK; - else { - DEBUGF(infof(data, - "Multi mode finished polling for response from " - "proxy CONNECT\n")); - } - } - else { - DEBUGF(infof(data, "Easy mode waiting response from proxy CONNECT\n")); - } - - /* at this point, either: - 1) we're in easy-mode and so it's okay to block waiting for a CONNECT - response - 2) we're in multi-mode and we didn't block - it's either an error or we - now have some data waiting. - In any case, the tunnel_connecting phase is over. */ - - { /* BEGIN NEGOTIATION PHASE */ - size_t nread; /* total size read */ - int perline; /* count bytes per line */ - int keepon=TRUE; - ssize_t gotbytes; - char *ptr; - char *line_start; - - ptr=data->state.buffer; - line_start = ptr; - - nread=0; - perline=0; - keepon=TRUE; - - while((nreadnow); /* spent time */ - if(check <= 0) { - failf(data, "Proxy CONNECT aborted due to timeout"); - error = SELECT_TIMEOUT; /* already too little time */ - break; - } - - /* loop every second at least, less if the timeout is near */ - switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, - check<1000L?check:1000)) { - case -1: /* select() error, stop reading */ - error = SELECT_ERROR; - failf(data, "Proxy CONNECT aborted due to select/poll error"); - break; - case 0: /* timeout */ - break; - default: - DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1); - result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread, - &gotbytes); - if(result==CURLE_AGAIN) - continue; /* go loop yourself */ - else if(result) - keepon = FALSE; - else if(gotbytes <= 0) { - keepon = FALSE; - if(data->set.proxyauth && data->state.authproxy.avail) { - /* proxy auth was requested and there was proxy auth available, - then deem this as "mere" proxy disconnect */ - conn->bits.proxy_connect_closed = TRUE; - } - else { - error = SELECT_ERROR; - failf(data, "Proxy CONNECT aborted"); - } - } - else { - /* - * We got a whole chunk of data, which can be anything from one - * byte to a set of lines and possibly just a piece of the last - * line. - */ - int i; - - nread += gotbytes; - - if(keepon > TRUE) { - /* This means we are currently ignoring a response-body */ - - nread = 0; /* make next read start over in the read buffer */ - ptr=data->state.buffer; - if(cl) { - /* A Content-Length based body: simply count down the counter - and make sure to break out of the loop when we're done! */ - cl -= gotbytes; - if(cl<=0) { - keepon = FALSE; - break; - } - } - else { - /* chunked-encoded body, so we need to do the chunked dance - properly to know when the end of the body is reached */ - CHUNKcode r; - ssize_t tookcareof=0; - - /* now parse the chunked piece of data so that we can - properly tell when the stream ends */ - r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ - infof(data, "chunk reading DONE\n"); - keepon = FALSE; - /* we did the full CONNECT treatment, go COMPLETE */ - conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; - } - else - infof(data, "Read %zd bytes of chunk, continue\n", - tookcareof); - } - } - else - for(i = 0; i < gotbytes; ptr++, i++) { - perline++; /* amount of bytes in this line so far */ - if(*ptr == 0x0a) { - char letter; - int writetype; - - /* convert from the network encoding */ - result = Curl_convert_from_network(data, line_start, - perline); - /* Curl_convert_from_network calls failf if unsuccessful */ - if(result) - return result; - - /* output debug if that is requested */ - if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, - line_start, (size_t)perline, conn); - - /* send the header to the callback */ - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - - result = Curl_client_write(conn, writetype, line_start, - perline); - if(result) - return result; - - /* Newlines are CRLF, so the CR is ignored as the line isn't - really terminated until the LF comes. Treat a following CR - as end-of-headers as well.*/ - - if(('\r' == line_start[0]) || - ('\n' == line_start[0])) { - /* end of response-headers from the proxy */ - nread = 0; /* make next read start over in the read - buffer */ - ptr=data->state.buffer; - if((407 == k->httpcode) && !data->state.authproblem) { - /* If we get a 407 response code with content length - when we have no auth problem, we must ignore the - whole response-body */ - keepon = 2; - - if(cl) { - - infof(data, "Ignore %" FORMAT_OFF_T - " bytes of response-body\n", cl); - /* remove the remaining chunk of what we already - read */ - cl -= (gotbytes - i); - - if(cl<=0) - /* if the whole thing was already read, we are done! - */ - keepon=FALSE; - } - else if(chunked_encoding) { - CHUNKcode r; - /* We set ignorebody true here since the chunked - decoder function will acknowledge that. Pay - attention so that this is cleared again when this - function returns! */ - k->ignorebody = TRUE; - infof(data, "%zd bytes of chunk left\n", gotbytes-i); - - if(line_start[1] == '\n') { - /* this can only be a LF if the letter at index 0 - was a CR */ - line_start++; - i++; - } - - /* now parse the chunked piece of data so that we can - properly tell when the stream ends */ - r = Curl_httpchunk_read(conn, line_start+1, - gotbytes -i, &gotbytes); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ - infof(data, "chunk reading DONE\n"); - keepon = FALSE; - /* we did the full CONNECT treatment, go to - COMPLETE */ - conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; - } - else - infof(data, "Read %zd bytes of chunk, continue\n", - gotbytes); - } - else { - /* without content-length or chunked encoding, we - can't keep the connection alive since the close is - the end signal so we bail out at once instead */ - keepon=FALSE; - } - } - else { - keepon = FALSE; - if(200 == data->info.httpproxycode) { - if(gotbytes - (i+1)) - failf(data, "Proxy CONNECT followed by %zd bytes " - "of opaque data. Data ignored (known bug #39)", - gotbytes - (i+1)); - } - } - /* we did the full CONNECT treatment, go to COMPLETE */ - conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; - break; /* breaks out of for-loop, not switch() */ - } - - /* keep a backup of the position we are about to blank */ - letter = line_start[perline]; - line_start[perline]=0; /* zero terminate the buffer */ - if((checkprefix("WWW-Authenticate:", line_start) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", line_start) && - (407 == k->httpcode))) { - result = Curl_http_input_auth(conn, k->httpcode, - line_start); - if(result) - return result; - } - else if(checkprefix("Content-Length:", line_start)) { - cl = curlx_strtoofft(line_start + - strlen("Content-Length:"), NULL, 10); - } - else if(Curl_compareheader(line_start, - "Connection:", "close")) - closeConnection = TRUE; - else if(Curl_compareheader(line_start, - "Transfer-Encoding:", - "chunked")) { - infof(data, "CONNECT responded chunked\n"); - chunked_encoding = TRUE; - /* init our chunky engine */ - Curl_httpchunk_init(conn); - } - else if(Curl_compareheader(line_start, - "Proxy-Connection:", "close")) - closeConnection = TRUE; - else if(2 == sscanf(line_start, "HTTP/1.%d %d", - &subversion, - &k->httpcode)) { - /* store the HTTP code from the proxy */ - data->info.httpproxycode = k->httpcode; - } - /* put back the letter we blanked out before */ - line_start[perline]= letter; - - perline=0; /* line starts over here */ - line_start = ptr+1; /* this skips the zero byte we wrote */ - } - } - } - break; - } /* switch */ - if(Curl_pgrsUpdate(conn)) - return CURLE_ABORTED_BY_CALLBACK; - } /* while there's buffer left and loop is requested */ - - if(error) - return CURLE_RECV_ERROR; - - if(data->info.httpproxycode != 200) { - /* Deal with the possibly already received authenticate - headers. 'newurl' is set to a new URL if we must loop. */ - result = Curl_http_auth_act(conn); - if(result) - return result; - - if(conn->bits.close) - /* the connection has been marked for closure, most likely in the - Curl_http_auth_act() function and thus we can kill it at once - below - */ - closeConnection = TRUE; - } - - if(closeConnection && data->req.newurl) { - /* Connection closed by server. Don't use it anymore */ - Curl_closesocket(conn, conn->sock[sockindex]); - conn->sock[sockindex] = CURL_SOCKET_BAD; - break; - } - } /* END NEGOTIATION PHASE */ - - /* If we are supposed to continue and request a new URL, which basically - * means the HTTP authentication is still going on so if the tunnel - * is complete we start over in INIT state */ - if(data->req.newurl && - (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) { - conn->tunnel_state[sockindex] = TUNNEL_INIT; - infof(data, "TUNNEL_STATE switched to: %d\n", - conn->tunnel_state[sockindex]); - } - - } while(data->req.newurl); - - if(200 != data->req.httpcode) { - failf(data, "Received HTTP code %d from proxy after CONNECT", - data->req.httpcode); - - if(closeConnection && data->req.newurl) - conn->bits.proxy_connect_closed = TRUE; - - /* to back to init state */ - conn->tunnel_state[sockindex] = TUNNEL_INIT; - - return CURLE_RECV_ERROR; - } - - conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; - - /* If a proxy-authorization header was used for the proxy, then we should - make sure that it isn't accidentally used for the document request - after we've connected. So let's free and clear it here. */ - Curl_safefree(conn->allocptr.proxyuserpwd); - conn->allocptr.proxyuserpwd = NULL; - - data->state.authproxy.done = TRUE; - - infof (data, "Proxy replied OK to CONNECT request\n"); - data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */ - return CURLE_OK; -} -#endif /* CURL_DISABLE_PROXY */ diff --git a/lib/idn_win32.c b/lib/idn_win32.c deleted file mode 100644 index eca225483..000000000 --- a/lib/idn_win32.c +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - - /* - * IDN conversions using Windows kernel32 and normaliz libraries. - */ - -#include "curl_setup.h" - -#ifdef USE_WIN32_IDN - -#include "curl_multibyte.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifdef WANT_IDN_PROTOTYPES -WINBASEAPI int WINAPI IdnToAscii(DWORD, const WCHAR *, int, WCHAR *, int); -WINBASEAPI int WINAPI IdnToUnicode(DWORD, const WCHAR *, int, WCHAR *, int); -#endif - -#define IDN_MAX_LENGTH 255 - -int curl_win32_idn_to_ascii(const char *in, char **out); -int curl_win32_ascii_to_idn(const char *in, size_t in_len, char **out_utf8); - -int curl_win32_idn_to_ascii(const char *in, char **out) -{ - wchar_t *in_w = Curl_convert_UTF8_to_wchar(in); - if(in_w) { - wchar_t punycode[IDN_MAX_LENGTH]; - if(IdnToAscii(0, in_w, -1, punycode, IDN_MAX_LENGTH) == 0) { - wprintf(L"ERROR %d converting to Punycode\n", GetLastError()); - free(in_w); - return 0; - } - free(in_w); - - *out = Curl_convert_wchar_to_UTF8(punycode); - if(!*out) - return 0; - } - return 1; -} - -int curl_win32_ascii_to_idn(const char *in, size_t in_len, char **out_utf8) -{ - (void)in_len; /* unused */ - if(in) { - WCHAR unicode[IDN_MAX_LENGTH]; - - if(IdnToUnicode(0, (wchar_t *)in, -1, unicode, IDN_MAX_LENGTH) == 0) { - wprintf(L"ERROR %d converting to Punycode\n", GetLastError()); - return 0; - } - else { - *out_utf8 = Curl_convert_wchar_to_UTF8(unicode); - if(!*out_utf8) - return 0; - } - } - return 1; -} - -#endif /* USE_WIN32_IDN */ diff --git a/lib/if2ip.c b/lib/if2ip.c deleted file mode 100644 index db9c78446..000000000 --- a/lib/if2ip.c +++ /dev/null @@ -1,189 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -# include -#endif -#ifdef HAVE_ARPA_INET_H -# include -#endif -#ifdef HAVE_NET_IF_H -# include -#endif -#ifdef HAVE_SYS_IOCTL_H -# include -#endif -#ifdef HAVE_NETDB_H -# include -#endif -#ifdef HAVE_SYS_SOCKIO_H -# include -#endif -#ifdef HAVE_IFADDRS_H -# include -#endif -#ifdef HAVE_STROPTS_H -# include -#endif -#ifdef __VMS -# include -#endif - -#include "curl_inet_ntop.h" -#include "curl_strequal.h" -#include "curl_if2ip.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* ------------------------------------------------------------------ */ - -#if defined(HAVE_GETIFADDRS) - -bool Curl_if_is_interface_name(const char *interf) -{ - bool result = FALSE; - - struct ifaddrs *iface, *head; - - if(getifaddrs(&head) >= 0) { - for(iface=head; iface != NULL; iface=iface->ifa_next) { - if(curl_strequal(iface->ifa_name, interf)) { - result = TRUE; - break; - } - } - freeifaddrs(head); - } - return result; -} - -char *Curl_if2ip(int af, const char *interf, char *buf, int buf_size) -{ - struct ifaddrs *iface, *head; - char *ip = NULL; - - if(getifaddrs(&head) >= 0) { - for(iface=head; iface != NULL; iface=iface->ifa_next) { - if((iface->ifa_addr != NULL) && - (iface->ifa_addr->sa_family == af) && - curl_strequal(iface->ifa_name, interf)) { - void *addr; - char scope[12]=""; -#ifdef ENABLE_IPV6 - if(af == AF_INET6) { - unsigned int scopeid = 0; - addr = &((struct sockaddr_in6 *)iface->ifa_addr)->sin6_addr; -#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID - /* Include the scope of this interface as part of the address */ - scopeid = ((struct sockaddr_in6 *)iface->ifa_addr)->sin6_scope_id; -#endif - if(scopeid) - snprintf(scope, sizeof(scope), "%%%u", scopeid); - } - else -#endif - addr = &((struct sockaddr_in *)iface->ifa_addr)->sin_addr; - ip = (char *) Curl_inet_ntop(af, addr, buf, buf_size); - strlcat(buf, scope, buf_size); - break; - } - } - freeifaddrs(head); - } - return ip; -} - -#elif defined(HAVE_IOCTL_SIOCGIFADDR) - -bool Curl_if_is_interface_name(const char *interf) -{ - /* This is here just to support the old interfaces */ - char buf[256]; - - char *ip = Curl_if2ip(AF_INET, interf, buf, sizeof(buf)); - - return (ip != NULL) ? TRUE : FALSE; -} - -char *Curl_if2ip(int af, const char *interf, char *buf, int buf_size) -{ - struct ifreq req; - struct in_addr in; - struct sockaddr_in *s; - curl_socket_t dummy; - size_t len; - char *ip; - - if(!interf || (af != AF_INET)) - return NULL; - - len = strlen(interf); - if(len >= sizeof(req.ifr_name)) - return NULL; - - dummy = socket(AF_INET, SOCK_STREAM, 0); - if(CURL_SOCKET_BAD == dummy) - return NULL; - - memset(&req, 0, sizeof(req)); - memcpy(req.ifr_name, interf, len+1); - req.ifr_addr.sa_family = AF_INET; - - if(ioctl(dummy, SIOCGIFADDR, &req) < 0) { - sclose(dummy); - return NULL; - } - - s = (struct sockaddr_in *)&req.ifr_addr; - memcpy(&in, &s->sin_addr, sizeof(in)); - ip = (char *) Curl_inet_ntop(s->sin_family, &in, buf, buf_size); - - sclose(dummy); - return ip; -} - -#else - -bool Curl_if_is_interface_name(const char *interf) -{ - (void) interf; - - return FALSE; -} - -char *Curl_if2ip(int af, const char *interf, char *buf, int buf_size) -{ - (void) af; - (void) interf; - (void) buf; - (void) buf_size; - return NULL; -} - -#endif diff --git a/lib/imap.c b/lib/imap.c deleted file mode 100644 index 8175daa1a..000000000 --- a/lib/imap.c +++ /dev/null @@ -1,1139 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - * RFC3501 IMAPv4 protocol - * RFC5092 IMAP URL Scheme - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_IMAP - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_UTSNAME_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -#include -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_if2ip.h" -#include "curl_hostip.h" -#include "curl_progress.h" -#include "curl_transfer.h" -#include "curl_escape.h" -#include "curl_http.h" /* for HTTP proxy tunnel stuff */ -#include "curl_socks.h" -#include "curl_imap.h" - -#include "curl_strtoofft.h" -#include "curl_strequal.h" -#include "curl_sslgen.h" -#include "curl_connect.h" -#include "curl_strerror.h" -#include "curl_select.h" -#include "curl_multiif.h" -#include "curl_url.h" -#include "curl_rawstr.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* Local API functions */ -static CURLcode imap_parse_url_path(struct connectdata *conn); -static CURLcode imap_regular_transfer(struct connectdata *conn, bool *done); -static CURLcode imap_do(struct connectdata *conn, bool *done); -static CURLcode imap_done(struct connectdata *conn, CURLcode status, - bool premature); -static CURLcode imap_connect(struct connectdata *conn, bool *done); -static CURLcode imap_disconnect(struct connectdata *conn, bool dead); -static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done); -static int imap_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done); -static CURLcode imap_setup_connection(struct connectdata *conn); -static CURLcode imap_state_upgrade_tls(struct connectdata *conn); - -/* - * IMAP protocol handler. - */ - -const struct Curl_handler Curl_handler_imap = { - "IMAP", /* scheme */ - imap_setup_connection, /* setup_connection */ - imap_do, /* do_it */ - imap_done, /* done */ - ZERO_NULL, /* do_more */ - imap_connect, /* connect_it */ - imap_multi_statemach, /* connecting */ - imap_doing, /* doing */ - imap_getsock, /* proto_getsock */ - imap_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_IMAP, /* defport */ - CURLPROTO_IMAP, /* protocol */ - PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD - | PROTOPT_NOURLQUERY /* flags */ -}; - - -#ifdef USE_SSL -/* - * IMAPS protocol handler. - */ - -const struct Curl_handler Curl_handler_imaps = { - "IMAPS", /* scheme */ - imap_setup_connection, /* setup_connection */ - imap_do, /* do_it */ - imap_done, /* done */ - ZERO_NULL, /* do_more */ - imap_connect, /* connect_it */ - imap_multi_statemach, /* connecting */ - imap_doing, /* doing */ - imap_getsock, /* proto_getsock */ - imap_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_IMAPS, /* defport */ - CURLPROTO_IMAP | CURLPROTO_IMAPS, /* protocol */ - PROTOPT_CLOSEACTION | PROTOPT_SSL | PROTOPT_NEEDSPWD - | PROTOPT_NOURLQUERY /* flags */ -}; -#endif - -#ifndef CURL_DISABLE_HTTP -/* - * HTTP-proxyed IMAP protocol handler. - */ - -static const struct Curl_handler Curl_handler_imap_proxy = { - "IMAP", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_IMAP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - - -#ifdef USE_SSL -/* - * HTTP-proxyed IMAPS protocol handler. - */ - -static const struct Curl_handler Curl_handler_imaps_proxy = { - "IMAPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_IMAPS, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; -#endif -#endif - -/*********************************************************************** - * - * imap_sendf() - * - * Sends the formated string as an IMAP command to the server. - * - * Designed to never block. - */ -static CURLcode imap_sendf(struct connectdata *conn, - const char *idstr, /* command id to wait for */ - const char *fmt, ...) -{ - CURLcode res; - struct imap_conn *imapc = &conn->proto.imapc; - va_list ap; - va_start(ap, fmt); - - imapc->idstr = idstr; - - res = Curl_pp_vsendf(&imapc->pp, fmt, ap); - - va_end(ap); - - return res; -} - -static const char *getcmdid(struct connectdata *conn) -{ - static const char * const ids[]= { - "A", - "B", - "C", - "D" - }; - - struct imap_conn *imapc = &conn->proto.imapc; - - /* Get the next id, but wrap at end of table */ - imapc->cmdid = (int)((imapc->cmdid + 1) % (sizeof(ids) / sizeof(ids[0]))); - - return ids[imapc->cmdid]; -} - -/*********************************************************************** - * - * imap_atom() - * - * Checks the input string for characters that need escaping and returns an - * atom ready for sending to the server. - * - * The returned string needs to be freed. - * - */ -static char* imap_atom(const char* str) -{ - const char *p1; - char *p2; - size_t backsp_count = 0; - size_t quote_count = 0; - bool space_exists = FALSE; - size_t newlen = 0; - char *newstr = NULL; - - if(!str) - return NULL; - - /* Count any unescapped characters */ - p1 = str; - while(*p1) { - if(*p1 == '\\') - backsp_count++; - else if(*p1 == '"') - quote_count++; - else if(*p1 == ' ') - space_exists = TRUE; - - p1++; - } - - /* Does the input contain any unescapped characters? */ - if(!backsp_count && !quote_count && !space_exists) - return strdup(str); - - /* Calculate the new string length */ - newlen = strlen(str) + backsp_count + quote_count + (space_exists ? 2 : 0); - - /* Allocate the new string */ - newstr = (char *) malloc((newlen + 1) * sizeof(char)); - if(!newstr) - return NULL; - - /* Surround the string in quotes if necessary */ - p2 = newstr; - if(space_exists) { - newstr[0] = '"'; - newstr[newlen - 1] = '"'; - p2++; - } - - /* Copy the string, escaping backslash and quote characters along the way */ - p1 = str; - while(*p1) { - if(*p1 == '\\' || *p1 == '"') { - *p2 = '\\'; - p2++; - } - - *p2 = *p1; - - p1++; - p2++; - } - - /* Terminate the string */ - newstr[newlen] = '\0'; - - return newstr; -} - -/* Function that checks for an ending imap status code at the start of the - given string. */ -static int imap_endofresp(struct pingpong *pp, int *resp) -{ - char *line = pp->linestart_resp; - size_t len = pp->nread_resp; - struct imap_conn *imapc = &pp->conn->proto.imapc; - const char *id = imapc->idstr; - size_t id_len = strlen(id); - - /* Do we have a generic command response? */ - if(len >= id_len + 3) { - if(!memcmp(id, line, id_len) && line[id_len] == ' ') { - *resp = line[id_len + 1]; /* O, N or B */ - return TRUE; - } - } - - /* Are we processing FETCH command responses? */ - if(imapc->state == IMAP_FETCH) { - /* Do we have a valid response? */ - if(len >= 2 && !memcmp("* ", line, 2)) { - *resp = '*'; - return TRUE; - } - } - - return FALSE; /* nothing for us */ -} - -/* This is the ONLY way to change IMAP state! */ -static void state(struct connectdata *conn, - imapstate newstate) -{ - struct imap_conn *imapc = &conn->proto.imapc; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ - static const char * const names[]={ - "STOP", - "SERVERGREET", - "STARTTLS", - "UPGRADETLS", - "LOGIN", - "SELECT", - "FETCH", - "LOGOUT", - /* LAST */ - }; - - if(imapc->state != newstate) - infof(conn->data, "IMAP %p state change from %s to %s\n", - imapc, names[imapc->state], names[newstate]); -#endif - - imapc->state = newstate; -} - -static CURLcode imap_state_login(struct connectdata *conn) -{ - CURLcode result; - struct FTP *imap = conn->data->state.proto.imap; - const char *str = getcmdid(conn); - char *user = imap_atom(imap->user); - char *passwd = imap_atom(imap->passwd); - - /* send USER and password */ - result = imap_sendf(conn, str, "%s LOGIN %s %s", str, - user ? user : "", passwd ? passwd : ""); - - Curl_safefree(user); - Curl_safefree(passwd); - - if(result) - return result; - - state(conn, IMAP_LOGIN); - - return CURLE_OK; -} - -/* For the IMAP "protocol connect" and "doing" phases only */ -static int imap_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - return Curl_pp_getsock(&conn->proto.imapc.pp, socks, numsocks); -} - -#ifdef USE_SSL -static void imap_to_imaps(struct connectdata *conn) -{ - conn->handler = &Curl_handler_imaps; -} -#else -#define imap_to_imaps(x) Curl_nop_stmt -#endif - -/* For the initial server greeting */ -static CURLcode imap_state_servergreet_resp(struct connectdata *conn, - int imapcode, - imapstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(imapcode != 'O') { - failf(data, "Got unexpected imap-server response"); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - - if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch - to TLS connection now */ - const char *str = getcmdid(conn); - result = imap_sendf(conn, str, "%s STARTTLS", str); - state(conn, IMAP_STARTTLS); - } - else - result = imap_state_login(conn); - - return result; -} - -/* For STARTTLS responses */ -static CURLcode imap_state_starttls_resp(struct connectdata *conn, - int imapcode, - imapstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(imapcode != 'O') { - if(data->set.use_ssl != CURLUSESSL_TRY) { - failf(data, "STARTTLS denied. %c", imapcode); - result = CURLE_USE_SSL_FAILED; - } - else - result = imap_state_login(conn); - } - else { - if(data->state.used_interface == Curl_if_multi) { - state(conn, IMAP_UPGRADETLS); - result = imap_state_upgrade_tls(conn); - } - else { - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(CURLE_OK == result) { - imap_to_imaps(conn); - result = imap_state_login(conn); - } - } - } - - return result; -} - -static CURLcode imap_state_upgrade_tls(struct connectdata *conn) -{ - struct imap_conn *imapc = &conn->proto.imapc; - CURLcode result; - - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); - - if(imapc->ssldone) { - imap_to_imaps(conn); - result = imap_state_login(conn); - } - - return result; -} - -/* For LOGIN responses */ -static CURLcode imap_state_login_resp(struct connectdata *conn, - int imapcode, - imapstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(imapcode != 'O') { - failf(data, "Access denied. %c", imapcode); - result = CURLE_LOGIN_DENIED; - } - else - /* End of connect phase */ - state(conn, IMAP_STOP); - - return result; -} - -/* Start the DO phase */ -static CURLcode imap_select(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; - const char *str = getcmdid(conn); - - result = imap_sendf(conn, str, "%s SELECT %s", str, - imapc->mailbox?imapc->mailbox:""); - if(result) - return result; - - state(conn, IMAP_SELECT); - - return result; -} - -static CURLcode imap_fetch(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - const char *str = getcmdid(conn); - - /* TODO: make this select the correct mail - * Use "1 body[text]" to get the full mail body of mail 1 - */ - result = imap_sendf(conn, str, "%s FETCH 1 BODY[TEXT]", str); - if(result) - return result; - - /* - * When issued, the server will respond with a single line similar to - * '* 1 FETCH (BODY[TEXT] {2021}' - * - * Identifying the fetch and how many bytes of contents we can expect. We - * must extract that number before continuing to "download as usual". - */ - - state(conn, IMAP_FETCH); - - return result; -} - -/* For SELECT responses */ -static CURLcode imap_state_select_resp(struct connectdata *conn, - int imapcode, - imapstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(imapcode != 'O') { - failf(data, "Select failed"); - result = CURLE_LOGIN_DENIED; - } - else - result = imap_fetch(conn); - - return result; -} - -/* For the (first line of) FETCH BODY[TEXT] response */ -static CURLcode imap_state_fetch_resp(struct connectdata *conn, - int imapcode, - imapstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct imap_conn *imapc = &conn->proto.imapc; - struct FTP *imap = data->state.proto.imap; - struct pingpong *pp = &imapc->pp; - const char *ptr = data->state.buffer; - - (void)instate; /* no use for this yet */ - - if('*' != imapcode) { - Curl_pgrsSetDownloadSize(data, 0); - state(conn, IMAP_STOP); - return CURLE_OK; - } - - /* Something like this comes "* 1 FETCH (BODY[TEXT] {2021}\r" */ - while(*ptr && (*ptr != '{')) - ptr++; - - if(*ptr == '{') { - curl_off_t filesize = curlx_strtoofft(ptr + 1, NULL, 10); - if(filesize) - Curl_pgrsSetDownloadSize(data, filesize); - - infof(data, "Found %" FORMAT_OFF_TU " bytes to download\n", filesize); - - if(pp->cache) { - /* At this point there is a bunch of data in the header "cache" that is - actually body content, send it as body and then skip it. Do note - that there may even be additional "headers" after the body. */ - size_t chunk = pp->cache_size; - - if(chunk > (size_t)filesize) - /* the conversion from curl_off_t to size_t is always fine here */ - chunk = (size_t)filesize; - - result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk); - if(result) - return result; - - filesize -= chunk; - - /* we've now used parts of or the entire cache */ - if(pp->cache_size > chunk) { - /* part of, move the trailing data to the start and reduce the size */ - memmove(pp->cache, pp->cache+chunk, - pp->cache_size - chunk); - pp->cache_size -= chunk; - } - else { - /* cache is drained */ - Curl_safefree(pp->cache); - pp->cache = NULL; - pp->cache_size = 0; - } - } - - infof(data, "Filesize left: %" FORMAT_OFF_T "\n", filesize); - - if(!filesize) - /* the entire data is already transferred! */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - else - /* IMAP download */ - Curl_setup_transfer(conn, FIRSTSOCKET, filesize, FALSE, - imap->bytecountp, -1, NULL); /* no upload here */ - - data->req.maxdownload = filesize; - } - else - /* We don't know how to parse this line */ - result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */ - - /* End of do phase */ - state(conn, IMAP_STOP); - - return result; -} - -static CURLcode imap_statemach_act(struct connectdata *conn) -{ - CURLcode result; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int imapcode; - struct imap_conn *imapc = &conn->proto.imapc; - struct pingpong *pp = &imapc->pp; - size_t nread = 0; - - /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */ - if(imapc->state == IMAP_UPGRADETLS) - return imap_state_upgrade_tls(conn); - - /* Flush any data that needs to be sent */ - if(pp->sendleft) - return Curl_pp_flushsend(pp); - - /* Read the response from the server */ - result = Curl_pp_readresp(sock, pp, &imapcode, &nread); - if(result) - return result; - - if(imapcode) { - /* We have now received a full IMAP server response */ - switch(imapc->state) { - case IMAP_SERVERGREET: - result = imap_state_servergreet_resp(conn, imapcode, imapc->state); - break; - - case IMAP_STARTTLS: - result = imap_state_starttls_resp(conn, imapcode, imapc->state); - break; - - case IMAP_LOGIN: - result = imap_state_login_resp(conn, imapcode, imapc->state); - break; - - case IMAP_FETCH: - result = imap_state_fetch_resp(conn, imapcode, imapc->state); - break; - - case IMAP_SELECT: - result = imap_state_select_resp(conn, imapcode, imapc->state); - break; - - case IMAP_LOGOUT: - /* fallthrough, just stop! */ - default: - /* internal error */ - state(conn, IMAP_STOP); - break; - } - } - - return result; -} - -/* Called repeatedly until done from curl_multi.c */ -static CURLcode imap_multi_statemach(struct connectdata *conn, - bool *done) -{ - struct imap_conn *imapc = &conn->proto.imapc; - CURLcode result; - - if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); - else - result = Curl_pp_multi_statemach(&imapc->pp); - - *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE; - - return result; -} - -static CURLcode imap_easy_statemach(struct connectdata *conn) -{ - struct imap_conn *imapc = &conn->proto.imapc; - struct pingpong *pp = &imapc->pp; - CURLcode result = CURLE_OK; - - while(imapc->state != IMAP_STOP) { - result = Curl_pp_easy_statemach(pp); - if(result) - break; - } - - return result; -} - -/* Allocate and initialize the struct IMAP for the current SessionHandle if - required */ -static CURLcode imap_init(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - struct FTP *imap = data->state.proto.imap; - - if(!imap) { - imap = data->state.proto.imap = calloc(sizeof(struct FTP), 1); - if(!imap) - return CURLE_OUT_OF_MEMORY; - } - - /* Get some initial data into the imap struct */ - imap->bytecountp = &data->req.bytecount; - - /* No need to duplicate user+password, the connectdata struct won't change - during a session, but we re-init them here since on subsequent inits - since the conn struct may have changed or been replaced. - */ - imap->user = conn->user; - imap->passwd = conn->passwd; - - return CURLE_OK; -} - -/*********************************************************************** - * - * imap_connect() should do everything that is to be considered a part of - * the connection phase. - * - * The variable 'done' points to will be TRUE if the protocol-layer connect - * phase is done when this function returns, or FALSE is not. When called as - * a part of the easy interface, it will always be TRUE. - */ -static CURLcode imap_connect(struct connectdata *conn, - bool *done) /* see description above */ -{ - CURLcode result; - struct imap_conn *imapc = &conn->proto.imapc; - struct SessionHandle *data=conn->data; - struct pingpong *pp = &imapc->pp; - - *done = FALSE; /* default to not done yet */ - - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - result = imap_init(conn); - if(CURLE_OK != result) - return result; - - /* We always support persistent connections on imap */ - conn->bits.close = FALSE; - - pp->response_time = RESP_TIMEOUT; /* set default response time-out */ - pp->statemach_act = imap_statemach_act; - pp->endofresp = imap_endofresp; - pp->conn = conn; - - if((conn->handler->flags & PROTOPT_SSL) && - data->state.used_interface != Curl_if_multi) { - /* IMAPS is simply imap with SSL for the control channel */ - /* so perform the SSL initialization for this socket */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(result) - return result; - } - - /* Initialise the response reader stuff */ - Curl_pp_init(pp); - - /* Start off waiting for the server greeting response */ - state(conn, IMAP_SERVERGREET); - - /* Start off with an id of '*' */ - imapc->idstr = "*"; - - if(data->state.used_interface == Curl_if_multi) - result = imap_multi_statemach(conn, done); - else { - result = imap_easy_statemach(conn); - if(!result) - *done = TRUE; - } - - return result; -} - -/*********************************************************************** - * - * imap_done() - * - * The DONE function. This does what needs to be done after a single DO has - * performed. - * - * Input argument is already checked for validity. - */ -static CURLcode imap_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - struct SessionHandle *data = conn->data; - struct FTP *imap = data->state.proto.imap; - CURLcode result=CURLE_OK; - - (void)premature; - - if(!imap) - /* When the easy handle is removed from the multi while libcurl is still - * trying to resolve the host name, it seems that the imap struct is not - * yet initialized, but the removal action calls Curl_done() which calls - * this function. So we simply return success if no imap pointer is set. - */ - return CURLE_OK; - - if(status) { - conn->bits.close = TRUE; /* marked for closure */ - result = status; /* use the already set error code */ - } - - /* Clear the transfer mode for the next connection */ - imap->transfer = FTPTRANSFER_BODY; - - return result; -} - -/*********************************************************************** - * - * imap_perform() - * - * This is the actual DO function for IMAP. Get a file/directory according to - * the options previously setup. - */ -static CURLcode imap_perform(struct connectdata *conn, bool *connected, - bool *dophase_done) -{ - /* This is IMAP and no proxy */ - CURLcode result = CURLE_OK; - - DEBUGF(infof(conn->data, "DO phase starts\n")); - - if(conn->data->set.opt_no_body) { - /* Requested no body means no transfer */ - struct FTP *imap = conn->data->state.proto.imap; - imap->transfer = FTPTRANSFER_INFO; - } - - *dophase_done = FALSE; /* not done yet */ - - /* Start the first command in the DO phase */ - result = imap_select(conn); - if(result) - return result; - - /* Run the state-machine */ - if(conn->data->state.used_interface == Curl_if_multi) - result = imap_multi_statemach(conn, dophase_done); - else { - result = imap_easy_statemach(conn); - *dophase_done = TRUE; /* with the easy interface we are done here */ - } - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); - - return result; -} - -/*********************************************************************** - * - * imap_do() - * - * This function is registered as 'curl_do' function. It decodes the path - * parts etc as a wrapper to the actual DO function (imap_perform). - * - * The input argument is already checked for validity. - */ -static CURLcode imap_do(struct connectdata *conn, bool *done) -{ - CURLcode retcode = CURLE_OK; - - *done = FALSE; /* default to false */ - - /* - Since connections can be re-used between SessionHandles, this might be a - connection already existing but on a fresh SessionHandle struct so we must - make sure we have a good 'struct IMAP' to play with. For new connections, - the struct IMAP is allocated and setup in the imap_connect() function. - */ - Curl_reset_reqproto(conn); - retcode = imap_init(conn); - if(retcode) - return retcode; - - /* Parse the URL path */ - retcode = imap_parse_url_path(conn); - if(retcode) - return retcode; - - retcode = imap_regular_transfer(conn, done); - - return retcode; -} - -/*********************************************************************** - * - * imap_logout() - * - * This should be called before calling sclose(). We should then wait for the - * response from the server before returning. The calling code should then try - * to close the connection. - * - */ -static CURLcode imap_logout(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - const char *str = getcmdid(conn); - - result = imap_sendf(conn, str, "%s LOGOUT", str, NULL); - if(result) - return result; - - state(conn, IMAP_LOGOUT); - - result = imap_easy_statemach(conn); - - return result; -} - -/*********************************************************************** - * - * imap_disconnect() - * - * Disconnect from an IMAP server. Cleanup protocol-specific per-connection - * resources. BLOCKING. - */ -static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection) -{ - struct imap_conn *imapc= &conn->proto.imapc; - - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to */ - - /* The IMAP session may or may not have been allocated/setup at this - point! */ - if(!dead_connection && imapc->pp.conn) - (void)imap_logout(conn); /* ignore errors on the LOGOUT */ - - /* Disconnect from the server */ - Curl_pp_disconnect(&imapc->pp); - - /* Cleanup our connection based variables */ - Curl_safefree(imapc->mailbox); - - return CURLE_OK; -} - -/*********************************************************************** - * - * imap_parse_url_path() - * - * Parse the URL path into separate path components. - * - */ -static CURLcode imap_parse_url_path(struct connectdata *conn) -{ - /* The imap struct is already inited in imap_connect() */ - struct imap_conn *imapc = &conn->proto.imapc; - struct SessionHandle *data = conn->data; - const char *path = data->state.path; - - if(!*path) - path = "INBOX"; - - /* URL decode the path and use this mailbox */ - return Curl_urldecode(data, path, 0, &imapc->mailbox, NULL, TRUE); -} - -/* Call this when the DO phase has completed */ -static CURLcode imap_dophase_done(struct connectdata *conn, bool connected) -{ - struct FTP *imap = conn->data->state.proto.imap; - - (void)connected; - - if(imap->transfer != FTPTRANSFER_BODY) - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - - return CURLE_OK; -} - -/* Called from curl_multi.c while DOing */ -static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done) -{ - CURLcode result = imap_multi_statemach(conn, dophase_done); - - if(result) - DEBUGF(infof(conn->data, "DO phase failed\n")); - else { - if(*dophase_done) { - result = imap_dophase_done(conn, FALSE /* not connected */); - - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - } - - return result; -} - -/*********************************************************************** - * - * imap_regular_transfer() - * - * The input argument is already checked for validity. - * - * Performs all commands done before a regular transfer between a local and a - * remote host. - */ -static CURLcode imap_regular_transfer(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - bool connected = FALSE; - struct SessionHandle *data = conn->data; - - /* Make sure size is unknown at this point */ - data->req.size = -1; - - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - Curl_pgrsSetUploadSize(data, 0); - Curl_pgrsSetDownloadSize(data, 0); - - result = imap_perform(conn, &connected, dophase_done); - - if(CURLE_OK == result) { - if(!*dophase_done) - /* The DO phase has not completed yet */ - return CURLE_OK; - - result = imap_dophase_done(conn, connected); - if(result) - return result; - } - - return result; -} - -static CURLcode imap_setup_connection(struct connectdata * conn) -{ - struct SessionHandle *data = conn->data; - - if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel imap operations through the proxy, we - switch and use HTTP operations only */ -#ifndef CURL_DISABLE_HTTP - if(conn->handler == &Curl_handler_imap) - conn->handler = &Curl_handler_imap_proxy; - else { -#ifdef USE_SSL - conn->handler = &Curl_handler_imaps_proxy; -#else - failf(data, "IMAPS not supported!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - /* We explicitly mark this connection as persistent here as we're doing - IMAP over HTTP and thus we accidentally avoid setting this value - otherwise */ - conn->bits.close = FALSE; -#else - failf(data, "IMAP over http proxy requires HTTP support built-in!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - data->state.path++; /* don't include the initial slash */ - - return CURLE_OK; -} - -#endif /* CURL_DISABLE_IMAP */ diff --git a/lib/inet_ntop.c b/lib/inet_ntop.c deleted file mode 100644 index b184029f6..000000000 --- a/lib/inet_ntop.c +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 1996-2001 Internet Software Consortium. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM - * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL - * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING - * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, - * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION - * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ -/* - * Original code by Paul Vixie. "curlified" by Gisle Vanem. - */ - -#include "curl_setup.h" - -#ifndef HAVE_INET_NTOP - -#ifdef HAVE_SYS_PARAM_H -#include -#endif -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_inet_ntop.h" - -#define IN6ADDRSZ 16 -#define INADDRSZ 4 -#define INT16SZ 2 - -/* - * Format an IPv4 address, more or less like inet_ntoa(). - * - * Returns `dst' (as a const) - * Note: - * - uses no statics - * - takes a unsigned char* not an in_addr as input - */ -static char *inet_ntop4 (const unsigned char *src, char *dst, size_t size) -{ - char tmp[sizeof "255.255.255.255"]; - size_t len; - - DEBUGASSERT(size >= 16); - - tmp[0] = '\0'; - (void)snprintf(tmp, sizeof(tmp), "%d.%d.%d.%d", - ((int)((unsigned char)src[0])) & 0xff, - ((int)((unsigned char)src[1])) & 0xff, - ((int)((unsigned char)src[2])) & 0xff, - ((int)((unsigned char)src[3])) & 0xff); - - len = strlen(tmp); - if(len == 0 || len >= size) { - SET_ERRNO(ENOSPC); - return (NULL); - } - strcpy(dst, tmp); - return dst; -} - -#ifdef ENABLE_IPV6 -/* - * Convert IPv6 binary address into presentation (printable) format. - */ -static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) -{ - /* - * Note that int32_t and int16_t need only be "at least" large enough - * to contain a value of the specified size. On some systems, like - * Crays, there is no such thing as an integer variable with 16 bits. - * Keep this in mind if you think this function should have been coded - * to use pointer overlays. All the world's not a VAX. - */ - char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; - char *tp; - struct { - long base; - long len; - } best, cur; - unsigned long words[IN6ADDRSZ / INT16SZ]; - int i; - - /* Preprocess: - * Copy the input (bytewise) array into a wordwise array. - * Find the longest run of 0x00's in src[] for :: shorthanding. - */ - memset(words, '\0', sizeof(words)); - for(i = 0; i < IN6ADDRSZ; i++) - words[i/2] |= (src[i] << ((1 - (i % 2)) << 3)); - - best.base = -1; - cur.base = -1; - best.len = 0; - cur.len = 0; - - for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { - if(words[i] == 0) { - if(cur.base == -1) - cur.base = i, cur.len = 1; - else - cur.len++; - } - else if(cur.base != -1) { - if(best.base == -1 || cur.len > best.len) - best = cur; - cur.base = -1; - } - } - if((cur.base != -1) && (best.base == -1 || cur.len > best.len)) - best = cur; - if(best.base != -1 && best.len < 2) - best.base = -1; - /* Format the result. */ - tp = tmp; - for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { - /* Are we inside the best run of 0x00's? */ - if(best.base != -1 && i >= best.base && i < (best.base + best.len)) { - if(i == best.base) - *tp++ = ':'; - continue; - } - - /* Are we following an initial run of 0x00s or any real hex? - */ - if(i != 0) - *tp++ = ':'; - - /* Is this address an encapsulated IPv4? - */ - if(i == 6 && best.base == 0 && - (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) { - if(!inet_ntop4(src+12, tp, sizeof(tmp) - (tp - tmp))) { - SET_ERRNO(ENOSPC); - return (NULL); - } - tp += strlen(tp); - break; - } - tp += snprintf(tp, 5, "%lx", words[i]); - } - - /* Was it a trailing run of 0x00's? - */ - if(best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ)) - *tp++ = ':'; - *tp++ = '\0'; - - /* Check for overflow, copy, and we're done. - */ - if((size_t)(tp - tmp) > size) { - SET_ERRNO(ENOSPC); - return (NULL); - } - strcpy(dst, tmp); - return dst; -} -#endif /* ENABLE_IPV6 */ - -/* - * Convert a network format address to presentation format. - * - * Returns pointer to presentation format address (`buf'). - * Returns NULL on error and errno set with the specific - * error, EAFNOSUPPORT or ENOSPC. - * - * On Windows we store the error in the thread errno, not - * in the winsock error code. This is to avoid losing the - * actual last winsock error. So use macro ERRNO to fetch the - * errno this function sets when returning NULL, not SOCKERRNO. - */ -char *Curl_inet_ntop(int af, const void *src, char *buf, size_t size) -{ - switch (af) { - case AF_INET: - return inet_ntop4((const unsigned char*)src, buf, size); -#ifdef ENABLE_IPV6 - case AF_INET6: - return inet_ntop6((const unsigned char*)src, buf, size); -#endif - default: - SET_ERRNO(EAFNOSUPPORT); - return NULL; - } -} -#endif /* HAVE_INET_NTOP */ diff --git a/lib/inet_pton.c b/lib/inet_pton.c deleted file mode 100644 index 175f938cd..000000000 --- a/lib/inet_pton.c +++ /dev/null @@ -1,234 +0,0 @@ -/* This is from the BIND 4.9.4 release, modified to compile by itself */ - -/* Copyright (c) 1996 by Internet Software Consortium. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS - * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE - * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR - * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS - * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS - * SOFTWARE. - */ - -#include "curl_setup.h" - -#ifndef HAVE_INET_PTON - -#ifdef HAVE_SYS_PARAM_H -#include -#endif -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif - -#include "curl_inet_pton.h" - -#define IN6ADDRSZ 16 -#define INADDRSZ 4 -#define INT16SZ 2 - -/* - * WARNING: Don't even consider trying to compile this on a system where - * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. - */ - -static int inet_pton4(const char *src, unsigned char *dst); -#ifdef ENABLE_IPV6 -static int inet_pton6(const char *src, unsigned char *dst); -#endif - -/* int - * inet_pton(af, src, dst) - * convert from presentation format (which usually means ASCII printable) - * to network format (which is usually some kind of binary format). - * return: - * 1 if the address was valid for the specified address family - * 0 if the address wasn't valid (`dst' is untouched in this case) - * -1 if some other error occurred (`dst' is untouched in this case, too) - * notice: - * On Windows we store the error in the thread errno, not - * in the winsock error code. This is to avoid losing the - * actual last winsock error. So use macro ERRNO to fetch the - * errno this function sets when returning (-1), not SOCKERRNO. - * author: - * Paul Vixie, 1996. - */ -int -Curl_inet_pton(int af, const char *src, void *dst) -{ - switch (af) { - case AF_INET: - return (inet_pton4(src, (unsigned char *)dst)); -#ifdef ENABLE_IPV6 - case AF_INET6: - return (inet_pton6(src, (unsigned char *)dst)); -#endif - default: - SET_ERRNO(EAFNOSUPPORT); - return (-1); - } - /* NOTREACHED */ -} - -/* int - * inet_pton4(src, dst) - * like inet_aton() but without all the hexadecimal and shorthand. - * return: - * 1 if `src' is a valid dotted quad, else 0. - * notice: - * does not touch `dst' unless it's returning 1. - * author: - * Paul Vixie, 1996. - */ -static int -inet_pton4(const char *src, unsigned char *dst) -{ - static const char digits[] = "0123456789"; - int saw_digit, octets, ch; - unsigned char tmp[INADDRSZ], *tp; - - saw_digit = 0; - octets = 0; - tp = tmp; - *tp = 0; - while((ch = *src++) != '\0') { - const char *pch; - - if((pch = strchr(digits, ch)) != NULL) { - unsigned int val = *tp * 10 + (unsigned int)(pch - digits); - - if(saw_digit && *tp == 0) - return (0); - if(val > 255) - return (0); - *tp = (unsigned char)val; - if(! saw_digit) { - if(++octets > 4) - return (0); - saw_digit = 1; - } - } - else if(ch == '.' && saw_digit) { - if(octets == 4) - return (0); - *++tp = 0; - saw_digit = 0; - } - else - return (0); - } - if(octets < 4) - return (0); - memcpy(dst, tmp, INADDRSZ); - return (1); -} - -#ifdef ENABLE_IPV6 -/* int - * inet_pton6(src, dst) - * convert presentation level address to network order binary form. - * return: - * 1 if `src' is a valid [RFC1884 2.2] address, else 0. - * notice: - * (1) does not touch `dst' unless it's returning 1. - * (2) :: in a full address is silently ignored. - * credit: - * inspired by Mark Andrews. - * author: - * Paul Vixie, 1996. - */ -static int -inet_pton6(const char *src, unsigned char *dst) -{ - static const char xdigits_l[] = "0123456789abcdef", - xdigits_u[] = "0123456789ABCDEF"; - unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp; - const char *xdigits, *curtok; - int ch, saw_xdigit; - size_t val; - - memset((tp = tmp), 0, IN6ADDRSZ); - endp = tp + IN6ADDRSZ; - colonp = NULL; - /* Leading :: requires some special handling. */ - if(*src == ':') - if(*++src != ':') - return (0); - curtok = src; - saw_xdigit = 0; - val = 0; - while((ch = *src++) != '\0') { - const char *pch; - - if((pch = strchr((xdigits = xdigits_l), ch)) == NULL) - pch = strchr((xdigits = xdigits_u), ch); - if(pch != NULL) { - val <<= 4; - val |= (pch - xdigits); - if(++saw_xdigit > 4) - return (0); - continue; - } - if(ch == ':') { - curtok = src; - if(!saw_xdigit) { - if(colonp) - return (0); - colonp = tp; - continue; - } - if(tp + INT16SZ > endp) - return (0); - *tp++ = (unsigned char) (val >> 8) & 0xff; - *tp++ = (unsigned char) val & 0xff; - saw_xdigit = 0; - val = 0; - continue; - } - if(ch == '.' && ((tp + INADDRSZ) <= endp) && - inet_pton4(curtok, tp) > 0) { - tp += INADDRSZ; - saw_xdigit = 0; - break; /* '\0' was seen by inet_pton4(). */ - } - return (0); - } - if(saw_xdigit) { - if(tp + INT16SZ > endp) - return (0); - *tp++ = (unsigned char) (val >> 8) & 0xff; - *tp++ = (unsigned char) val & 0xff; - } - if(colonp != NULL) { - /* - * Since some memmove()'s erroneously fail to handle - * overlapping regions, we'll do the shift by hand. - */ - const ssize_t n = tp - colonp; - ssize_t i; - - if(tp == endp) - return (0); - for(i = 1; i <= n; i++) { - *(endp - i) = *(colonp + n - i); - *(colonp + n - i) = 0; - } - tp = endp; - } - if(tp != endp) - return (0); - memcpy(dst, tmp, IN6ADDRSZ); - return (1); -} -#endif /* ENABLE_IPV6 */ - -#endif /* HAVE_INET_PTON */ diff --git a/lib/krb4.c b/lib/krb4.c deleted file mode 100644 index 8ba326ed7..000000000 --- a/lib/krb4.c +++ /dev/null @@ -1,440 +0,0 @@ -/* This source code was modified by Martin Hedenfalk for - * use in Curl. Martin's 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. - * - * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan - * (Royal Institute of Technology, Stockholm, Sweden). - * Copyright (c) 2004 - 2011 Daniel Stenberg - * 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 "curl_setup.h" - -#ifndef CURL_DISABLE_FTP -#ifdef HAVE_KRB4 - -#ifdef HAVE_NETDB_H -#include -#endif -#include -#include - -#include "curl_urldata.h" -#include "curl_base64.h" -#include "curl_ftp.h" -#include "curl_sendf.h" -#include "curl_krb4.h" -#include "curl_inet_ntop.h" -#include "curl_memory.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#define LOCAL_ADDR (&conn->local_addr) -#define REMOTE_ADDR conn->ip_addr->ai_addr -#define myctladdr LOCAL_ADDR -#define hisctladdr REMOTE_ADDR - -struct krb4_data { - des_cblock key; - des_key_schedule schedule; - char name[ANAME_SZ]; - char instance[INST_SZ]; - char realm[REALM_SZ]; -}; - -#ifndef HAVE_STRLCPY -/* if it ever goes non-static, make it Curl_ prefixed! */ -static size_t -strlcpy (char *dst, const char *src, size_t dst_sz) -{ - size_t n; - char *p; - - for(p = dst, n = 0; - n + 1 < dst_sz && *src != '\0'; - ++p, ++src, ++n) - *p = *src; - *p = '\0'; - if(*src == '\0') - return n; - else - return n + strlen (src); -} -#else -size_t strlcpy (char *dst, const char *src, size_t dst_sz); -#endif - -static int -krb4_check_prot(void *app_data, int level) -{ - app_data = NULL; /* prevent compiler warning */ - if(level == PROT_CONFIDENTIAL) - return -1; - return 0; -} - -static int -krb4_decode(void *app_data, void *buf, int len, int level, - struct connectdata *conn) -{ - MSG_DAT m; - int e; - struct krb4_data *d = app_data; - - if(level == PROT_SAFE) - e = krb_rd_safe(buf, len, &d->key, - (struct sockaddr_in *)REMOTE_ADDR, - (struct sockaddr_in *)LOCAL_ADDR, &m); - else - e = krb_rd_priv(buf, len, d->schedule, &d->key, - (struct sockaddr_in *)REMOTE_ADDR, - (struct sockaddr_in *)LOCAL_ADDR, &m); - if(e) { - struct SessionHandle *data = conn->data; - infof(data, "krb4_decode: %s\n", krb_get_err_text(e)); - return -1; - } - memmove(buf, m.app_data, m.app_length); - return m.app_length; -} - -static int -krb4_overhead(void *app_data, int level, int len) -{ - /* no arguments are used, just init them to prevent compiler warnings */ - app_data = NULL; - level = 0; - len = 0; - return 31; -} - -static int -krb4_encode(void *app_data, const void *from, int length, int level, void **to, - struct connectdata *conn) -{ - struct krb4_data *d = app_data; - *to = malloc(length + 31); - if(!*to) - return -1; - if(level == PROT_SAFE) - /* NOTE that the void* cast is safe, krb_mk_safe/priv don't modify the - * input buffer - */ - return krb_mk_safe((void*)from, *to, length, &d->key, - (struct sockaddr_in *)LOCAL_ADDR, - (struct sockaddr_in *)REMOTE_ADDR); - else if(level == PROT_PRIVATE) - return krb_mk_priv((void*)from, *to, length, d->schedule, &d->key, - (struct sockaddr_in *)LOCAL_ADDR, - (struct sockaddr_in *)REMOTE_ADDR); - else - return -1; -} - -static int -mk_auth(struct krb4_data *d, KTEXT adat, - const char *service, char *host, int checksum) -{ - int ret; - CREDENTIALS cred; - char sname[SNAME_SZ], inst[INST_SZ], realm[REALM_SZ]; - - strlcpy(sname, service, sizeof(sname)); - strlcpy(inst, krb_get_phost(host), sizeof(inst)); - strlcpy(realm, krb_realmofhost(host), sizeof(realm)); - ret = krb_mk_req(adat, sname, inst, realm, checksum); - if(ret) - return ret; - strlcpy(sname, service, sizeof(sname)); - strlcpy(inst, krb_get_phost(host), sizeof(inst)); - strlcpy(realm, krb_realmofhost(host), sizeof(realm)); - ret = krb_get_cred(sname, inst, realm, &cred); - memmove(&d->key, &cred.session, sizeof(des_cblock)); - des_key_sched(&d->key, d->schedule); - memset(&cred, 0, sizeof(cred)); - return ret; -} - -#ifdef HAVE_KRB_GET_OUR_IP_FOR_REALM -int krb_get_our_ip_for_realm(char *, struct in_addr *); -#endif - -static int -krb4_auth(void *app_data, struct connectdata *conn) -{ - int ret; - char *p; - unsigned char *ptr; - size_t len = 0; - KTEXT_ST adat; - MSG_DAT msg_data; - int checksum; - u_int32_t cs; - struct krb4_data *d = app_data; - char *host = conn->host.name; - ssize_t nread; - int l = sizeof(conn->local_addr); - struct SessionHandle *data = conn->data; - CURLcode result; - size_t base64_sz = 0; - - if(getsockname(conn->sock[FIRSTSOCKET], - (struct sockaddr *)LOCAL_ADDR, &l) < 0) - perror("getsockname()"); - - checksum = getpid(); - ret = mk_auth(d, &adat, "ftp", host, checksum); - if(ret == KDC_PR_UNKNOWN) - ret = mk_auth(d, &adat, "rcmd", host, checksum); - if(ret) { - infof(data, "%s\n", krb_get_err_text(ret)); - return AUTH_CONTINUE; - } - -#ifdef HAVE_KRB_GET_OUR_IP_FOR_REALM - if(krb_get_config_bool("nat_in_use")) { - struct sockaddr_in *localaddr = (struct sockaddr_in *)LOCAL_ADDR; - struct in_addr natAddr; - - if(krb_get_our_ip_for_realm(krb_realmofhost(host), - &natAddr) != KSUCCESS - && krb_get_our_ip_for_realm(NULL, &natAddr) != KSUCCESS) - infof(data, "Can't get address for realm %s\n", - krb_realmofhost(host)); - else { - if(natAddr.s_addr != localaddr->sin_addr.s_addr) { - char addr_buf[128]; - if(Curl_inet_ntop(AF_INET, natAddr, addr_buf, sizeof(addr_buf))) - infof(data, "Using NAT IP address (%s) for kerberos 4\n", addr_buf); - localaddr->sin_addr = natAddr; - } - } - } -#endif - - result = Curl_base64_encode(conn->data, (char *)adat.dat, adat.length, - &p, &base64_sz); - if(result) { - Curl_failf(data, "base64-encoding: %s", curl_easy_strerror(result)); - return AUTH_CONTINUE; - } - - result = Curl_ftpsendf(conn, "ADAT %s", p); - - free(p); - - if(result) - return -2; - - if(Curl_GetFTPResponse(&nread, conn, NULL)) - return -1; - - if(data->state.buffer[0] != '2') { - Curl_failf(data, "Server didn't accept auth data"); - return AUTH_ERROR; - } - - p = strstr(data->state.buffer, "ADAT="); - if(!p) { - Curl_failf(data, "Remote host didn't send adat reply"); - return AUTH_ERROR; - } - p += 5; - result = Curl_base64_decode(p, &ptr, &len); - if(result) { - Curl_failf(data, "base64-decoding: %s", curl_easy_strerror(result)); - return AUTH_ERROR; - } - if(len > sizeof(adat.dat)-1) { - free(ptr); - ptr = NULL; - len = 0; - } - if(!len || !ptr) { - Curl_failf(data, "Failed to decode base64 from server"); - return AUTH_ERROR; - } - memcpy((char *)adat.dat, ptr, len); - free(ptr); - adat.length = len; - ret = krb_rd_safe(adat.dat, adat.length, &d->key, - (struct sockaddr_in *)hisctladdr, - (struct sockaddr_in *)myctladdr, &msg_data); - if(ret) { - Curl_failf(data, "Error reading reply from server: %s", - krb_get_err_text(ret)); - return AUTH_ERROR; - } - krb_get_int(msg_data.app_data, &cs, 4, 0); - if(cs - checksum != 1) { - Curl_failf(data, "Bad checksum returned from server"); - return AUTH_ERROR; - } - return AUTH_OK; -} - -struct Curl_sec_client_mech Curl_krb4_client_mech = { - "KERBEROS_V4", - sizeof(struct krb4_data), - NULL, /* init */ - krb4_auth, - NULL, /* end */ - krb4_check_prot, - krb4_overhead, - krb4_encode, - krb4_decode -}; - -static enum protection_level -krb4_set_command_prot(struct connectdata *conn, enum protection_level level) -{ - enum protection_level old = conn->command_prot; - DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); - conn->command_prot = level; - return old; -} - -CURLcode Curl_krb_kauth(struct connectdata *conn) -{ - des_cblock key; - des_key_schedule schedule; - KTEXT_ST tkt, tktcopy; - char *name; - char *p; - char passwd[100]; - size_t tmp = 0; - ssize_t nread; - enum protection_level save; - CURLcode result; - unsigned char *ptr; - size_t base64_sz = 0; - - save = krb4_set_command_prot(conn, PROT_PRIVATE); - - result = Curl_ftpsendf(conn, "SITE KAUTH %s", conn->user); - - if(result) - return result; - - result = Curl_GetFTPResponse(&nread, conn, NULL); - if(result) - return result; - - if(conn->data->state.buffer[0] != '3') { - krb4_set_command_prot(conn, save); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - - p = strstr(conn->data->state.buffer, "T="); - if(!p) { - Curl_failf(conn->data, "Bad reply from server"); - krb4_set_command_prot(conn, save); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - - p += 2; - result = Curl_base64_decode(p, &ptr, &tmp); - if(result) { - Curl_failf(conn->data, "base64-decoding: %s", curl_easy_strerror(result)); - return result; - } - if(tmp >= sizeof(tkt.dat)) { - free(ptr); - ptr = NULL; - tmp = 0; - } - if(!tmp || !ptr) { - Curl_failf(conn->data, "Failed to decode base64 in reply"); - krb4_set_command_prot(conn, save); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - memcpy((char *)tkt.dat, ptr, tmp); - free(ptr); - tkt.length = tmp; - tktcopy.length = tkt.length; - - p = strstr(conn->data->state.buffer, "P="); - if(!p) { - Curl_failf(conn->data, "Bad reply from server"); - krb4_set_command_prot(conn, save); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - name = p + 2; - for(; *p && *p != ' ' && *p != '\r' && *p != '\n'; p++); - *p = 0; - - des_string_to_key (conn->passwd, &key); - des_key_sched(&key, schedule); - - des_pcbc_encrypt((void *)tkt.dat, (void *)tktcopy.dat, - tkt.length, - schedule, &key, DES_DECRYPT); - if(strcmp ((char*)tktcopy.dat + 8, - KRB_TICKET_GRANTING_TICKET) != 0) { - afs_string_to_key(passwd, - krb_realmofhost(conn->host.name), - &key); - des_key_sched(&key, schedule); - des_pcbc_encrypt((void *)tkt.dat, (void *)tktcopy.dat, - tkt.length, - schedule, &key, DES_DECRYPT); - } - memset(key, 0, sizeof(key)); - memset(schedule, 0, sizeof(schedule)); - memset(passwd, 0, sizeof(passwd)); - result = Curl_base64_encode(conn->data, (char *)tktcopy.dat, tktcopy.length, - &p, &base64_sz); - if(result) { - Curl_failf(conn->data, "base64-encoding: %s", curl_easy_strerror(result)); - krb4_set_command_prot(conn, save); - return result; - } - memset (tktcopy.dat, 0, tktcopy.length); - - result = Curl_ftpsendf(conn, "SITE KAUTH %s %s", name, p); - free(p); - if(result) - return result; - - result = Curl_GetFTPResponse(&nread, conn, NULL); - if(result) - return result; - krb4_set_command_prot(conn, save); - - return CURLE_OK; -} - -#endif /* HAVE_KRB4 */ -#endif /* CURL_DISABLE_FTP */ diff --git a/lib/krb5.c b/lib/krb5.c deleted file mode 100644 index d793fef02..000000000 --- a/lib/krb5.c +++ /dev/null @@ -1,341 +0,0 @@ -/* GSSAPI/krb5 support for FTP - loosely based on old curl_krb4.c - * - * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan - * (Royal Institute of Technology, Stockholm, Sweden). - * Copyright (c) 2004 - 2013 Daniel Stenberg - * 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 "curl_setup.h" - -#ifndef CURL_DISABLE_FTP -#ifdef HAVE_GSSAPI - -#ifdef HAVE_OLD_GSSMIT -#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name -#define NCOMPAT 1 -#endif - -#ifdef HAVE_NETDB_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_base64.h" -#include "curl_ftp.h" -#include "curl_gssapi.h" -#include "curl_sendf.h" -#include "curl_krb4.h" -#include "curl_memory.h" -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#define LOCAL_ADDR (&conn->local_addr) -#define REMOTE_ADDR conn->ip_addr->ai_addr - -static int -krb5_init(void *app_data) -{ - gss_ctx_id_t *context = app_data; - /* Make sure our context is initialized for krb5_end. */ - *context = GSS_C_NO_CONTEXT; - return 0; -} - -static int -krb5_check_prot(void *app_data, int level) -{ - (void)app_data; /* unused */ - if(level == PROT_CONFIDENTIAL) - return -1; - return 0; -} - -static int -krb5_decode(void *app_data, void *buf, int len, - int level UNUSED_PARAM, - struct connectdata *conn UNUSED_PARAM) -{ - gss_ctx_id_t *context = app_data; - OM_uint32 maj, min; - gss_buffer_desc enc, dec; - - (void)level; - (void)conn; - - 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 = curlx_uztosi(dec.length); - gss_release_buffer(&min, &dec); - - return len; -} - -static int -krb5_overhead(void *app_data, int level, int len) -{ - /* no arguments are used */ - (void)app_data; - (void)level; - (void)len; - return 0; -} - -static int -krb5_encode(void *app_data, const void *from, int length, int level, void **to, - struct connectdata *conn UNUSED_PARAM) -{ - gss_ctx_id_t *context = app_data; - gss_buffer_desc dec, enc; - OM_uint32 maj, min; - int state; - int len; - - /* shut gcc up */ - conn = NULL; - - /* NOTE that the cast is safe, neither of the krb5, gnu gss and heimdal - * libraries modify the input buffer in gss_seal() - */ - dec.value = (void*)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 = curlx_uztosi(enc.length); - gss_release_buffer(&min, &enc); - return len; -} - -static int -krb5_auth(void *app_data, struct connectdata *conn) -{ - int ret = AUTH_OK; - char *p; - const char *host = conn->host.name; - ssize_t nread; - curl_socklen_t l = sizeof(conn->local_addr); - struct SessionHandle *data = conn->data; - CURLcode result; - const char *service = "ftp", *srv_host = "host"; - gss_buffer_desc input_buffer, output_buffer, _gssresp, *gssresp; - OM_uint32 maj, min; - gss_name_t gssname; - gss_ctx_id_t *context = app_data; - struct gss_channel_bindings_struct chan; - size_t base64_sz = 0; - - 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) */ - for(;;) { - /* 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; - } - - input_buffer.value = data->state.buffer; - input_buffer.length = snprintf(input_buffer.value, BUFSIZE, "%s@%s", - service, host); - maj = gss_import_name(&min, &input_buffer, GSS_C_NT_HOSTBASED_SERVICE, - &gssname); - if(maj != GSS_S_COMPLETE) { - gss_release_name(&min, &gssname); - if(service == srv_host) { - Curl_failf(data, "Error importing service name %s", - input_buffer.value); - return AUTH_ERROR; - } - service = srv_host; - continue; - } - /* We pass NULL as |output_name_type| to avoid a leak. */ - gss_display_name(&min, gssname, &output_buffer, NULL); - Curl_infof(data, "Trying against %s\n", output_buffer.value); - gssresp = GSS_C_NO_BUFFER; - *context = GSS_C_NO_CONTEXT; - - do { - /* Release the buffer at each iteration to avoid leaking: the first time - we are releasing the memory from gss_display_name. The last item is - taken care by a final gss_release_buffer. */ - gss_release_buffer(&min, &output_buffer); - ret = AUTH_OK; - maj = Curl_gss_init_sec_context(data, - &min, - context, - gssname, - &chan, - gssresp, - &output_buffer, - NULL); - - if(gssresp) { - free(_gssresp.value); - gssresp = NULL; - } - - if(GSS_ERROR(maj)) { - Curl_infof(data, "Error creating security context\n"); - ret = AUTH_ERROR; - break; - } - - if(output_buffer.length != 0) { - result = Curl_base64_encode(data, (char *)output_buffer.value, - output_buffer.length, &p, &base64_sz); - if(result) { - Curl_infof(data,"base64-encoding: %s\n", curl_easy_strerror(result)); - 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) { - result = Curl_base64_decode(p + 5, - (unsigned char **)&_gssresp.value, - &_gssresp.length); - if(result) { - Curl_failf(data,"base64-decoding: %s", curl_easy_strerror(result)); - ret = AUTH_CONTINUE; - break; - } - } - - gssresp = &_gssresp; - } - } while(maj == GSS_S_CONTINUE_NEEDED); - - gss_release_name(&min, &gssname); - gss_release_buffer(&min, &output_buffer); - - if(gssresp) - free(_gssresp.value); - - if(ret == AUTH_OK || service == srv_host) - return ret; - - service = srv_host; - } - return ret; -} - -static void krb5_end(void *app_data) -{ - OM_uint32 min; - gss_ctx_id_t *context = app_data; - if(*context != GSS_C_NO_CONTEXT) { -#ifdef DEBUGBUILD - OM_uint32 maj = -#endif - gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER); - DEBUGASSERT(maj == GSS_S_COMPLETE); - } -} - -struct Curl_sec_client_mech Curl_krb5_client_mech = { - "GSSAPI", - sizeof(gss_ctx_id_t), - krb5_init, - krb5_auth, - krb5_end, - krb5_check_prot, - krb5_overhead, - krb5_encode, - krb5_decode -}; - -#endif /* HAVE_GSSAPI */ -#endif /* CURL_DISABLE_FTP */ diff --git a/lib/ldap.c b/lib/ldap.c deleted file mode 100644 index 59f3b832e..000000000 --- a/lib/ldap.c +++ /dev/null @@ -1,725 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_LDAP) && !defined(USE_OPENLDAP) - -/* - * Notice that USE_OPENLDAP is only a source code selection switch. When - * libcurl is built with USE_OPENLDAP defined the libcurl source code that - * gets compiled is the code from curl_openldap.c, otherwise the code that - * gets compiled is the code from curl_ldap.c. - * - * When USE_OPENLDAP is defined a recent version of the OpenLDAP library - * might be required for compilation and runtime. In order to use ancient - * OpenLDAP library versions, USE_OPENLDAP shall not be defined. - */ - -#ifdef CURL_LDAP_WIN /* Use Windows LDAP implementation. */ -# include -# ifndef LDAP_VENDOR_NAME -# error Your Platform SDK is NOT sufficient for LDAP support! \ - Update your Platform SDK, or disable LDAP support! -# else -# include -# endif -#else -# define LDAP_DEPRECATED 1 /* Be sure ldap_init() is defined. */ -# ifdef HAVE_LBER_H -# include -# endif -# include -# if (defined(HAVE_LDAP_SSL) && defined(HAVE_LDAP_SSL_H)) -# include -# endif /* HAVE_LDAP_SSL && HAVE_LDAP_SSL_H */ -#endif - -#include "curl_urldata.h" -#include -#include "curl_sendf.h" -#include "curl_escape.h" -#include "curl_progress.h" -#include "curl_transfer.h" -#include "curl_strequal.h" -#include "curl_strtok.h" -#include "curl_ldap.h" -#include "curl_memory.h" -#include "curl_base64.h" -#include "curl_rawstr.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memdebug.h" - -#ifndef HAVE_LDAP_URL_PARSE - -/* Use our own implementation. */ - -typedef struct { - char *lud_host; - int lud_port; - char *lud_dn; - char **lud_attrs; - int lud_scope; - char *lud_filter; - char **lud_exts; -} CURL_LDAPURLDesc; - -#undef LDAPURLDesc -#define LDAPURLDesc CURL_LDAPURLDesc - -static int _ldap_url_parse (const struct connectdata *conn, - LDAPURLDesc **ludp); -static void _ldap_free_urldesc (LDAPURLDesc *ludp); - -#undef ldap_free_urldesc -#define ldap_free_urldesc _ldap_free_urldesc -#endif - -#ifdef DEBUG_LDAP - #define LDAP_TRACE(x) do { \ - _ldap_trace ("%u: ", __LINE__); \ - _ldap_trace x; \ - } WHILE_FALSE - - static void _ldap_trace (const char *fmt, ...); -#else - #define LDAP_TRACE(x) Curl_nop_stmt -#endif - - -static CURLcode Curl_ldap(struct connectdata *conn, bool *done); - -/* - * LDAP protocol handler. - */ - -const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_ldap, /* do_it */ - ZERO_NULL, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_LDAP, /* defport */ - CURLPROTO_LDAP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -#ifdef HAVE_LDAP_SSL -/* - * LDAPS protocol handler. - */ - -const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_ldap, /* do_it */ - ZERO_NULL, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_LDAPS, /* defport */ - CURLPROTO_LDAP | CURLPROTO_LDAPS, /* protocol */ - PROTOPT_SSL /* flags */ -}; -#endif - - -static CURLcode Curl_ldap(struct connectdata *conn, bool *done) -{ - CURLcode status = CURLE_OK; - int rc = 0; - LDAP *server = NULL; - LDAPURLDesc *ludp = NULL; - LDAPMessage *result = NULL; - LDAPMessage *entryIterator; - int num = 0; - struct SessionHandle *data=conn->data; - int ldap_proto = LDAP_VERSION3; - int ldap_ssl = 0; - char *val_b64 = NULL; - size_t val_b64_sz = 0; - curl_off_t dlsize = 0; -#ifdef LDAP_OPT_NETWORK_TIMEOUT - struct timeval ldap_timeout = {10,0}; /* 10 sec connection/search timeout */ -#endif - - *done = TRUE; /* unconditionally */ - infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d\n", - LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); - infof(data, "LDAP local: %s\n", data->change.url); - -#ifdef HAVE_LDAP_URL_PARSE - rc = ldap_url_parse(data->change.url, &ludp); -#else - rc = _ldap_url_parse(conn, &ludp); -#endif - if(rc != 0) { - failf(data, "LDAP local: %s", ldap_err2string(rc)); - status = CURLE_LDAP_INVALID_URL; - goto quit; - } - - /* Get the URL scheme ( either ldap or ldaps ) */ - if(conn->given->flags & PROTOPT_SSL) - ldap_ssl = 1; - infof(data, "LDAP local: trying to establish %s connection\n", - ldap_ssl ? "encrypted" : "cleartext"); - -#ifdef LDAP_OPT_NETWORK_TIMEOUT - ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &ldap_timeout); -#endif - ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); - - if(ldap_ssl) { -#ifdef HAVE_LDAP_SSL -#ifdef CURL_LDAP_WIN - /* Win32 LDAP SDK doesn't support insecure mode without CA! */ - server = ldap_sslinit(conn->host.name, (int)conn->port, 1); - ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON); -#else - int ldap_option; - char* ldap_ca = data->set.str[STRING_SSL_CAFILE]; -#if defined(CURL_HAS_NOVELL_LDAPSDK) - rc = ldapssl_client_init(NULL, NULL); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ldapssl_client_init %s", ldap_err2string(rc)); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } - if(data->set.ssl.verifypeer) { - /* Novell SDK supports DER or BASE64 files. */ - int cert_type = LDAPSSL_CERT_FILETYPE_B64; - if((data->set.str[STRING_CERT_TYPE]) && - (Curl_raw_equal(data->set.str[STRING_CERT_TYPE], "DER"))) - cert_type = LDAPSSL_CERT_FILETYPE_DER; - if(!ldap_ca) { - failf(data, "LDAP local: ERROR %s CA cert not set!", - (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM")); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } - infof(data, "LDAP local: using %s CA cert '%s'\n", - (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), - ldap_ca); - rc = ldapssl_add_trusted_cert(ldap_ca, cert_type); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ERROR setting %s CA cert: %s", - (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), - ldap_err2string(rc)); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } - ldap_option = LDAPSSL_VERIFY_SERVER; - } - else - ldap_option = LDAPSSL_VERIFY_NONE; - rc = ldapssl_set_verify_mode(ldap_option); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ERROR setting cert verify mode: %s", - ldap_err2string(rc)); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } - server = ldapssl_init(conn->host.name, (int)conn->port, 1); - if(server == NULL) { - failf(data, "LDAP local: Cannot connect to %s:%hu", - conn->host.name, conn->port); - status = CURLE_COULDNT_CONNECT; - goto quit; - } -#elif defined(LDAP_OPT_X_TLS) - if(data->set.ssl.verifypeer) { - /* OpenLDAP SDK supports BASE64 files. */ - if((data->set.str[STRING_CERT_TYPE]) && - (!Curl_raw_equal(data->set.str[STRING_CERT_TYPE], "PEM"))) { - failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type!"); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } - if(!ldap_ca) { - failf(data, "LDAP local: ERROR PEM CA cert not set!"); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } - infof(data, "LDAP local: using PEM CA cert: %s\n", ldap_ca); - rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ldap_ca); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ERROR setting PEM CA cert: %s", - ldap_err2string(rc)); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } - ldap_option = LDAP_OPT_X_TLS_DEMAND; - } - else - ldap_option = LDAP_OPT_X_TLS_NEVER; - - rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, &ldap_option); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ERROR setting cert verify mode: %s", - ldap_err2string(rc)); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } - server = ldap_init(conn->host.name, (int)conn->port); - if(server == NULL) { - failf(data, "LDAP local: Cannot connect to %s:%hu", - conn->host.name, conn->port); - status = CURLE_COULDNT_CONNECT; - goto quit; - } - ldap_option = LDAP_OPT_X_TLS_HARD; - rc = ldap_set_option(server, LDAP_OPT_X_TLS, &ldap_option); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ERROR setting SSL/TLS mode: %s", - ldap_err2string(rc)); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } -/* - rc = ldap_start_tls_s(server, NULL, NULL); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ERROR starting SSL/TLS mode: %s", - ldap_err2string(rc)); - status = CURLE_SSL_CERTPROBLEM; - goto quit; - } -*/ -#else - /* we should probably never come up to here since configure - should check in first place if we can support LDAP SSL/TLS */ - failf(data, "LDAP local: SSL/TLS not supported with this version " - "of the OpenLDAP toolkit\n"); - status = CURLE_SSL_CERTPROBLEM; - goto quit; -#endif -#endif -#endif /* CURL_LDAP_USE_SSL */ - } - else { - server = ldap_init(conn->host.name, (int)conn->port); - if(server == NULL) { - failf(data, "LDAP local: Cannot connect to %s:%hu", - conn->host.name, conn->port); - status = CURLE_COULDNT_CONNECT; - goto quit; - } - } -#ifdef CURL_LDAP_WIN - ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); -#endif - - rc = ldap_simple_bind_s(server, - conn->bits.user_passwd ? conn->user : NULL, - conn->bits.user_passwd ? conn->passwd : NULL); - if(!ldap_ssl && rc != 0) { - ldap_proto = LDAP_VERSION2; - ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); - rc = ldap_simple_bind_s(server, - conn->bits.user_passwd ? conn->user : NULL, - conn->bits.user_passwd ? conn->passwd : NULL); - } - if(rc != 0) { - failf(data, "LDAP local: ldap_simple_bind_s %s", ldap_err2string(rc)); - status = CURLE_LDAP_CANNOT_BIND; - goto quit; - } - - rc = ldap_search_s(server, ludp->lud_dn, ludp->lud_scope, - ludp->lud_filter, ludp->lud_attrs, 0, &result); - - if(rc != 0 && rc != LDAP_SIZELIMIT_EXCEEDED) { - failf(data, "LDAP remote: %s", ldap_err2string(rc)); - status = CURLE_LDAP_SEARCH_FAILED; - goto quit; - } - - for(num = 0, entryIterator = ldap_first_entry(server, result); - entryIterator; - entryIterator = ldap_next_entry(server, entryIterator), num++) { - BerElement *ber = NULL; - char *attribute; /*! suspicious that this isn't 'const' */ - char *dn = ldap_get_dn(server, entryIterator); - int i; - - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)dn, 0); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); - - dlsize += strlen(dn)+5; - - for(attribute = ldap_first_attribute(server, entryIterator, &ber); - attribute; - attribute = ldap_next_attribute(server, entryIterator, ber)) { - BerValue **vals = ldap_get_values_len(server, entryIterator, attribute); - - if(vals != NULL) { - for(i = 0; (vals[i] != NULL); i++) { - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *) attribute, 0); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)": ", 2); - dlsize += strlen(attribute)+3; - - if((strlen(attribute) > 7) && - (strcmp(";binary", - (char *)attribute + - (strlen((char *)attribute) - 7)) == 0)) { - /* Binary attribute, encode to base64. */ - CURLcode error = Curl_base64_encode(data, - vals[i]->bv_val, - vals[i]->bv_len, - &val_b64, - &val_b64_sz); - if(error) { - ldap_value_free_len(vals); - ldap_memfree(attribute); - ldap_memfree(dn); - if(ber) - ber_free(ber, 0); - status = error; - goto quit; - } - if(val_b64_sz > 0) { - Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, val_b64_sz); - free(val_b64); - dlsize += val_b64_sz; - } - } - else { - Curl_client_write(conn, CLIENTWRITE_BODY, vals[i]->bv_val, - vals[i]->bv_len); - dlsize += vals[i]->bv_len; - } - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); - dlsize++; - } - - /* Free memory used to store values */ - ldap_value_free_len(vals); - } - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); - dlsize++; - Curl_pgrsSetDownloadCounter(data, dlsize); - ldap_memfree(attribute); - } - ldap_memfree(dn); - if(ber) - ber_free(ber, 0); - } - -quit: - if(result) { - ldap_msgfree(result); - LDAP_TRACE (("Received %d entries\n", num)); - } - if(rc == LDAP_SIZELIMIT_EXCEEDED) - infof(data, "There are more than %d entries\n", num); - if(ludp) - ldap_free_urldesc(ludp); - if(server) - ldap_unbind_s(server); -#if defined(HAVE_LDAP_SSL) && defined(CURL_HAS_NOVELL_LDAPSDK) - if(ldap_ssl) - ldapssl_client_deinit(); -#endif /* HAVE_LDAP_SSL && CURL_HAS_NOVELL_LDAPSDK */ - - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - conn->bits.close = TRUE; - - return status; -} - -#ifdef DEBUG_LDAP -static void _ldap_trace (const char *fmt, ...) -{ - static int do_trace = -1; - va_list args; - - if(do_trace == -1) { - const char *env = getenv("CURL_TRACE"); - do_trace = (env && strtol(env, NULL, 10) > 0); - } - if(!do_trace) - return; - - va_start (args, fmt); - vfprintf (stderr, fmt, args); - va_end (args); -} -#endif - -#ifndef HAVE_LDAP_URL_PARSE - -/* - * Return scope-value for a scope-string. - */ -static int str2scope (const char *p) -{ - if(strequal(p, "one")) - return LDAP_SCOPE_ONELEVEL; - if(strequal(p, "onetree")) - return LDAP_SCOPE_ONELEVEL; - if(strequal(p, "base")) - return LDAP_SCOPE_BASE; - if(strequal(p, "sub")) - return LDAP_SCOPE_SUBTREE; - if(strequal( p, "subtree")) - return LDAP_SCOPE_SUBTREE; - return (-1); -} - -/* - * Split 'str' into strings separated by commas. - * Note: res[] points into 'str'. - */ -static char **split_str (char *str) -{ - char **res, *lasts, *s; - int i; - - for(i = 2, s = strchr(str,','); s; i++) - s = strchr(++s,','); - - res = calloc(i, sizeof(char*)); - if(!res) - return NULL; - - for(i = 0, s = strtok_r(str, ",", &lasts); s; - s = strtok_r(NULL, ",", &lasts), i++) - res[i] = s; - return res; -} - -/* - * Unescape the LDAP-URL components - */ -static bool unescape_elements (void *data, LDAPURLDesc *ludp) -{ - int i; - - if(ludp->lud_filter) { - ludp->lud_filter = curl_easy_unescape(data, ludp->lud_filter, 0, NULL); - if(!ludp->lud_filter) - return (FALSE); - } - - for(i = 0; ludp->lud_attrs && ludp->lud_attrs[i]; i++) { - ludp->lud_attrs[i] = curl_easy_unescape(data, ludp->lud_attrs[i], 0, NULL); - if(!ludp->lud_attrs[i]) - return (FALSE); - } - - for(i = 0; ludp->lud_exts && ludp->lud_exts[i]; i++) { - ludp->lud_exts[i] = curl_easy_unescape(data, ludp->lud_exts[i], 0, NULL); - if(!ludp->lud_exts[i]) - return (FALSE); - } - - if(ludp->lud_dn) { - char *dn = ludp->lud_dn; - char *new_dn = curl_easy_unescape(data, dn, 0, NULL); - - free(dn); - ludp->lud_dn = new_dn; - if(!new_dn) - return (FALSE); - } - return (TRUE); -} - -/* - * Break apart the pieces of an LDAP URL. - * Syntax: - * ldap://:/???? - * - * already known from 'conn->host.name'. - * already known from 'conn->remote_port'. - * extract the rest from 'conn->data->state.path+1'. All fields are optional. - * e.g. - * ldap://:/??? - * yields ludp->lud_dn = "". - * - * Defined in RFC4516 section 2. - */ -static int _ldap_url_parse2 (const struct connectdata *conn, LDAPURLDesc *ludp) -{ - char *p, *q; - int i; - - if(!conn->data || - !conn->data->state.path || - conn->data->state.path[0] != '/' || - !checkprefix("LDAP", conn->data->change.url)) - return LDAP_INVALID_SYNTAX; - - ludp->lud_scope = LDAP_SCOPE_BASE; - ludp->lud_port = conn->remote_port; - ludp->lud_host = conn->host.name; - - /* parse DN (Distinguished Name). - */ - ludp->lud_dn = strdup(conn->data->state.path+1); - if(!ludp->lud_dn) - return LDAP_NO_MEMORY; - - p = strchr(ludp->lud_dn, '?'); - LDAP_TRACE (("DN '%.*s'\n", p ? (size_t)(p-ludp->lud_dn) : - strlen(ludp->lud_dn), ludp->lud_dn)); - - if(!p) - goto success; - - *p++ = '\0'; - - /* parse attributes. skip "??". - */ - q = strchr(p, '?'); - if(q) - *q++ = '\0'; - - if(*p && *p != '?') { - ludp->lud_attrs = split_str(p); - if(!ludp->lud_attrs) - return LDAP_NO_MEMORY; - - for(i = 0; ludp->lud_attrs[i]; i++) - LDAP_TRACE (("attr[%d] '%s'\n", i, ludp->lud_attrs[i])); - } - - p = q; - if(!p) - goto success; - - /* parse scope. skip "??" - */ - q = strchr(p, '?'); - if(q) - *q++ = '\0'; - - if(*p && *p != '?') { - ludp->lud_scope = str2scope(p); - if(ludp->lud_scope == -1) - return LDAP_INVALID_SYNTAX; - LDAP_TRACE (("scope %d\n", ludp->lud_scope)); - } - - p = q; - if(!p) - goto success; - - /* parse filter - */ - q = strchr(p, '?'); - if(q) - *q++ = '\0'; - if(!*p) - return LDAP_INVALID_SYNTAX; - - ludp->lud_filter = p; - LDAP_TRACE (("filter '%s'\n", ludp->lud_filter)); - - p = q; - if(!p) - goto success; - - /* parse extensions - */ - ludp->lud_exts = split_str(p); - if(!ludp->lud_exts) - return LDAP_NO_MEMORY; - - for(i = 0; ludp->lud_exts[i]; i++) - LDAP_TRACE (("exts[%d] '%s'\n", i, ludp->lud_exts[i])); - - success: - if(!unescape_elements(conn->data, ludp)) - return LDAP_NO_MEMORY; - return LDAP_SUCCESS; -} - -static int _ldap_url_parse (const struct connectdata *conn, - LDAPURLDesc **ludpp) -{ - LDAPURLDesc *ludp = calloc(1, sizeof(*ludp)); - int rc; - - *ludpp = NULL; - if(!ludp) - return LDAP_NO_MEMORY; - - rc = _ldap_url_parse2 (conn, ludp); - if(rc != LDAP_SUCCESS) { - _ldap_free_urldesc(ludp); - ludp = NULL; - } - *ludpp = ludp; - return (rc); -} - -static void _ldap_free_urldesc (LDAPURLDesc *ludp) -{ - int i; - - if(!ludp) - return; - - if(ludp->lud_dn) - free(ludp->lud_dn); - - if(ludp->lud_filter) - free(ludp->lud_filter); - - if(ludp->lud_attrs) { - for(i = 0; ludp->lud_attrs[i]; i++) - free(ludp->lud_attrs[i]); - free(ludp->lud_attrs); - } - - if(ludp->lud_exts) { - for(i = 0; ludp->lud_exts[i]; i++) - free(ludp->lud_exts[i]); - free(ludp->lud_exts); - } - free (ludp); -} -#endif /* !HAVE_LDAP_URL_PARSE */ -#endif /* !CURL_DISABLE_LDAP && !USE_OPENLDAP */ diff --git a/lib/llist.c b/lib/llist.c deleted file mode 100644 index 46a8d9960..000000000 --- a/lib/llist.c +++ /dev/null @@ -1,212 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_llist.h" -#include "curl_memory.h" - -/* this must be the last include file */ -#include "curl_memdebug.h" - -/* - * @unittest: 1300 - */ -static void -llist_init(struct curl_llist *l, curl_llist_dtor dtor) -{ - l->size = 0; - l->dtor = dtor; - l->head = NULL; - l->tail = NULL; -} - -struct curl_llist * -Curl_llist_alloc(curl_llist_dtor dtor) -{ - struct curl_llist *list; - - list = malloc(sizeof(struct curl_llist)); - if(!list) - return NULL; - - llist_init(list, dtor); - - return list; -} - -/* - * Curl_llist_insert_next() - * - * Inserts a new list element after the given one 'e'. If the given existing - * entry is NULL and the list already has elements, the new one will be - * inserted first in the list. - * - * Returns: 1 on success and 0 on failure. - * - * @unittest: 1300 - */ -int -Curl_llist_insert_next(struct curl_llist *list, struct curl_llist_element *e, - const void *p) -{ - struct curl_llist_element *ne = malloc(sizeof(struct curl_llist_element)); - if(!ne) - return 0; - - ne->ptr = (void *) p; - if(list->size == 0) { - list->head = ne; - list->head->prev = NULL; - list->head->next = NULL; - list->tail = ne; - } - else { - /* if 'e' is NULL here, we insert the new element first in the list */ - ne->next = e?e->next:list->head; - ne->prev = e; - if(!e) { - list->head->prev = ne; - list->head = ne; - } - else if(e->next) { - e->next->prev = ne; - } - else { - list->tail = ne; - } - if(e) - e->next = ne; - } - - ++list->size; - - return 1; -} - -/* - * @unittest: 1300 - */ -int -Curl_llist_remove(struct curl_llist *list, struct curl_llist_element *e, - void *user) -{ - if(e == NULL || list->size == 0) - return 1; - - if(e == list->head) { - list->head = e->next; - - if(list->head == NULL) - list->tail = NULL; - else - e->next->prev = NULL; - } - else { - e->prev->next = e->next; - if(!e->next) - list->tail = e->prev; - else - e->next->prev = e->prev; - } - - list->dtor(user, e->ptr); - - e->ptr = NULL; - e->prev = NULL; - e->next = NULL; - - free(e); - --list->size; - - return 1; -} - -void -Curl_llist_destroy(struct curl_llist *list, void *user) -{ - if(list) { - while(list->size > 0) - Curl_llist_remove(list, list->tail, user); - - free(list); - } -} - -size_t -Curl_llist_count(struct curl_llist *list) -{ - return list->size; -} - -/* - * @unittest: 1300 - */ -int Curl_llist_move(struct curl_llist *list, struct curl_llist_element *e, - struct curl_llist *to_list, - struct curl_llist_element *to_e) -{ - /* Remove element from list */ - if(e == NULL || list->size == 0) - return 0; - - if(e == list->head) { - list->head = e->next; - - if(list->head == NULL) - list->tail = NULL; - else - e->next->prev = NULL; - } - else { - e->prev->next = e->next; - if(!e->next) - list->tail = e->prev; - else - e->next->prev = e->prev; - } - - --list->size; - - /* Add element to to_list after to_e */ - if(to_list->size == 0) { - to_list->head = e; - to_list->head->prev = NULL; - to_list->head->next = NULL; - to_list->tail = e; - } - else { - e->next = to_e->next; - e->prev = to_e; - if(to_e->next) { - to_e->next->prev = e; - } - else { - to_list->tail = e; - } - to_e->next = e; - } - - ++to_list->size; - - return 1; -} diff --git a/lib/md4.c b/lib/md4.c deleted file mode 100644 index d64b472ea..000000000 --- a/lib/md4.c +++ /dev/null @@ -1,282 +0,0 @@ -/*- - Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. - - License to copy and use this software is granted provided that it - is identified as the "RSA Data Security, Inc. MD4 Message-Digest - Algorithm" in all material mentioning or referencing this software - or this function. - - License is also granted to make and use derivative works provided - that such works are identified as "derived from the RSA Data - Security, Inc. MD4 Message-Digest Algorithm" in all material - mentioning or referencing the derived work. - - RSA Data Security, Inc. makes no representations concerning either - the merchantability of this software or the suitability of this - software for any particular purpose. It is provided "as is" - without express or implied warranty of any kind. - - These notices must be retained in any copies of any part of this - documentation and/or software. - */ - -#include "curl_setup.h" - -/* NSS crypto library does not provide the MD4 hash algorithm, so that we have - * a local implementation of it */ -#ifdef USE_NSS - -#include "curl_md4.h" -#include "curl_warnless.h" - -typedef unsigned int UINT4; - -typedef struct MD4Context { - UINT4 state[4]; /* state (ABCD) */ - UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ - unsigned char buffer[64]; /* input buffer */ -} MD4_CTX; - -/* Constants for MD4Transform routine. - */ -#define S11 3 -#define S12 7 -#define S13 11 -#define S14 19 -#define S21 3 -#define S22 5 -#define S23 9 -#define S24 13 -#define S31 3 -#define S32 9 -#define S33 11 -#define S34 15 - -static void MD4Transform(UINT4 [4], const unsigned char [64]); -static void Encode(unsigned char *, UINT4 *, unsigned int); -static void Decode(UINT4 *, const unsigned char *, unsigned int); - -static unsigned char PADDING[64] = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -/* F, G and H are basic MD4 functions. - */ -#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) -#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) -#define H(x, y, z) ((x) ^ (y) ^ (z)) - -/* ROTATE_LEFT rotates x left n bits. - */ -#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) - -/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ -/* Rotation is separate from addition to prevent recomputation */ -#define FF(a, b, c, d, x, s) { \ - (a) += F ((b), (c), (d)) + (x); \ - (a) = ROTATE_LEFT ((a), (s)); \ - } -#define GG(a, b, c, d, x, s) { \ - (a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; \ - (a) = ROTATE_LEFT ((a), (s)); \ - } -#define HH(a, b, c, d, x, s) { \ - (a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; \ - (a) = ROTATE_LEFT ((a), (s)); \ - } - -/* MD4 initialization. Begins an MD4 operation, writing a new context. - */ -static void MD4Init(MD4_CTX *context) -{ - context->count[0] = context->count[1] = 0; - - /* Load magic initialization constants. - */ - context->state[0] = 0x67452301; - context->state[1] = 0xefcdab89; - context->state[2] = 0x98badcfe; - context->state[3] = 0x10325476; -} - -/* MD4 block update operation. Continues an MD4 message-digest - operation, processing another message block, and updating the - context. - */ -static void MD4Update(MD4_CTX *context, const unsigned char *input, - unsigned int inputLen) -{ - unsigned int i, bufindex, partLen; - - /* Compute number of bytes mod 64 */ - bufindex = (unsigned int)((context->count[0] >> 3) & 0x3F); - /* Update number of bits */ - if((context->count[0] += ((UINT4)inputLen << 3)) - < ((UINT4)inputLen << 3)) - context->count[1]++; - context->count[1] += ((UINT4)inputLen >> 29); - - partLen = 64 - bufindex; - /* Transform as many times as possible. - */ - if(inputLen >= partLen) { - memcpy(&context->buffer[bufindex], input, partLen); - MD4Transform (context->state, context->buffer); - - for(i = partLen; i + 63 < inputLen; i += 64) - MD4Transform (context->state, &input[i]); - - bufindex = 0; - } - else - i = 0; - - /* Buffer remaining input */ - memcpy(&context->buffer[bufindex], &input[i], inputLen-i); -} - -/* MD4 padding. */ -static void MD4Pad(MD4_CTX *context) -{ - unsigned char bits[8]; - unsigned int bufindex, padLen; - - /* Save number of bits */ - Encode (bits, context->count, 8); - - /* Pad out to 56 mod 64. - */ - bufindex = (unsigned int)((context->count[0] >> 3) & 0x3f); - padLen = (bufindex < 56) ? (56 - bufindex) : (120 - bufindex); - MD4Update (context, PADDING, padLen); - - /* Append length (before padding) */ - MD4Update (context, bits, 8); -} - -/* MD4 finalization. Ends an MD4 message-digest operation, writing the - the message digest and zeroizing the context. - */ -static void MD4Final (unsigned char digest[16], MD4_CTX *context) -{ - /* Do padding */ - MD4Pad (context); - - /* Store state in digest */ - Encode (digest, context->state, 16); - - /* Zeroize sensitive information. - */ - memset(context, 0, sizeof(*context)); -} - -/* MD4 basic transformation. Transforms state based on block. - */ -static void MD4Transform (UINT4 state[4], const unsigned char block[64]) -{ - UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; - - Decode (x, block, 64); - - /* Round 1 */ - FF (a, b, c, d, x[ 0], S11); /* 1 */ - FF (d, a, b, c, x[ 1], S12); /* 2 */ - FF (c, d, a, b, x[ 2], S13); /* 3 */ - FF (b, c, d, a, x[ 3], S14); /* 4 */ - FF (a, b, c, d, x[ 4], S11); /* 5 */ - FF (d, a, b, c, x[ 5], S12); /* 6 */ - FF (c, d, a, b, x[ 6], S13); /* 7 */ - FF (b, c, d, a, x[ 7], S14); /* 8 */ - FF (a, b, c, d, x[ 8], S11); /* 9 */ - FF (d, a, b, c, x[ 9], S12); /* 10 */ - FF (c, d, a, b, x[10], S13); /* 11 */ - FF (b, c, d, a, x[11], S14); /* 12 */ - FF (a, b, c, d, x[12], S11); /* 13 */ - FF (d, a, b, c, x[13], S12); /* 14 */ - FF (c, d, a, b, x[14], S13); /* 15 */ - FF (b, c, d, a, x[15], S14); /* 16 */ - - /* Round 2 */ - GG (a, b, c, d, x[ 0], S21); /* 17 */ - GG (d, a, b, c, x[ 4], S22); /* 18 */ - GG (c, d, a, b, x[ 8], S23); /* 19 */ - GG (b, c, d, a, x[12], S24); /* 20 */ - GG (a, b, c, d, x[ 1], S21); /* 21 */ - GG (d, a, b, c, x[ 5], S22); /* 22 */ - GG (c, d, a, b, x[ 9], S23); /* 23 */ - GG (b, c, d, a, x[13], S24); /* 24 */ - GG (a, b, c, d, x[ 2], S21); /* 25 */ - GG (d, a, b, c, x[ 6], S22); /* 26 */ - GG (c, d, a, b, x[10], S23); /* 27 */ - GG (b, c, d, a, x[14], S24); /* 28 */ - GG (a, b, c, d, x[ 3], S21); /* 29 */ - GG (d, a, b, c, x[ 7], S22); /* 30 */ - GG (c, d, a, b, x[11], S23); /* 31 */ - GG (b, c, d, a, x[15], S24); /* 32 */ - - /* Round 3 */ - HH (a, b, c, d, x[ 0], S31); /* 33 */ - HH (d, a, b, c, x[ 8], S32); /* 34 */ - HH (c, d, a, b, x[ 4], S33); /* 35 */ - HH (b, c, d, a, x[12], S34); /* 36 */ - HH (a, b, c, d, x[ 2], S31); /* 37 */ - HH (d, a, b, c, x[10], S32); /* 38 */ - HH (c, d, a, b, x[ 6], S33); /* 39 */ - HH (b, c, d, a, x[14], S34); /* 40 */ - HH (a, b, c, d, x[ 1], S31); /* 41 */ - HH (d, a, b, c, x[ 9], S32); /* 42 */ - HH (c, d, a, b, x[ 5], S33); /* 43 */ - HH (b, c, d, a, x[13], S34); /* 44 */ - HH (a, b, c, d, x[ 3], S31); /* 45 */ - HH (d, a, b, c, x[11], S32); /* 46 */ - HH (c, d, a, b, x[ 7], S33); /* 47 */ - HH (b, c, d, a, x[15], S34); /* 48 */ - - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - - /* Zeroize sensitive information. - */ - memset(x, 0, sizeof(x)); -} - -/* Encodes input (UINT4) into output (unsigned char). Assumes len is - a multiple of 4. - */ -static void Encode(unsigned char *output, UINT4 *input, unsigned int len) -{ - unsigned int i, j; - - for(i = 0, j = 0; j < len; i++, j += 4) { - output[j] = (unsigned char)(input[i] & 0xff); - output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); - output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); - output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); - } -} - -/* Decodes input (unsigned char) into output (UINT4). Assumes len is - a multiple of 4. - */ -static void Decode (UINT4 *output, const unsigned char *input, - unsigned int len) -{ - unsigned int i, j; - - for(i = 0, j = 0; j < len; i++, j += 4) - output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | - (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); -} - -void Curl_md4it(unsigned char *output, const unsigned char *input, size_t len) -{ - MD4_CTX ctx; - MD4Init(&ctx); - MD4Update(&ctx, input, curlx_uztoui(len)); - MD4Final(output, &ctx); -} -#endif /* USE_NSS */ diff --git a/lib/md5.c b/lib/md5.c deleted file mode 100644 index 74f53f61b..000000000 --- a/lib/md5.c +++ /dev/null @@ -1,521 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_CRYPTO_AUTH - -#include "curl_md5.h" -#include "curl_hmac.h" -#include "curl_warnless.h" - -#include "curl_memory.h" - -#if defined(USE_GNUTLS_NETTLE) - -#include -/* The last #include file should be: */ -#include "curl_memdebug.h" - -typedef struct md5_ctx MD5_CTX; - -static void MD5_Init(MD5_CTX * ctx) -{ - md5_init(ctx); -} - -static void MD5_Update(MD5_CTX * ctx, - const unsigned char * input, - unsigned int inputLen) -{ - md5_update(ctx, inputLen, input); -} - -static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx) -{ - md5_digest(ctx, 16, digest); -} - -#elif defined(USE_GNUTLS) - -#include -/* The last #include file should be: */ -#include "curl_memdebug.h" - -typedef gcry_md_hd_t MD5_CTX; - -static void MD5_Init(MD5_CTX * ctx) -{ - gcry_md_open(ctx, GCRY_MD_MD5, 0); -} - -static void MD5_Update(MD5_CTX * ctx, - const unsigned char * input, - unsigned int inputLen) -{ - gcry_md_write(*ctx, input, inputLen); -} - -static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx) -{ - memcpy(digest, gcry_md_read(*ctx, 0), 16); - gcry_md_close(*ctx); -} - -#elif defined(USE_SSLEAY) -/* When OpenSSL is available we use the MD5-function from OpenSSL */ - -# ifdef USE_OPENSSL -# include -# else -# include -# endif - -#elif defined(__MAC_10_4) || defined(__IPHONE_5_0) - -/* For Apple operating systems: CommonCrypto has the functions we need. - The library's headers are even backward-compatible with OpenSSL's - headers as long as we define COMMON_DIGEST_FOR_OPENSSL first. - - These functions are available on Tiger and later, as well as iOS 5.0 - and later. If you're building for an older cat, well, sorry. */ -# define COMMON_DIGEST_FOR_OPENSSL -# include - -#elif defined(_WIN32) - -#include - -typedef struct { - HCRYPTPROV hCryptProv; - HCRYPTHASH hHash; -} MD5_CTX; - -static void MD5_Init(MD5_CTX *ctx) -{ - if(CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, - PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - CryptCreateHash(ctx->hCryptProv, CALG_MD5, 0, 0, &ctx->hHash); - } -} - -static void MD5_Update(MD5_CTX *ctx, - const unsigned char *input, - unsigned int inputLen) -{ - CryptHashData(ctx->hHash, (unsigned char *)input, inputLen, 0); -} - -static void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) -{ - unsigned long length; - CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); - if(length == 16) - CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &length, 0); - if(ctx->hHash) - CryptDestroyHash(ctx->hHash); - if(ctx->hCryptProv) - CryptReleaseContext(ctx->hCryptProv, 0); -} - -#else -/* When no other crypto library is available we use this code segment */ - -/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All -rights reserved. - -License to copy and use this software is granted provided that it -is identified as the "RSA Data Security, Inc. MD5 Message-Digest -Algorithm" in all material mentioning or referencing this software -or this function. - -License is also granted to make and use derivative works provided -that such works are identified as "derived from the RSA Data -Security, Inc. MD5 Message-Digest Algorithm" in all material -mentioning or referencing the derived work. - -RSA Data Security, Inc. makes no representations concerning either -the merchantability of this software or the suitability of this -software for any particular purpose. It is provided "as is" -without express or implied warranty of any kind. - -These notices must be retained in any copies of any part of this -documentation and/or software. - */ - -/* UINT4 defines a four byte word */ -typedef unsigned int UINT4; - -/* MD5 context. */ -struct md5_ctx { - UINT4 state[4]; /* state (ABCD) */ - UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ - unsigned char buffer[64]; /* input buffer */ -}; - -typedef struct md5_ctx MD5_CTX; - -static void MD5_Init(struct md5_ctx *); -static void MD5_Update(struct md5_ctx *, const unsigned char *, unsigned int); -static void MD5_Final(unsigned char [16], struct md5_ctx *); - -/* Constants for MD5Transform routine. - */ - -#define S11 7 -#define S12 12 -#define S13 17 -#define S14 22 -#define S21 5 -#define S22 9 -#define S23 14 -#define S24 20 -#define S31 4 -#define S32 11 -#define S33 16 -#define S34 23 -#define S41 6 -#define S42 10 -#define S43 15 -#define S44 21 - -static void MD5Transform(UINT4 [4], const unsigned char [64]); -static void Encode(unsigned char *, UINT4 *, unsigned int); -static void Decode(UINT4 *, const unsigned char *, unsigned int); - -static const unsigned char PADDING[64] = { - 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -/* F, G, H and I are basic MD5 functions. - */ -#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) -#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) -#define H(x, y, z) ((x) ^ (y) ^ (z)) -#define I(x, y, z) ((y) ^ ((x) | (~z))) - -/* ROTATE_LEFT rotates x left n bits. - */ -#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) - -/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. -Rotation is separate from addition to prevent recomputation. - */ -#define FF(a, b, c, d, x, s, ac) { \ - (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ - (a) = ROTATE_LEFT ((a), (s)); \ - (a) += (b); \ - } -#define GG(a, b, c, d, x, s, ac) { \ - (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ - (a) = ROTATE_LEFT ((a), (s)); \ - (a) += (b); \ - } -#define HH(a, b, c, d, x, s, ac) { \ - (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ - (a) = ROTATE_LEFT ((a), (s)); \ - (a) += (b); \ - } -#define II(a, b, c, d, x, s, ac) { \ - (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ - (a) = ROTATE_LEFT ((a), (s)); \ - (a) += (b); \ - } - -/* MD5 initialization. Begins an MD5 operation, writing a new context. - */ -static void MD5_Init(struct md5_ctx *context) -{ - context->count[0] = context->count[1] = 0; - /* Load magic initialization constants. */ - context->state[0] = 0x67452301; - context->state[1] = 0xefcdab89; - context->state[2] = 0x98badcfe; - context->state[3] = 0x10325476; -} - -/* MD5 block update operation. Continues an MD5 message-digest - operation, processing another message block, and updating the - context. - */ -static void MD5_Update (struct md5_ctx *context, /* context */ - const unsigned char *input, /* input block */ - unsigned int inputLen) /* length of input block */ -{ - unsigned int i, bufindex, partLen; - - /* Compute number of bytes mod 64 */ - bufindex = (unsigned int)((context->count[0] >> 3) & 0x3F); - - /* Update number of bits */ - if((context->count[0] += ((UINT4)inputLen << 3)) - < ((UINT4)inputLen << 3)) - context->count[1]++; - context->count[1] += ((UINT4)inputLen >> 29); - - partLen = 64 - bufindex; - - /* Transform as many times as possible. */ - if(inputLen >= partLen) { - memcpy(&context->buffer[bufindex], input, partLen); - MD5Transform(context->state, context->buffer); - - for(i = partLen; i + 63 < inputLen; i += 64) - MD5Transform(context->state, &input[i]); - - bufindex = 0; - } - else - i = 0; - - /* Buffer remaining input */ - memcpy(&context->buffer[bufindex], &input[i], inputLen-i); -} - -/* MD5 finalization. Ends an MD5 message-digest operation, writing the - the message digest and zeroizing the context. -*/ -static void MD5_Final(unsigned char digest[16], /* message digest */ - struct md5_ctx *context) /* context */ -{ - unsigned char bits[8]; - unsigned int count, padLen; - - /* Save number of bits */ - Encode (bits, context->count, 8); - - /* Pad out to 56 mod 64. */ - count = (unsigned int)((context->count[0] >> 3) & 0x3f); - padLen = (count < 56) ? (56 - count) : (120 - count); - MD5_Update (context, PADDING, padLen); - - /* Append length (before padding) */ - MD5_Update (context, bits, 8); - - /* Store state in digest */ - Encode (digest, context->state, 16); - - /* Zeroize sensitive information. */ - memset ((void *)context, 0, sizeof (*context)); -} - -/* MD5 basic transformation. Transforms state based on block. */ -static void MD5Transform(UINT4 state[4], - const unsigned char block[64]) -{ - UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; - - Decode (x, block, 64); - - /* Round 1 */ - FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ - FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ - FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ - FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ - FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ - FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ - FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ - FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ - FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ - FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ - FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ - FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ - FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ - FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ - FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ - FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ - - /* Round 2 */ - GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ - GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ - GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ - GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ - GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ - GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ - GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ - GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ - GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ - GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ - GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ - GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ - GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ - GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ - GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ - GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ - - /* Round 3 */ - HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ - HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ - HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ - HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ - HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ - HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ - HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ - HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ - HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ - HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ - HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ - HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ - HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ - HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ - HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ - HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ - - /* Round 4 */ - II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ - II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ - II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ - II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ - II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ - II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ - II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ - II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ - II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ - II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ - II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ - II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ - II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ - II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ - II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ - II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ - - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - - /* Zeroize sensitive information. */ - memset((void *)x, 0, sizeof (x)); -} - -/* Encodes input (UINT4) into output (unsigned char). Assumes len is - a multiple of 4. - */ -static void Encode (unsigned char *output, - UINT4 *input, - unsigned int len) -{ - unsigned int i, j; - - for(i = 0, j = 0; j < len; i++, j += 4) { - output[j] = (unsigned char)(input[i] & 0xff); - output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); - output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); - output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); - } -} - -/* Decodes input (unsigned char) into output (UINT4). Assumes len is - a multiple of 4. -*/ -static void Decode (UINT4 *output, - const unsigned char *input, - unsigned int len) -{ - unsigned int i, j; - - for(i = 0, j = 0; j < len; i++, j += 4) - output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | - (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); -} - -#endif /* CRYPTO LIBS */ - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -const HMAC_params Curl_HMAC_MD5[] = { - { - (HMAC_hinit_func) MD5_Init, /* Hash initialization function. */ - (HMAC_hupdate_func) MD5_Update, /* Hash update function. */ - (HMAC_hfinal_func) MD5_Final, /* Hash computation end function. */ - sizeof(MD5_CTX), /* Size of hash context structure. */ - 64, /* Maximum key length. */ - 16 /* Result size. */ - } -}; - -const MD5_params Curl_DIGEST_MD5[] = { - { - (Curl_MD5_init_func) MD5_Init, /* Digest initialization function */ - (Curl_MD5_update_func) MD5_Update, /* Digest update function */ - (Curl_MD5_final_func) MD5_Final, /* Digest computation end function */ - sizeof(MD5_CTX), /* Size of digest context struct */ - 16 /* Result size */ - } -}; - -void Curl_md5it(unsigned char *outbuffer, /* 16 bytes */ - const unsigned char *input) -{ - MD5_CTX ctx; - MD5_Init(&ctx); - MD5_Update(&ctx, input, curlx_uztoui(strlen((char *)input))); - MD5_Final(outbuffer, &ctx); -} - -MD5_context *Curl_MD5_init(const MD5_params *md5params) -{ - MD5_context *ctxt; - - /* Create MD5 context */ - ctxt = malloc(sizeof *ctxt); - - if(!ctxt) - return ctxt; - - ctxt->md5_hashctx = malloc(md5params->md5_ctxtsize); - - if(!ctxt->md5_hashctx) { - free(ctxt); - return NULL; - } - - ctxt->md5_hash = md5params; - - (*md5params->md5_init_func)(ctxt->md5_hashctx); - - return ctxt; -} - -int Curl_MD5_update(MD5_context *context, - const unsigned char *data, - unsigned int len) -{ - (*context->md5_hash->md5_update_func)(context->md5_hashctx, data, len); - - return 0; -} - -int Curl_MD5_final(MD5_context *context, unsigned char *result) -{ - (*context->md5_hash->md5_final_func)(result, context->md5_hashctx); - - free(context->md5_hashctx); - free(context); - - return 0; -} - -#endif /* CURL_DISABLE_CRYPTO_AUTH */ diff --git a/lib/memdebug.c b/lib/memdebug.c deleted file mode 100644 index e756126b2..000000000 --- a/lib/memdebug.c +++ /dev/null @@ -1,445 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef CURLDEBUG - -#include - -#define _MPRINTF_REPLACE -#include -#include "curl_urldata.h" - -#define MEMDEBUG_NODEFINES /* don't redefine the standard functions */ -#include "curl_memory.h" -#include "curl_memdebug.h" - -#ifndef HAVE_ASSERT_H -# define assert(x) Curl_nop_stmt -#endif - -/* - * Until 2011-08-17 libcurl's Memory Tracking feature also performed - * automatic malloc and free filling operations using 0xA5 and 0x13 - * values. Our own preinitialization of dynamically allocated memory - * might be useful when not using third party memory debuggers, but - * on the other hand this would fool memory debuggers into thinking - * that all dynamically allocated memory is properly initialized. - * - * As a default setting, libcurl's Memory Tracking feature no longer - * performs preinitialization of dynamically allocated memory on its - * own. If you know what you are doing, and really want to retain old - * behavior, you can achieve this compiling with preprocessor symbols - * CURL_MT_MALLOC_FILL and CURL_MT_FREE_FILL defined with appropriate - * values. - */ - -#ifdef CURL_MT_MALLOC_FILL -# if (CURL_MT_MALLOC_FILL < 0) || (CURL_MT_MALLOC_FILL > 0xff) -# error "invalid CURL_MT_MALLOC_FILL or out of range" -# endif -#endif - -#ifdef CURL_MT_FREE_FILL -# if (CURL_MT_FREE_FILL < 0) || (CURL_MT_FREE_FILL > 0xff) -# error "invalid CURL_MT_FREE_FILL or out of range" -# endif -#endif - -#if defined(CURL_MT_MALLOC_FILL) && defined(CURL_MT_FREE_FILL) -# if (CURL_MT_MALLOC_FILL == CURL_MT_FREE_FILL) -# error "CURL_MT_MALLOC_FILL same as CURL_MT_FREE_FILL" -# endif -#endif - -#ifdef CURL_MT_MALLOC_FILL -# define mt_malloc_fill(buf,len) memset((buf), CURL_MT_MALLOC_FILL, (len)) -#else -# define mt_malloc_fill(buf,len) Curl_nop_stmt -#endif - -#ifdef CURL_MT_FREE_FILL -# define mt_free_fill(buf,len) memset((buf), CURL_MT_FREE_FILL, (len)) -#else -# define mt_free_fill(buf,len) Curl_nop_stmt -#endif - -struct memdebug { - size_t size; - union { - curl_off_t o; - double d; - void * p; - } mem[1]; - /* I'm hoping this is the thing with the strictest alignment - * requirements. That also means we waste some space :-( */ -}; - -/* - * Note that these debug functions are very simple and they are meant to - * remain so. For advanced analysis, record a log file and write perl scripts - * to analyze them! - * - * Don't use these with multithreaded test programs! - */ - -#define logfile curl_debuglogfile -FILE *curl_debuglogfile = NULL; -static bool memlimit = FALSE; /* enable memory limit */ -static long memsize = 0; /* set number of mallocs allowed */ - -/* this sets the log file name */ -void curl_memdebug(const char *logname) -{ - if(!logfile) { - if(logname && *logname) - logfile = fopen(logname, "w"); - else - logfile = stderr; -#ifdef MEMDEBUG_LOG_SYNC - /* Flush the log file after every line so the log isn't lost in a crash */ - setvbuf(logfile, (char *)NULL, _IOLBF, 0); -#endif - } -} - -/* This function sets the number of malloc() calls that should return - successfully! */ -void curl_memlimit(long limit) -{ - if(!memlimit) { - memlimit = TRUE; - memsize = limit; - } -} - -/* returns TRUE if this isn't allowed! */ -static bool countcheck(const char *func, int line, const char *source) -{ - /* if source is NULL, then the call is made internally and this check - should not be made */ - if(memlimit && source) { - if(!memsize) { - if(source) { - /* log to file */ - curl_memlog("LIMIT %s:%d %s reached memlimit\n", - source, line, func); - /* log to stderr also */ - fprintf(stderr, "LIMIT %s:%d %s reached memlimit\n", - source, line, func); - } - SET_ERRNO(ENOMEM); - return TRUE; /* RETURN ERROR! */ - } - else - memsize--; /* countdown */ - - /* log the countdown */ - if(source) - curl_memlog("LIMIT %s:%d %ld ALLOCS left\n", - source, line, memsize); - - } - - return FALSE; /* allow this */ -} - -void *curl_domalloc(size_t wantedsize, int line, const char *source) -{ - struct memdebug *mem; - size_t size; - - assert(wantedsize != 0); - - if(countcheck("malloc", line, source)) - return NULL; - - /* alloc at least 64 bytes */ - size = sizeof(struct memdebug)+wantedsize; - - mem = (Curl_cmalloc)(size); - if(mem) { - /* fill memory with junk */ - mt_malloc_fill(mem->mem, wantedsize); - mem->size = wantedsize; - } - - if(source) - curl_memlog("MEM %s:%d malloc(%zd) = %p\n", - source, line, wantedsize, mem ? mem->mem : 0); - return (mem ? mem->mem : NULL); -} - -void *curl_docalloc(size_t wanted_elements, size_t wanted_size, - int line, const char *source) -{ - struct memdebug *mem; - size_t size, user_size; - - assert(wanted_elements != 0); - assert(wanted_size != 0); - - if(countcheck("calloc", line, source)) - return NULL; - - /* alloc at least 64 bytes */ - user_size = wanted_size * wanted_elements; - size = sizeof(struct memdebug) + user_size; - - mem = (Curl_ccalloc)(1, size); - if(mem) - mem->size = user_size; - - if(source) - curl_memlog("MEM %s:%d calloc(%zu,%zu) = %p\n", - source, line, wanted_elements, wanted_size, mem?mem->mem:0); - return (mem ? mem->mem : NULL); -} - -char *curl_dostrdup(const char *str, int line, const char *source) -{ - char *mem; - size_t len; - - assert(str != NULL); - - if(countcheck("strdup", line, source)) - return NULL; - - len=strlen(str)+1; - - mem=curl_domalloc(len, 0, NULL); /* NULL prevents logging */ - if(mem) - memcpy(mem, str, len); - - if(source) - curl_memlog("MEM %s:%d strdup(%p) (%zu) = %p\n", - source, line, str, len, mem); - - return mem; -} - -/* We provide a realloc() that accepts a NULL as pointer, which then - performs a malloc(). In order to work with ares. */ -void *curl_dorealloc(void *ptr, size_t wantedsize, - int line, const char *source) -{ - struct memdebug *mem=NULL; - - size_t size = sizeof(struct memdebug)+wantedsize; - - assert(wantedsize != 0); - - if(countcheck("realloc", line, source)) - return NULL; - -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:1684) - /* 1684: conversion from pointer to same-sized integral type */ -#endif - - if(ptr) - mem = (void *)((char *)ptr - offsetof(struct memdebug, mem)); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif - - mem = (Curl_crealloc)(mem, size); - if(source) - curl_memlog("MEM %s:%d realloc(%p, %zu) = %p\n", - source, line, ptr, wantedsize, mem?mem->mem:NULL); - - if(mem) { - mem->size = wantedsize; - return mem->mem; - } - - return NULL; -} - -void curl_dofree(void *ptr, int line, const char *source) -{ - struct memdebug *mem; - - assert(ptr != NULL); - -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:1684) - /* 1684: conversion from pointer to same-sized integral type */ -#endif - - mem = (void *)((char *)ptr - offsetof(struct memdebug, mem)); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif - - /* destroy */ - mt_free_fill(mem->mem, mem->size); - - /* free for real */ - (Curl_cfree)(mem); - - if(source) - curl_memlog("MEM %s:%d free(%p)\n", source, line, ptr); -} - -curl_socket_t curl_socket(int domain, int type, int protocol, - int line, const char *source) -{ - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d socket() = %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d socket() = %ld\n" : - "FD %s:%d socket() = %zd\n" ; - - curl_socket_t sockfd = socket(domain, type, protocol); - if(source && (sockfd != CURL_SOCKET_BAD)) - curl_memlog(fmt, source, line, sockfd); - return sockfd; -} - -#ifdef HAVE_SOCKETPAIR -int curl_socketpair(int domain, int type, int protocol, - curl_socket_t socket_vector[2], - int line, const char *source) -{ - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d socketpair() = %d %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d socketpair() = %ld %ld\n" : - "FD %s:%d socketpair() = %zd %zd\n" ; - - int res = socketpair(domain, type, protocol, socket_vector); - if(source && (0 == res)) - curl_memlog(fmt, source, line, socket_vector[0], socket_vector[1]); - return res; -} -#endif - -curl_socket_t curl_accept(curl_socket_t s, void *saddr, void *saddrlen, - int line, const char *source) -{ - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d accept() = %d\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d accept() = %ld\n" : - "FD %s:%d accept() = %zd\n" ; - - struct sockaddr *addr = (struct sockaddr *)saddr; - curl_socklen_t *addrlen = (curl_socklen_t *)saddrlen; - curl_socket_t sockfd = accept(s, addr, addrlen); - if(source && (sockfd != CURL_SOCKET_BAD)) - curl_memlog(fmt, source, line, sockfd); - return sockfd; -} - -/* separate function to allow libcurl to mark a "faked" close */ -void curl_mark_sclose(curl_socket_t sockfd, int line, const char *source) -{ - const char *fmt = (sizeof(curl_socket_t) == sizeof(int)) ? - "FD %s:%d sclose(%d)\n" : - (sizeof(curl_socket_t) == sizeof(long)) ? - "FD %s:%d sclose(%ld)\n" : - "FD %s:%d sclose(%zd)\n" ; - - if(source) - curl_memlog(fmt, source, line, sockfd); -} - -/* this is our own defined way to close sockets on *ALL* platforms */ -int curl_sclose(curl_socket_t sockfd, int line, const char *source) -{ - int res=sclose(sockfd); - curl_mark_sclose(sockfd, line, source); - return res; -} - -FILE *curl_fopen(const char *file, const char *mode, - int line, const char *source) -{ - FILE *res=fopen(file, mode); - if(source) - curl_memlog("FILE %s:%d fopen(\"%s\",\"%s\") = %p\n", - source, line, file, mode, res); - return res; -} - -#ifdef HAVE_FDOPEN -FILE *curl_fdopen(int filedes, const char *mode, - int line, const char *source) -{ - FILE *res=fdopen(filedes, mode); - if(source) - curl_memlog("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n", - source, line, filedes, mode, res); - return res; -} -#endif - -int curl_fclose(FILE *file, int line, const char *source) -{ - int res; - - assert(file != NULL); - - res=fclose(file); - if(source) - curl_memlog("FILE %s:%d fclose(%p)\n", - source, line, file); - return res; -} - -#define LOGLINE_BUFSIZE 1024 - -/* this does the writting to the memory tracking log file */ -void curl_memlog(const char *format, ...) -{ - char *buf; - int nchars; - va_list ap; - - if(!logfile) - return; - - buf = (Curl_cmalloc)(LOGLINE_BUFSIZE); - if(!buf) - return; - - va_start(ap, format); - nchars = vsnprintf(buf, LOGLINE_BUFSIZE, format, ap); - va_end(ap); - - if(nchars > LOGLINE_BUFSIZE - 1) - nchars = LOGLINE_BUFSIZE - 1; - - if(nchars > 0) - fwrite(buf, 1, nchars, logfile); - - (Curl_cfree)(buf); -} - -#endif /* CURLDEBUG */ diff --git a/lib/mprintf.c b/lib/mprintf.c deleted file mode 100644 index 35b9f644f..000000000 --- a/lib/mprintf.c +++ /dev/null @@ -1,1197 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1999 - 2013, 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 - * 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. - * - * - * Purpose: - * A merge of Bjorn Reese's format() function and Daniel's dsprintf() - * 1.0. A full blooded printf() clone with full support for $ - * everywhere (parameters, widths and precisions) including variabled - * sized parameters (like doubles, long longs, long doubles and even - * void * in 64-bit architectures). - * - * Current restrictions: - * - Max 128 parameters - * - No 'long double' support. - * - * If you ever want truly portable and good *printf() clones, the project that - * took on from here is named 'Trio' and you find more details on the trio web - * page at http://daniel.haxx.se/trio/ - */ - -#include "curl_setup.h" - -#if defined(DJGPP) && (DJGPP_MINOR < 4) -#undef _MPRINTF_REPLACE /* don't use x_was_used() here */ -#endif - -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifndef SIZEOF_LONG_DOUBLE -#define SIZEOF_LONG_DOUBLE 0 -#endif - -/* - * If SIZEOF_SIZE_T has not been defined, default to the size of long. - */ - -#ifndef SIZEOF_SIZE_T -# define SIZEOF_SIZE_T CURL_SIZEOF_LONG -#endif - -#ifdef HAVE_LONGLONG -# define LONG_LONG_TYPE long long -# define HAVE_LONG_LONG_TYPE -#else -# if defined(_MSC_VER) && (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) -# define LONG_LONG_TYPE __int64 -# define HAVE_LONG_LONG_TYPE -# else -# undef LONG_LONG_TYPE -# undef HAVE_LONG_LONG_TYPE -# endif -#endif - -/* - * Max integer data types that curl_mprintf.c is capable - */ - -#ifdef HAVE_LONG_LONG_TYPE -# define mp_intmax_t LONG_LONG_TYPE -# define mp_uintmax_t unsigned LONG_LONG_TYPE -#else -# define mp_intmax_t long -# define mp_uintmax_t unsigned long -#endif - -#define BUFFSIZE 256 /* buffer for long-to-str and float-to-str calcs */ -#define MAX_PARAMETERS 128 /* lame static limit */ - -#ifdef __AMIGA__ -# undef FORMAT_INT -#endif - -/* Lower-case digits. */ -static const char lower_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; - -/* Upper-case digits. */ -static const char upper_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - -#define OUTCHAR(x) \ - do{ \ - if(stream((unsigned char)(x), (FILE *)data) != -1) \ - done++; \ - else \ - return done; /* return immediately on failure */ \ - } WHILE_FALSE - -/* Data type to read from the arglist */ -typedef enum { - FORMAT_UNKNOWN = 0, - FORMAT_STRING, - FORMAT_PTR, - FORMAT_INT, - FORMAT_INTPTR, - FORMAT_LONG, - FORMAT_LONGLONG, - FORMAT_DOUBLE, - FORMAT_LONGDOUBLE, - FORMAT_WIDTH /* For internal use */ -} FormatType; - -/* conversion and display flags */ -enum { - FLAGS_NEW = 0, - FLAGS_SPACE = 1<<0, - FLAGS_SHOWSIGN = 1<<1, - FLAGS_LEFT = 1<<2, - FLAGS_ALT = 1<<3, - FLAGS_SHORT = 1<<4, - FLAGS_LONG = 1<<5, - FLAGS_LONGLONG = 1<<6, - FLAGS_LONGDOUBLE = 1<<7, - FLAGS_PAD_NIL = 1<<8, - FLAGS_UNSIGNED = 1<<9, - FLAGS_OCTAL = 1<<10, - FLAGS_HEX = 1<<11, - FLAGS_UPPER = 1<<12, - FLAGS_WIDTH = 1<<13, /* '*' or '*$' used */ - FLAGS_WIDTHPARAM = 1<<14, /* width PARAMETER was specified */ - FLAGS_PREC = 1<<15, /* precision was specified */ - FLAGS_PRECPARAM = 1<<16, /* precision PARAMETER was specified */ - FLAGS_CHAR = 1<<17, /* %c story */ - FLAGS_FLOATE = 1<<18, /* %e or %E */ - FLAGS_FLOATG = 1<<19 /* %g or %G */ -}; - -typedef struct { - FormatType type; - int flags; - long width; /* width OR width parameter number */ - long precision; /* precision OR precision parameter number */ - union { - char *str; - void *ptr; - union { - mp_intmax_t as_signed; - mp_uintmax_t as_unsigned; - } num; - double dnum; - } data; -} va_stack_t; - -struct nsprintf { - char *buffer; - size_t length; - size_t max; -}; - -struct asprintf { - char *buffer; /* allocated buffer */ - size_t len; /* length of string */ - size_t alloc; /* length of alloc */ - int fail; /* (!= 0) if an alloc has failed and thus - the output is not the complete data */ -}; - -static long dprintf_DollarString(char *input, char **end) -{ - int number=0; - while(ISDIGIT(*input)) { - number *= 10; - number += *input-'0'; - input++; - } - if(number && ('$'==*input++)) { - *end = input; - return number; - } - return 0; -} - -static int dprintf_IsQualifierNoDollar(char c) -{ - switch (c) { - case '-': case '+': case ' ': case '#': case '.': - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'h': case 'l': case 'L': case 'z': case 'q': - case '*': case 'O': - return 1; /* true */ - default: - return 0; /* false */ - } -} - -#ifdef DPRINTF_DEBUG2 -static void dprintf_Pass1Report(va_stack_t *vto, int max) -{ - int i; - char buffer[256]; - int bit; - int flags; - - for(i=0; i max_param) - max_param = this_param; - - /* - * The parameter with number 'i' should be used. Next, we need - * to get SIZE and TYPE of the parameter. Add the information - * to our array. - */ - - width = 0; - precision = 0; - - /* Handle the flags */ - - while(dprintf_IsQualifierNoDollar(*fmt)) { - switch (*fmt++) { - case ' ': - flags |= FLAGS_SPACE; - break; - case '+': - flags |= FLAGS_SHOWSIGN; - break; - case '-': - flags |= FLAGS_LEFT; - flags &= ~FLAGS_PAD_NIL; - break; - case '#': - flags |= FLAGS_ALT; - break; - case '.': - flags |= FLAGS_PREC; - if('*' == *fmt) { - /* The precision is picked from a specified parameter */ - - flags |= FLAGS_PRECPARAM; - fmt++; - param_num++; - - i = dprintf_DollarString(fmt, &fmt); - if(i) - precision = i; - else - precision = param_num; - - if(precision > max_param) - max_param = precision; - } - else { - flags |= FLAGS_PREC; - precision = strtol(fmt, &fmt, 10); - } - break; - case 'h': - flags |= FLAGS_SHORT; - break; - case 'l': - if(flags & FLAGS_LONG) - flags |= FLAGS_LONGLONG; - else - flags |= FLAGS_LONG; - break; - case 'L': - flags |= FLAGS_LONGDOUBLE; - break; - case 'q': - flags |= FLAGS_LONGLONG; - break; - case 'z': - /* the code below generates a warning if -Wunreachable-code is - used */ -#if (SIZEOF_SIZE_T > CURL_SIZEOF_LONG) - flags |= FLAGS_LONGLONG; -#else - flags |= FLAGS_LONG; -#endif - break; - case 'O': -#if (CURL_SIZEOF_CURL_OFF_T > CURL_SIZEOF_LONG) - flags |= FLAGS_LONGLONG; -#else - flags |= FLAGS_LONG; -#endif - break; - case '0': - if(!(flags & FLAGS_LEFT)) - flags |= FLAGS_PAD_NIL; - /* FALLTHROUGH */ - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - flags |= FLAGS_WIDTH; - width = strtol(fmt-1, &fmt, 10); - break; - case '*': /* Special case */ - flags |= FLAGS_WIDTHPARAM; - param_num++; - - i = dprintf_DollarString(fmt, &fmt); - if(i) - width = i; - else - width = param_num; - if(width > max_param) - max_param=width; - break; - default: - break; - } - } /* switch */ - - /* Handle the specifier */ - - i = this_param - 1; - - switch (*fmt) { - case 'S': - flags |= FLAGS_ALT; - /* FALLTHROUGH */ - case 's': - vto[i].type = FORMAT_STRING; - break; - case 'n': - vto[i].type = FORMAT_INTPTR; - break; - case 'p': - vto[i].type = FORMAT_PTR; - break; - case 'd': case 'i': - vto[i].type = FORMAT_INT; - break; - case 'u': - vto[i].type = FORMAT_INT; - flags |= FLAGS_UNSIGNED; - break; - case 'o': - vto[i].type = FORMAT_INT; - flags |= FLAGS_OCTAL; - break; - case 'x': - vto[i].type = FORMAT_INT; - flags |= FLAGS_HEX; - break; - case 'X': - vto[i].type = FORMAT_INT; - flags |= FLAGS_HEX|FLAGS_UPPER; - break; - case 'c': - vto[i].type = FORMAT_INT; - flags |= FLAGS_CHAR; - break; - case 'f': - vto[i].type = FORMAT_DOUBLE; - break; - case 'e': - vto[i].type = FORMAT_DOUBLE; - flags |= FLAGS_FLOATE; - break; - case 'E': - vto[i].type = FORMAT_DOUBLE; - flags |= FLAGS_FLOATE|FLAGS_UPPER; - break; - case 'g': - vto[i].type = FORMAT_DOUBLE; - flags |= FLAGS_FLOATG; - break; - case 'G': - vto[i].type = FORMAT_DOUBLE; - flags |= FLAGS_FLOATG|FLAGS_UPPER; - break; - default: - vto[i].type = FORMAT_UNKNOWN; - break; - } /* switch */ - - vto[i].flags = flags; - vto[i].width = width; - vto[i].precision = precision; - - if(flags & FLAGS_WIDTHPARAM) { - /* we have the width specified from a parameter, so we make that - parameter's info setup properly */ - vto[i].width = width - 1; - i = width - 1; - vto[i].type = FORMAT_WIDTH; - vto[i].flags = FLAGS_NEW; - vto[i].precision = vto[i].width = 0; /* can't use width or precision - of width! */ - } - if(flags & FLAGS_PRECPARAM) { - /* we have the precision specified from a parameter, so we make that - parameter's info setup properly */ - vto[i].precision = precision - 1; - i = precision - 1; - vto[i].type = FORMAT_WIDTH; - vto[i].flags = FLAGS_NEW; - vto[i].precision = vto[i].width = 0; /* can't use width or precision - of width! */ - } - *endpos++ = fmt + 1; /* end of this sequence */ - } - } - -#ifdef DPRINTF_DEBUG2 - dprintf_Pass1Report(vto, max_param); -#endif - - /* Read the arg list parameters into our data list */ - for(i=0; i$ sequence */ - param=dprintf_DollarString(f, &f); - - if(!param) - param = param_num; - else - --param; - - param_num++; /* increase this always to allow "%2$s %1$s %s" and then the - third %s will pick the 3rd argument */ - - p = &vto[param]; - - /* pick up the specified width */ - if(p->flags & FLAGS_WIDTHPARAM) - width = (long)vto[p->width].data.num.as_signed; - else - width = p->width; - - /* pick up the specified precision */ - if(p->flags & FLAGS_PRECPARAM) { - prec = (long)vto[p->precision].data.num.as_signed; - param_num++; /* since the precision is extraced from a parameter, we - must skip that to get to the next one properly */ - } - else if(p->flags & FLAGS_PREC) - prec = p->precision; - else - prec = -1; - - is_alt = (p->flags & FLAGS_ALT) ? 1 : 0; - - switch (p->type) { - case FORMAT_INT: - num = p->data.num.as_unsigned; - if(p->flags & FLAGS_CHAR) { - /* Character. */ - if(!(p->flags & FLAGS_LEFT)) - while(--width > 0) - OUTCHAR(' '); - OUTCHAR((char) num); - if(p->flags & FLAGS_LEFT) - while(--width > 0) - OUTCHAR(' '); - break; - } - if(p->flags & FLAGS_UNSIGNED) { - /* Decimal unsigned integer. */ - base = 10; - goto unsigned_number; - } - if(p->flags & FLAGS_OCTAL) { - /* Octal unsigned integer. */ - base = 8; - goto unsigned_number; - } - if(p->flags & FLAGS_HEX) { - /* Hexadecimal unsigned integer. */ - - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; - base = 16; - goto unsigned_number; - } - - /* Decimal integer. */ - base = 10; - - is_neg = (p->data.num.as_signed < (mp_intmax_t)0) ? 1 : 0; - if(is_neg) { - /* signed_num might fail to hold absolute negative minimum by 1 */ - signed_num = p->data.num.as_signed + (mp_intmax_t)1; - signed_num = -signed_num; - num = (mp_uintmax_t)signed_num; - num += (mp_uintmax_t)1; - } - - goto number; - - unsigned_number: - /* Unsigned number of base BASE. */ - is_neg = 0; - - number: - /* Number of base BASE. */ - { - char *workend = &work[sizeof(work) - 1]; - char *w; - - /* Supply a default precision if none was given. */ - if(prec == -1) - prec = 1; - - /* Put the number in WORK. */ - w = workend; - while(num > 0) { - *w-- = digits[num % base]; - num /= base; - } - width -= (long)(workend - w); - prec -= (long)(workend - w); - - if(is_alt && base == 8 && prec <= 0) { - *w-- = '0'; - --width; - } - - if(prec > 0) { - width -= prec; - while(prec-- > 0) - *w-- = '0'; - } - - if(is_alt && base == 16) - width -= 2; - - if(is_neg || (p->flags & FLAGS_SHOWSIGN) || (p->flags & FLAGS_SPACE)) - --width; - - if(!(p->flags & FLAGS_LEFT) && !(p->flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR(' '); - - if(is_neg) - OUTCHAR('-'); - else if(p->flags & FLAGS_SHOWSIGN) - OUTCHAR('+'); - else if(p->flags & FLAGS_SPACE) - OUTCHAR(' '); - - if(is_alt && base == 16) { - OUTCHAR('0'); - if(p->flags & FLAGS_UPPER) - OUTCHAR('X'); - else - OUTCHAR('x'); - } - - if(!(p->flags & FLAGS_LEFT) && (p->flags & FLAGS_PAD_NIL)) - while(width-- > 0) - OUTCHAR('0'); - - /* Write the number. */ - while(++w <= workend) { - OUTCHAR(*w); - } - - if(p->flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - } - break; - - case FORMAT_STRING: - /* String. */ - { - static const char null[] = "(nil)"; - const char *str; - size_t len; - - str = (char *) p->data.str; - if(str == NULL) { - /* Write null[] if there's space. */ - if(prec == -1 || prec >= (long) sizeof(null) - 1) { - str = null; - len = sizeof(null) - 1; - /* Disable quotes around (nil) */ - p->flags &= (~FLAGS_ALT); - } - else { - str = ""; - len = 0; - } - } - else - len = strlen(str); - - if(prec != -1 && (size_t) prec < len) - len = (size_t)prec; - width -= (long)len; - - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); - - if(!(p->flags&FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); - - while(len-- > 0) - OUTCHAR(*str++); - if(p->flags&FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); - } - break; - - case FORMAT_PTR: - /* Generic pointer. */ - { - void *ptr; - ptr = (void *) p->data.ptr; - if(ptr != NULL) { - /* If the pointer is not NULL, write it as a %#x spec. */ - base = 16; - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; - is_alt = 1; - num = (size_t) ptr; - is_neg = 0; - goto number; - } - else { - /* Write "(nil)" for a nil pointer. */ - static const char strnil[] = "(nil)"; - const char *point; - - width -= (long)(sizeof(strnil) - 1); - if(p->flags & FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); - for(point = strnil; *point != '\0'; ++point) - OUTCHAR(*point); - if(! (p->flags & FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); - } - } - break; - - case FORMAT_DOUBLE: - { - char formatbuf[32]="%"; - char *fptr; - size_t left = sizeof(formatbuf)-strlen(formatbuf); - int len; - - width = -1; - if(p->flags & FLAGS_WIDTH) - width = p->width; - else if(p->flags & FLAGS_WIDTHPARAM) - width = (long)vto[p->width].data.num.as_signed; - - prec = -1; - if(p->flags & FLAGS_PREC) - prec = p->precision; - else if(p->flags & FLAGS_PRECPARAM) - prec = (long)vto[p->precision].data.num.as_signed; - - if(p->flags & FLAGS_LEFT) - strcat(formatbuf, "-"); - if(p->flags & FLAGS_SHOWSIGN) - strcat(formatbuf, "+"); - if(p->flags & FLAGS_SPACE) - strcat(formatbuf, " "); - if(p->flags & FLAGS_ALT) - strcat(formatbuf, "#"); - - fptr=&formatbuf[strlen(formatbuf)]; - - if(width >= 0) { - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, "%ld", width); - fptr += len; - left -= len; - } - if(prec >= 0) { - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, ".%ld", prec); - fptr += len; - } - if(p->flags & FLAGS_LONG) - *fptr++ = 'l'; - - if(p->flags & FLAGS_FLOATE) - *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'E':'e'); - else if(p->flags & FLAGS_FLOATG) - *fptr++ = (char)((p->flags & FLAGS_UPPER) ? 'G' : 'g'); - else - *fptr++ = 'f'; - - *fptr = 0; /* and a final zero termination */ - - /* NOTE NOTE NOTE!! Not all sprintf() implementations returns number - of output characters */ - (sprintf)(work, formatbuf, p->data.dnum); - - for(fptr=work; *fptr; fptr++) - OUTCHAR(*fptr); - } - break; - - case FORMAT_INTPTR: - /* Answer the count of characters written. */ -#ifdef HAVE_LONG_LONG_TYPE - if(p->flags & FLAGS_LONGLONG) - *(LONG_LONG_TYPE *) p->data.ptr = (LONG_LONG_TYPE)done; - else -#endif - if(p->flags & FLAGS_LONG) - *(long *) p->data.ptr = (long)done; - else if(!(p->flags & FLAGS_SHORT)) - *(int *) p->data.ptr = (int)done; - else - *(short *) p->data.ptr = (short)done; - break; - - default: - break; - } - f = *end++; /* goto end of %-code */ - - } - return done; -} - -/* fputc() look-alike */ -static int addbyter(int output, FILE *data) -{ - struct nsprintf *infop=(struct nsprintf *)data; - unsigned char outc = (unsigned char)output; - - if(infop->length < infop->max) { - /* only do this if we haven't reached max length yet */ - infop->buffer[0] = outc; /* store */ - infop->buffer++; /* increase pointer */ - infop->length++; /* we are now one byte larger */ - return outc; /* fputc() returns like this on success */ - } - return -1; -} - -int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, - va_list ap_save) -{ - int retcode; - struct nsprintf info; - - info.buffer = buffer; - info.length = 0; - info.max = maxlength; - - retcode = dprintf_formatf(&info, addbyter, format, ap_save); - if(info.max) { - /* we terminate this with a zero byte */ - if(info.max == info.length) - /* we're at maximum, scrap the last letter */ - info.buffer[-1] = 0; - else - info.buffer[0] = 0; - } - return retcode; -} - -int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) -{ - int retcode; - va_list ap_save; /* argument pointer */ - va_start(ap_save, format); - retcode = curl_mvsnprintf(buffer, maxlength, format, ap_save); - va_end(ap_save); - return retcode; -} - -/* fputc() look-alike */ -static int alloc_addbyter(int output, FILE *data) -{ - struct asprintf *infop=(struct asprintf *)data; - unsigned char outc = (unsigned char)output; - - if(!infop->buffer) { - infop->buffer = malloc(32); - if(!infop->buffer) { - infop->fail = 1; - return -1; /* fail */ - } - infop->alloc = 32; - infop->len =0; - } - else if(infop->len+1 >= infop->alloc) { - char *newptr; - - newptr = realloc(infop->buffer, infop->alloc*2); - - if(!newptr) { - infop->fail = 1; - return -1; /* fail */ - } - infop->buffer = newptr; - infop->alloc *= 2; - } - - infop->buffer[ infop->len ] = outc; - - infop->len++; - - return outc; /* fputc() returns like this on success */ -} - -char *curl_maprintf(const char *format, ...) -{ - va_list ap_save; /* argument pointer */ - int retcode; - struct asprintf info; - - info.buffer = NULL; - info.len = 0; - info.alloc = 0; - info.fail = 0; - - va_start(ap_save, format); - retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save); - va_end(ap_save); - if((-1 == retcode) || info.fail) { - if(info.alloc) - free(info.buffer); - return NULL; - } - if(info.alloc) { - info.buffer[info.len] = 0; /* we terminate this with a zero byte */ - return info.buffer; - } - else - return strdup(""); -} - -char *curl_mvaprintf(const char *format, va_list ap_save) -{ - int retcode; - struct asprintf info; - - info.buffer = NULL; - info.len = 0; - info.alloc = 0; - info.fail = 0; - - retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save); - if((-1 == retcode) || info.fail) { - if(info.alloc) - free(info.buffer); - return NULL; - } - - if(info.alloc) { - info.buffer[info.len] = 0; /* we terminate this with a zero byte */ - return info.buffer; - } - else - return strdup(""); -} - -static int storebuffer(int output, FILE *data) -{ - char **buffer = (char **)data; - unsigned char outc = (unsigned char)output; - **buffer = outc; - (*buffer)++; - return outc; /* act like fputc() ! */ -} - -int curl_msprintf(char *buffer, const char *format, ...) -{ - va_list ap_save; /* argument pointer */ - int retcode; - va_start(ap_save, format); - retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); - va_end(ap_save); - *buffer=0; /* we terminate this with a zero byte */ - return retcode; -} - -int curl_mprintf(const char *format, ...) -{ - int retcode; - va_list ap_save; /* argument pointer */ - va_start(ap_save, format); - - retcode = dprintf_formatf(stdout, fputc, format, ap_save); - va_end(ap_save); - return retcode; -} - -int curl_mfprintf(FILE *whereto, const char *format, ...) -{ - int retcode; - va_list ap_save; /* argument pointer */ - va_start(ap_save, format); - retcode = dprintf_formatf(whereto, fputc, format, ap_save); - va_end(ap_save); - return retcode; -} - -int curl_mvsprintf(char *buffer, const char *format, va_list ap_save) -{ - int retcode; - retcode = dprintf_formatf(&buffer, storebuffer, format, ap_save); - *buffer=0; /* we terminate this with a zero byte */ - return retcode; -} - -int curl_mvprintf(const char *format, va_list ap_save) -{ - return dprintf_formatf(stdout, fputc, format, ap_save); -} - -int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save) -{ - return dprintf_formatf(whereto, fputc, format, ap_save); -} diff --git a/lib/multi.c b/lib/multi.c deleted file mode 100644 index 9553883cb..000000000 --- a/lib/multi.c +++ /dev/null @@ -1,2814 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include - -#include "curl_urldata.h" -#include "curl_transfer.h" -#include "curl_url.h" -#include "curl_connect.h" -#include "curl_progress.h" -#include "curl_easyif.h" -#include "curl_multiif.h" -#include "curl_sendf.h" -#include "curl_timeval.h" -#include "curl_http.h" -#include "curl_select.h" -#include "curl_warnless.h" -#include "curl_speedcheck.h" -#include "curl_conncache.h" -#include "curl_bundles.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - CURL_SOCKET_HASH_TABLE_SIZE should be a prime number. Increasing it from 97 - to 911 takes on a 32-bit machine 4 x 804 = 3211 more bytes. Still, every - CURL handle takes 45-50 K memory, therefore this 3K are not significant. -*/ -#ifndef CURL_SOCKET_HASH_TABLE_SIZE -#define CURL_SOCKET_HASH_TABLE_SIZE 911 -#endif - -struct Curl_message { - /* the 'CURLMsg' is the part that is visible to the external user */ - struct CURLMsg extmsg; -}; - -/* NOTE: if you add a state here, add the name to the statename[] array as - well! -*/ -typedef enum { - CURLM_STATE_INIT, /* 0 - start in this state */ - CURLM_STATE_CONNECT, /* 1 - resolve/connect has been sent off */ - CURLM_STATE_WAITRESOLVE, /* 2 - awaiting the resolve to finalize */ - CURLM_STATE_WAITCONNECT, /* 3 - awaiting the connect to finalize */ - CURLM_STATE_WAITPROXYCONNECT, /* 4 - awaiting proxy CONNECT to finalize */ - CURLM_STATE_PROTOCONNECT, /* 5 - completing the protocol-specific connect - phase */ - CURLM_STATE_WAITDO, /* 6 - wait for our turn to send the request */ - CURLM_STATE_DO, /* 7 - start send off the request (part 1) */ - CURLM_STATE_DOING, /* 8 - sending off the request (part 1) */ - CURLM_STATE_DO_MORE, /* 9 - send off the request (part 2) */ - CURLM_STATE_DO_DONE, /* 10 - done sending off request */ - CURLM_STATE_WAITPERFORM, /* 11 - wait for our turn to read the response */ - CURLM_STATE_PERFORM, /* 12 - transfer data */ - CURLM_STATE_TOOFAST, /* 13 - wait because limit-rate exceeded */ - CURLM_STATE_DONE, /* 14 - post data transfer operation */ - CURLM_STATE_COMPLETED, /* 15 - operation complete */ - CURLM_STATE_MSGSENT, /* 16 - the operation complete message is sent */ - CURLM_STATE_LAST /* 17 - not a true state, never use this */ -} CURLMstate; - -/* we support N sockets per easy handle. Set the corresponding bit to what - action we should wait for */ -#define MAX_SOCKSPEREASYHANDLE 5 -#define GETSOCK_READABLE (0x00ff) -#define GETSOCK_WRITABLE (0xff00) - -struct Curl_one_easy { - /* first, two fields for the linked list of these */ - struct Curl_one_easy *next; - struct Curl_one_easy *prev; - - struct SessionHandle *easy_handle; /* the easy handle for this unit */ - struct connectdata *easy_conn; /* the "unit's" connection */ - - CURLMstate state; /* the handle's state */ - CURLcode result; /* previous result */ - - struct Curl_message msg; /* A single posted message. */ - - /* Array with the plain socket numbers this handle takes care of, in no - particular order. Note that all sockets are added to the sockhash, where - the state etc are also kept. This array is mostly used to detect when a - socket is to be removed from the hash. See singlesocket(). */ - curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; - int numsocks; -}; - -#define CURL_MULTI_HANDLE 0x000bab1e - -#define GOOD_MULTI_HANDLE(x) \ - ((x) && (((struct Curl_multi *)(x))->type == CURL_MULTI_HANDLE)) -#define GOOD_EASY_HANDLE(x) \ - ((x) && (((struct SessionHandle *)(x))->magic == CURLEASY_MAGIC_NUMBER)) - -/* This is the struct known as CURLM on the outside */ -struct Curl_multi { - /* First a simple identifier to easier detect if a user mix up - this multi handle with an easy handle. Set this to CURL_MULTI_HANDLE. */ - long type; - - /* We have a doubly-linked circular list with easy handles */ - struct Curl_one_easy easy; - - int num_easy; /* amount of entries in the linked list above. */ - int num_alive; /* amount of easy handles that are added but have not yet - reached COMPLETE state */ - - struct curl_llist *msglist; /* a list of messages from completed transfers */ - - /* callback function and user data pointer for the *socket() API */ - curl_socket_callback socket_cb; - void *socket_userp; - - /* Hostname cache */ - struct curl_hash *hostcache; - - /* timetree points to the splay-tree of time nodes to figure out expire - times of all currently set timers */ - struct Curl_tree *timetree; - - /* 'sockhash' is the lookup hash for socket descriptor => easy handles (note - the pluralis form, there can be more than one easy handle waiting on the - same actual socket) */ - struct curl_hash *sockhash; - - /* Whether pipelining is enabled for this multi handle */ - bool pipelining_enabled; - - /* Shared connection cache (bundles)*/ - struct conncache *conn_cache; - - /* This handle will be used for closing the cached connections in - curl_multi_cleanup() */ - struct SessionHandle *closure_handle; - - long maxconnects; /* if >0, a fixed limit of the maximum number of entries - we're allowed to grow the connection cache to */ - - /* timer callback and user data pointer for the *socket() API */ - curl_multi_timer_callback timer_cb; - void *timer_userp; - struct timeval timer_lastcall; /* the fixed time for the timeout for the - previous callback */ -}; - -static void singlesocket(struct Curl_multi *multi, - struct Curl_one_easy *easy); -static int update_timer(struct Curl_multi *multi); - -static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, - struct connectdata *conn); -static int checkPendPipeline(struct connectdata *conn); -static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle, - struct connectdata *conn); -static void moveHandleFromRecvToDonePipeline(struct SessionHandle *handle, - struct connectdata *conn); -static bool isHandleAtHead(struct SessionHandle *handle, - struct curl_llist *pipeline); -static CURLMcode add_next_timeout(struct timeval now, - struct Curl_multi *multi, - struct SessionHandle *d); - -#ifdef DEBUGBUILD -static const char * const statename[]={ - "INIT", - "CONNECT", - "WAITRESOLVE", - "WAITCONNECT", - "WAITPROXYCONNECT", - "PROTOCONNECT", - "WAITDO", - "DO", - "DOING", - "DO_MORE", - "DO_DONE", - "WAITPERFORM", - "PERFORM", - "TOOFAST", - "DONE", - "COMPLETED", - "MSGSENT", -}; -#endif - -static void multi_freetimeout(void *a, void *b); - -/* always use this function to change state, to make debugging easier */ -static void multistate(struct Curl_one_easy *easy, CURLMstate state) -{ -#ifdef DEBUGBUILD - long connection_id = -5000; -#endif - CURLMstate oldstate = easy->state; - - if(oldstate == state) - /* don't bother when the new state is the same as the old state */ - return; - - easy->state = state; - -#ifdef DEBUGBUILD - if(easy->easy_conn) { - if(easy->state > CURLM_STATE_CONNECT && - easy->state < CURLM_STATE_COMPLETED) - connection_id = easy->easy_conn->connection_id; - - infof(easy->easy_handle, - "STATE: %s => %s handle %p; (connection #%ld) \n", - statename[oldstate], statename[easy->state], - (char *)easy, connection_id); - } -#endif - if(state == CURLM_STATE_COMPLETED) - /* changing to COMPLETED means there's one less easy handle 'alive' */ - easy->easy_handle->multi->num_alive--; -} - -/* - * We add one of these structs to the sockhash for a particular socket - */ - -struct Curl_sh_entry { - struct SessionHandle *easy; - time_t timestamp; - int action; /* what action READ/WRITE this socket waits for */ - curl_socket_t socket; /* mainly to ease debugging */ - void *socketp; /* settable by users with curl_multi_assign() */ -}; -/* bits for 'action' having no bits means this socket is not expecting any - action */ -#define SH_READ 1 -#define SH_WRITE 2 - -/* make sure this socket is present in the hash for this handle */ -static struct Curl_sh_entry *sh_addentry(struct curl_hash *sh, - curl_socket_t s, - struct SessionHandle *data) -{ - struct Curl_sh_entry *there = - Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); - struct Curl_sh_entry *check; - - if(there) - /* it is present, return fine */ - return there; - - /* not present, add it */ - check = calloc(1, sizeof(struct Curl_sh_entry)); - if(!check) - return NULL; /* major failure */ - check->easy = data; - check->socket = s; - - /* make/add new hash entry */ - if(NULL == Curl_hash_add(sh, (char *)&s, sizeof(curl_socket_t), check)) { - free(check); - return NULL; /* major failure */ - } - - return check; /* things are good in sockhash land */ -} - - -/* delete the given socket + handle from the hash */ -static void sh_delentry(struct curl_hash *sh, curl_socket_t s) -{ - struct Curl_sh_entry *there = - Curl_hash_pick(sh, (char *)&s, sizeof(curl_socket_t)); - - if(there) { - /* this socket is in the hash */ - /* We remove the hash entry. (This'll end up in a call to - sh_freeentry().) */ - Curl_hash_delete(sh, (char *)&s, sizeof(curl_socket_t)); - } -} - -/* - * free a sockhash entry - */ -static void sh_freeentry(void *freethis) -{ - struct Curl_sh_entry *p = (struct Curl_sh_entry *) freethis; - - if(p) - free(p); -} - -static size_t fd_key_compare(void*k1, size_t k1_len, void*k2, size_t k2_len) -{ - (void) k1_len; (void) k2_len; - - return (*((int* ) k1)) == (*((int* ) k2)); -} - -static size_t hash_fd(void* key, size_t key_length, size_t slots_num) -{ - int fd = * ((int* ) key); - (void) key_length; - - return (fd % (int)slots_num); -} - -/* - * sh_init() creates a new socket hash and returns the handle for it. - * - * Quote from README.multi_socket: - * - * "Some tests at 7000 and 9000 connections showed that the socket hash lookup - * is somewhat of a bottle neck. Its current implementation may be a bit too - * limiting. It simply has a fixed-size array, and on each entry in the array - * it has a linked list with entries. So the hash only checks which list to - * scan through. The code I had used so for used a list with merely 7 slots - * (as that is what the DNS hash uses) but with 7000 connections that would - * make an average of 1000 nodes in each list to run through. I upped that to - * 97 slots (I believe a prime is suitable) and noticed a significant speed - * increase. I need to reconsider the hash implementation or use a rather - * large default value like this. At 9000 connections I was still below 10us - * per call." - * - */ -static struct curl_hash *sh_init(void) -{ - return Curl_hash_alloc(CURL_SOCKET_HASH_TABLE_SIZE, hash_fd, fd_key_compare, - sh_freeentry); -} - -/* - * multi_addmsg() - * - * Called when a transfer is completed. Adds the given msg pointer to - * the list kept in the multi handle. - */ -static CURLMcode multi_addmsg(struct Curl_multi *multi, - struct Curl_message *msg) -{ - if(!Curl_llist_insert_next(multi->msglist, multi->msglist->tail, msg)) - return CURLM_OUT_OF_MEMORY; - - return CURLM_OK; -} - -/* - * multi_freeamsg() - * - * Callback used by the llist system when a single list entry is destroyed. - */ -static void multi_freeamsg(void *a, void *b) -{ - (void)a; - (void)b; -} - -CURLM *curl_multi_init(void) -{ - struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); - - if(!multi) - return NULL; - - multi->type = CURL_MULTI_HANDLE; - - multi->hostcache = Curl_mk_dnscache(); - if(!multi->hostcache) - goto error; - - multi->sockhash = sh_init(); - if(!multi->sockhash) - goto error; - - multi->conn_cache = Curl_conncache_init(CONNCACHE_MULTI); - if(!multi->conn_cache) - goto error; - - multi->msglist = Curl_llist_alloc(multi_freeamsg); - if(!multi->msglist) - goto error; - - /* Let's make the doubly-linked list a circular list. This makes - the linked list code simpler and allows inserting at the end - with less work (we didn't keep a tail pointer before). */ - multi->easy.next = &multi->easy; - multi->easy.prev = &multi->easy; - - return (CURLM *) multi; - - error: - - Curl_hash_destroy(multi->sockhash); - multi->sockhash = NULL; - Curl_hash_destroy(multi->hostcache); - multi->hostcache = NULL; - Curl_conncache_destroy(multi->conn_cache); - multi->conn_cache = NULL; - - free(multi); - return NULL; -} - -CURLMcode curl_multi_add_handle(CURLM *multi_handle, - CURL *easy_handle) -{ - struct curl_llist *timeoutlist; - struct Curl_one_easy *easy; - struct Curl_multi *multi = (struct Curl_multi *)multi_handle; - struct SessionHandle *data = (struct SessionHandle *)easy_handle; - struct SessionHandle *new_closure = NULL; - struct curl_hash *hostcache = NULL; - - /* First, make some basic checks that the CURLM handle is a good handle */ - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(easy_handle)) - return CURLM_BAD_EASY_HANDLE; - - /* Prevent users from adding same easy handle more than - once and prevent adding to more than one multi stack */ - if(data->multi) - /* possibly we should create a new unique error code for this condition */ - return CURLM_BAD_EASY_HANDLE; - - /* Allocate and initialize timeout list for easy handle */ - timeoutlist = Curl_llist_alloc(multi_freetimeout); - if(!timeoutlist) - return CURLM_OUT_OF_MEMORY; - - /* Allocate new node for the doubly-linked circular list of - Curl_one_easy structs that holds pointers to easy handles */ - easy = calloc(1, sizeof(struct Curl_one_easy)); - if(!easy) { - Curl_llist_destroy(timeoutlist, NULL); - return CURLM_OUT_OF_MEMORY; - } - - /* In case multi handle has no hostcache yet, allocate one */ - if(!multi->hostcache) { - hostcache = Curl_mk_dnscache(); - if(!hostcache) { - free(easy); - Curl_llist_destroy(timeoutlist, NULL); - return CURLM_OUT_OF_MEMORY; - } - } - - /* In case multi handle has no closure_handle yet, allocate - a new easy handle to use when closing cached connections */ - if(!multi->closure_handle) { - new_closure = (struct SessionHandle *)curl_easy_init(); - if(!new_closure) { - Curl_hash_destroy(hostcache); - free(easy); - Curl_llist_destroy(timeoutlist, NULL); - return CURLM_OUT_OF_MEMORY; - } - } - - /* - ** No failure allowed in this function beyond this point. And - ** no modification of easy nor multi handle allowed before this - ** except for potential multi's connection cache growing which - ** won't be undone in this function no matter what. - */ - - /* In case a new closure handle has been initialized above, it - is associated now with the multi handle which lacked one. */ - if(new_closure) { - multi->closure_handle = new_closure; - Curl_easy_addmulti(multi->closure_handle, multi_handle); - multi->closure_handle->state.conn_cache = multi->conn_cache; - } - - /* In case hostcache has been allocated above, - it is associated now with the multi handle. */ - if(hostcache) - multi->hostcache = hostcache; - - /* Make easy handle use timeout list initialized above */ - data->state.timeoutlist = timeoutlist; - timeoutlist = NULL; - - /* set the easy handle */ - easy->easy_handle = data; - multistate(easy, CURLM_STATE_INIT); - - /* set the back pointer to one_easy to assist in removal */ - easy->easy_handle->multi_pos = easy; - - /* for multi interface connections, we share DNS cache automatically if the - easy handle's one is currently private. */ - if(easy->easy_handle->dns.hostcache && - (easy->easy_handle->dns.hostcachetype == HCACHE_PRIVATE)) { - Curl_hash_destroy(easy->easy_handle->dns.hostcache); - easy->easy_handle->dns.hostcache = NULL; - easy->easy_handle->dns.hostcachetype = HCACHE_NONE; - } - - if(!easy->easy_handle->dns.hostcache || - (easy->easy_handle->dns.hostcachetype == HCACHE_NONE)) { - easy->easy_handle->dns.hostcache = multi->hostcache; - easy->easy_handle->dns.hostcachetype = HCACHE_MULTI; - } - - /* On a multi stack the connection cache, owned by the multi handle, - is shared between all easy handles within the multi handle. - Therefore we free the private connection cache if there is one */ - if(easy->easy_handle->state.conn_cache && - easy->easy_handle->state.conn_cache->type == CONNCACHE_PRIVATE) { - Curl_conncache_destroy(easy->easy_handle->state.conn_cache); - } - - /* Point now to this multi's connection cache */ - easy->easy_handle->state.conn_cache = multi->conn_cache; - - /* This adds the new entry at the 'end' of the doubly-linked circular - list of Curl_one_easy structs to try and maintain a FIFO queue so - the pipelined requests are in order. */ - - /* We add this new entry last in the list. We make our 'next' point to the - 'first' struct and our 'prev' point to the previous 'prev' */ - easy->next = &multi->easy; - easy->prev = multi->easy.prev; - - /* make 'easy' the last node in the chain */ - multi->easy.prev = easy; - - /* if there was a prev node, make sure its 'next' pointer links to - the new node */ - easy->prev->next = easy; - - /* make the SessionHandle refer back to this multi handle */ - Curl_easy_addmulti(easy_handle, multi_handle); - - /* make the SessionHandle struct refer back to this struct */ - easy->easy_handle->set.one_easy = easy; - - /* Set the timeout for this handle to expire really soon so that it will - be taken care of even when this handle is added in the midst of operation - when only the curl_multi_socket() API is used. During that flow, only - sockets that time-out or have actions will be dealt with. Since this - handle has no action yet, we make sure it times out to get things to - happen. */ - Curl_expire(easy->easy_handle, 1); - - /* increase the node-counter */ - multi->num_easy++; - - /* increase the alive-counter */ - multi->num_alive++; - - /* A somewhat crude work-around for a little glitch in update_timer() that - happens if the lastcall time is set to the same time when the handle is - removed as when the next handle is added, as then the check in - update_timer() that prevents calling the application multiple times with - the same timer infor will not trigger and then the new handle's timeout - will not be notified to the app. - - The work-around is thus simply to clear the 'lastcall' variable to force - update_timer() to always trigger a callback to the app when a new easy - handle is added */ - memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); - - update_timer(multi); - return CURLM_OK; -} - -#if 0 -/* Debug-function, used like this: - * - * Curl_hash_print(multi->sockhash, debug_print_sock_hash); - * - * Enable the hash print function first by editing curl_hash.c - */ -static void debug_print_sock_hash(void *p) -{ - struct Curl_sh_entry *sh = (struct Curl_sh_entry *)p; - - fprintf(stderr, " [easy %p/magic %x/socket %d]", - (void *)sh->easy, sh->easy->magic, (int)sh->socket); -} -#endif - -CURLMcode curl_multi_remove_handle(CURLM *multi_handle, - CURL *curl_handle) -{ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - struct Curl_one_easy *easy; - struct SessionHandle *data = curl_handle; - - /* First, make some basic checks that the CURLM handle is a good handle */ - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - /* Verify that we got a somewhat good easy handle too */ - if(!GOOD_EASY_HANDLE(curl_handle)) - return CURLM_BAD_EASY_HANDLE; - - /* pick-up from the 'curl_handle' the kept position in the list */ - easy = data->multi_pos; - - if(easy) { - bool premature = (easy->state < CURLM_STATE_COMPLETED) ? TRUE : FALSE; - bool easy_owns_conn = (easy->easy_conn && - (easy->easy_conn->data == easy->easy_handle)) ? - TRUE : FALSE; - - /* If the 'state' is not INIT or COMPLETED, we might need to do something - nice to put the easy_handle in a good known state when this returns. */ - if(premature) - /* this handle is "alive" so we need to count down the total number of - alive connections when this is removed */ - multi->num_alive--; - - if(easy->easy_conn && - (easy->easy_conn->send_pipe->size + - easy->easy_conn->recv_pipe->size > 1) && - easy->state > CURLM_STATE_WAITDO && - easy->state < CURLM_STATE_COMPLETED) { - /* If the handle is in a pipeline and has started sending off its - request but not received its response yet, we need to close - connection. */ - easy->easy_conn->bits.close = TRUE; - /* Set connection owner so that Curl_done() closes it. - We can sefely do this here since connection is killed. */ - easy->easy_conn->data = easy->easy_handle; - } - - /* The timer must be shut down before easy->multi is set to NULL, - else the timenode will remain in the splay tree after - curl_easy_cleanup is called. */ - Curl_expire(easy->easy_handle, 0); - - /* destroy the timeout list that is held in the easy handle */ - if(data->state.timeoutlist) { - Curl_llist_destroy(data->state.timeoutlist, NULL); - data->state.timeoutlist = NULL; - } - - if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) { - if(multi->num_easy == 1) { - if(easy_owns_conn) { - Curl_resolver_cancel(easy->easy_conn); - if(easy->easy_conn->dns_entry) { - Curl_resolv_unlock(easy->easy_handle, easy->easy_conn->dns_entry); - easy->easy_conn->dns_entry = NULL; - } - } - Curl_hostcache_destroy(easy->easy_handle); - multi->hostcache = NULL; - } - /* clear out the usage of the shared DNS cache */ - easy->easy_handle->dns.hostcache = NULL; - easy->easy_handle->dns.hostcachetype = HCACHE_NONE; - } - - if(easy->easy_conn) { - - /* we must call Curl_done() here (if we still "own it") so that we don't - leave a half-baked one around */ - if(easy_owns_conn) { - - /* Curl_done() clears the conn->data field to lose the association - between the easy handle and the connection - - Note that this ignores the return code simply because there's - nothing really useful to do with it anyway! */ - (void)Curl_done(&easy->easy_conn, easy->result, premature); - } - else - /* Clear connection pipelines, if Curl_done above was not called */ - Curl_getoff_all_pipelines(easy->easy_handle, easy->easy_conn); - } - - if(easy->easy_handle->state.conn_cache->type == CONNCACHE_MULTI) { - /* if this was using the shared connection cache we clear the pointer - to that since we're not part of that handle anymore */ - easy->easy_handle->state.conn_cache = NULL; - easy->easy_handle->state.lastconnect = NULL; - } - - /* change state without using multistate(), only to make singlesocket() do - what we want */ - easy->state = CURLM_STATE_COMPLETED; - singlesocket(multi, easy); /* to let the application know what sockets - that vanish with this handle */ - - /* Remove the association between the connection and the handle */ - if(easy->easy_conn) { - easy->easy_conn->data = NULL; - easy->easy_conn = NULL; - } - - Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association - to this multi handle */ - - { - /* make sure there's no pending message in the queue sent from this easy - handle */ - struct curl_llist_element *e; - - for(e = multi->msglist->head; e; e = e->next) { - struct Curl_message *msg = e->ptr; - - if(msg->extmsg.easy_handle == easy->easy_handle) { - Curl_llist_remove(multi->msglist, e, NULL); - /* there can only be one from this specific handle */ - break; - } - } - } - - /* make the previous node point to our next */ - if(easy->prev) - easy->prev->next = easy->next; - /* make our next point to our previous node */ - if(easy->next) - easy->next->prev = easy->prev; - - easy->easy_handle->set.one_easy = NULL; /* detached */ - - /* Null the position in the controlling structure */ - easy->easy_handle->multi_pos = NULL; - - /* NOTE NOTE NOTE - We do not touch the easy handle here! */ - free(easy); - - multi->num_easy--; /* one less to care about now */ - - update_timer(multi); - return CURLM_OK; - } - else - return CURLM_BAD_EASY_HANDLE; /* twasn't found */ -} - -bool Curl_multi_canPipeline(const struct Curl_multi* multi) -{ - return multi->pipelining_enabled; -} - -void Curl_multi_handlePipeBreak(struct SessionHandle *data) -{ - struct Curl_one_easy *one_easy = data->set.one_easy; - - if(one_easy) - one_easy->easy_conn = NULL; -} - -static int waitconnect_getsock(struct connectdata *conn, - curl_socket_t *sock, - int numsocks) -{ - if(!numsocks) - return GETSOCK_BLANK; - - sock[0] = conn->sock[FIRSTSOCKET]; - - /* when we've sent a CONNECT to a proxy, we should rather wait for the - socket to become readable to be able to get the response headers */ - if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) - return GETSOCK_READSOCK(0); - - return GETSOCK_WRITESOCK(0); -} - -static int domore_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - if(conn && conn->handler->domore_getsock) - return conn->handler->domore_getsock(conn, socks, numsocks); - return GETSOCK_BLANK; -} - -/* returns bitmapped flags for this handle and its sockets */ -static int multi_getsock(struct Curl_one_easy *easy, - curl_socket_t *socks, /* points to numsocks number - of sockets */ - int numsocks) -{ - /* If the pipe broke, or if there's no connection left for this easy handle, - then we MUST bail out now with no bitmask set. The no connection case can - happen when this is called from curl_multi_remove_handle() => - singlesocket() => multi_getsock(). - */ - if(easy->easy_handle->state.pipe_broke || !easy->easy_conn) - return 0; - - if(easy->state > CURLM_STATE_CONNECT && - easy->state < CURLM_STATE_COMPLETED) { - /* Set up ownership correctly */ - easy->easy_conn->data = easy->easy_handle; - } - - switch(easy->state) { - default: -#if 0 /* switch back on these cases to get the compiler to check for all enums - to be present */ - case CURLM_STATE_TOOFAST: /* returns 0, so will not select. */ - case CURLM_STATE_COMPLETED: - case CURLM_STATE_MSGSENT: - case CURLM_STATE_INIT: - case CURLM_STATE_CONNECT: - case CURLM_STATE_WAITDO: - case CURLM_STATE_DONE: - case CURLM_STATE_LAST: - /* this will get called with CURLM_STATE_COMPLETED when a handle is - removed */ -#endif - return 0; - - case CURLM_STATE_WAITRESOLVE: - return Curl_resolver_getsock(easy->easy_conn, socks, numsocks); - - case CURLM_STATE_PROTOCONNECT: - return Curl_protocol_getsock(easy->easy_conn, socks, numsocks); - - case CURLM_STATE_DO: - case CURLM_STATE_DOING: - return Curl_doing_getsock(easy->easy_conn, socks, numsocks); - - case CURLM_STATE_WAITPROXYCONNECT: - case CURLM_STATE_WAITCONNECT: - return waitconnect_getsock(easy->easy_conn, socks, numsocks); - - case CURLM_STATE_DO_MORE: - return domore_getsock(easy->easy_conn, socks, numsocks); - - case CURLM_STATE_DO_DONE: /* since is set after DO is completed, we switch - to waiting for the same as the *PERFORM - states */ - case CURLM_STATE_PERFORM: - case CURLM_STATE_WAITPERFORM: - return Curl_single_getsock(easy->easy_conn, socks, numsocks); - } - -} - -CURLMcode curl_multi_fdset(CURLM *multi_handle, - fd_set *read_fd_set, fd_set *write_fd_set, - fd_set *exc_fd_set, int *max_fd) -{ - /* Scan through all the easy handles to get the file descriptors set. - Some easy handles may not have connected to the remote host yet, - and then we must make sure that is done. */ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - struct Curl_one_easy *easy; - int this_max_fd=-1; - curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; - int bitmap; - int i; - (void)exc_fd_set; /* not used */ - - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - easy=multi->easy.next; - while(easy != &multi->easy) { - bitmap = multi_getsock(easy, sockbunch, MAX_SOCKSPEREASYHANDLE); - - for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; - - if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { - FD_SET(sockbunch[i], read_fd_set); - s = sockbunch[i]; - } - if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { - FD_SET(sockbunch[i], write_fd_set); - s = sockbunch[i]; - } - if(s == CURL_SOCKET_BAD) - /* this socket is unused, break out of loop */ - break; - else { - if((int)s > this_max_fd) - this_max_fd = (int)s; - } - } - - easy = easy->next; /* check next handle */ - } - - *max_fd = this_max_fd; - - return CURLM_OK; -} - -CURLMcode curl_multi_wait(CURLM *multi_handle, - struct curl_waitfd extra_fds[], - unsigned int extra_nfds, - int timeout_ms, - int *ret) -{ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - struct Curl_one_easy *easy; - curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; - int bitmap; - unsigned int i; - unsigned int nfds = extra_nfds; - struct pollfd *ufds = NULL; - - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - /* Count up how many fds we have from the multi handle */ - easy=multi->easy.next; - while(easy != &multi->easy) { - bitmap = multi_getsock(easy, sockbunch, MAX_SOCKSPEREASYHANDLE); - - for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; - - if(bitmap & GETSOCK_READSOCK(i)) { - ++nfds; - s = sockbunch[i]; - } - if(bitmap & GETSOCK_WRITESOCK(i)) { - ++nfds; - s = sockbunch[i]; - } - if(s == CURL_SOCKET_BAD) { - break; - } - } - - easy = easy->next; /* check next handle */ - } - - if(nfds) { - ufds = malloc(nfds * sizeof(struct pollfd)); - if(!ufds) - return CURLM_OUT_OF_MEMORY; - } - nfds = 0; - - /* Add the curl handles to our pollfds first */ - easy=multi->easy.next; - while(easy != &multi->easy) { - bitmap = multi_getsock(easy, sockbunch, MAX_SOCKSPEREASYHANDLE); - - for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; - - if(bitmap & GETSOCK_READSOCK(i)) { - ufds[nfds].fd = sockbunch[i]; - ufds[nfds].events = POLLIN; - ++nfds; - s = sockbunch[i]; - } - if(bitmap & GETSOCK_WRITESOCK(i)) { - ufds[nfds].fd = sockbunch[i]; - ufds[nfds].events = POLLOUT; - ++nfds; - s = sockbunch[i]; - } - if(s == CURL_SOCKET_BAD) { - break; - } - } - - easy = easy->next; /* check next handle */ - } - - /* Add external file descriptions from poll-like struct curl_waitfd */ - for(i = 0; i < extra_nfds; i++) { - ufds[nfds].fd = extra_fds[i].fd; - ufds[nfds].events = 0; - if(extra_fds[i].events & CURL_WAIT_POLLIN) - ufds[nfds].events |= POLLIN; - if(extra_fds[i].events & CURL_WAIT_POLLPRI) - ufds[nfds].events |= POLLPRI; - if(extra_fds[i].events & CURL_WAIT_POLLOUT) - ufds[nfds].events |= POLLOUT; - ++nfds; - } - - if(nfds) - /* wait... */ - i = Curl_poll(ufds, nfds, timeout_ms); - else - i = 0; - - Curl_safefree(ufds); - if(ret) - *ret = i; - return CURLM_OK; -} - -static CURLMcode multi_runsingle(struct Curl_multi *multi, - struct timeval now, - struct Curl_one_easy *easy) -{ - struct Curl_message *msg = NULL; - bool connected; - bool async; - bool protocol_connect = FALSE; - bool dophase_done; - bool done = FALSE; - CURLMcode result = CURLM_OK; - struct SingleRequest *k; - struct SessionHandle *data; - long timeout_ms; - - if(!GOOD_EASY_HANDLE(easy->easy_handle)) - return CURLM_BAD_EASY_HANDLE; - - data = easy->easy_handle; - - do { - /* this is a single-iteration do-while loop just to allow a - break to skip to the end of it */ - bool disconnect_conn = FALSE; - - /* Handle the case when the pipe breaks, i.e., the connection - we're using gets cleaned up and we're left with nothing. */ - if(data->state.pipe_broke) { - infof(data, "Pipe broke: handle 0x%p, url = %s\n", - easy, data->state.path); - - if(easy->state < CURLM_STATE_COMPLETED) { - /* Head back to the CONNECT state */ - multistate(easy, CURLM_STATE_CONNECT); - result = CURLM_CALL_MULTI_PERFORM; - easy->result = CURLE_OK; - } - - data->state.pipe_broke = FALSE; - easy->easy_conn = NULL; - break; - } - - if(!easy->easy_conn && - easy->state > CURLM_STATE_CONNECT && - easy->state < CURLM_STATE_DONE) { - /* In all these states, the code will blindly access 'easy->easy_conn' - so this is precaution that it isn't NULL. And it silences static - analyzers. */ - failf(data, "In state %d with no easy_conn, bail out!\n", easy->state); - return CURLM_INTERNAL_ERROR; - } - - if(easy->easy_conn && easy->state > CURLM_STATE_CONNECT && - easy->state < CURLM_STATE_COMPLETED) - /* Make sure we set the connection's current owner */ - easy->easy_conn->data = data; - - if(easy->easy_conn && - (easy->state >= CURLM_STATE_CONNECT) && - (easy->state < CURLM_STATE_COMPLETED)) { - /* we need to wait for the connect state as only then is the start time - stored, but we must not check already completed handles */ - - timeout_ms = Curl_timeleft(data, &now, - (easy->state <= CURLM_STATE_WAITDO)? - TRUE:FALSE); - - if(timeout_ms < 0) { - /* Handle timed out */ - if(easy->state == CURLM_STATE_WAITRESOLVE) - failf(data, "Resolving timed out after %ld milliseconds", - Curl_tvdiff(now, data->progress.t_startsingle)); - else if(easy->state == CURLM_STATE_WAITCONNECT) - failf(data, "Connection timed out after %ld milliseconds", - Curl_tvdiff(now, data->progress.t_startsingle)); - else { - k = &data->req; - failf(data, "Operation timed out after %ld milliseconds with %" - FORMAT_OFF_T " out of %" FORMAT_OFF_T " bytes received", - Curl_tvdiff(now, data->progress.t_startsingle), k->bytecount, - k->size); - } - - /* Force the connection closed because the server could continue to - send us stuff at any time. (The disconnect_conn logic used below - doesn't work at this point). */ - easy->easy_conn->bits.close = TRUE; - easy->result = CURLE_OPERATION_TIMEDOUT; - multistate(easy, CURLM_STATE_COMPLETED); - break; - } - } - - switch(easy->state) { - case CURLM_STATE_INIT: - /* init this transfer. */ - easy->result=Curl_pretransfer(data); - - if(CURLE_OK == easy->result) { - /* after init, go CONNECT */ - multistate(easy, CURLM_STATE_CONNECT); - result = CURLM_CALL_MULTI_PERFORM; - - data->state.used_interface = Curl_if_multi; - } - break; - - case CURLM_STATE_CONNECT: - /* Connect. We get a connection identifier filled in. */ - Curl_pgrsTime(data, TIMER_STARTSINGLE); - easy->result = Curl_connect(data, &easy->easy_conn, - &async, &protocol_connect); - - if(CURLE_OK == easy->result) { - /* Add this handle to the send or pend pipeline */ - easy->result = addHandleToSendOrPendPipeline(data, - easy->easy_conn); - if(CURLE_OK != easy->result) - disconnect_conn = TRUE; - else { - if(async) - /* We're now waiting for an asynchronous name lookup */ - multistate(easy, CURLM_STATE_WAITRESOLVE); - else { - /* after the connect has been sent off, go WAITCONNECT unless the - protocol connect is already done and we can go directly to - WAITDO or DO! */ - result = CURLM_CALL_MULTI_PERFORM; - - if(protocol_connect) - multistate(easy, multi->pipelining_enabled? - CURLM_STATE_WAITDO:CURLM_STATE_DO); - else { -#ifndef CURL_DISABLE_HTTP - if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) - multistate(easy, CURLM_STATE_WAITPROXYCONNECT); - else -#endif - multistate(easy, CURLM_STATE_WAITCONNECT); - } - } - } - } - break; - - case CURLM_STATE_WAITRESOLVE: - /* awaiting an asynch name resolve to complete */ - { - struct Curl_dns_entry *dns = NULL; - - /* check if we have the name resolved by now */ - easy->result = Curl_resolver_is_resolved(easy->easy_conn, &dns); - - /* Update sockets here, because the socket(s) may have been - closed and the application thus needs to be told, even if it - is likely that the same socket(s) will again be used further - down. If the name has not yet been resolved, it is likely - that new sockets have been opened in an attempt to contact - another resolver. */ - singlesocket(multi, easy); - - if(dns) { - /* Perform the next step in the connection phase, and then move on - to the WAITCONNECT state */ - easy->result = Curl_async_resolved(easy->easy_conn, - &protocol_connect); - - if(CURLE_OK != easy->result) - /* if Curl_async_resolved() returns failure, the connection struct - is already freed and gone */ - easy->easy_conn = NULL; /* no more connection */ - else { - /* call again please so that we get the next socket setup */ - result = CURLM_CALL_MULTI_PERFORM; - if(protocol_connect) - multistate(easy, multi->pipelining_enabled? - CURLM_STATE_WAITDO:CURLM_STATE_DO); - else { -#ifndef CURL_DISABLE_HTTP - if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) - multistate(easy, CURLM_STATE_WAITPROXYCONNECT); - else -#endif - multistate(easy, CURLM_STATE_WAITCONNECT); - } - } - } - - if(CURLE_OK != easy->result) { - /* failure detected */ - disconnect_conn = TRUE; - break; - } - } - break; - -#ifndef CURL_DISABLE_HTTP - case CURLM_STATE_WAITPROXYCONNECT: - /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ - easy->result = Curl_http_connect(easy->easy_conn, &protocol_connect); - - if(easy->easy_conn->bits.proxy_connect_closed) { - /* reset the error buffer */ - if(data->set.errorbuffer) - data->set.errorbuffer[0] = '\0'; - data->state.errorbuf = FALSE; - - easy->result = CURLE_OK; - result = CURLM_CALL_MULTI_PERFORM; - multistate(easy, CURLM_STATE_CONNECT); - } - else if(CURLE_OK == easy->result) { - if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_COMPLETE) - multistate(easy, CURLM_STATE_WAITCONNECT); - } - break; -#endif - - case CURLM_STATE_WAITCONNECT: - /* awaiting a completion of an asynch connect */ - easy->result = Curl_is_connected(easy->easy_conn, - FIRSTSOCKET, - &connected); - if(connected) { - - if(!easy->result) - /* if everything is still fine we do the protocol-specific connect - setup */ - easy->result = Curl_protocol_connect(easy->easy_conn, - &protocol_connect); - } - - if(CURLE_OK != easy->result) { - /* failure detected */ - /* Just break, the cleaning up is handled all in one place */ - disconnect_conn = TRUE; - break; - } - - if(connected) { - if(!protocol_connect) { - /* We have a TCP connection, but 'protocol_connect' may be false - and then we continue to 'STATE_PROTOCONNECT'. If protocol - connect is TRUE, we move on to STATE_DO. - BUT if we are using a proxy we must change to WAITPROXYCONNECT - */ -#ifndef CURL_DISABLE_HTTP - if(easy->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) - multistate(easy, CURLM_STATE_WAITPROXYCONNECT); - else -#endif - multistate(easy, CURLM_STATE_PROTOCONNECT); - - } - else - /* after the connect has completed, go WAITDO or DO */ - multistate(easy, multi->pipelining_enabled? - CURLM_STATE_WAITDO:CURLM_STATE_DO); - - result = CURLM_CALL_MULTI_PERFORM; - } - break; - - case CURLM_STATE_PROTOCONNECT: - /* protocol-specific connect phase */ - easy->result = Curl_protocol_connecting(easy->easy_conn, - &protocol_connect); - if((easy->result == CURLE_OK) && protocol_connect) { - /* after the connect has completed, go WAITDO or DO */ - multistate(easy, multi->pipelining_enabled? - CURLM_STATE_WAITDO:CURLM_STATE_DO); - result = CURLM_CALL_MULTI_PERFORM; - } - else if(easy->result) { - /* failure detected */ - Curl_posttransfer(data); - Curl_done(&easy->easy_conn, easy->result, TRUE); - disconnect_conn = TRUE; - } - break; - - case CURLM_STATE_WAITDO: - /* Wait for our turn to DO when we're pipelining requests */ -#ifdef DEBUGBUILD - infof(data, "WAITDO: Conn %ld send pipe %zu inuse %d athead %d\n", - easy->easy_conn->connection_id, - easy->easy_conn->send_pipe->size, - easy->easy_conn->writechannel_inuse?1:0, - isHandleAtHead(data, - easy->easy_conn->send_pipe)?1:0); -#endif - if(!easy->easy_conn->writechannel_inuse && - isHandleAtHead(data, - easy->easy_conn->send_pipe)) { - /* Grab the channel */ - easy->easy_conn->writechannel_inuse = TRUE; - multistate(easy, CURLM_STATE_DO); - result = CURLM_CALL_MULTI_PERFORM; - } - break; - - case CURLM_STATE_DO: - if(data->set.connect_only) { - /* keep connection open for application to use the socket */ - easy->easy_conn->bits.close = FALSE; - multistate(easy, CURLM_STATE_DONE); - easy->result = CURLE_OK; - result = CURLM_CALL_MULTI_PERFORM; - } - else { - /* Perform the protocol's DO action */ - easy->result = Curl_do(&easy->easy_conn, - &dophase_done); - - if(CURLE_OK == easy->result) { - if(!dophase_done) { - /* some steps needed for wildcard matching */ - if(data->set.wildcardmatch) { - struct WildcardData *wc = &data->wildcard; - if(wc->state == CURLWC_DONE || wc->state == CURLWC_SKIP) { - /* skip some states if it is important */ - Curl_done(&easy->easy_conn, CURLE_OK, FALSE); - multistate(easy, CURLM_STATE_DONE); - result = CURLM_CALL_MULTI_PERFORM; - break; - } - } - /* DO was not completed in one function call, we must continue - DOING... */ - multistate(easy, CURLM_STATE_DOING); - result = CURLM_OK; - } - - /* after DO, go DO_DONE... or DO_MORE */ - else if(easy->easy_conn->bits.do_more) { - /* we're supposed to do more, but we need to sit down, relax - and wait a little while first */ - multistate(easy, CURLM_STATE_DO_MORE); - result = CURLM_OK; - } - else { - /* we're done with the DO, now DO_DONE */ - multistate(easy, CURLM_STATE_DO_DONE); - result = CURLM_CALL_MULTI_PERFORM; - } - } - else if((CURLE_SEND_ERROR == easy->result) && - easy->easy_conn->bits.reuse) { - /* - * In this situation, a connection that we were trying to use - * may have unexpectedly died. If possible, send the connection - * back to the CONNECT phase so we can try again. - */ - char *newurl = NULL; - followtype follow=FOLLOW_NONE; - CURLcode drc; - bool retry = FALSE; - - drc = Curl_retry_request(easy->easy_conn, &newurl); - if(drc) { - /* a failure here pretty much implies an out of memory */ - easy->result = drc; - disconnect_conn = TRUE; - } - else - retry = (newurl)?TRUE:FALSE; - - Curl_posttransfer(data); - drc = Curl_done(&easy->easy_conn, easy->result, FALSE); - - /* When set to retry the connection, we must to go back to - * the CONNECT state */ - if(retry) { - if((drc == CURLE_OK) || (drc == CURLE_SEND_ERROR)) { - follow = FOLLOW_RETRY; - drc = Curl_follow(data, newurl, follow); - if(drc == CURLE_OK) { - multistate(easy, CURLM_STATE_CONNECT); - result = CURLM_CALL_MULTI_PERFORM; - easy->result = CURLE_OK; - } - else { - /* Follow failed */ - easy->result = drc; - free(newurl); - } - } - else { - /* done didn't return OK or SEND_ERROR */ - easy->result = drc; - free(newurl); - } - } - else { - /* Have error handler disconnect conn if we can't retry */ - disconnect_conn = TRUE; - } - } - else { - /* failure detected */ - Curl_posttransfer(data); - Curl_done(&easy->easy_conn, easy->result, FALSE); - disconnect_conn = TRUE; - } - } - break; - - case CURLM_STATE_DOING: - /* we continue DOING until the DO phase is complete */ - easy->result = Curl_protocol_doing(easy->easy_conn, - &dophase_done); - if(CURLE_OK == easy->result) { - if(dophase_done) { - /* after DO, go DO_DONE or DO_MORE */ - multistate(easy, easy->easy_conn->bits.do_more? - CURLM_STATE_DO_MORE: - CURLM_STATE_DO_DONE); - result = CURLM_CALL_MULTI_PERFORM; - } /* dophase_done */ - } - else { - /* failure detected */ - Curl_posttransfer(data); - Curl_done(&easy->easy_conn, easy->result, FALSE); - disconnect_conn = TRUE; - } - break; - - case CURLM_STATE_DO_MORE: - /* - * When we are connected, DO MORE and then go DO_DONE - */ - easy->result = Curl_do_more(easy->easy_conn, &dophase_done); - - /* No need to remove this handle from the send pipeline here since that - is done in Curl_done() */ - if(CURLE_OK == easy->result) { - if(dophase_done) { - multistate(easy, CURLM_STATE_DO_DONE); - result = CURLM_CALL_MULTI_PERFORM; - } - else - /* stay in DO_MORE */ - result = CURLM_OK; - } - else { - /* failure detected */ - Curl_posttransfer(data); - Curl_done(&easy->easy_conn, easy->result, FALSE); - disconnect_conn = TRUE; - } - break; - - case CURLM_STATE_DO_DONE: - /* Move ourselves from the send to recv pipeline */ - moveHandleFromSendToRecvPipeline(data, easy->easy_conn); - /* Check if we can move pending requests to send pipe */ - checkPendPipeline(easy->easy_conn); - multistate(easy, CURLM_STATE_WAITPERFORM); - result = CURLM_CALL_MULTI_PERFORM; - break; - - case CURLM_STATE_WAITPERFORM: - /* Wait for our turn to PERFORM */ - if(!easy->easy_conn->readchannel_inuse && - isHandleAtHead(data, - easy->easy_conn->recv_pipe)) { - /* Grab the channel */ - easy->easy_conn->readchannel_inuse = TRUE; - multistate(easy, CURLM_STATE_PERFORM); - result = CURLM_CALL_MULTI_PERFORM; - } -#ifdef DEBUGBUILD - else { - infof(data, "WAITPERFORM: Conn %ld recv pipe %zu inuse %d athead %d\n", - easy->easy_conn->connection_id, - easy->easy_conn->recv_pipe->size, - easy->easy_conn->readchannel_inuse?1:0, - isHandleAtHead(data, - easy->easy_conn->recv_pipe)?1:0); - } -#endif - break; - - case CURLM_STATE_TOOFAST: /* limit-rate exceeded in either direction */ - /* if both rates are within spec, resume transfer */ - if(Curl_pgrsUpdate(easy->easy_conn)) - easy->result = CURLE_ABORTED_BY_CALLBACK; - else - easy->result = Curl_speedcheck(data, now); - - if(( (data->set.max_send_speed == 0) || - (data->progress.ulspeed < data->set.max_send_speed )) && - ( (data->set.max_recv_speed == 0) || - (data->progress.dlspeed < data->set.max_recv_speed))) - multistate(easy, CURLM_STATE_PERFORM); - break; - - case CURLM_STATE_PERFORM: - { - char *newurl = NULL; - bool retry = FALSE; - - /* check if over send speed */ - if((data->set.max_send_speed > 0) && - (data->progress.ulspeed > data->set.max_send_speed)) { - int buffersize; - - multistate(easy, CURLM_STATE_TOOFAST); - - /* calculate upload rate-limitation timeout. */ - buffersize = (int)(data->set.buffer_size ? - data->set.buffer_size : BUFSIZE); - timeout_ms = Curl_sleep_time(data->set.max_send_speed, - data->progress.ulspeed, buffersize); - Curl_expire(data, timeout_ms); - break; - } - - /* check if over recv speed */ - if((data->set.max_recv_speed > 0) && - (data->progress.dlspeed > data->set.max_recv_speed)) { - int buffersize; - - multistate(easy, CURLM_STATE_TOOFAST); - - /* Calculate download rate-limitation timeout. */ - buffersize = (int)(data->set.buffer_size ? - data->set.buffer_size : BUFSIZE); - timeout_ms = Curl_sleep_time(data->set.max_recv_speed, - data->progress.dlspeed, buffersize); - Curl_expire(data, timeout_ms); - break; - } - - /* read/write data if it is ready to do so */ - easy->result = Curl_readwrite(easy->easy_conn, &done); - - k = &data->req; - - if(!(k->keepon & KEEP_RECV)) { - /* We're done receiving */ - easy->easy_conn->readchannel_inuse = FALSE; - } - - if(!(k->keepon & KEEP_SEND)) { - /* We're done sending */ - easy->easy_conn->writechannel_inuse = FALSE; - } - - if(done || (easy->result == CURLE_RECV_ERROR)) { - /* If CURLE_RECV_ERROR happens early enough, we assume it was a race - * condition and the server closed the re-used connection exactly when - * we wanted to use it, so figure out if that is indeed the case. - */ - CURLcode ret = Curl_retry_request(easy->easy_conn, &newurl); - if(!ret) - retry = (newurl)?TRUE:FALSE; - - if(retry) - /* if we are to retry, set the result to OK */ - easy->result = CURLE_OK; - } - - if(easy->result) { - /* - * The transfer phase returned error, we mark the connection to get - * closed to prevent being re-used. This is because we can't possibly - * know if the connection is in a good shape or not now. Unless it is - * a protocol which uses two "channels" like FTP, as then the error - * happened in the data connection. - */ - - if(!(easy->easy_conn->handler->flags & PROTOPT_DUAL)) - easy->easy_conn->bits.close = TRUE; - - Curl_posttransfer(data); - Curl_done(&easy->easy_conn, easy->result, FALSE); - } - else if(done) { - followtype follow=FOLLOW_NONE; - - /* call this even if the readwrite function returned error */ - Curl_posttransfer(data); - - /* we're no longer receiving */ - moveHandleFromRecvToDonePipeline(data, - easy->easy_conn); - - /* expire the new receiving pipeline head */ - if(easy->easy_conn->recv_pipe->head) - Curl_expire(easy->easy_conn->recv_pipe->head->ptr, 1); - - /* Check if we can move pending requests to send pipe */ - checkPendPipeline(easy->easy_conn); - - /* When we follow redirects or is set to retry the connection, we must - to go back to the CONNECT state */ - if(data->req.newurl || retry) { - if(!retry) { - /* if the URL is a follow-location and not just a retried request - then figure out the URL here */ - newurl = data->req.newurl; - data->req.newurl = NULL; - follow = FOLLOW_REDIR; - } - else - follow = FOLLOW_RETRY; - easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE); - if(easy->result == CURLE_OK) - easy->result = Curl_follow(data, newurl, follow); - if(CURLE_OK == easy->result) { - multistate(easy, CURLM_STATE_CONNECT); - result = CURLM_CALL_MULTI_PERFORM; - newurl = NULL; /* handed over the memory ownership to - Curl_follow(), make sure we don't free() it - here */ - } - } - else { - /* after the transfer is done, go DONE */ - - /* but first check to see if we got a location info even though we're - not following redirects */ - if(data->req.location) { - if(newurl) - free(newurl); - newurl = data->req.location; - data->req.location = NULL; - easy->result = Curl_follow(data, newurl, FOLLOW_FAKE); - if(CURLE_OK == easy->result) - newurl = NULL; /* allocation was handed over Curl_follow() */ - else - disconnect_conn = TRUE; - } - - multistate(easy, CURLM_STATE_DONE); - result = CURLM_CALL_MULTI_PERFORM; - } - } - - if(newurl) - free(newurl); - break; - } - - case CURLM_STATE_DONE: - - if(easy->easy_conn) { - /* Remove ourselves from the receive and done pipelines. Handle - should be on one of these lists, depending upon how we got here. */ - Curl_removeHandleFromPipeline(data, - easy->easy_conn->recv_pipe); - Curl_removeHandleFromPipeline(data, - easy->easy_conn->done_pipe); - /* Check if we can move pending requests to send pipe */ - checkPendPipeline(easy->easy_conn); - - if(easy->easy_conn->bits.stream_was_rewound) { - /* This request read past its response boundary so we quickly let - the other requests consume those bytes since there is no - guarantee that the socket will become active again */ - result = CURLM_CALL_MULTI_PERFORM; - } - - /* post-transfer command */ - easy->result = Curl_done(&easy->easy_conn, CURLE_OK, FALSE); - /* - * If there are other handles on the pipeline, Curl_done won't set - * easy_conn to NULL. In such a case, curl_multi_remove_handle() can - * access free'd data, if the connection is free'd and the handle - * removed before we perform the processing in CURLM_STATE_COMPLETED - */ - if(easy->easy_conn) - easy->easy_conn = NULL; - } - - if(data->set.wildcardmatch) { - if(data->wildcard.state != CURLWC_DONE) { - /* if a wildcard is set and we are not ending -> lets start again - with CURLM_STATE_INIT */ - result = CURLM_CALL_MULTI_PERFORM; - multistate(easy, CURLM_STATE_INIT); - break; - } - } - - /* after we have DONE what we're supposed to do, go COMPLETED, and - it doesn't matter what the Curl_done() returned! */ - multistate(easy, CURLM_STATE_COMPLETED); - - break; - - case CURLM_STATE_COMPLETED: - /* this is a completed transfer, it is likely to still be connected */ - - /* This node should be delinked from the list now and we should post - an information message that we are complete. */ - - /* Important: reset the conn pointer so that we don't point to memory - that could be freed anytime */ - easy->easy_conn = NULL; - - Curl_expire(data, 0); /* stop all timers */ - break; - - case CURLM_STATE_MSGSENT: - return CURLM_OK; /* do nothing */ - - default: - return CURLM_INTERNAL_ERROR; - } - - if(easy->state < CURLM_STATE_COMPLETED) { - if(CURLE_OK != easy->result) { - /* - * If an error was returned, and we aren't in completed state now, - * then we go to completed and consider this transfer aborted. - */ - - /* NOTE: no attempt to disconnect connections must be made - in the case blocks above - cleanup happens only here */ - - data->state.pipe_broke = FALSE; - - if(easy->easy_conn) { - /* if this has a connection, unsubscribe from the pipelines */ - easy->easy_conn->writechannel_inuse = FALSE; - easy->easy_conn->readchannel_inuse = FALSE; - Curl_removeHandleFromPipeline(data, - easy->easy_conn->send_pipe); - Curl_removeHandleFromPipeline(data, - easy->easy_conn->recv_pipe); - Curl_removeHandleFromPipeline(data, - easy->easy_conn->done_pipe); - /* Check if we can move pending requests to send pipe */ - checkPendPipeline(easy->easy_conn); - - if(disconnect_conn) { - /* disconnect properly */ - Curl_disconnect(easy->easy_conn, /* dead_connection */ FALSE); - - /* This is where we make sure that the easy_conn pointer is reset. - We don't have to do this in every case block above where a - failure is detected */ - easy->easy_conn = NULL; - } - } - else if(easy->state == CURLM_STATE_CONNECT) { - /* Curl_connect() failed */ - (void)Curl_posttransfer(data); - } - - multistate(easy, CURLM_STATE_COMPLETED); - } - /* if there's still a connection to use, call the progress function */ - else if(easy->easy_conn && Curl_pgrsUpdate(easy->easy_conn)) { - /* aborted due to progress callback return code must close the - connection */ - easy->easy_conn->bits.close = TRUE; - - /* if not yet in DONE state, go there, otherwise COMPLETED */ - multistate(easy, (easy->state < CURLM_STATE_DONE)? - CURLM_STATE_DONE: CURLM_STATE_COMPLETED); - result = CURLM_CALL_MULTI_PERFORM; - } - } - } WHILE_FALSE; /* just to break out from! */ - - if(CURLM_STATE_COMPLETED == easy->state) { - /* now fill in the Curl_message with this info */ - msg = &easy->msg; - - msg->extmsg.msg = CURLMSG_DONE; - msg->extmsg.easy_handle = data; - msg->extmsg.data.result = easy->result; - - result = multi_addmsg(multi, msg); - - multistate(easy, CURLM_STATE_MSGSENT); - } - - return result; -} - - -CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) -{ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - struct Curl_one_easy *easy; - CURLMcode returncode=CURLM_OK; - struct Curl_tree *t; - struct timeval now = Curl_tvnow(); - - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - easy=multi->easy.next; - while(easy != &multi->easy) { - CURLMcode result; - struct WildcardData *wc = &easy->easy_handle->wildcard; - - if(easy->easy_handle->set.wildcardmatch) { - if(!wc->filelist) { - CURLcode ret = Curl_wildcard_init(wc); /* init wildcard structures */ - if(ret) - return CURLM_OUT_OF_MEMORY; - } - } - - do - result = multi_runsingle(multi, now, easy); - while(CURLM_CALL_MULTI_PERFORM == result); - - if(easy->easy_handle->set.wildcardmatch) { - /* destruct wildcard structures if it is needed */ - if(wc->state == CURLWC_DONE || result) - Curl_wildcard_dtor(wc); - } - - if(result) - returncode = result; - - easy = easy->next; /* operate on next handle */ - } - - /* - * Simply remove all expired timers from the splay since handles are dealt - * with unconditionally by this function and curl_multi_timeout() requires - * that already passed/handled expire times are removed from the splay. - * - * It is important that the 'now' value is set at the entry of this function - * and not for the current time as it may have ticked a little while since - * then and then we risk this loop to remove timers that actually have not - * been handled! - */ - do { - multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) - /* the removed may have another timeout in queue */ - (void)add_next_timeout(now, multi, t->payload); - - } while(t); - - *running_handles = multi->num_alive; - - if(CURLM_OK >= returncode) - update_timer(multi); - - return returncode; -} - -static void close_all_connections(struct Curl_multi *multi) -{ - struct connectdata *conn; - - conn = Curl_conncache_find_first_connection(multi->conn_cache); - while(conn) { - conn->data = multi->closure_handle; - - /* This will remove the connection from the cache */ - (void)Curl_disconnect(conn, FALSE); - - conn = Curl_conncache_find_first_connection(multi->conn_cache); - } -} - -CURLMcode curl_multi_cleanup(CURLM *multi_handle) -{ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - struct Curl_one_easy *easy; - struct Curl_one_easy *nexteasy; - - if(GOOD_MULTI_HANDLE(multi)) { - multi->type = 0; /* not good anymore */ - - /* Close all the connections in the connection cache */ - close_all_connections(multi); - - Curl_close(multi->closure_handle); - multi->closure_handle = NULL; - - Curl_hash_destroy(multi->sockhash); - multi->sockhash = NULL; - - Curl_conncache_destroy(multi->conn_cache); - multi->conn_cache = NULL; - - /* remove the pending list of messages */ - Curl_llist_destroy(multi->msglist, NULL); - multi->msglist = NULL; - - /* remove all easy handles */ - easy = multi->easy.next; - while(easy != &multi->easy) { - nexteasy=easy->next; - if(easy->easy_handle->dns.hostcachetype == HCACHE_MULTI) { - /* clear out the usage of the shared DNS cache */ - Curl_hostcache_clean(easy->easy_handle); - easy->easy_handle->dns.hostcache = NULL; - easy->easy_handle->dns.hostcachetype = HCACHE_NONE; - } - - /* Clear the pointer to the connection cache */ - easy->easy_handle->state.conn_cache = NULL; - - Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association */ - - free(easy); - easy = nexteasy; - } - - Curl_hash_destroy(multi->hostcache); - multi->hostcache = NULL; - - free(multi); - - return CURLM_OK; - } - else - return CURLM_BAD_HANDLE; -} - -/* - * curl_multi_info_read() - * - * This function is the primary way for a multi/multi_socket application to - * figure out if a transfer has ended. We MUST make this function as fast as - * possible as it will be polled frequently and we MUST NOT scan any lists in - * here to figure out things. We must scale fine to thousands of handles and - * beyond. The current design is fully O(1). - */ - -CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue) -{ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - struct Curl_message *msg; - - *msgs_in_queue = 0; /* default to none */ - - if(GOOD_MULTI_HANDLE(multi) && Curl_llist_count(multi->msglist)) { - /* there is one or more messages in the list */ - struct curl_llist_element *e; - - /* extract the head of the list to return */ - e = multi->msglist->head; - - msg = e->ptr; - - /* remove the extracted entry */ - Curl_llist_remove(multi->msglist, e, NULL); - - *msgs_in_queue = curlx_uztosi(Curl_llist_count(multi->msglist)); - - return &msg->extmsg; - } - else - return NULL; -} - -/* - * singlesocket() checks what sockets we deal with and their "action state" - * and if we have a different state in any of those sockets from last time we - * call the callback accordingly. - */ -static void singlesocket(struct Curl_multi *multi, - struct Curl_one_easy *easy) -{ - curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; - int i; - struct Curl_sh_entry *entry; - curl_socket_t s; - int num; - unsigned int curraction; - struct Curl_one_easy *easy_by_hash; - bool remove_sock_from_hash; - - for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) - socks[i] = CURL_SOCKET_BAD; - - /* Fill in the 'current' struct with the state as it is now: what sockets to - supervise and for what actions */ - curraction = multi_getsock(easy, socks, MAX_SOCKSPEREASYHANDLE); - - /* We have 0 .. N sockets already and we get to know about the 0 .. M - sockets we should have from now on. Detect the differences, remove no - longer supervised ones and add new ones */ - - /* walk over the sockets we got right now */ - for(i=0; (i< MAX_SOCKSPEREASYHANDLE) && - (curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))); - i++) { - int action = CURL_POLL_NONE; - - s = socks[i]; - - /* get it from the hash */ - entry = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s)); - - if(curraction & GETSOCK_READSOCK(i)) - action |= CURL_POLL_IN; - if(curraction & GETSOCK_WRITESOCK(i)) - action |= CURL_POLL_OUT; - - if(entry) { - /* yeps, already present so check if it has the same action set */ - if(entry->action == action) - /* same, continue */ - continue; - } - else { - /* this is a socket we didn't have before, add it! */ - entry = sh_addentry(multi->sockhash, s, easy->easy_handle); - if(!entry) - /* fatal */ - return; - } - - /* we know (entry != NULL) at this point, see the logic above */ - if(multi->socket_cb) - multi->socket_cb(easy->easy_handle, - s, - action, - multi->socket_userp, - entry->socketp); - - entry->action = action; /* store the current action state */ - } - - num = i; /* number of sockets */ - - /* when we've walked over all the sockets we should have right now, we must - make sure to detect sockets that are removed */ - for(i=0; i< easy->numsocks; i++) { - int j; - s = easy->sockets[i]; - for(j=0; jsockhash, (char *)&s, sizeof(s)); - if(entry) { - /* check if the socket to be removed serves a connection which has - other easy-s in a pipeline. In this case the socket should not be - removed. */ - struct connectdata *easy_conn; - - easy_by_hash = entry->easy->multi_pos; - easy_conn = easy_by_hash->easy_conn; - if(easy_conn) { - if(easy_conn->recv_pipe && easy_conn->recv_pipe->size > 1) { - /* the handle should not be removed from the pipe yet */ - remove_sock_from_hash = FALSE; - - /* Update the sockhash entry to instead point to the next in line - for the recv_pipe, or the first (in case this particular easy - isn't already) */ - if(entry->easy == easy->easy_handle) { - if(isHandleAtHead(easy->easy_handle, easy_conn->recv_pipe)) - entry->easy = easy_conn->recv_pipe->head->next->ptr; - else - entry->easy = easy_conn->recv_pipe->head->ptr; - } - } - if(easy_conn->send_pipe && easy_conn->send_pipe->size > 1) { - /* the handle should not be removed from the pipe yet */ - remove_sock_from_hash = FALSE; - - /* Update the sockhash entry to instead point to the next in line - for the send_pipe, or the first (in case this particular easy - isn't already) */ - if(entry->easy == easy->easy_handle) { - if(isHandleAtHead(easy->easy_handle, easy_conn->send_pipe)) - entry->easy = easy_conn->send_pipe->head->next->ptr; - else - entry->easy = easy_conn->send_pipe->head->ptr; - } - } - /* Don't worry about overwriting recv_pipe head with send_pipe_head, - when action will be asked on the socket (see multi_socket()), the - head of the correct pipe will be taken according to the - action. */ - } - } - else - /* just a precaution, this socket really SHOULD be in the hash already - but in case it isn't, we don't have to tell the app to remove it - either since it never got to know about it */ - remove_sock_from_hash = FALSE; - - if(remove_sock_from_hash) { - /* in this case 'entry' is always non-NULL */ - if(multi->socket_cb) - multi->socket_cb(easy->easy_handle, - s, - CURL_POLL_REMOVE, - multi->socket_userp, - entry->socketp); - sh_delentry(multi->sockhash, s); - } - - } - } - - memcpy(easy->sockets, socks, num*sizeof(curl_socket_t)); - easy->numsocks = num; -} - -/* - * add_next_timeout() - * - * Each SessionHandle has a list of timeouts. The add_next_timeout() is called - * when it has just been removed from the splay tree because the timeout has - * expired. This function is then to advance in the list to pick the next - * timeout to use (skip the already expired ones) and add this node back to - * the splay tree again. - * - * The splay tree only has each sessionhandle as a single node and the nearest - * timeout is used to sort it on. - */ -static CURLMcode add_next_timeout(struct timeval now, - struct Curl_multi *multi, - struct SessionHandle *d) -{ - struct timeval *tv = &d->state.expiretime; - struct curl_llist *list = d->state.timeoutlist; - struct curl_llist_element *e; - - /* move over the timeout list for this specific handle and remove all - timeouts that are now passed tense and store the next pending - timeout in *tv */ - for(e = list->head; e; ) { - struct curl_llist_element *n = e->next; - long diff = curlx_tvdiff(*(struct timeval *)e->ptr, now); - if(diff <= 0) - /* remove outdated entry */ - Curl_llist_remove(list, e, NULL); - else - /* the list is sorted so get out on the first mismatch */ - break; - e = n; - } - e = list->head; - if(!e) { - /* clear the expire times within the handles that we remove from the - splay tree */ - tv->tv_sec = 0; - tv->tv_usec = 0; - } - else { - /* copy the first entry to 'tv' */ - memcpy(tv, e->ptr, sizeof(*tv)); - - /* remove first entry from list */ - Curl_llist_remove(list, e, NULL); - - /* insert this node again into the splay */ - multi->timetree = Curl_splayinsert(*tv, multi->timetree, - &d->state.timenode); - } - return CURLM_OK; -} - - -static CURLMcode multi_socket(struct Curl_multi *multi, - bool checkall, - curl_socket_t s, - int ev_bitmask, - int *running_handles) -{ - CURLMcode result = CURLM_OK; - struct SessionHandle *data = NULL; - struct Curl_tree *t; - struct timeval now = Curl_tvnow(); - - if(checkall) { - struct Curl_one_easy *easyp; - /* *perform() deals with running_handles on its own */ - result = curl_multi_perform(multi, running_handles); - - /* walk through each easy handle and do the socket state change magic - and callbacks */ - easyp=multi->easy.next; - while(easyp != &multi->easy) { - singlesocket(multi, easyp); - easyp = easyp->next; - } - - /* or should we fall-through and do the timer-based stuff? */ - return result; - } - else if(s != CURL_SOCKET_TIMEOUT) { - - struct Curl_sh_entry *entry = - Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s)); - - if(!entry) - /* Unmatched socket, we can't act on it but we ignore this fact. In - real-world tests it has been proved that libevent can in fact give - the application actions even though the socket was just previously - asked to get removed, so thus we better survive stray socket actions - and just move on. */ - ; - else { - data = entry->easy; - - if(data->magic != CURLEASY_MAGIC_NUMBER) - /* bad bad bad bad bad bad bad */ - return CURLM_INTERNAL_ERROR; - - /* If the pipeline is enabled, take the handle which is in the head of - the pipeline. If we should write into the socket, take the send_pipe - head. If we should read from the socket, take the recv_pipe head. */ - if(data->set.one_easy->easy_conn) { - if((ev_bitmask & CURL_POLL_OUT) && - data->set.one_easy->easy_conn->send_pipe && - data->set.one_easy->easy_conn->send_pipe->head) - data = data->set.one_easy->easy_conn->send_pipe->head->ptr; - else if((ev_bitmask & CURL_POLL_IN) && - data->set.one_easy->easy_conn->recv_pipe && - data->set.one_easy->easy_conn->recv_pipe->head) - data = data->set.one_easy->easy_conn->recv_pipe->head->ptr; - } - - if(data->set.one_easy->easy_conn && - !(data->set.one_easy->easy_conn->handler->flags & PROTOPT_DIRLOCK)) - /* set socket event bitmask if they're not locked */ - data->set.one_easy->easy_conn->cselect_bits = ev_bitmask; - - do - result = multi_runsingle(multi, now, data->set.one_easy); - while(CURLM_CALL_MULTI_PERFORM == result); - - if(data->set.one_easy->easy_conn && - !(data->set.one_easy->easy_conn->handler->flags & PROTOPT_DIRLOCK)) - /* clear the bitmask only if not locked */ - data->set.one_easy->easy_conn->cselect_bits = 0; - - if(CURLM_OK >= result) - /* get the socket(s) and check if the state has been changed since - last */ - singlesocket(multi, data->set.one_easy); - - /* Now we fall-through and do the timer-based stuff, since we don't want - to force the user to have to deal with timeouts as long as at least - one connection in fact has traffic. */ - - data = NULL; /* set data to NULL again to avoid calling - multi_runsingle() in case there's no need to */ - } - } - - now.tv_usec += 40000; /* compensate for bad precision timers that might've - triggered too early */ - if(now.tv_usec >= 1000000) { - now.tv_sec++; - now.tv_usec -= 1000000; - } - - /* - * The loop following here will go on as long as there are expire-times left - * to process in the splay and 'data' will be re-assigned for every expired - * handle we deal with. - */ - do { - /* the first loop lap 'data' can be NULL */ - if(data) { - do - result = multi_runsingle(multi, now, data->set.one_easy); - while(CURLM_CALL_MULTI_PERFORM == result); - - if(CURLM_OK >= result) - /* get the socket(s) and check if the state has been changed since - last */ - singlesocket(multi, data->set.one_easy); - } - - /* Check if there's one (more) expired timer to deal with! This function - extracts a matching node if there is one */ - - multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) { - data = t->payload; /* assign this for next loop */ - (void)add_next_timeout(now, multi, t->payload); - } - - } while(t); - - *running_handles = multi->num_alive; - return result; -} - -#undef curl_multi_setopt -CURLMcode curl_multi_setopt(CURLM *multi_handle, - CURLMoption option, ...) -{ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - CURLMcode res = CURLM_OK; - va_list param; - - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - va_start(param, option); - - switch(option) { - case CURLMOPT_SOCKETFUNCTION: - multi->socket_cb = va_arg(param, curl_socket_callback); - break; - case CURLMOPT_SOCKETDATA: - multi->socket_userp = va_arg(param, void *); - break; - case CURLMOPT_PIPELINING: - multi->pipelining_enabled = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLMOPT_TIMERFUNCTION: - multi->timer_cb = va_arg(param, curl_multi_timer_callback); - break; - case CURLMOPT_TIMERDATA: - multi->timer_userp = va_arg(param, void *); - break; - case CURLMOPT_MAXCONNECTS: - multi->maxconnects = va_arg(param, long); - break; - default: - res = CURLM_UNKNOWN_OPTION; - break; - } - va_end(param); - return res; -} - -/* we define curl_multi_socket() in the public multi.h header */ -#undef curl_multi_socket - -CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, - int *running_handles) -{ - CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s, - 0, running_handles); - if(CURLM_OK >= result) - update_timer((struct Curl_multi *)multi_handle); - return result; -} - -CURLMcode curl_multi_socket_action(CURLM *multi_handle, curl_socket_t s, - int ev_bitmask, int *running_handles) -{ - CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, FALSE, s, - ev_bitmask, running_handles); - if(CURLM_OK >= result) - update_timer((struct Curl_multi *)multi_handle); - return result; -} - -CURLMcode curl_multi_socket_all(CURLM *multi_handle, int *running_handles) - -{ - CURLMcode result = multi_socket((struct Curl_multi *)multi_handle, - TRUE, CURL_SOCKET_BAD, 0, running_handles); - if(CURLM_OK >= result) - update_timer((struct Curl_multi *)multi_handle); - return result; -} - -static CURLMcode multi_timeout(struct Curl_multi *multi, - long *timeout_ms) -{ - static struct timeval tv_zero = {0,0}; - - if(multi->timetree) { - /* we have a tree of expire times */ - struct timeval now = Curl_tvnow(); - - /* splay the lowest to the bottom */ - multi->timetree = Curl_splay(tv_zero, multi->timetree); - - if(Curl_splaycomparekeys(multi->timetree->key, now) > 0) { - /* some time left before expiration */ - *timeout_ms = curlx_tvdiff(multi->timetree->key, now); - if(!*timeout_ms) - /* - * Since we only provide millisecond resolution on the returned value - * and the diff might be less than one millisecond here, we don't - * return zero as that may cause short bursts of busyloops on fast - * processors while the diff is still present but less than one - * millisecond! instead we return 1 until the time is ripe. - */ - *timeout_ms=1; - } - else - /* 0 means immediately */ - *timeout_ms = 0; - } - else - *timeout_ms = -1; - - return CURLM_OK; -} - -CURLMcode curl_multi_timeout(CURLM *multi_handle, - long *timeout_ms) -{ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - - /* First, make some basic checks that the CURLM handle is a good handle */ - if(!GOOD_MULTI_HANDLE(multi)) - return CURLM_BAD_HANDLE; - - return multi_timeout(multi, timeout_ms); -} - -/* - * Tell the application it should update its timers, if it subscribes to the - * update timer callback. - */ -static int update_timer(struct Curl_multi *multi) -{ - long timeout_ms; - - if(!multi->timer_cb) - return 0; - if(multi_timeout(multi, &timeout_ms)) { - return -1; - } - if(timeout_ms < 0) { - static const struct timeval none={0,0}; - if(Curl_splaycomparekeys(none, multi->timer_lastcall)) { - multi->timer_lastcall = none; - /* there's no timeout now but there was one previously, tell the app to - disable it */ - return multi->timer_cb((CURLM*)multi, -1, multi->timer_userp); - } - return 0; - } - - /* When multi_timeout() is done, multi->timetree points to the node with the - * timeout we got the (relative) time-out time for. We can thus easily check - * if this is the same (fixed) time as we got in a previous call and then - * avoid calling the callback again. */ - if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0) - return 0; - - multi->timer_lastcall = multi->timetree->key; - - return multi->timer_cb((CURLM*)multi, timeout_ms, multi->timer_userp); -} - -static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, - struct connectdata *conn) -{ - size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size; - struct curl_llist_element *sendhead = conn->send_pipe->head; - struct curl_llist *pipeline; - CURLcode rc; - - if(!Curl_isPipeliningEnabled(handle) || - pipeLen == 0) - pipeline = conn->send_pipe; - else { - if(conn->server_supports_pipelining && - pipeLen < MAX_PIPELINE_LENGTH) - pipeline = conn->send_pipe; - else - pipeline = conn->pend_pipe; - } - - rc = Curl_addHandleToPipeline(handle, pipeline); - - if(pipeline == conn->send_pipe && sendhead != conn->send_pipe->head) { - /* this is a new one as head, expire it */ - conn->writechannel_inuse = FALSE; /* not in use yet */ -#ifdef DEBUGBUILD - infof(conn->data, "%p is at send pipe head!\n", - conn->send_pipe->head->ptr); -#endif - Curl_expire(conn->send_pipe->head->ptr, 1); - } - - return rc; -} - -static int checkPendPipeline(struct connectdata *conn) -{ - int result = 0; - struct curl_llist_element *sendhead = conn->send_pipe->head; - - size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size; - if(conn->server_supports_pipelining || pipeLen == 0) { - struct curl_llist_element *curr = conn->pend_pipe->head; - const size_t maxPipeLen = - conn->server_supports_pipelining ? MAX_PIPELINE_LENGTH : 1; - - while(pipeLen < maxPipeLen && curr) { - Curl_llist_move(conn->pend_pipe, curr, - conn->send_pipe, conn->send_pipe->tail); - Curl_pgrsTime(curr->ptr, TIMER_PRETRANSFER); - ++result; /* count how many handles we moved */ - curr = conn->pend_pipe->head; - ++pipeLen; - } - } - - if(result) { - conn->now = Curl_tvnow(); - /* something moved, check for a new send pipeline leader */ - if(sendhead != conn->send_pipe->head) { - /* this is a new one as head, expire it */ - conn->writechannel_inuse = FALSE; /* not in use yet */ -#ifdef DEBUGBUILD - infof(conn->data, "%p is at send pipe head!\n", - conn->send_pipe->head->ptr); -#endif - Curl_expire(conn->send_pipe->head->ptr, 1); - } - } - - return result; -} - -/* Move this transfer from the sending list to the receiving list. - - Pay special attention to the new sending list "leader" as it needs to get - checked to update what sockets it acts on. - -*/ -static void moveHandleFromSendToRecvPipeline(struct SessionHandle *handle, - struct connectdata *conn) -{ - struct curl_llist_element *curr; - - curr = conn->send_pipe->head; - while(curr) { - if(curr->ptr == handle) { - Curl_llist_move(conn->send_pipe, curr, - conn->recv_pipe, conn->recv_pipe->tail); - - if(conn->send_pipe->head) { - /* Since there's a new easy handle at the start of the send pipeline, - set its timeout value to 1ms to make it trigger instantly */ - conn->writechannel_inuse = FALSE; /* not used now */ -#ifdef DEBUGBUILD - infof(conn->data, "%p is at send pipe head B!\n", - conn->send_pipe->head->ptr); -#endif - Curl_expire(conn->send_pipe->head->ptr, 1); - } - - /* The receiver's list is not really interesting here since either this - handle is now first in the list and we'll deal with it soon, or - another handle is already first and thus is already taken care of */ - - break; /* we're done! */ - } - curr = curr->next; - } -} - -static void moveHandleFromRecvToDonePipeline(struct SessionHandle *handle, - struct connectdata *conn) -{ - struct curl_llist_element *curr; - - curr = conn->recv_pipe->head; - while(curr) { - if(curr->ptr == handle) { - Curl_llist_move(conn->recv_pipe, curr, - conn->done_pipe, conn->done_pipe->tail); - break; - } - curr = curr->next; - } -} -static bool isHandleAtHead(struct SessionHandle *handle, - struct curl_llist *pipeline) -{ - struct curl_llist_element *curr = pipeline->head; - if(curr) - return (curr->ptr == handle) ? TRUE : FALSE; - - return FALSE; -} - -/* - * multi_freetimeout() - * - * Callback used by the llist system when a single timeout list entry is - * destroyed. - */ -static void multi_freetimeout(void *user, void *entryptr) -{ - (void)user; - - /* the entry was plain malloc()'ed */ - free(entryptr); -} - -/* - * multi_addtimeout() - * - * Add a timestamp to the list of timeouts. Keep the list sorted so that head - * of list is always the timeout nearest in time. - * - */ -static CURLMcode -multi_addtimeout(struct curl_llist *timeoutlist, - struct timeval *stamp) -{ - struct curl_llist_element *e; - struct timeval *timedup; - struct curl_llist_element *prev = NULL; - - timedup = malloc(sizeof(*timedup)); - if(!timedup) - return CURLM_OUT_OF_MEMORY; - - /* copy the timestamp */ - memcpy(timedup, stamp, sizeof(*timedup)); - - if(Curl_llist_count(timeoutlist)) { - /* find the correct spot in the list */ - for(e = timeoutlist->head; e; e = e->next) { - struct timeval *checktime = e->ptr; - long diff = curlx_tvdiff(*checktime, *timedup); - if(diff > 0) - break; - prev = e; - } - - } - /* else - this is the first timeout on the list */ - - if(!Curl_llist_insert_next(timeoutlist, prev, timedup)) { - free(timedup); - return CURLM_OUT_OF_MEMORY; - } - - return CURLM_OK; -} - -/* - * Curl_expire() - * - * given a number of milliseconds from now to use to set the 'act before - * this'-time for the transfer, to be extracted by curl_multi_timeout() - * - * Note that the timeout will be added to a queue of timeouts if it defines a - * moment in time that is later than the current head of queue. - * - * Pass zero to clear all timeout values for this handle. -*/ -void Curl_expire(struct SessionHandle *data, long milli) -{ - struct Curl_multi *multi = data->multi; - struct timeval *nowp = &data->state.expiretime; - int rc; - - /* this is only interesting for multi-interface using libcurl, and only - while there is still a multi interface struct remaining! */ - if(!multi) - return; - - if(!milli) { - /* No timeout, clear the time data. */ - if(nowp->tv_sec || nowp->tv_usec) { - /* Since this is an cleared time, we must remove the previous entry from - the splay tree */ - struct curl_llist *list = data->state.timeoutlist; - - rc = Curl_splayremovebyaddr(multi->timetree, - &data->state.timenode, - &multi->timetree); - if(rc) - infof(data, "Internal error clearing splay node = %d\n", rc); - - /* flush the timeout list too */ - while(list->size > 0) - Curl_llist_remove(list, list->tail, NULL); - -#ifdef DEBUGBUILD - infof(data, "Expire cleared\n"); -#endif - nowp->tv_sec = 0; - nowp->tv_usec = 0; - } - } - else { - struct timeval set; - - set = Curl_tvnow(); - set.tv_sec += milli/1000; - set.tv_usec += (milli%1000)*1000; - - if(set.tv_usec >= 1000000) { - set.tv_sec++; - set.tv_usec -= 1000000; - } - - if(nowp->tv_sec || nowp->tv_usec) { - /* This means that the struct is added as a node in the splay tree. - Compare if the new time is earlier, and only remove-old/add-new if it - is. */ - long diff = curlx_tvdiff(set, *nowp); - if(diff > 0) { - /* the new expire time was later so just add it to the queue - and get out */ - multi_addtimeout(data->state.timeoutlist, &set); - return; - } - - /* the new time is newer than the presently set one, so add the current - to the queue and update the head */ - multi_addtimeout(data->state.timeoutlist, nowp); - - /* Since this is an updated time, we must remove the previous entry from - the splay tree first and then re-add the new value */ - rc = Curl_splayremovebyaddr(multi->timetree, - &data->state.timenode, - &multi->timetree); - if(rc) - infof(data, "Internal error removing splay node = %d\n", rc); - } - - *nowp = set; - data->state.timenode.payload = data; - multi->timetree = Curl_splayinsert(*nowp, - multi->timetree, - &data->state.timenode); - } -#if 0 - Curl_splayprint(multi->timetree, 0, TRUE); -#endif -} - -CURLMcode curl_multi_assign(CURLM *multi_handle, - curl_socket_t s, void *hashp) -{ - struct Curl_sh_entry *there = NULL; - struct Curl_multi *multi = (struct Curl_multi *)multi_handle; - - if(s != CURL_SOCKET_BAD) - there = Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(curl_socket_t)); - - if(!there) - return CURLM_BAD_SOCKET; - - there->socketp = hashp; - - return CURLM_OK; -} - -#ifdef DEBUGBUILD -void Curl_multi_dump(const struct Curl_multi *multi_handle) -{ - struct Curl_multi *multi=(struct Curl_multi *)multi_handle; - struct Curl_one_easy *easy; - int i; - fprintf(stderr, "* Multi status: %d handles, %d alive\n", - multi->num_easy, multi->num_alive); - for(easy=multi->easy.next; easy != &multi->easy; easy = easy->next) { - if(easy->state < CURLM_STATE_COMPLETED) { - /* only display handles that are not completed */ - fprintf(stderr, "handle %p, state %s, %d sockets\n", - (void *)easy->easy_handle, - statename[easy->state], easy->numsocks); - for(i=0; i < easy->numsocks; i++) { - curl_socket_t s = easy->sockets[i]; - struct Curl_sh_entry *entry = - Curl_hash_pick(multi->sockhash, (char *)&s, sizeof(s)); - - fprintf(stderr, "%d ", (int)s); - if(!entry) { - fprintf(stderr, "INTERNAL CONFUSION\n"); - continue; - } - fprintf(stderr, "[%s %s] ", - entry->action&CURL_POLL_IN?"RECVING":"", - entry->action&CURL_POLL_OUT?"SENDING":""); - } - if(easy->numsocks) - fprintf(stderr, "\n"); - } - } -} -#endif diff --git a/lib/netrc.c b/lib/netrc.c deleted file mode 100644 index 10853d395..000000000 --- a/lib/netrc.c +++ /dev/null @@ -1,186 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_PWD_H -#include -#endif -#ifdef __VMS -#include -#endif - -#include -#include "curl_netrc.h" - -#include "curl_strequal.h" -#include "curl_strtok.h" -#include "curl_memory.h" -#include "curl_rawstr.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* Get user and password from .netrc when given a machine name */ - -enum host_lookup_state { - NOTHING, - HOSTFOUND, /* the 'machine' keyword was found */ - HOSTVALID /* this is "our" machine! */ -}; - -/* - * @unittest: 1304 - */ -int Curl_parsenetrc(const char *host, - char *login, - char *password, - char *netrcfile) -{ - FILE *file; - int retcode=1; - int specific_login = (login[0] != 0); - char *home = NULL; - bool home_alloc = FALSE; - bool netrc_alloc = FALSE; - enum host_lookup_state state=NOTHING; - - char state_login=0; /* Found a login keyword */ - char state_password=0; /* Found a password keyword */ - int state_our_login=FALSE; /* With specific_login, found *our* login name */ - -#define NETRC DOT_CHAR "netrc" - - if(!netrcfile) { - home = curl_getenv("HOME"); /* portable environment reader */ - if(home) { - home_alloc = TRUE; -#if defined(HAVE_GETPWUID) && defined(HAVE_GETEUID) - } - else { - struct passwd *pw; - pw= getpwuid(geteuid()); - if(pw) { -#ifdef __VMS - home = decc_translate_vms(pw->pw_dir); -#else - home = pw->pw_dir; -#endif - } -#endif - } - - if(!home) - return -1; - - netrcfile = curl_maprintf("%s%s%s", home, DIR_CHAR, NETRC); - if(!netrcfile) { - if(home_alloc) - free(home); - return -1; - } - netrc_alloc = TRUE; - } - - file = fopen(netrcfile, "r"); - if(file) { - char *tok; - char *tok_buf; - bool done=FALSE; - char netrcbuffer[256]; - int netrcbuffsize = (int)sizeof(netrcbuffer); - - while(!done && fgets(netrcbuffer, netrcbuffsize, file)) { - tok=strtok_r(netrcbuffer, " \t\n", &tok_buf); - while(!done && tok) { - - if(login[0] && password[0]) { - done=TRUE; - break; - } - - switch(state) { - case NOTHING: - if(Curl_raw_equal("machine", tok)) { - /* the next tok is the machine name, this is in itself the - delimiter that starts the stuff entered for this machine, - after this we need to search for 'login' and - 'password'. */ - state=HOSTFOUND; - } - break; - case HOSTFOUND: - if(Curl_raw_equal(host, tok)) { - /* and yes, this is our host! */ - state=HOSTVALID; - retcode=0; /* we did find our host */ - } - else - /* not our host */ - state=NOTHING; - break; - case HOSTVALID: - /* we are now parsing sub-keywords concerning "our" host */ - if(state_login) { - if(specific_login) { - state_our_login = Curl_raw_equal(login, tok); - } - else { - strncpy(login, tok, LOGINSIZE-1); - } - state_login=0; - } - else if(state_password) { - if(state_our_login || !specific_login) { - strncpy(password, tok, PASSWORDSIZE-1); - } - state_password=0; - } - else if(Curl_raw_equal("login", tok)) - state_login=1; - else if(Curl_raw_equal("password", tok)) - state_password=1; - else if(Curl_raw_equal("machine", tok)) { - /* ok, there's machine here go => */ - state = HOSTFOUND; - state_our_login = FALSE; - } - break; - } /* switch (state) */ - - tok = strtok_r(NULL, " \t\n", &tok_buf); - } /* while(tok) */ - } /* while fgets() */ - - fclose(file); - } - - if(home_alloc) - free(home); - if(netrc_alloc) - free(netrcfile); - - return retcode; -} diff --git a/lib/non-ascii.c b/lib/non-ascii.c deleted file mode 100644 index 68b33a92a..000000000 --- a/lib/non-ascii.c +++ /dev/null @@ -1,343 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef CURL_DOES_CONVERSIONS - -#include - -#include "curl_non_ascii.h" -#include "curl_formdata.h" -#include "curl_sendf.h" -#include "curl_urldata.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifdef HAVE_ICONV -#include -/* set default codesets for iconv */ -#ifndef CURL_ICONV_CODESET_OF_NETWORK -#define CURL_ICONV_CODESET_OF_NETWORK "ISO8859-1" -#endif -#ifndef CURL_ICONV_CODESET_FOR_UTF8 -#define CURL_ICONV_CODESET_FOR_UTF8 "UTF-8" -#endif -#define ICONV_ERROR (size_t)-1 -#endif /* HAVE_ICONV */ - -/* - * Curl_convert_clone() returns a malloced copy of the source string (if - * returning CURLE_OK), with the data converted to network format. - */ -CURLcode Curl_convert_clone(struct SessionHandle *data, - const char *indata, - size_t insize, - char **outbuf) -{ - char *convbuf; - CURLcode result; - - convbuf = malloc(insize); - if(!convbuf) - return CURLE_OUT_OF_MEMORY; - - memcpy(convbuf, indata, insize); - result = Curl_convert_to_network(data, convbuf, insize); - if(result) { - free(convbuf); - return result; - } - - *outbuf = convbuf; /* return the converted buffer */ - - return CURLE_OK; -} - -/* - * Curl_convert_to_network() is an internal function for performing ASCII - * conversions on non-ASCII platforms. It convers the buffer _in place_. - */ -CURLcode Curl_convert_to_network(struct SessionHandle *data, - char *buffer, size_t length) -{ - CURLcode rc; - - if(data->set.convtonetwork) { - /* use translation callback */ - rc = data->set.convtonetwork(buffer, length); - if(rc != CURLE_OK) { - failf(data, - "CURLOPT_CONV_TO_NETWORK_FUNCTION callback returned %d: %s", - (int)rc, curl_easy_strerror(rc)); - } - return rc; - } - else { -#ifdef HAVE_ICONV - /* do the translation ourselves */ - char *input_ptr, *output_ptr; - size_t in_bytes, out_bytes, rc; - int error; - - /* open an iconv conversion descriptor if necessary */ - if(data->outbound_cd == (iconv_t)-1) { - data->outbound_cd = iconv_open(CURL_ICONV_CODESET_OF_NETWORK, - CURL_ICONV_CODESET_OF_HOST); - if(data->outbound_cd == (iconv_t)-1) { - error = ERRNO; - failf(data, - "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", - CURL_ICONV_CODESET_OF_NETWORK, - CURL_ICONV_CODESET_OF_HOST, - error, strerror(error)); - return CURLE_CONV_FAILED; - } - } - /* call iconv */ - input_ptr = output_ptr = buffer; - in_bytes = out_bytes = length; - rc = iconv(data->outbound_cd, (const char**)&input_ptr, &in_bytes, - &output_ptr, &out_bytes); - if((rc == ICONV_ERROR) || (in_bytes != 0)) { - error = ERRNO; - failf(data, - "The Curl_convert_to_network iconv call failed with errno %i: %s", - error, strerror(error)); - return CURLE_CONV_FAILED; - } -#else - failf(data, "CURLOPT_CONV_TO_NETWORK_FUNCTION callback required"); - return CURLE_CONV_REQD; -#endif /* HAVE_ICONV */ - } - - return CURLE_OK; -} - -/* - * Curl_convert_from_network() is an internal function for performing ASCII - * conversions on non-ASCII platforms. It convers the buffer _in place_. - */ -CURLcode Curl_convert_from_network(struct SessionHandle *data, - char *buffer, size_t length) -{ - CURLcode rc; - - if(data->set.convfromnetwork) { - /* use translation callback */ - rc = data->set.convfromnetwork(buffer, length); - if(rc != CURLE_OK) { - failf(data, - "CURLOPT_CONV_FROM_NETWORK_FUNCTION callback returned %d: %s", - (int)rc, curl_easy_strerror(rc)); - } - return rc; - } - else { -#ifdef HAVE_ICONV - /* do the translation ourselves */ - char *input_ptr, *output_ptr; - size_t in_bytes, out_bytes, rc; - int error; - - /* open an iconv conversion descriptor if necessary */ - if(data->inbound_cd == (iconv_t)-1) { - data->inbound_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_OF_NETWORK); - if(data->inbound_cd == (iconv_t)-1) { - error = ERRNO; - failf(data, - "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", - CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_OF_NETWORK, - error, strerror(error)); - return CURLE_CONV_FAILED; - } - } - /* call iconv */ - input_ptr = output_ptr = buffer; - in_bytes = out_bytes = length; - rc = iconv(data->inbound_cd, (const char **)&input_ptr, &in_bytes, - &output_ptr, &out_bytes); - if((rc == ICONV_ERROR) || (in_bytes != 0)) { - error = ERRNO; - failf(data, - "Curl_convert_from_network iconv call failed with errno %i: %s", - error, strerror(error)); - return CURLE_CONV_FAILED; - } -#else - failf(data, "CURLOPT_CONV_FROM_NETWORK_FUNCTION callback required"); - return CURLE_CONV_REQD; -#endif /* HAVE_ICONV */ - } - - return CURLE_OK; -} - -/* - * Curl_convert_from_utf8() is an internal function for performing UTF-8 - * conversions on non-ASCII platforms. - */ -CURLcode Curl_convert_from_utf8(struct SessionHandle *data, - char *buffer, size_t length) -{ - CURLcode rc; - - if(data->set.convfromutf8) { - /* use translation callback */ - rc = data->set.convfromutf8(buffer, length); - if(rc != CURLE_OK) { - failf(data, - "CURLOPT_CONV_FROM_UTF8_FUNCTION callback returned %d: %s", - (int)rc, curl_easy_strerror(rc)); - } - return rc; - } - else { -#ifdef HAVE_ICONV - /* do the translation ourselves */ - const char *input_ptr; - char *output_ptr; - size_t in_bytes, out_bytes, rc; - int error; - - /* open an iconv conversion descriptor if necessary */ - if(data->utf8_cd == (iconv_t)-1) { - data->utf8_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_FOR_UTF8); - if(data->utf8_cd == (iconv_t)-1) { - error = ERRNO; - failf(data, - "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", - CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_FOR_UTF8, - error, strerror(error)); - return CURLE_CONV_FAILED; - } - } - /* call iconv */ - input_ptr = output_ptr = buffer; - in_bytes = out_bytes = length; - rc = iconv(data->utf8_cd, &input_ptr, &in_bytes, - &output_ptr, &out_bytes); - if((rc == ICONV_ERROR) || (in_bytes != 0)) { - error = ERRNO; - failf(data, - "The Curl_convert_from_utf8 iconv call failed with errno %i: %s", - error, strerror(error)); - return CURLE_CONV_FAILED; - } - if(output_ptr < input_ptr) { - /* null terminate the now shorter output string */ - *output_ptr = 0x00; - } -#else - failf(data, "CURLOPT_CONV_FROM_UTF8_FUNCTION callback required"); - return CURLE_CONV_REQD; -#endif /* HAVE_ICONV */ - } - - return CURLE_OK; -} - -/* - * Init conversion stuff for a SessionHandle - */ -void Curl_convert_init(struct SessionHandle *data) -{ -#if defined(CURL_DOES_CONVERSIONS) && defined(HAVE_ICONV) - /* conversion descriptors for iconv calls */ - data->outbound_cd = (iconv_t)-1; - data->inbound_cd = (iconv_t)-1; - data->utf8_cd = (iconv_t)-1; -#else - (void)data; -#endif /* CURL_DOES_CONVERSIONS && HAVE_ICONV */ -} - -/* - * Setup conversion stuff for a SessionHandle - */ -void Curl_convert_setup(struct SessionHandle *data) -{ - data->inbound_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_OF_NETWORK); - data->outbound_cd = iconv_open(CURL_ICONV_CODESET_OF_NETWORK, - CURL_ICONV_CODESET_OF_HOST); - data->utf8_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_FOR_UTF8); -} - -/* - * Close conversion stuff for a SessionHandle - */ - -void Curl_convert_close(struct SessionHandle *data) -{ -#ifdef HAVE_ICONV - /* close iconv conversion descriptors */ - if(data->inbound_cd != (iconv_t)-1) { - iconv_close(data->inbound_cd); - } - if(data->outbound_cd != (iconv_t)-1) { - iconv_close(data->outbound_cd); - } - if(data->utf8_cd != (iconv_t)-1) { - iconv_close(data->utf8_cd); - } -#else - (void)data; -#endif /* HAVE_ICONV */ -} - -/* - * Curl_convert_form() is used from curl_http.c, this converts any form items - * that need to be sent in the network encoding. Returns CURLE_OK on success. - */ -CURLcode Curl_convert_form(struct SessionHandle *data, struct FormData *form) -{ - struct FormData *next; - CURLcode rc; - - if(!form) - return CURLE_OK; - - if(!data) - return CURLE_BAD_FUNCTION_ARGUMENT; - - do { - next=form->next; /* the following form line */ - if(form->type == FORM_DATA) { - rc = Curl_convert_to_network(data, form->line, form->length); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(rc != CURLE_OK) - return rc; - } - } while((form = next) != NULL); /* continue */ - return CURLE_OK; -} - -#endif /* CURL_DOES_CONVERSIONS */ diff --git a/lib/nonblock.c b/lib/nonblock.c deleted file mode 100644 index 6e6d1287f..000000000 --- a/lib/nonblock.c +++ /dev/null @@ -1,91 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_SYS_IOCTL_H -#include -#endif -#ifdef HAVE_FCNTL_H -#include -#endif - -#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE)) -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#include "curl_nonblock.h" - -/* - * curlx_nonblock() set the given socket to either blocking or non-blocking - * mode based on the 'nonblock' boolean argument. This function is highly - * portable. - */ -int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ - int nonblock /* TRUE or FALSE */) -{ -#if defined(USE_BLOCKING_SOCKETS) - - return 0; /* returns success */ - -#elif defined(HAVE_FCNTL_O_NONBLOCK) - - /* most recent unix versions */ - int flags; - flags = sfcntl(sockfd, F_GETFL, 0); - if(nonblock) - return sfcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - else - return sfcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); - -#elif defined(HAVE_IOCTL_FIONBIO) - - /* older unix versions */ - int flags = nonblock ? 1 : 0; - return ioctl(sockfd, FIONBIO, &flags); - -#elif defined(HAVE_IOCTLSOCKET_FIONBIO) - - /* Windows */ - unsigned long flags = nonblock ? 1UL : 0UL; - return ioctlsocket(sockfd, FIONBIO, &flags); - -#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) - - /* Amiga */ - long flags = nonblock ? 1L : 0L; - return IoctlSocket(sockfd, FIONBIO, flags); - -#elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK) - - /* BeOS */ - long b = nonblock ? 1L : 0L; - return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b)); - -#else -# error "no non-blocking method was found/used/set" -#endif -} diff --git a/lib/nss.c b/lib/nss.c deleted file mode 100644 index 15e92a722..000000000 --- a/lib/nss.c +++ /dev/null @@ -1,1572 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -/* - * Source file for all NSS-specific code for the TLS/SSL layer. No code - * but curl_sslgen.c should ever call or use these functions. - */ - -#include "curl_setup.h" - -#ifdef USE_NSS - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_formdata.h" /* for the boundary function */ -#include "curl_url.h" /* for the ssl config check function */ -#include "curl_connect.h" -#include "curl_strequal.h" -#include "curl_select.h" -#include "curl_sslgen.h" -#include "curl_llist.h" - -#define _MPRINTF_REPLACE /* use the internal *printf() functions */ -#include - -#include "curl_nssg.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "curl_memory.h" -#include "curl_rawstr.h" -#include "curl_warnless.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#define SSL_DIR "/etc/pki/nssdb" - -/* enough to fit the string "PEM Token #[0|1]" */ -#define SLOTSIZE 13 - -PRFileDesc *PR_ImportTCPSocket(PRInt32 osfd); - -PRLock * nss_initlock = NULL; -PRLock * nss_crllock = NULL; -#ifdef HAVE_NSS_INITCONTEXT -NSSInitContext * nss_context = NULL; -#endif - -volatile int initialized = 0; - -typedef struct { - const char *name; - int num; -} cipher_s; - -#define PK11_SETATTRS(_attr, _idx, _type, _val, _len) do { \ - CK_ATTRIBUTE *ptr = (_attr) + ((_idx)++); \ - ptr->type = (_type); \ - ptr->pValue = (_val); \ - ptr->ulValueLen = (_len); \ -} WHILE_FALSE - -#define CERT_NewTempCertificate __CERT_NewTempCertificate - -#define NUM_OF_CIPHERS sizeof(cipherlist)/sizeof(cipherlist[0]) -static const cipher_s cipherlist[] = { - /* SSL2 cipher suites */ - {"rc4", SSL_EN_RC4_128_WITH_MD5}, - {"rc4-md5", SSL_EN_RC4_128_WITH_MD5}, - {"rc4export", SSL_EN_RC4_128_EXPORT40_WITH_MD5}, - {"rc2", SSL_EN_RC2_128_CBC_WITH_MD5}, - {"rc2export", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5}, - {"des", SSL_EN_DES_64_CBC_WITH_MD5}, - {"desede3", SSL_EN_DES_192_EDE3_CBC_WITH_MD5}, - /* SSL3/TLS cipher suites */ - {"rsa_rc4_128_md5", SSL_RSA_WITH_RC4_128_MD5}, - {"rsa_rc4_128_sha", SSL_RSA_WITH_RC4_128_SHA}, - {"rsa_3des_sha", SSL_RSA_WITH_3DES_EDE_CBC_SHA}, - {"rsa_des_sha", SSL_RSA_WITH_DES_CBC_SHA}, - {"rsa_rc4_40_md5", SSL_RSA_EXPORT_WITH_RC4_40_MD5}, - {"rsa_rc2_40_md5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5}, - {"rsa_null_md5", SSL_RSA_WITH_NULL_MD5}, - {"rsa_null_sha", SSL_RSA_WITH_NULL_SHA}, - {"fips_3des_sha", SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA}, - {"fips_des_sha", SSL_RSA_FIPS_WITH_DES_CBC_SHA}, - {"fortezza", SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA}, - {"fortezza_rc4_128_sha", SSL_FORTEZZA_DMS_WITH_RC4_128_SHA}, - {"fortezza_null", SSL_FORTEZZA_DMS_WITH_NULL_SHA}, - /* TLS 1.0: Exportable 56-bit Cipher Suites. */ - {"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA}, - {"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA}, - /* AES ciphers. */ - {"rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA}, - {"rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA}, -#ifdef NSS_ENABLE_ECC - /* ECC ciphers. */ - {"ecdh_ecdsa_null_sha", TLS_ECDH_ECDSA_WITH_NULL_SHA}, - {"ecdh_ecdsa_rc4_128_sha", TLS_ECDH_ECDSA_WITH_RC4_128_SHA}, - {"ecdh_ecdsa_3des_sha", TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_ecdsa_aes_128_sha", TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA}, - {"ecdh_ecdsa_aes_256_sha", TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA}, - {"ecdhe_ecdsa_null_sha", TLS_ECDHE_ECDSA_WITH_NULL_SHA}, - {"ecdhe_ecdsa_rc4_128_sha", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}, - {"ecdhe_ecdsa_3des_sha", TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, - {"ecdhe_ecdsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, - {"ecdh_rsa_null_sha", TLS_ECDH_RSA_WITH_NULL_SHA}, - {"ecdh_rsa_128_sha", TLS_ECDH_RSA_WITH_RC4_128_SHA}, - {"ecdh_rsa_3des_sha", TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_rsa_aes_128_sha", TLS_ECDH_RSA_WITH_AES_128_CBC_SHA}, - {"ecdh_rsa_aes_256_sha", TLS_ECDH_RSA_WITH_AES_256_CBC_SHA}, - {"echde_rsa_null", TLS_ECDHE_RSA_WITH_NULL_SHA}, - {"ecdhe_rsa_rc4_128_sha", TLS_ECDHE_RSA_WITH_RC4_128_SHA}, - {"ecdhe_rsa_3des_sha", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - {"ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, - {"ecdh_anon_null_sha", TLS_ECDH_anon_WITH_NULL_SHA}, - {"ecdh_anon_rc4_128sha", TLS_ECDH_anon_WITH_RC4_128_SHA}, - {"ecdh_anon_3des_sha", TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_anon_aes_128_sha", TLS_ECDH_anon_WITH_AES_128_CBC_SHA}, - {"ecdh_anon_aes_256_sha", TLS_ECDH_anon_WITH_AES_256_CBC_SHA}, -#endif -}; - -/* following ciphers are new in NSS 3.4 and not enabled by default, therefore - they are enabled explicitly */ -static const int enable_ciphers_by_default[] = { - TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - TLS_RSA_WITH_AES_128_CBC_SHA, - TLS_RSA_WITH_AES_256_CBC_SHA, - SSL_NULL_WITH_NULL_NULL -}; - -static const char* pem_library = "libnsspem.so"; -SECMODModule* mod = NULL; - -static const char* nss_error_to_name(PRErrorCode code) -{ - const char *name = PR_ErrorToName(code); - if(name) - return name; - - return "unknown error"; -} - -static void nss_print_error_message(struct SessionHandle *data, PRUint32 err) -{ - failf(data, "%s", PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT)); -} - -static SECStatus set_ciphers(struct SessionHandle *data, PRFileDesc * model, - char *cipher_list) -{ - unsigned int i; - PRBool cipher_state[NUM_OF_CIPHERS]; - PRBool found; - char *cipher; - SECStatus rv; - - /* First disable all ciphers. This uses a different max value in case - * NSS adds more ciphers later we don't want them available by - * accident - */ - for(i=0; iset.str[cert_kind]; - const char *n; - - if(!is_file(str)) - /* no such file exists, use the string as nickname */ - return strdup(str); - - /* search the last slash; we require at least one slash in a file name */ - n = strrchr(str, '/'); - if(!n) { - infof(data, "warning: certificate file name \"%s\" handled as nickname; " - "please use \"./%s\" to force file name\n", str, str); - return strdup(str); - } - - /* we'll use the PEM reader to read the certificate from file */ - return NULL; -} - -/* Call PK11_CreateGenericObject() with the given obj_class and filename. If - * the call succeeds, append the object handle to the list of objects so that - * the object can be destroyed in Curl_nss_close(). */ -static CURLcode nss_create_object(struct ssl_connect_data *ssl, - CK_OBJECT_CLASS obj_class, - const char *filename, bool cacert) -{ - PK11SlotInfo *slot; - PK11GenericObject *obj; - CK_BBOOL cktrue = CK_TRUE; - CK_BBOOL ckfalse = CK_FALSE; - CK_ATTRIBUTE attrs[/* max count of attributes */ 4]; - int attr_cnt = 0; - CURLcode err = (cacert) - ? CURLE_SSL_CACERT_BADFILE - : CURLE_SSL_CERTPROBLEM; - - const int slot_id = (cacert) ? 0 : 1; - char *slot_name = aprintf("PEM Token #%d", slot_id); - if(!slot_name) - return CURLE_OUT_OF_MEMORY; - - slot = PK11_FindSlotByName(slot_name); - free(slot_name); - if(!slot) - return err; - - PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class)); - PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL)); - PK11_SETATTRS(attrs, attr_cnt, CKA_LABEL, (unsigned char *)filename, - strlen(filename) + 1); - - if(CKO_CERTIFICATE == obj_class) { - CK_BBOOL *pval = (cacert) ? (&cktrue) : (&ckfalse); - PK11_SETATTRS(attrs, attr_cnt, CKA_TRUST, pval, sizeof(*pval)); - } - - obj = PK11_CreateGenericObject(slot, attrs, attr_cnt, PR_FALSE); - PK11_FreeSlot(slot); - if(!obj) - return err; - - if(!Curl_llist_insert_next(ssl->obj_list, ssl->obj_list->tail, obj)) { - PK11_DestroyGenericObject(obj); - return CURLE_OUT_OF_MEMORY; - } - - if(!cacert && CKO_CERTIFICATE == obj_class) - /* store reference to a client certificate */ - ssl->obj_clicert = obj; - - return CURLE_OK; -} - -/* Destroy the NSS object whose handle is given by ptr. This function is - * a callback of Curl_llist_alloc() used by Curl_llist_destroy() to destroy - * NSS objects in Curl_nss_close() */ -static void nss_destroy_object(void *user, void *ptr) -{ - PK11GenericObject *obj = (PK11GenericObject *)ptr; - (void) user; - PK11_DestroyGenericObject(obj); -} - -static CURLcode nss_load_cert(struct ssl_connect_data *ssl, - const char *filename, PRBool cacert) -{ - CURLcode err = (cacert) - ? CURLE_SSL_CACERT_BADFILE - : CURLE_SSL_CERTPROBLEM; - - /* libnsspem.so leaks memory if the requested file does not exist. For more - * details, go to . */ - if(is_file(filename)) - err = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert); - - if(CURLE_OK == err && !cacert) { - /* we have successfully loaded a client certificate */ - CERTCertificate *cert; - char *nickname = NULL; - char *n = strrchr(filename, '/'); - if(n) - n++; - - /* The following undocumented magic helps to avoid a SIGSEGV on call - * of PK11_ReadRawAttribute() from SelectClientCert() when using an - * immature version of libnsspem.so. For more details, go to - * . */ - nickname = aprintf("PEM Token #1:%s", n); - if(nickname) { - cert = PK11_FindCertFromNickname(nickname, NULL); - if(cert) - CERT_DestroyCertificate(cert); - - free(nickname); - } - } - - return err; -} - -/* add given CRL to cache if it is not already there */ -static SECStatus nss_cache_crl(SECItem *crlDER) -{ - CERTCertDBHandle *db = CERT_GetDefaultCertDB(); - CERTSignedCrl *crl = SEC_FindCrlByDERCert(db, crlDER, 0); - if(crl) { - /* CRL already cached */ - SEC_DestroyCrl(crl); - SECITEM_FreeItem(crlDER, PR_FALSE); - return SECSuccess; - } - - /* acquire lock before call of CERT_CacheCRL() */ - PR_Lock(nss_crllock); - if(SECSuccess != CERT_CacheCRL(db, crlDER)) { - /* unable to cache CRL */ - PR_Unlock(nss_crllock); - SECITEM_FreeItem(crlDER, PR_FALSE); - return SECFailure; - } - - /* we need to clear session cache, so that the CRL could take effect */ - SSL_ClearSessionCache(); - PR_Unlock(nss_crllock); - return SECSuccess; -} - -static SECStatus nss_load_crl(const char* crlfilename) -{ - PRFileDesc *infile; - PRFileInfo info; - SECItem filedata = { 0, NULL, 0 }; - SECItem crlDER = { 0, NULL, 0 }; - char *body; - - infile = PR_Open(crlfilename, PR_RDONLY, 0); - if(!infile) - return SECFailure; - - if(PR_SUCCESS != PR_GetOpenFileInfo(infile, &info)) - goto fail; - - if(!SECITEM_AllocItem(NULL, &filedata, info.size + /* zero ended */ 1)) - goto fail; - - if(info.size != PR_Read(infile, filedata.data, info.size)) - goto fail; - - /* place a trailing zero right after the visible data */ - body = (char*)filedata.data; - body[--filedata.len] = '\0'; - - body = strstr(body, "-----BEGIN"); - if(body) { - /* assume ASCII */ - char *trailer; - char *begin = PORT_Strchr(body, '\n'); - if(!begin) - begin = PORT_Strchr(body, '\r'); - if(!begin) - goto fail; - - trailer = strstr(++begin, "-----END"); - if(!trailer) - goto fail; - - /* retrieve DER from ASCII */ - *trailer = '\0'; - if(ATOB_ConvertAsciiToItem(&crlDER, begin)) - goto fail; - - SECITEM_FreeItem(&filedata, PR_FALSE); - } - else - /* assume DER */ - crlDER = filedata; - - PR_Close(infile); - return nss_cache_crl(&crlDER); - -fail: - PR_Close(infile); - SECITEM_FreeItem(&filedata, PR_FALSE); - return SECFailure; -} - -static CURLcode nss_load_key(struct connectdata *conn, int sockindex, - char *key_file) -{ - PK11SlotInfo *slot; - SECStatus status; - CURLcode rv; - struct ssl_connect_data *ssl = conn->ssl; - (void)sockindex; /* unused */ - - rv = nss_create_object(ssl, CKO_PRIVATE_KEY, key_file, FALSE); - if(CURLE_OK != rv) { - PR_SetError(SEC_ERROR_BAD_KEY, 0); - return rv; - } - - slot = PK11_FindSlotByName("PEM Token #1"); - if(!slot) - return CURLE_SSL_CERTPROBLEM; - - /* This will force the token to be seen as re-inserted */ - SECMOD_WaitForAnyTokenEvent(mod, 0, 0); - PK11_IsPresent(slot); - - status = PK11_Authenticate(slot, PR_TRUE, - conn->data->set.str[STRING_KEY_PASSWD]); - PK11_FreeSlot(slot); - return (SECSuccess == status) - ? CURLE_OK - : CURLE_SSL_CERTPROBLEM; -} - -static int display_error(struct connectdata *conn, PRInt32 err, - const char *filename) -{ - switch(err) { - case SEC_ERROR_BAD_PASSWORD: - failf(conn->data, "Unable to load client key: Incorrect password"); - return 1; - case SEC_ERROR_UNKNOWN_CERT: - failf(conn->data, "Unable to load certificate %s", filename); - return 1; - default: - break; - } - return 0; /* The caller will print a generic error */ -} - -static CURLcode cert_stuff(struct connectdata *conn, int sockindex, - char *cert_file, char *key_file) -{ - struct SessionHandle *data = conn->data; - CURLcode rv; - - if(cert_file) { - rv = nss_load_cert(&conn->ssl[sockindex], cert_file, PR_FALSE); - if(CURLE_OK != rv) { - const PRErrorCode err = PR_GetError(); - if(!display_error(conn, err, cert_file)) { - const char *err_name = nss_error_to_name(err); - failf(data, "unable to load client cert: %d (%s)", err, err_name); - } - - return rv; - } - } - - if(key_file || (is_file(cert_file))) { - if(key_file) - rv = nss_load_key(conn, sockindex, key_file); - else - /* In case the cert file also has the key */ - rv = nss_load_key(conn, sockindex, cert_file); - if(CURLE_OK != rv) { - const PRErrorCode err = PR_GetError(); - if(!display_error(conn, err, key_file)) { - const char *err_name = nss_error_to_name(err); - failf(data, "unable to load client key: %d (%s)", err, err_name); - } - - return rv; - } - } - - return CURLE_OK; -} - -static char * nss_get_password(PK11SlotInfo * slot, PRBool retry, void *arg) -{ - (void)slot; /* unused */ - if(retry || NULL == arg) - return NULL; - else - return (char *)PORT_Strdup((char *)arg); -} - -/* bypass the default SSL_AuthCertificate() hook in case we do not want to - * verify peer */ -static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig, - PRBool isServer) -{ - struct connectdata *conn = (struct connectdata *)arg; - if(!conn->data->set.ssl.verifypeer) { - infof(conn->data, "skipping SSL peer certificate verification\n"); - return SECSuccess; - } - - return SSL_AuthCertificate(CERT_GetDefaultCertDB(), fd, checksig, isServer); -} - -/** - * Inform the application that the handshake is complete. - */ -static void HandshakeCallback(PRFileDesc *sock, void *arg) -{ - (void)sock; - (void)arg; -} - -static void display_cert_info(struct SessionHandle *data, - CERTCertificate *cert) -{ - char *subject, *issuer, *common_name; - PRExplodedTime printableTime; - char timeString[256]; - PRTime notBefore, notAfter; - - subject = CERT_NameToAscii(&cert->subject); - issuer = CERT_NameToAscii(&cert->issuer); - common_name = CERT_GetCommonName(&cert->subject); - infof(data, "\tsubject: %s\n", subject); - - CERT_GetCertTimes(cert, ¬Before, ¬After); - PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime); - PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); - infof(data, "\tstart date: %s\n", timeString); - PR_ExplodeTime(notAfter, PR_GMTParameters, &printableTime); - PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); - infof(data, "\texpire date: %s\n", timeString); - infof(data, "\tcommon name: %s\n", common_name); - infof(data, "\tissuer: %s\n", issuer); - - PR_Free(subject); - PR_Free(issuer); - PR_Free(common_name); -} - -static void display_conn_info(struct connectdata *conn, PRFileDesc *sock) -{ - SSLChannelInfo channel; - SSLCipherSuiteInfo suite; - CERTCertificate *cert; - - if(SSL_GetChannelInfo(sock, &channel, sizeof channel) == - SECSuccess && channel.length == sizeof channel && - channel.cipherSuite) { - if(SSL_GetCipherSuiteInfo(channel.cipherSuite, - &suite, sizeof suite) == SECSuccess) { - infof(conn->data, "SSL connection using %s\n", suite.cipherSuiteName); - } - } - - infof(conn->data, "Server certificate:\n"); - - cert = SSL_PeerCertificate(sock); - display_cert_info(conn->data, cert); - CERT_DestroyCertificate(cert); - - return; -} - -static SECStatus BadCertHandler(void *arg, PRFileDesc *sock) -{ - struct connectdata *conn = (struct connectdata *)arg; - struct SessionHandle *data = conn->data; - PRErrorCode err = PR_GetError(); - CERTCertificate *cert; - - /* remember the cert verification result */ - data->set.ssl.certverifyresult = err; - - if(err == SSL_ERROR_BAD_CERT_DOMAIN && !data->set.ssl.verifyhost) - /* we are asked not to verify the host name */ - return SECSuccess; - - /* print only info about the cert, the error is printed off the callback */ - cert = SSL_PeerCertificate(sock); - if(cert) { - infof(data, "Server certificate:\n"); - display_cert_info(data, cert); - CERT_DestroyCertificate(cert); - } - - return SECFailure; -} - -/** - * - * Check that the Peer certificate's issuer certificate matches the one found - * by issuer_nickname. This is not exactly the way OpenSSL and GNU TLS do the - * issuer check, so we provide comments that mimic the OpenSSL - * X509_check_issued function (in x509v3/v3_purp.c) - */ -static SECStatus check_issuer_cert(PRFileDesc *sock, - char *issuer_nickname) -{ - CERTCertificate *cert,*cert_issuer,*issuer; - SECStatus res=SECSuccess; - void *proto_win = NULL; - - /* - PRArenaPool *tmpArena = NULL; - CERTAuthKeyID *authorityKeyID = NULL; - SECITEM *caname = NULL; - */ - - cert = SSL_PeerCertificate(sock); - cert_issuer = CERT_FindCertIssuer(cert,PR_Now(),certUsageObjectSigner); - - proto_win = SSL_RevealPinArg(sock); - issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win); - - if((!cert_issuer) || (!issuer)) - res = SECFailure; - else if(SECITEM_CompareItem(&cert_issuer->derCert, - &issuer->derCert)!=SECEqual) - res = SECFailure; - - CERT_DestroyCertificate(cert); - CERT_DestroyCertificate(issuer); - CERT_DestroyCertificate(cert_issuer); - return res; -} - -/** - * - * Callback to pick the SSL client certificate. - */ -static SECStatus SelectClientCert(void *arg, PRFileDesc *sock, - struct CERTDistNamesStr *caNames, - struct CERTCertificateStr **pRetCert, - struct SECKEYPrivateKeyStr **pRetKey) -{ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)arg; - struct SessionHandle *data = connssl->data; - const char *nickname = connssl->client_nickname; - - if(connssl->obj_clicert) { - /* use the cert/key provided by PEM reader */ - static const char pem_slotname[] = "PEM Token #1"; - SECItem cert_der = { 0, NULL, 0 }; - void *proto_win = SSL_RevealPinArg(sock); - struct CERTCertificateStr *cert; - struct SECKEYPrivateKeyStr *key; - - PK11SlotInfo *slot = PK11_FindSlotByName(pem_slotname); - if(NULL == slot) { - failf(data, "NSS: PK11 slot not found: %s", pem_slotname); - return SECFailure; - } - - if(PK11_ReadRawAttribute(PK11_TypeGeneric, connssl->obj_clicert, CKA_VALUE, - &cert_der) != SECSuccess) { - failf(data, "NSS: CKA_VALUE not found in PK11 generic object"); - PK11_FreeSlot(slot); - return SECFailure; - } - - cert = PK11_FindCertFromDERCertItem(slot, &cert_der, proto_win); - SECITEM_FreeItem(&cert_der, PR_FALSE); - if(NULL == cert) { - failf(data, "NSS: client certificate from file not found"); - PK11_FreeSlot(slot); - return SECFailure; - } - - key = PK11_FindPrivateKeyFromCert(slot, cert, NULL); - PK11_FreeSlot(slot); - if(NULL == key) { - failf(data, "NSS: private key from file not found"); - CERT_DestroyCertificate(cert); - return SECFailure; - } - - infof(data, "NSS: client certificate from file\n"); - display_cert_info(data, cert); - - *pRetCert = cert; - *pRetKey = key; - return SECSuccess; - } - - /* use the default NSS hook */ - if(SECSuccess != NSS_GetClientAuthData((void *)nickname, sock, caNames, - pRetCert, pRetKey) - || NULL == *pRetCert) { - - if(NULL == nickname) - failf(data, "NSS: client certificate not found (nickname not " - "specified)"); - else - failf(data, "NSS: client certificate not found: %s", nickname); - - return SECFailure; - } - - /* get certificate nickname if any */ - nickname = (*pRetCert)->nickname; - if(NULL == nickname) - nickname = "[unknown]"; - - if(NULL == *pRetKey) { - failf(data, "NSS: private key not found for certificate: %s", nickname); - return SECFailure; - } - - infof(data, "NSS: using client certificate: %s\n", nickname); - display_cert_info(data, *pRetCert); - return SECSuccess; -} - -/* This function is supposed to decide, which error codes should be used - * to conclude server is TLS intolerant. - * - * taken from xulrunner - nsNSSIOLayer.cpp - */ -static PRBool -isTLSIntoleranceError(PRInt32 err) -{ - switch (err) { - case SSL_ERROR_BAD_MAC_ALERT: - case SSL_ERROR_BAD_MAC_READ: - case SSL_ERROR_HANDSHAKE_FAILURE_ALERT: - case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT: - case SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE: - case SSL_ERROR_ILLEGAL_PARAMETER_ALERT: - case SSL_ERROR_NO_CYPHER_OVERLAP: - case SSL_ERROR_BAD_SERVER: - case SSL_ERROR_BAD_BLOCK_PADDING: - case SSL_ERROR_UNSUPPORTED_VERSION: - case SSL_ERROR_PROTOCOL_VERSION_ALERT: - case SSL_ERROR_RX_MALFORMED_FINISHED: - case SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE: - case SSL_ERROR_DECODE_ERROR_ALERT: - case SSL_ERROR_RX_UNKNOWN_ALERT: - return PR_TRUE; - default: - return PR_FALSE; - } -} - -static CURLcode nss_init_core(struct SessionHandle *data, const char *cert_dir) -{ -#ifdef HAVE_NSS_INITCONTEXT - NSSInitParameters initparams; - - if(nss_context != NULL) - return CURLE_OK; - - memset((void *) &initparams, '\0', sizeof(initparams)); - initparams.length = sizeof(initparams); -#else /* HAVE_NSS_INITCONTEXT */ - SECStatus rv; - - if(NSS_IsInitialized()) - return CURLE_OK; -#endif - - if(cert_dir) { - const bool use_sql = NSS_VersionCheck("3.12.0"); - char *certpath = aprintf("%s%s", use_sql ? "sql:" : "", cert_dir); - if(!certpath) - return CURLE_OUT_OF_MEMORY; - - infof(data, "Initializing NSS with certpath: %s\n", certpath); -#ifdef HAVE_NSS_INITCONTEXT - nss_context = NSS_InitContext(certpath, "", "", "", &initparams, - NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); - free(certpath); - - if(nss_context != NULL) - return CURLE_OK; -#else /* HAVE_NSS_INITCONTEXT */ - rv = NSS_Initialize(certpath, "", "", "", NSS_INIT_READONLY); - free(certpath); - - if(rv == SECSuccess) - return CURLE_OK; -#endif - - infof(data, "Unable to initialize NSS database\n"); - } - - infof(data, "Initializing NSS with certpath: none\n"); -#ifdef HAVE_NSS_INITCONTEXT - nss_context = NSS_InitContext("", "", "", "", &initparams, NSS_INIT_READONLY - | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN - | NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD); - if(nss_context != NULL) - return CURLE_OK; -#else /* HAVE_NSS_INITCONTEXT */ - if(NSS_NoDB_Init(NULL) == SECSuccess) - return CURLE_OK; -#endif - - infof(data, "Unable to initialize NSS\n"); - return CURLE_SSL_CACERT_BADFILE; -} - -static CURLcode nss_init(struct SessionHandle *data) -{ - char *cert_dir; - struct_stat st; - CURLcode rv; - - if(initialized) - return CURLE_OK; - - /* First we check if $SSL_DIR points to a valid dir */ - cert_dir = getenv("SSL_DIR"); - if(cert_dir) { - if((stat(cert_dir, &st) != 0) || - (!S_ISDIR(st.st_mode))) { - cert_dir = NULL; - } - } - - /* Now we check if the default location is a valid dir */ - if(!cert_dir) { - if((stat(SSL_DIR, &st) == 0) && - (S_ISDIR(st.st_mode))) { - cert_dir = (char *)SSL_DIR; - } - } - - rv = nss_init_core(data, cert_dir); - if(rv) - return rv; - - if(num_enabled_ciphers() == 0) - NSS_SetDomesticPolicy(); - - initialized = 1; - return CURLE_OK; -} - -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -int Curl_nss_init(void) -{ - /* curl_global_init() is not thread-safe so this test is ok */ - if(nss_initlock == NULL) { - PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 256); - nss_initlock = PR_NewLock(); - nss_crllock = PR_NewLock(); - } - - /* We will actually initialize NSS later */ - - return 1; -} - -CURLcode Curl_nss_force_init(struct SessionHandle *data) -{ - CURLcode rv; - if(!nss_initlock) { - failf(data, - "unable to initialize NSS, curl_global_init() should have been " - "called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL"); - return CURLE_FAILED_INIT; - } - - PR_Lock(nss_initlock); - rv = nss_init(data); - PR_Unlock(nss_initlock); - return rv; -} - -/* Global cleanup */ -void Curl_nss_cleanup(void) -{ - /* This function isn't required to be threadsafe and this is only done - * as a safety feature. - */ - PR_Lock(nss_initlock); - if(initialized) { - /* Free references to client certificates held in the SSL session cache. - * Omitting this hampers destruction of the security module owning - * the certificates. */ - SSL_ClearSessionCache(); - - if(mod && SECSuccess == SECMOD_UnloadUserModule(mod)) { - SECMOD_DestroyModule(mod); - mod = NULL; - } -#ifdef HAVE_NSS_INITCONTEXT - NSS_ShutdownContext(nss_context); - nss_context = NULL; -#else /* HAVE_NSS_INITCONTEXT */ - NSS_Shutdown(); -#endif - } - PR_Unlock(nss_initlock); - - PR_DestroyLock(nss_initlock); - PR_DestroyLock(nss_crllock); - nss_initlock = NULL; - - initialized = 0; -} - -/* - * This function uses SSL_peek to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -int -Curl_nss_check_cxn(struct connectdata *conn) -{ - int rc; - char buf; - - rc = - PR_Recv(conn->ssl[FIRSTSOCKET].handle, (void *)&buf, 1, PR_MSG_PEEK, - PR_SecondsToInterval(1)); - if(rc > 0) - return 1; /* connection still in place */ - - if(rc == 0) - return 0; /* connection has been closed */ - - return -1; /* connection status unknown */ -} - -/* - * This function is called when an SSL connection is closed. - */ -void Curl_nss_close(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - if(connssl->handle) { - /* NSS closes the socket we previously handed to it, so we must mark it - as closed to avoid double close */ - fake_sclose(conn->sock[sockindex]); - conn->sock[sockindex] = CURL_SOCKET_BAD; - if(connssl->client_nickname != NULL) { - free(connssl->client_nickname); - connssl->client_nickname = NULL; - - /* force NSS to ask again for a client cert when connecting - * next time to the same server */ - SSL_InvalidateSession(connssl->handle); - } - /* destroy all NSS objects in order to avoid failure of NSS shutdown */ - Curl_llist_destroy(connssl->obj_list, NULL); - connssl->obj_list = NULL; - connssl->obj_clicert = NULL; - - PR_Close(connssl->handle); - connssl->handle = NULL; - } -} - -/* - * This function is called when the 'data' struct is going away. Close - * down everything and free all resources! - */ -int Curl_nss_close_all(struct SessionHandle *data) -{ - (void)data; - return 0; -} - -/* return true if NSS can provide error code (and possibly msg) for the - error */ -static bool is_nss_error(CURLcode err) -{ - switch(err) { - case CURLE_PEER_FAILED_VERIFICATION: - case CURLE_SSL_CACERT: - case CURLE_SSL_CACERT_BADFILE: - case CURLE_SSL_CERTPROBLEM: - case CURLE_SSL_CONNECT_ERROR: - case CURLE_SSL_CRL_BADFILE: - case CURLE_SSL_ISSUER_ERROR: - return true; - - default: - return false; - } -} - -/* return true if the given error code is related to a client certificate */ -static bool is_cc_error(PRInt32 err) -{ - switch(err) { - case SSL_ERROR_BAD_CERT_ALERT: - case SSL_ERROR_EXPIRED_CERT_ALERT: - case SSL_ERROR_REVOKED_CERT_ALERT: - return true; - - default: - return false; - } -} - -static Curl_recv nss_recv; -static Curl_send nss_send; - -static CURLcode nss_load_ca_certificates(struct connectdata *conn, - int sockindex) -{ - struct SessionHandle *data = conn->data; - const char *cafile = data->set.ssl.CAfile; - const char *capath = data->set.ssl.CApath; - - if(cafile) { - CURLcode rv = nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE); - if(CURLE_OK != rv) - return rv; - } - - if(capath) { - struct_stat st; - if(stat(capath, &st) == -1) - return CURLE_SSL_CACERT_BADFILE; - - if(S_ISDIR(st.st_mode)) { - PRDirEntry *entry; - PRDir *dir = PR_OpenDir(capath); - if(!dir) - return CURLE_SSL_CACERT_BADFILE; - - while((entry = PR_ReadDir(dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN))) { - char *fullpath = aprintf("%s/%s", capath, entry->name); - if(!fullpath) { - PR_CloseDir(dir); - return CURLE_OUT_OF_MEMORY; - } - - if(CURLE_OK != nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE)) - /* This is purposefully tolerant of errors so non-PEM files can - * be in the same directory */ - infof(data, "failed to load '%s' from CURLOPT_CAPATH\n", fullpath); - - free(fullpath); - } - - PR_CloseDir(dir); - } - else - infof(data, "warning: CURLOPT_CAPATH not a directory (%s)\n", capath); - } - - infof(data, " CAfile: %s\n CApath: %s\n", - cafile ? cafile : "none", - capath ? capath : "none"); - - return CURLE_OK; -} - -CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) -{ - PRErrorCode err = 0; - PRFileDesc *model = NULL; - PRBool ssl2 = PR_FALSE; - PRBool ssl3 = PR_FALSE; - PRBool tlsv1 = PR_FALSE; - PRBool ssl_no_cache; - PRBool ssl_cbc_random_iv; - struct SessionHandle *data = conn->data; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - CURLcode curlerr; - const int *cipher_to_enable; - PRSocketOptionData sock_opt; - long time_left; - PRUint32 timeout; - - if(connssl->state == ssl_connection_complete) - return CURLE_OK; - - connssl->data = data; - - /* list of all NSS objects we need to destroy in Curl_nss_close() */ - connssl->obj_list = Curl_llist_alloc(nss_destroy_object); - if(!connssl->obj_list) - return CURLE_OUT_OF_MEMORY; - - /* FIXME. NSS doesn't support multiple databases open at the same time. */ - PR_Lock(nss_initlock); - curlerr = nss_init(conn->data); - if(CURLE_OK != curlerr) { - PR_Unlock(nss_initlock); - goto error; - } - - curlerr = CURLE_SSL_CONNECT_ERROR; - - if(!mod) { - char *configstring = aprintf("library=%s name=PEM", pem_library); - if(!configstring) { - PR_Unlock(nss_initlock); - goto error; - } - mod = SECMOD_LoadUserModule(configstring, NULL, PR_FALSE); - free(configstring); - - if(!mod || !mod->loaded) { - if(mod) { - SECMOD_DestroyModule(mod); - mod = NULL; - } - infof(data, "WARNING: failed to load NSS PEM library %s. Using " - "OpenSSL PEM certificates will not work.\n", pem_library); - } - } - - PK11_SetPasswordFunc(nss_get_password); - PR_Unlock(nss_initlock); - - model = PR_NewTCPSocket(); - if(!model) - goto error; - model = SSL_ImportFD(NULL, model); - - /* make the socket nonblocking */ - sock_opt.option = PR_SockOpt_Nonblocking; - sock_opt.value.non_blocking = PR_TRUE; - if(PR_SetSocketOption(model, &sock_opt) != PR_SUCCESS) - goto error; - - if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess) - goto error; - - /* do not use SSL cache if we are not going to verify peer */ - ssl_no_cache = (data->set.ssl.verifypeer) ? PR_FALSE : PR_TRUE; - if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess) - goto error; - - switch (data->set.ssl.version) { - default: - case CURL_SSLVERSION_DEFAULT: - ssl3 = PR_TRUE; - if(data->state.ssl_connect_retry) - infof(data, "TLS disabled due to previous handshake failure\n"); - else - tlsv1 = PR_TRUE; - break; - case CURL_SSLVERSION_TLSv1: - tlsv1 = PR_TRUE; - break; - case CURL_SSLVERSION_SSLv2: - ssl2 = PR_TRUE; - break; - case CURL_SSLVERSION_SSLv3: - ssl3 = PR_TRUE; - break; - } - - if(SSL_OptionSet(model, SSL_ENABLE_SSL2, ssl2) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_ENABLE_SSL3, ssl3) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_ENABLE_TLS, tlsv1) != SECSuccess) - goto error; - - if(SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, ssl2) != SECSuccess) - goto error; - - ssl_cbc_random_iv = !data->set.ssl_enable_beast; -#ifdef SSL_CBC_RANDOM_IV - /* unless the user explicitly asks to allow the protocol vulnerability, we - use the work-around */ - if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess) - infof(data, "warning: failed to set SSL_CBC_RANDOM_IV = %d\n", - ssl_cbc_random_iv); -#else - if(ssl_cbc_random_iv) - infof(data, "warning: support for SSL_CBC_RANDOM_IV not compiled in\n"); -#endif - - /* reset the flag to avoid an infinite loop */ - data->state.ssl_connect_retry = FALSE; - - /* enable all ciphers from enable_ciphers_by_default */ - cipher_to_enable = enable_ciphers_by_default; - while(SSL_NULL_WITH_NULL_NULL != *cipher_to_enable) { - if(SSL_CipherPrefSet(model, *cipher_to_enable, PR_TRUE) != SECSuccess) { - curlerr = CURLE_SSL_CIPHER; - goto error; - } - cipher_to_enable++; - } - - if(data->set.ssl.cipher_list) { - if(set_ciphers(data, model, data->set.ssl.cipher_list) != SECSuccess) { - curlerr = CURLE_SSL_CIPHER; - goto error; - } - } - - if(!data->set.ssl.verifypeer && data->set.ssl.verifyhost) - infof(data, "warning: ignoring value of ssl.verifyhost\n"); - - /* bypass the default SSL_AuthCertificate() hook in case we do not want to - * verify peer */ - if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, conn) != SECSuccess) - goto error; - - data->set.ssl.certverifyresult=0; /* not checked yet */ - if(SSL_BadCertHook(model, BadCertHandler, conn) != SECSuccess) - goto error; - - if(SSL_HandshakeCallback(model, HandshakeCallback, NULL) != SECSuccess) - goto error; - - if(data->set.ssl.verifypeer) { - const CURLcode rv = nss_load_ca_certificates(conn, sockindex); - if(CURLE_OK != rv) { - curlerr = rv; - goto error; - } - } - - if(data->set.ssl.CRLfile) { - if(SECSuccess != nss_load_crl(data->set.ssl.CRLfile)) { - curlerr = CURLE_SSL_CRL_BADFILE; - goto error; - } - infof(data, - " CRLfile: %s\n", - data->set.ssl.CRLfile ? data->set.ssl.CRLfile : "none"); - } - - if(data->set.str[STRING_CERT]) { - char *nickname = dup_nickname(data, STRING_CERT); - if(nickname) { - /* we are not going to use libnsspem.so to read the client cert */ - connssl->obj_clicert = NULL; - } - else { - CURLcode rv = cert_stuff(conn, sockindex, data->set.str[STRING_CERT], - data->set.str[STRING_KEY]); - if(CURLE_OK != rv) { - /* failf() is already done in cert_stuff() */ - curlerr = rv; - goto error; - } - } - - /* store the nickname for SelectClientCert() called during handshake */ - connssl->client_nickname = nickname; - } - else - connssl->client_nickname = NULL; - - if(SSL_GetClientAuthDataHook(model, SelectClientCert, - (void *)connssl) != SECSuccess) { - curlerr = CURLE_SSL_CERTPROBLEM; - goto error; - } - - /* Import our model socket onto the existing file descriptor */ - connssl->handle = PR_ImportTCPSocket(sockfd); - connssl->handle = SSL_ImportFD(model, connssl->handle); - if(!connssl->handle) - goto error; - - PR_Close(model); /* We don't need this any more */ - model = NULL; - - /* This is the password associated with the cert that we're using */ - if(data->set.str[STRING_KEY_PASSWD]) { - SSL_SetPKCS11PinArg(connssl->handle, data->set.str[STRING_KEY_PASSWD]); - } - - /* Force handshake on next I/O */ - SSL_ResetHandshake(connssl->handle, /* asServer */ PR_FALSE); - - SSL_SetURL(connssl->handle, conn->host.name); - - /* check timeout situation */ - time_left = Curl_timeleft(data, NULL, TRUE); - if(time_left < 0L) { - failf(data, "timed out before SSL handshake"); - curlerr = CURLE_OPERATION_TIMEDOUT; - goto error; - } - timeout = PR_MillisecondsToInterval((PRUint32) time_left); - - /* Force the handshake now */ - if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) { - if(conn->data->set.ssl.certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) - curlerr = CURLE_PEER_FAILED_VERIFICATION; - else if(conn->data->set.ssl.certverifyresult!=0) - curlerr = CURLE_SSL_CACERT; - goto error; - } - - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = nss_recv; - conn->send[sockindex] = nss_send; - - display_conn_info(conn, connssl->handle); - - if(data->set.str[STRING_SSL_ISSUERCERT]) { - SECStatus ret = SECFailure; - char *nickname = dup_nickname(data, STRING_SSL_ISSUERCERT); - if(nickname) { - /* we support only nicknames in case of STRING_SSL_ISSUERCERT for now */ - ret = check_issuer_cert(connssl->handle, nickname); - free(nickname); - } - - if(SECFailure == ret) { - infof(data,"SSL certificate issuer check failed\n"); - curlerr = CURLE_SSL_ISSUER_ERROR; - goto error; - } - else { - infof(data, "SSL certificate issuer check ok\n"); - } - } - - return CURLE_OK; - - error: - /* reset the flag to avoid an infinite loop */ - data->state.ssl_connect_retry = FALSE; - - if(is_nss_error(curlerr)) { - /* read NSPR error code */ - err = PR_GetError(); - if(is_cc_error(err)) - curlerr = CURLE_SSL_CERTPROBLEM; - - /* print the error number and error string */ - infof(data, "NSS error %d (%s)\n", err, nss_error_to_name(err)); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); - } - - if(model) - PR_Close(model); - - /* cleanup on connection failure */ - Curl_llist_destroy(connssl->obj_list, NULL); - connssl->obj_list = NULL; - - if(ssl3 && tlsv1 && isTLSIntoleranceError(err)) { - /* schedule reconnect through Curl_retry_request() */ - data->state.ssl_connect_retry = TRUE; - infof(data, "Error in TLS handshake, trying SSLv3...\n"); - return CURLE_OK; - } - - return curlerr; -} - -static ssize_t nss_send(struct connectdata *conn, /* connection data */ - int sockindex, /* socketindex */ - const void *mem, /* send this data */ - size_t len, /* amount to write */ - CURLcode *curlcode) -{ - int rc; - - rc = PR_Send(conn->ssl[sockindex].handle, mem, (int)len, 0, -1); - - if(rc < 0) { - PRInt32 err = PR_GetError(); - if(err == PR_WOULD_BLOCK_ERROR) - *curlcode = CURLE_AGAIN; - else { - /* print the error number and error string */ - const char *err_name = nss_error_to_name(err); - infof(conn->data, "SSL write: error %d (%s)\n", err, err_name); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(conn->data, err); - - *curlcode = (is_cc_error(err)) - ? CURLE_SSL_CERTPROBLEM - : CURLE_SEND_ERROR; - } - return -1; - } - return rc; /* number of bytes */ -} - -static ssize_t nss_recv(struct connectdata * conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *curlcode) -{ - ssize_t nread; - - nread = PR_Recv(conn->ssl[num].handle, buf, (int)buffersize, 0, -1); - if(nread < 0) { - /* failed SSL read */ - PRInt32 err = PR_GetError(); - - if(err == PR_WOULD_BLOCK_ERROR) - *curlcode = CURLE_AGAIN; - else { - /* print the error number and error string */ - const char *err_name = nss_error_to_name(err); - infof(conn->data, "SSL read: errno %d (%s)\n", err, err_name); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(conn->data, err); - - *curlcode = (is_cc_error(err)) - ? CURLE_SSL_CERTPROBLEM - : CURLE_RECV_ERROR; - } - return -1; - } - return nread; -} - -size_t Curl_nss_version(char *buffer, size_t size) -{ - return snprintf(buffer, size, "NSS/%s", NSS_VERSION); -} - -int Curl_nss_seed(struct SessionHandle *data) -{ - /* TODO: implement? */ - (void) data; - return 0; -} - -void Curl_nss_random(struct SessionHandle *data, - unsigned char *entropy, - size_t length) -{ - Curl_nss_seed(data); /* Initiate the seed if not already done */ - PK11_GenerateRandom(entropy, curlx_uztosi(length)); -} - -void Curl_nss_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len) -{ - PK11Context *MD5pw = PK11_CreateDigestContext(SEC_OID_MD5); - unsigned int MD5out; - PK11_DigestOp(MD5pw, tmp, curlx_uztoui(tmplen)); - PK11_DigestFinal(MD5pw, md5sum, &MD5out, curlx_uztoui(md5len)); - PK11_DestroyContext(MD5pw, PR_TRUE); -} - -#endif /* USE_NSS */ diff --git a/lib/nwlib.c b/lib/nwlib.c deleted file mode 100644 index f63f16b83..000000000 --- a/lib/nwlib.c +++ /dev/null @@ -1,329 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef NETWARE /* Novell NetWare */ - -#ifdef __NOVELL_LIBC__ -/* For native LibC-based NLM we need to register as a real lib. */ -#include -#include -#include -#include -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -typedef struct -{ - int _errno; - void *twentybytes; -} libthreaddata_t; - -typedef struct -{ - int x; - int y; - int z; - void *tenbytes; - NXKey_t perthreadkey; /* if -1, no key obtained... */ - NXMutex_t *lock; -} libdata_t; - -int gLibId = -1; -void *gLibHandle = (void *) NULL; -rtag_t gAllocTag = (rtag_t) NULL; -NXMutex_t *gLibLock = (NXMutex_t *) NULL; - -/* internal library function prototypes... */ -int DisposeLibraryData( void * ); -void DisposeThreadData( void * ); -int GetOrSetUpData( int id, libdata_t **data, libthreaddata_t **threaddata ); - - -int _NonAppStart( void *NLMHandle, - void *errorScreen, - const char *cmdLine, - const char *loadDirPath, - size_t uninitializedDataLength, - void *NLMFileHandle, - int (*readRoutineP)( int conn, - void *fileHandle, size_t offset, - size_t nbytes, - size_t *bytesRead, - void *buffer ), - size_t customDataOffset, - size_t customDataSize, - int messageCount, - const char **messages ) -{ - NX_LOCK_INFO_ALLOC(liblock, "Per-Application Data Lock", 0); - -#ifndef __GNUC__ -#pragma unused(cmdLine) -#pragma unused(loadDirPath) -#pragma unused(uninitializedDataLength) -#pragma unused(NLMFileHandle) -#pragma unused(readRoutineP) -#pragma unused(customDataOffset) -#pragma unused(customDataSize) -#pragma unused(messageCount) -#pragma unused(messages) -#endif - - /* - * Here we process our command line, post errors (to the error screen), - * perform initializations and anything else we need to do before being able - * to accept calls into us. If we succeed, we return non-zero and the NetWare - * Loader will leave us up, otherwise we fail to load and get dumped. - */ - gAllocTag = AllocateResourceTag(NLMHandle, - " memory allocations", - AllocSignature); - - if(!gAllocTag) { - OutputToScreen(errorScreen, "Unable to allocate resource tag for " - "library memory allocations.\n"); - return -1; - } - - gLibId = register_library(DisposeLibraryData); - - if(gLibId < -1) { - OutputToScreen(errorScreen, "Unable to register library with kernel.\n"); - return -1; - } - - gLibHandle = NLMHandle; - - gLibLock = NXMutexAlloc(0, 0, &liblock); - - if(!gLibLock) { - OutputToScreen(errorScreen, "Unable to allocate library data lock.\n"); - return -1; - } - - return 0; -} - -/* - * Here we clean up any resources we allocated. Resource tags is a big part - * of what we created, but NetWare doesn't ask us to free those. - */ -void _NonAppStop( void ) -{ - (void) unregister_library(gLibId); - NXMutexFree(gLibLock); -} - -/* - * This function cannot be the first in the file for if the file is linked - * first, then the check-unload function's offset will be nlmname.nlm+0 - * which is how to tell that there isn't one. When the check function is - * first in the linked objects, it is ambiguous. For this reason, we will - * put it inside this file after the stop function. - * - * Here we check to see if it's alright to ourselves to be unloaded. If not, - * we return a non-zero value. Right now, there isn't any reason not to allow - * it. - */ -int _NonAppCheckUnload( void ) -{ - return 0; -} - -int GetOrSetUpData(int id, libdata_t **appData, - libthreaddata_t **threadData ) -{ - int err; - libdata_t *app_data; - libthreaddata_t *thread_data; - NXKey_t key; - NX_LOCK_INFO_ALLOC(liblock, "Application Data Lock", 0); - - err = 0; - thread_data = (libthreaddata_t *) NULL; - - /* - * Attempt to get our data for the application calling us. This is where we - * store whatever application-specific information we need to carry in - * support of calling applications. - */ - app_data = (libdata_t *) get_app_data(id); - - if(!app_data) { - /* - * This application hasn't called us before; set up application AND - * per-thread data. Of course, just in case a thread from this same - * application is calling us simultaneously, we better lock our application - * data-creation mutex. We also need to recheck for data after we acquire - * the lock because WE might be that other thread that was too late to - * create the data and the first thread in will have created it. - */ - NXLock(gLibLock); - - if(!(app_data = (libdata_t *) get_app_data(id))) { - app_data = malloc(sizeof(libdata_t)); - - if(app_data) { - memset(app_data, 0, sizeof(libdata_t)); - - app_data->tenbytes = malloc(10); - app_data->lock = NXMutexAlloc(0, 0, &liblock); - - if(!app_data->tenbytes || !app_data->lock) { - if(app_data->lock) - NXMutexFree(app_data->lock); - - free(app_data); - app_data = (libdata_t *) NULL; - err = ENOMEM; - } - - if(app_data) { - /* - * Here we burn in the application data that we were trying to get - * by calling get_app_data(). Next time we call the first function, - * we'll get this data we're just now setting. We also go on here to - * establish the per-thread data for the calling thread, something - * we'll have to do on each application thread the first time - * it calls us. - */ - err = set_app_data(gLibId, app_data); - - if(err) { - free(app_data); - app_data = (libdata_t *) NULL; - err = ENOMEM; - } - else { - /* create key for thread-specific data... */ - err = NXKeyCreate(DisposeThreadData, (void *) NULL, &key); - - if(err) /* (no more keys left?) */ - key = -1; - - app_data->perthreadkey = key; - } - } - } - } - - NXUnlock(gLibLock); - } - - if(app_data) { - key = app_data->perthreadkey; - - if(key != -1 /* couldn't create a key? no thread data */ - && !(err = NXKeyGetValue(key, (void **) &thread_data)) - && !thread_data) { - /* - * Allocate the per-thread data for the calling thread. Regardless of - * whether there was already application data or not, this may be the - * first call by a new thread. The fact that we allocation 20 bytes on - * a pointer is not very important, this just helps to demonstrate that - * we can have arbitrarily complex per-thread data. - */ - thread_data = malloc(sizeof(libthreaddata_t)); - - if(thread_data) { - thread_data->_errno = 0; - thread_data->twentybytes = malloc(20); - - if(!thread_data->twentybytes) { - free(thread_data); - thread_data = (libthreaddata_t *) NULL; - err = ENOMEM; - } - - if((err = NXKeySetValue(key, thread_data))) { - free(thread_data->twentybytes); - free(thread_data); - thread_data = (libthreaddata_t *) NULL; - } - } - } - } - - if(appData) - *appData = app_data; - - if(threadData) - *threadData = thread_data; - - return err; -} - -int DisposeLibraryData( void *data ) -{ - if(data) { - void *tenbytes = ((libdata_t *) data)->tenbytes; - - if(tenbytes) - free(tenbytes); - - free(data); - } - - return 0; -} - -void DisposeThreadData( void *data ) -{ - if(data) { - void *twentybytes = ((libthreaddata_t *) data)->twentybytes; - - if(twentybytes) - free(twentybytes); - - free(data); - } -} - -#else /* __NOVELL_LIBC__ */ -/* For native CLib-based NLM seems we can do a bit more simple. */ -#include - -int main ( void ) -{ - /* initialize any globals here... */ - - /* do this if any global initializing was done - SynchronizeStart(); - */ - ExitThread (TSR_THREAD, 0); - return 0; -} - -#endif /* __NOVELL_LIBC__ */ - -#else /* NETWARE */ - -#ifdef __POCC__ -# pragma warn(disable:2024) /* Disable warning #2024: Empty input file */ -#endif - -#endif /* NETWARE */ diff --git a/lib/nwos.c b/lib/nwos.c deleted file mode 100644 index 23ff2a717..000000000 --- a/lib/nwos.c +++ /dev/null @@ -1,88 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef NETWARE /* Novell NetWare */ - -#ifdef __NOVELL_LIBC__ -/* For native LibC-based NLM we need to do nothing. */ -int netware_init ( void ) -{ - return 0; -} - -#else /* __NOVELL_LIBC__ */ - -/* For native CLib-based NLM we need to initialize the LONG namespace. */ -#include -#include -#include -/* Make the CLIB Ctx stuff link */ -#include -NETDB_DEFINE_CONTEXT -/* Make the CLIB Inet stuff link */ -#include -#include -NETINET_DEFINE_CONTEXT - -int netware_init ( void ) -{ - int rc = 0; - unsigned int myHandle = GetNLMHandle(); - /* import UnAugmentAsterisk dynamically for NW4.x compatibility */ - void (*pUnAugmentAsterisk)(int) = (void(*)(int)) - ImportSymbol(myHandle, "UnAugmentAsterisk"); - /* import UseAccurateCaseForPaths dynamically for NW3.x compatibility */ - void (*pUseAccurateCaseForPaths)(int) = (void(*)(int)) - ImportSymbol(myHandle, "UseAccurateCaseForPaths"); - if(pUnAugmentAsterisk) - pUnAugmentAsterisk(1); - if(pUseAccurateCaseForPaths) - pUseAccurateCaseForPaths(1); - UnimportSymbol(myHandle, "UnAugmentAsterisk"); - UnimportSymbol(myHandle, "UseAccurateCaseForPaths"); - /* set long name space */ - if((SetCurrentNameSpace(4) == 255)) { - rc = 1; - } - if((SetTargetNameSpace(4) == 255)) { - rc = rc + 2; - } - return rc; -} - -/* dummy function to satisfy newer prelude */ -int __init_environment ( void ) -{ - return 0; -} - -/* dummy function to satisfy newer prelude */ -int __deinit_environment ( void ) -{ - return 0; -} - -#endif /* __NOVELL_LIBC__ */ - -#endif /* NETWARE */ diff --git a/lib/openldap.c b/lib/openldap.c deleted file mode 100644 index b10d31e18..000000000 --- a/lib/openldap.c +++ /dev/null @@ -1,652 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2010, Howard Chu, - * Copyright (C) 2011 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP) - -/* - * Notice that USE_OPENLDAP is only a source code selection switch. When - * libcurl is built with USE_OPENLDAP defined the libcurl source code that - * gets compiled is the code from curl_openldap.c, otherwise the code that - * gets compiled is the code from curl_ldap.c. - * - * When USE_OPENLDAP is defined a recent version of the OpenLDAP library - * might be required for compilation and runtime. In order to use ancient - * OpenLDAP library versions, USE_OPENLDAP shall not be defined. - */ - -#include - -#include "curl_urldata.h" -#include -#include "curl_sendf.h" -#include "curl_sslgen.h" -#include "curl_transfer.h" -#include "curl_ldap.h" -#include "curl_memory.h" -#include "curl_base64.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memdebug.h" - -#ifndef _LDAP_PVT_H -extern int ldap_pvt_url_scheme2proto(const char *); -extern int ldap_init_fd(ber_socket_t fd, int proto, const char *url, - LDAP **ld); -#endif - -static CURLcode ldap_setup(struct connectdata *conn); -static CURLcode ldap_do(struct connectdata *conn, bool *done); -static CURLcode ldap_done(struct connectdata *conn, CURLcode, bool); -static CURLcode ldap_connect(struct connectdata *conn, bool *done); -static CURLcode ldap_connecting(struct connectdata *conn, bool *done); -static CURLcode ldap_disconnect(struct connectdata *conn, bool dead); - -static Curl_recv ldap_recv; - -/* - * LDAP protocol handler. - */ - -const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ - ldap_setup, /* setup_connection */ - ldap_do, /* do_it */ - ldap_done, /* done */ - ZERO_NULL, /* do_more */ - ldap_connect, /* connect_it */ - ldap_connecting, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_LDAP, /* defport */ - CURLPROTO_LDAP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -#ifdef USE_SSL -/* - * LDAPS protocol handler. - */ - -const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ - ldap_setup, /* setup_connection */ - ldap_do, /* do_it */ - ldap_done, /* done */ - ZERO_NULL, /* do_more */ - ldap_connect, /* connect_it */ - ldap_connecting, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_LDAPS, /* defport */ - CURLPROTO_LDAP, /* protocol */ - PROTOPT_SSL /* flags */ -}; -#endif - -static const char *url_errs[] = { - "success", - "out of memory", - "bad parameter", - "unrecognized scheme", - "unbalanced delimiter", - "bad URL", - "bad host or port", - "bad or missing attributes", - "bad or missing scope", - "bad or missing filter", - "bad or missing extensions" -}; - -typedef struct ldapconninfo { - LDAP *ld; - Curl_recv *recv; /* for stacking SSL handler */ - Curl_send *send; - int proto; - int msgid; - bool ssldone; - bool sslinst; - bool didbind; -} ldapconninfo; - -typedef struct ldapreqinfo { - int msgid; - int nument; -} ldapreqinfo; - -static CURLcode ldap_setup(struct connectdata *conn) -{ - ldapconninfo *li; - LDAPURLDesc *lud; - struct SessionHandle *data=conn->data; - int rc, proto; - CURLcode status; - - rc = ldap_url_parse(data->change.url, &lud); - if(rc != LDAP_URL_SUCCESS) { - const char *msg = "url parsing problem"; - status = CURLE_URL_MALFORMAT; - if(rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) { - if(rc == LDAP_URL_ERR_MEM) - status = CURLE_OUT_OF_MEMORY; - msg = url_errs[rc]; - } - failf(conn->data, "LDAP local: %s", msg); - return status; - } - proto = ldap_pvt_url_scheme2proto(lud->lud_scheme); - ldap_free_urldesc(lud); - - li = calloc(1, sizeof(ldapconninfo)); - if(!li) - return CURLE_OUT_OF_MEMORY; - li->proto = proto; - conn->proto.generic = li; - conn->bits.close = FALSE; - /* TODO: - * - provide option to choose SASL Binds instead of Simple - */ - return CURLE_OK; -} - -#ifdef USE_SSL -static Sockbuf_IO ldapsb_tls; -#endif - -static CURLcode ldap_connect(struct connectdata *conn, bool *done) -{ - ldapconninfo *li = conn->proto.generic; - struct SessionHandle *data=conn->data; - int rc, proto = LDAP_VERSION3; - char hosturl[1024], *ptr; - - strcpy(hosturl, "ldap"); - ptr = hosturl+4; - if(conn->handler->flags & PROTOPT_SSL) - *ptr++ = 's'; - snprintf(ptr, sizeof(hosturl)-(ptr-hosturl), "://%s:%d", - conn->host.name, conn->remote_port); - - rc = ldap_init_fd(conn->sock[FIRSTSOCKET], li->proto, hosturl, &li->ld); - if(rc) { - failf(data, "LDAP local: Cannot connect to %s, %s", - hosturl, ldap_err2string(rc)); - return CURLE_COULDNT_CONNECT; - } - - ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); - -#ifdef USE_SSL - if(conn->handler->flags & PROTOPT_SSL) { - CURLcode res; - if(data->state.used_interface == Curl_if_easy) { - res = Curl_ssl_connect(conn, FIRSTSOCKET); - if(res) - return res; - li->ssldone = TRUE; - } - else { - res = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &li->ssldone); - if(res) - return res; - } - } -#endif - - if(data->state.used_interface == Curl_if_easy) - return ldap_connecting(conn, done); - - return CURLE_OK; -} - -static CURLcode ldap_connecting(struct connectdata *conn, bool *done) -{ - ldapconninfo *li = conn->proto.generic; - struct SessionHandle *data=conn->data; - LDAPMessage *result = NULL; - struct timeval tv = {0,1}, *tvp; - int rc, err; - char *info = NULL; - -#ifdef USE_SSL - if(conn->handler->flags & PROTOPT_SSL) { - /* Is the SSL handshake complete yet? */ - if(!li->ssldone) { - CURLcode res = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, - &li->ssldone); - if(res || !li->ssldone) - return res; - } - /* Have we installed the libcurl SSL handlers into the sockbuf yet? */ - if(!li->sslinst) { - Sockbuf *sb; - ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); - ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, conn); - li->sslinst = TRUE; - li->recv = conn->recv[FIRSTSOCKET]; - li->send = conn->send[FIRSTSOCKET]; - } - } -#endif - - if(data->state.used_interface == Curl_if_easy) - tvp = NULL; /* let ldap_result block indefinitely */ - else - tvp = &tv; - -retry: - if(!li->didbind) { - char *binddn; - struct berval passwd; - - if(conn->bits.user_passwd) { - binddn = conn->user; - passwd.bv_val = conn->passwd; - passwd.bv_len = strlen(passwd.bv_val); - } - else { - binddn = NULL; - passwd.bv_val = NULL; - passwd.bv_len = 0; - } - rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, - NULL, NULL, &li->msgid); - if(rc) - return CURLE_LDAP_CANNOT_BIND; - li->didbind = TRUE; - if(tvp) - return CURLE_OK; - } - - rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, tvp, &result); - if(rc < 0) { - failf(data, "LDAP local: bind ldap_result %s", ldap_err2string(rc)); - return CURLE_LDAP_CANNOT_BIND; - } - if(rc == 0) { - /* timed out */ - return CURLE_OK; - } - rc = ldap_parse_result(li->ld, result, &err, NULL, &info, NULL, NULL, 1); - if(rc) { - failf(data, "LDAP local: bind ldap_parse_result %s", ldap_err2string(rc)); - return CURLE_LDAP_CANNOT_BIND; - } - /* Try to fallback to LDAPv2? */ - if(err == LDAP_PROTOCOL_ERROR) { - int proto; - ldap_get_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); - if(proto == LDAP_VERSION3) { - if(info) { - ldap_memfree(info); - info = NULL; - } - proto = LDAP_VERSION2; - ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); - li->didbind = FALSE; - goto retry; - } - } - - if(err) { - failf(data, "LDAP remote: bind failed %s %s", ldap_err2string(rc), - info ? info : ""); - if(info) - ldap_memfree(info); - return CURLE_LOGIN_DENIED; - } - - if(info) - ldap_memfree(info); - conn->recv[FIRSTSOCKET] = ldap_recv; - *done = TRUE; - return CURLE_OK; -} - -static CURLcode ldap_disconnect(struct connectdata *conn, bool dead_connection) -{ - ldapconninfo *li = conn->proto.generic; - (void) dead_connection; - - if(li) { - if(li->ld) { - ldap_unbind_ext(li->ld, NULL, NULL); - li->ld = NULL; - } - conn->proto.generic = NULL; - free(li); - } - return CURLE_OK; -} - -static CURLcode ldap_do(struct connectdata *conn, bool *done) -{ - ldapconninfo *li = conn->proto.generic; - ldapreqinfo *lr; - CURLcode status = CURLE_OK; - int rc = 0; - LDAPURLDesc *ludp = NULL; - int msgid; - struct SessionHandle *data=conn->data; - - conn->bits.close = FALSE; - - infof(data, "LDAP local: %s\n", data->change.url); - - rc = ldap_url_parse(data->change.url, &ludp); - if(rc != LDAP_URL_SUCCESS) { - const char *msg = "url parsing problem"; - status = CURLE_URL_MALFORMAT; - if(rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) { - if(rc == LDAP_URL_ERR_MEM) - status = CURLE_OUT_OF_MEMORY; - msg = url_errs[rc]; - } - failf(conn->data, "LDAP local: %s", msg); - return status; - } - - rc = ldap_search_ext(li->ld, ludp->lud_dn, ludp->lud_scope, - ludp->lud_filter, ludp->lud_attrs, 0, - NULL, NULL, NULL, 0, &msgid); - ldap_free_urldesc(ludp); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); - return CURLE_LDAP_SEARCH_FAILED; - } - lr = calloc(1,sizeof(ldapreqinfo)); - if(!lr) - return CURLE_OUT_OF_MEMORY; - lr->msgid = msgid; - data->state.proto.generic = lr; - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, NULL, -1, NULL); - *done = TRUE; - return CURLE_OK; -} - -static CURLcode ldap_done(struct connectdata *conn, CURLcode res, - bool premature) -{ - ldapreqinfo *lr = conn->data->state.proto.generic; - (void)res; - (void)premature; - - if(lr) { - /* if there was a search in progress, abandon it */ - if(lr->msgid) { - ldapconninfo *li = conn->proto.generic; - ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL); - lr->msgid = 0; - } - conn->data->state.proto.generic = NULL; - free(lr); - } - return CURLE_OK; -} - -static ssize_t ldap_recv(struct connectdata *conn, int sockindex, char *buf, - size_t len, CURLcode *err) -{ - ldapconninfo *li = conn->proto.generic; - struct SessionHandle *data=conn->data; - ldapreqinfo *lr = data->state.proto.generic; - int rc, ret; - LDAPMessage *result = NULL; - LDAPMessage *ent; - BerElement *ber = NULL; - struct timeval tv = {0,1}; - (void)len; - (void)buf; - (void)sockindex; - - rc = ldap_result(li->ld, lr->msgid, LDAP_MSG_RECEIVED, &tv, &result); - if(rc < 0) { - failf(data, "LDAP local: search ldap_result %s", ldap_err2string(rc)); - *err = CURLE_RECV_ERROR; - return -1; - } - - *err = CURLE_AGAIN; - ret = -1; - - /* timed out */ - if(result == NULL) - return ret; - - for(ent = ldap_first_message(li->ld, result); ent; - ent = ldap_next_message(li->ld, ent)) { - struct berval bv, *bvals, **bvp = &bvals; - int binary = 0, msgtype; - - msgtype = ldap_msgtype(ent); - if(msgtype == LDAP_RES_SEARCH_RESULT) { - int code; - char *info = NULL; - rc = ldap_parse_result(li->ld, ent, &code, NULL, &info, NULL, NULL, 0); - if(rc) { - failf(data, "LDAP local: search ldap_parse_result %s", - ldap_err2string(rc)); - *err = CURLE_LDAP_SEARCH_FAILED; - } - else if(code && code != LDAP_SIZELIMIT_EXCEEDED) { - failf(data, "LDAP remote: search failed %s %s", ldap_err2string(rc), - info ? info : ""); - *err = CURLE_LDAP_SEARCH_FAILED; - } - else { - /* successful */ - if(code == LDAP_SIZELIMIT_EXCEEDED) - infof(data, "There are more than %d entries\n", lr->nument); - data->req.size = data->req.bytecount; - *err = CURLE_OK; - ret = 0; - } - lr->msgid = 0; - ldap_memfree(info); - break; - } - else if(msgtype != LDAP_RES_SEARCH_ENTRY) - continue; - - lr->nument++; - rc = ldap_get_dn_ber(li->ld, ent, &ber, &bv); - if(rc < 0) { - /* TODO: verify that this is really how this return code should be - handled */ - *err = CURLE_RECV_ERROR; - return -1; - } - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, bv.bv_len); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); - data->req.bytecount += bv.bv_len + 5; - - for(rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp); - rc == LDAP_SUCCESS; - rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp)) { - int i; - - if(bv.bv_val == NULL) break; - - if(bv.bv_len > 7 && !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7)) - binary = 1; - else - binary = 0; - - for(i=0; bvals[i].bv_val != NULL; i++) { - int binval = 0; - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, - bv.bv_len); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)":", 1); - data->req.bytecount += bv.bv_len + 2; - - if(!binary) { - /* check for leading or trailing whitespace */ - if(ISSPACE(bvals[i].bv_val[0]) || - ISSPACE(bvals[i].bv_val[bvals[i].bv_len-1])) - binval = 1; - else { - /* check for unprintable characters */ - unsigned int j; - for(j=0; jreq.bytecount += 2; - if(val_b64_sz > 0) { - Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, val_b64_sz); - free(val_b64); - data->req.bytecount += val_b64_sz; - } - } - else { - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)" ", 1); - Curl_client_write(conn, CLIENTWRITE_BODY, bvals[i].bv_val, - bvals[i].bv_len); - data->req.bytecount += bvals[i].bv_len + 1; - } - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); - data->req.bytecount++; - } - ber_memfree(bvals); - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); - data->req.bytecount++; - } - Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); - data->req.bytecount++; - ber_free(ber, 0); - } - ldap_msgfree(result); - return ret; -} - -#ifdef USE_SSL -static int -ldapsb_tls_setup(Sockbuf_IO_Desc *sbiod, void *arg) -{ - sbiod->sbiod_pvt = arg; - return 0; -} - -static int -ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod) -{ - sbiod->sbiod_pvt = NULL; - return 0; -} - -/* We don't need to do anything because libcurl does it already */ -static int -ldapsb_tls_close(Sockbuf_IO_Desc *sbiod) -{ - (void)sbiod; - return 0; -} - -static int -ldapsb_tls_ctrl(Sockbuf_IO_Desc *sbiod, int opt, void *arg) -{ - (void)arg; - if(opt == LBER_SB_OPT_DATA_READY) { - struct connectdata *conn = sbiod->sbiod_pvt; - return Curl_ssl_data_pending(conn, FIRSTSOCKET); - } - return 0; -} - -static ber_slen_t -ldapsb_tls_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) -{ - struct connectdata *conn = sbiod->sbiod_pvt; - ldapconninfo *li = conn->proto.generic; - ber_slen_t ret; - CURLcode err = CURLE_RECV_ERROR; - - ret = li->recv(conn, FIRSTSOCKET, buf, len, &err); - if(ret < 0 && err == CURLE_AGAIN) { - SET_SOCKERRNO(EWOULDBLOCK); - } - return ret; -} - -static ber_slen_t -ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) -{ - struct connectdata *conn = sbiod->sbiod_pvt; - ldapconninfo *li = conn->proto.generic; - ber_slen_t ret; - CURLcode err = CURLE_SEND_ERROR; - - ret = li->send(conn, FIRSTSOCKET, buf, len, &err); - if(ret < 0 && err == CURLE_AGAIN) { - SET_SOCKERRNO(EWOULDBLOCK); - } - return ret; -} - -static Sockbuf_IO ldapsb_tls = -{ - ldapsb_tls_setup, - ldapsb_tls_remove, - ldapsb_tls_ctrl, - ldapsb_tls_read, - ldapsb_tls_write, - ldapsb_tls_close -}; -#endif /* USE_SSL */ - -#endif /* !CURL_DISABLE_LDAP && USE_OPENLDAP */ diff --git a/lib/parsedate.c b/lib/parsedate.c deleted file mode 100644 index a50b6074e..000000000 --- a/lib/parsedate.c +++ /dev/null @@ -1,580 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ -/* - A brief summary of the date string formats this parser groks: - - RFC 2616 3.3.1 - - Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 - Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 - Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format - - we support dates without week day name: - - 06 Nov 1994 08:49:37 GMT - 06-Nov-94 08:49:37 GMT - Nov 6 08:49:37 1994 - - without the time zone: - - 06 Nov 1994 08:49:37 - 06-Nov-94 08:49:37 - - weird order: - - 1994 Nov 6 08:49:37 (GNU date fails) - GMT 08:49:37 06-Nov-94 Sunday - 94 6 Nov 08:49:37 (GNU date fails) - - time left out: - - 1994 Nov 6 - 06-Nov-94 - Sun Nov 6 94 - - unusual separators: - - 1994.Nov.6 - Sun/Nov/6/94/GMT - - commonly used time zone names: - - Sun, 06 Nov 1994 08:49:37 CET - 06 Nov 1994 08:49:37 EST - - time zones specified using RFC822 style: - - Sun, 12 Sep 2004 15:05:58 -0700 - Sat, 11 Sep 2004 21:32:11 +0200 - - compact numerical date strings: - - 20040912 15:05:58 -0700 - 20040911 +0200 - -*/ - -#include "curl_setup.h" - -#ifdef HAVE_LIMITS_H -#include -#endif - -#include -#include "curl_rawstr.h" -#include "curl_warnless.h" -#include "curl_parsedate.h" - -const char * const Curl_wkday[] = -{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; -static const char * const weekday[] = -{ "Monday", "Tuesday", "Wednesday", "Thursday", - "Friday", "Saturday", "Sunday" }; -const char * const Curl_month[]= -{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - -struct tzinfo { - char name[5]; - int offset; /* +/- in minutes */ -}; - -/* - * parsedate() - * - * Returns: - * - * PARSEDATE_OK - a fine conversion - * PARSEDATE_FAIL - failed to convert - * PARSEDATE_LATER - time overflow at the far end of time_t - * PARSEDATE_SOONER - time underflow at the low end of time_t - */ - -static int parsedate(const char *date, time_t *output); - -#define PARSEDATE_OK 0 -#define PARSEDATE_FAIL -1 -#define PARSEDATE_LATER 1 -#define PARSEDATE_SOONER 2 - -/* Here's a bunch of frequently used time zone names. These were supported - by the old getdate parser. */ -#define tDAYZONE -60 /* offset for daylight savings time */ -static const struct tzinfo tz[]= { - {"GMT", 0}, /* Greenwich Mean */ - {"UTC", 0}, /* Universal (Coordinated) */ - {"WET", 0}, /* Western European */ - {"BST", 0 tDAYZONE}, /* British Summer */ - {"WAT", 60}, /* West Africa */ - {"AST", 240}, /* Atlantic Standard */ - {"ADT", 240 tDAYZONE}, /* Atlantic Daylight */ - {"EST", 300}, /* Eastern Standard */ - {"EDT", 300 tDAYZONE}, /* Eastern Daylight */ - {"CST", 360}, /* Central Standard */ - {"CDT", 360 tDAYZONE}, /* Central Daylight */ - {"MST", 420}, /* Mountain Standard */ - {"MDT", 420 tDAYZONE}, /* Mountain Daylight */ - {"PST", 480}, /* Pacific Standard */ - {"PDT", 480 tDAYZONE}, /* Pacific Daylight */ - {"YST", 540}, /* Yukon Standard */ - {"YDT", 540 tDAYZONE}, /* Yukon Daylight */ - {"HST", 600}, /* Hawaii Standard */ - {"HDT", 600 tDAYZONE}, /* Hawaii Daylight */ - {"CAT", 600}, /* Central Alaska */ - {"AHST", 600}, /* Alaska-Hawaii Standard */ - {"NT", 660}, /* Nome */ - {"IDLW", 720}, /* International Date Line West */ - {"CET", -60}, /* Central European */ - {"MET", -60}, /* Middle European */ - {"MEWT", -60}, /* Middle European Winter */ - {"MEST", -60 tDAYZONE}, /* Middle European Summer */ - {"CEST", -60 tDAYZONE}, /* Central European Summer */ - {"MESZ", -60 tDAYZONE}, /* Middle European Summer */ - {"FWT", -60}, /* French Winter */ - {"FST", -60 tDAYZONE}, /* French Summer */ - {"EET", -120}, /* Eastern Europe, USSR Zone 1 */ - {"WAST", -420}, /* West Australian Standard */ - {"WADT", -420 tDAYZONE}, /* West Australian Daylight */ - {"CCT", -480}, /* China Coast, USSR Zone 7 */ - {"JST", -540}, /* Japan Standard, USSR Zone 8 */ - {"EAST", -600}, /* Eastern Australian Standard */ - {"EADT", -600 tDAYZONE}, /* Eastern Australian Daylight */ - {"GST", -600}, /* Guam Standard, USSR Zone 9 */ - {"NZT", -720}, /* New Zealand */ - {"NZST", -720}, /* New Zealand Standard */ - {"NZDT", -720 tDAYZONE}, /* New Zealand Daylight */ - {"IDLE", -720}, /* International Date Line East */ - /* Next up: Military timezone names. RFC822 allowed these, but (as noted in - RFC 1123) had their signs wrong. Here we use the correct signs to match - actual military usage. - */ - {"A", +1 * 60}, /* Alpha */ - {"B", +2 * 60}, /* Bravo */ - {"C", +3 * 60}, /* Charlie */ - {"D", +4 * 60}, /* Delta */ - {"E", +5 * 60}, /* Echo */ - {"F", +6 * 60}, /* Foxtrot */ - {"G", +7 * 60}, /* Golf */ - {"H", +8 * 60}, /* Hotel */ - {"I", +9 * 60}, /* India */ - /* "J", Juliet is not used as a timezone, to indicate the observer's local - time */ - {"K", +10 * 60}, /* Kilo */ - {"L", +11 * 60}, /* Lima */ - {"M", +12 * 60}, /* Mike */ - {"N", -1 * 60}, /* November */ - {"O", -2 * 60}, /* Oscar */ - {"P", -3 * 60}, /* Papa */ - {"Q", -4 * 60}, /* Quebec */ - {"R", -5 * 60}, /* Romeo */ - {"S", -6 * 60}, /* Sierra */ - {"T", -7 * 60}, /* Tango */ - {"U", -8 * 60}, /* Uniform */ - {"V", -9 * 60}, /* Victor */ - {"W", -10 * 60}, /* Whiskey */ - {"X", -11 * 60}, /* X-ray */ - {"Y", -12 * 60}, /* Yankee */ - {"Z", 0}, /* Zulu, zero meridian, a.k.a. UTC */ -}; - -/* returns: - -1 no day - 0 monday - 6 sunday -*/ - -static int checkday(const char *check, size_t len) -{ - int i; - const char * const *what; - bool found= FALSE; - if(len > 3) - what = &weekday[0]; - else - what = &Curl_wkday[0]; - for(i=0; i<7; i++) { - if(Curl_raw_equal(check, what[0])) { - found=TRUE; - break; - } - what++; - } - return found?i:-1; -} - -static int checkmonth(const char *check) -{ - int i; - const char * const *what; - bool found= FALSE; - - what = &Curl_month[0]; - for(i=0; i<12; i++) { - if(Curl_raw_equal(check, what[0])) { - found=TRUE; - break; - } - what++; - } - return found?i:-1; /* return the offset or -1, no real offset is -1 */ -} - -/* return the time zone offset between GMT and the input one, in number - of seconds or -1 if the timezone wasn't found/legal */ - -static int checktz(const char *check) -{ - unsigned int i; - const struct tzinfo *what; - bool found= FALSE; - - what = tz; - for(i=0; i< sizeof(tz)/sizeof(tz[0]); i++) { - if(Curl_raw_equal(check, what->name)) { - found=TRUE; - break; - } - what++; - } - return found?what->offset*60:-1; -} - -static void skip(const char **date) -{ - /* skip everything that aren't letters or digits */ - while(**date && !ISALNUM(**date)) - (*date)++; -} - -enum assume { - DATE_MDAY, - DATE_YEAR, - DATE_TIME -}; - -/* this is a clone of 'struct tm' but with all fields we don't need or use - cut out */ -struct my_tm { - int tm_sec; - int tm_min; - int tm_hour; - int tm_mday; - int tm_mon; - int tm_year; -}; - -/* struct tm to time since epoch in GMT time zone. - * This is similar to the standard mktime function but for GMT only, and - * doesn't suffer from the various bugs and portability problems that - * some systems' implementations have. - */ -static time_t my_timegm(struct my_tm *tm) -{ - static const int month_days_cumulative [12] = - { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; - int month, year, leap_days; - - if(tm->tm_year < 70) - /* we don't support years before 1970 as they will cause this function - to return a negative value */ - return -1; - - year = tm->tm_year + 1900; - month = tm->tm_mon; - if(month < 0) { - year += (11 - month) / 12; - month = 11 - (11 - month) % 12; - } - else if(month >= 12) { - year -= month / 12; - month = month % 12; - } - - leap_days = year - (tm->tm_mon <= 1); - leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400) - - (1969 / 4) + (1969 / 100) - (1969 / 400)); - - return ((((time_t) (year - 1970) * 365 - + leap_days + month_days_cumulative [month] + tm->tm_mday - 1) * 24 - + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec; -} - -/* - * parsedate() - * - * Returns: - * - * PARSEDATE_OK - a fine conversion - * PARSEDATE_FAIL - failed to convert - * PARSEDATE_LATER - time overflow at the far end of time_t - * PARSEDATE_SOONER - time underflow at the low end of time_t - */ - -static int parsedate(const char *date, time_t *output) -{ - time_t t = 0; - int wdaynum=-1; /* day of the week number, 0-6 (mon-sun) */ - int monnum=-1; /* month of the year number, 0-11 */ - int mdaynum=-1; /* day of month, 1 - 31 */ - int hournum=-1; - int minnum=-1; - int secnum=-1; - int yearnum=-1; - int tzoff=-1; - struct my_tm tm; - enum assume dignext = DATE_MDAY; - const char *indate = date; /* save the original pointer */ - int part = 0; /* max 6 parts */ - - while(*date && (part < 6)) { - bool found=FALSE; - - skip(&date); - - if(ISALPHA(*date)) { - /* a name coming up */ - char buf[32]=""; - size_t len; - sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]", - buf); - len = strlen(buf); - - if(wdaynum == -1) { - wdaynum = checkday(buf, len); - if(wdaynum != -1) - found = TRUE; - } - if(!found && (monnum == -1)) { - monnum = checkmonth(buf); - if(monnum != -1) - found = TRUE; - } - - if(!found && (tzoff == -1)) { - /* this just must be a time zone string */ - tzoff = checktz(buf); - if(tzoff != -1) - found = TRUE; - } - - if(!found) - return PARSEDATE_FAIL; /* bad string */ - - date += len; - } - else if(ISDIGIT(*date)) { - /* a digit */ - int val; - char *end; - if((secnum == -1) && - (3 == sscanf(date, "%02d:%02d:%02d", &hournum, &minnum, &secnum))) { - /* time stamp! */ - date += 8; - } - else if((secnum == -1) && - (2 == sscanf(date, "%02d:%02d", &hournum, &minnum))) { - /* time stamp without seconds */ - date += 5; - secnum = 0; - } - else { - long lval; - int error; - int old_errno; - - old_errno = ERRNO; - SET_ERRNO(0); - lval = strtol(date, &end, 10); - error = ERRNO; - if(error != old_errno) - SET_ERRNO(old_errno); - - if(error) - return PARSEDATE_FAIL; - - if((lval > (long)INT_MAX) || (lval < (long)INT_MIN)) - return PARSEDATE_FAIL; - - val = curlx_sltosi(lval); - - if((tzoff == -1) && - ((end - date) == 4) && - (val <= 1400) && - (indate< date) && - ((date[-1] == '+' || date[-1] == '-'))) { - /* four digits and a value less than or equal to 1400 (to take into - account all sorts of funny time zone diffs) and it is preceded - with a plus or minus. This is a time zone indication. 1400 is - picked since +1300 is frequently used and +1400 is mentioned as - an edge number in the document "ISO C 200X Proposal: Timezone - Functions" at http://david.tribble.com/text/c0xtimezone.html If - anyone has a more authoritative source for the exact maximum time - zone offsets, please speak up! */ - found = TRUE; - tzoff = (val/100 * 60 + val%100)*60; - - /* the + and - prefix indicates the local time compared to GMT, - this we need ther reversed math to get what we want */ - tzoff = date[-1]=='+'?-tzoff:tzoff; - } - - if(((end - date) == 8) && - (yearnum == -1) && - (monnum == -1) && - (mdaynum == -1)) { - /* 8 digits, no year, month or day yet. This is YYYYMMDD */ - found = TRUE; - yearnum = val/10000; - monnum = (val%10000)/100-1; /* month is 0 - 11 */ - mdaynum = val%100; - } - - if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { - if((val > 0) && (val<32)) { - mdaynum = val; - found = TRUE; - } - dignext = DATE_YEAR; - } - - if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) { - yearnum = val; - found = TRUE; - if(yearnum < 1900) { - if(yearnum > 70) - yearnum += 1900; - else - yearnum += 2000; - } - if(mdaynum == -1) - dignext = DATE_MDAY; - } - - if(!found) - return PARSEDATE_FAIL; - - date = end; - } - } - - part++; - } - - if(-1 == secnum) - secnum = minnum = hournum = 0; /* no time, make it zero */ - - if((-1 == mdaynum) || - (-1 == monnum) || - (-1 == yearnum)) - /* lacks vital info, fail */ - return PARSEDATE_FAIL; - -#if SIZEOF_TIME_T < 5 - /* 32 bit time_t can only hold dates to the beginning of 2038 */ - if(yearnum > 2037) { - *output = 0x7fffffff; - return PARSEDATE_LATER; - } -#endif - - if(yearnum < 1970) { - *output = 0; - return PARSEDATE_SOONER; - } - - if((mdaynum > 31) || (monnum > 11) || - (hournum > 23) || (minnum > 59) || (secnum > 60)) - return PARSEDATE_FAIL; /* clearly an illegal date */ - - tm.tm_sec = secnum; - tm.tm_min = minnum; - tm.tm_hour = hournum; - tm.tm_mday = mdaynum; - tm.tm_mon = monnum; - tm.tm_year = yearnum - 1900; - - /* my_timegm() returns a time_t. time_t is often 32 bits, even on many - architectures that feature 64 bit 'long'. - - Some systems have 64 bit time_t and deal with years beyond 2038. However, - even on some of the systems with 64 bit time_t mktime() returns -1 for - dates beyond 03:14:07 UTC, January 19, 2038. (Such as AIX 5100-06) - */ - t = my_timegm(&tm); - - /* time zone adjust (cast t to int to compare to negative one) */ - if(-1 != (int)t) { - - /* Add the time zone diff between local time zone and GMT. */ - long delta = (long)(tzoff!=-1?tzoff:0); - - if((delta>0) && (t + delta < t)) - return -1; /* time_t overflow */ - - t += delta; - } - - *output = t; - - return PARSEDATE_OK; -} - -time_t curl_getdate(const char *p, const time_t *now) -{ - time_t parsed; - int rc = parsedate(p, &parsed); - (void)now; /* legacy argument from the past that we ignore */ - - switch(rc) { - case PARSEDATE_OK: - case PARSEDATE_LATER: - case PARSEDATE_SOONER: - return parsed; - } - /* everything else is fail */ - return -1; -} - -/* - * Curl_gmtime() is a gmtime() replacement for portability. Do not use the - * gmtime_r() or gmtime() functions anywhere else but here. - * - * To make sure no such function calls slip in, we define them to cause build - * errors, which is why we use the name within parentheses in this function. - * - */ - -CURLcode Curl_gmtime(time_t intime, struct tm *store) -{ - const struct tm *tm; -#ifdef HAVE_GMTIME_R - /* thread-safe version */ - tm = (struct tm *)gmtime_r(&intime, store); -#else - tm = gmtime(&intime); - if(tm) - *store = *tm; /* copy the pointed struct to the local copy */ -#endif - - if(!tm) - return CURLE_BAD_FUNCTION_ARGUMENT; - return CURLE_OK; -} diff --git a/lib/pingpong.c b/lib/pingpong.c deleted file mode 100644 index d28e78aa2..000000000 --- a/lib/pingpong.c +++ /dev/null @@ -1,538 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - * 'pingpong' is for generic back-and-forth support functions used by FTP, - * IMAP, POP3, SMTP and whatever more that likes them. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_select.h" -#include "curl_progress.h" -#include "curl_speedcheck.h" -#include "curl_pingpong.h" -#include "curl_multiif.h" -#include "curl_non_ascii.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifdef USE_PINGPONG - -/* Returns timeout in ms. 0 or negative number means the timeout has already - triggered */ -long Curl_pp_state_timeout(struct pingpong *pp) -{ - struct connectdata *conn = pp->conn; - struct SessionHandle *data=conn->data; - long timeout_ms; /* in milliseconds */ - long timeout2_ms; /* in milliseconds */ - long response_time= (data->set.server_response_timeout)? - data->set.server_response_timeout: pp->response_time; - - /* if CURLOPT_SERVER_RESPONSE_TIMEOUT is set, use that to determine - remaining time, or use pp->response because SERVER_RESPONSE_TIMEOUT is - supposed to govern the response for any given server response, not for - the time from connect to the given server response. */ - - /* Without a requested timeout, we only wait 'response_time' seconds for the - full response to arrive before we bail out */ - timeout_ms = response_time - - Curl_tvdiff(Curl_tvnow(), pp->response); /* spent time */ - - if(data->set.timeout) { - /* if timeout is requested, find out how much remaining time we have */ - timeout2_ms = data->set.timeout - /* timeout time */ - Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ - - /* pick the lowest number */ - timeout_ms = CURLMIN(timeout_ms, timeout2_ms); - } - - return timeout_ms; -} - - -/* - * Curl_pp_multi_statemach() - * - * called repeatedly until done when the multi interface is used. - */ -CURLcode Curl_pp_multi_statemach(struct pingpong *pp) -{ - struct connectdata *conn = pp->conn; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int rc; - struct SessionHandle *data=conn->data; - CURLcode result = CURLE_OK; - long timeout_ms = Curl_pp_state_timeout(pp); - - if(timeout_ms <= 0) { - failf(data, "server response timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ - pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ - 0); - - if(rc == -1) { - failf(data, "select/poll error"); - return CURLE_OUT_OF_MEMORY; - } - else if(rc != 0) - result = pp->statemach_act(conn); - - /* if rc == 0, then select() timed out */ - - return result; -} - -/* - * Curl_pp_easy_statemach() - * - * called repeatedly until done when the easy interface is used. - */ -CURLcode Curl_pp_easy_statemach(struct pingpong *pp) -{ - struct connectdata *conn = pp->conn; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int rc; - long interval_ms; - long timeout_ms = Curl_pp_state_timeout(pp); - struct SessionHandle *data=conn->data; - CURLcode result; - - if(timeout_ms <=0 ) { - failf(data, "server response timeout"); - return CURLE_OPERATION_TIMEDOUT; /* already too little time */ - } - - interval_ms = 1000; /* use 1 second timeout intervals */ - if(timeout_ms < interval_ms) - interval_ms = timeout_ms; - - rc = Curl_socket_ready(pp->sendleft?CURL_SOCKET_BAD:sock, /* reading */ - pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ - interval_ms); - - if(Curl_pgrsUpdate(conn)) - result = CURLE_ABORTED_BY_CALLBACK; - else - result = Curl_speedcheck(data, Curl_tvnow()); - - if(result) - ; - else if(rc == -1) { - failf(data, "select/poll error"); - result = CURLE_OUT_OF_MEMORY; - } - else if(rc) - result = pp->statemach_act(conn); - - return result; -} - -/* initialize stuff to prepare for reading a fresh new response */ -void Curl_pp_init(struct pingpong *pp) -{ - struct connectdata *conn = pp->conn; - pp->nread_resp = 0; - pp->linestart_resp = conn->data->state.buffer; - pp->pending_resp = TRUE; - pp->response = Curl_tvnow(); /* start response time-out now! */ -} - - - -/*********************************************************************** - * - * Curl_pp_vsendf() - * - * Send the formated string as a command to a pingpong server. Note that - * the string should not have any CRLF appended, as this function will - * append the necessary things itself. - * - * made to never block - */ -CURLcode Curl_pp_vsendf(struct pingpong *pp, - const char *fmt, - va_list args) -{ - ssize_t bytes_written; - size_t write_len; - char *fmt_crlf; - char *s; - CURLcode error; - struct connectdata *conn = pp->conn; - struct SessionHandle *data = conn->data; - -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - enum protection_level data_sec = conn->data_prot; -#endif - - DEBUGASSERT(pp->sendleft == 0); - DEBUGASSERT(pp->sendsize == 0); - DEBUGASSERT(pp->sendthis == NULL); - - fmt_crlf = aprintf("%s\r\n", fmt); /* append a trailing CRLF */ - if(!fmt_crlf) - return CURLE_OUT_OF_MEMORY; - - s = vaprintf(fmt_crlf, args); /* trailing CRLF appended */ - free(fmt_crlf); - if(!s) - return CURLE_OUT_OF_MEMORY; - - bytes_written = 0; - write_len = strlen(s); - - Curl_pp_init(pp); - - error = Curl_convert_to_network(data, s, write_len); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(error) { - free(s); - return error; - } - -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - conn->data_prot = PROT_CMD; -#endif - error = Curl_write(conn, conn->sock[FIRSTSOCKET], s, write_len, - &bytes_written); -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); - conn->data_prot = data_sec; -#endif - - if(error) { - free(s); - return error; - } - - if(conn->data->set.verbose) - Curl_debug(conn->data, CURLINFO_HEADER_OUT, - s, (size_t)bytes_written, conn); - - if(bytes_written != (ssize_t)write_len) { - /* the whole chunk was not sent, keep it around and adjust sizes */ - pp->sendthis = s; - pp->sendsize = write_len; - pp->sendleft = write_len - bytes_written; - } - else { - free(s); - pp->sendthis = NULL; - pp->sendleft = pp->sendsize = 0; - pp->response = Curl_tvnow(); - } - - return CURLE_OK; -} - - -/*********************************************************************** - * - * Curl_pp_sendf() - * - * Send the formated string as a command to a pingpong server. Note that - * the string should not have any CRLF appended, as this function will - * append the necessary things itself. - * - * made to never block - */ -CURLcode Curl_pp_sendf(struct pingpong *pp, - const char *fmt, ...) -{ - CURLcode res; - va_list ap; - va_start(ap, fmt); - - res = Curl_pp_vsendf(pp, fmt, ap); - - va_end(ap); - - return res; -} - -/* - * Curl_pp_readresp() - * - * Reads a piece of a server response. - */ -CURLcode Curl_pp_readresp(curl_socket_t sockfd, - struct pingpong *pp, - int *code, /* return the server code if done */ - size_t *size) /* size of the response */ -{ - ssize_t perline; /* count bytes per line */ - bool keepon=TRUE; - ssize_t gotbytes; - char *ptr; - struct connectdata *conn = pp->conn; - struct SessionHandle *data = conn->data; - char * const buf = data->state.buffer; - CURLcode result = CURLE_OK; - - *code = 0; /* 0 for errors or not done */ - *size = 0; - - ptr=buf + pp->nread_resp; - - /* number of bytes in the current line, so far */ - perline = (ssize_t)(ptr-pp->linestart_resp); - - keepon=TRUE; - - while((pp->nread_respcache) { - /* we had data in the "cache", copy that instead of doing an actual - * read - * - * pp->cache_size is cast to ssize_t here. This should be safe, because - * it would have been populated with something of size int to begin - * with, even though its datatype may be larger than an int. - */ - DEBUGASSERT((ptr+pp->cache_size) <= (buf+BUFSIZE+1)); - memcpy(ptr, pp->cache, pp->cache_size); - gotbytes = (ssize_t)pp->cache_size; - free(pp->cache); /* free the cache */ - pp->cache = NULL; /* clear the pointer */ - pp->cache_size = 0; /* zero the size just in case */ - } - else { - int res; -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - enum protection_level prot = conn->data_prot; - conn->data_prot = PROT_CLEAR; -#endif - DEBUGASSERT((ptr+BUFSIZE-pp->nread_resp) <= (buf+BUFSIZE+1)); - res = Curl_read(conn, sockfd, ptr, BUFSIZE-pp->nread_resp, - &gotbytes); -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); - conn->data_prot = prot; -#endif - if(res == CURLE_AGAIN) - return CURLE_OK; /* return */ - - if((res == CURLE_OK) && (gotbytes > 0)) - /* convert from the network encoding */ - res = Curl_convert_from_network(data, ptr, gotbytes); - /* Curl_convert_from_network calls failf if unsuccessful */ - - if(CURLE_OK != res) { - result = (CURLcode)res; /* Set outer result variable to this error. */ - keepon = FALSE; - } - } - - if(!keepon) - ; - else if(gotbytes <= 0) { - keepon = FALSE; - result = CURLE_RECV_ERROR; - failf(data, "response reading failed"); - } - else { - /* we got a whole chunk of data, which can be anything from one - * byte to a set of lines and possible just a piece of the last - * line */ - ssize_t i; - ssize_t clipamount = 0; - bool restart = FALSE; - - data->req.headerbytecount += (long)gotbytes; - - pp->nread_resp += gotbytes; - for(i = 0; i < gotbytes; ptr++, i++) { - perline++; - if(*ptr=='\n') { - /* a newline is CRLF in pp-talk, so the CR is ignored as - 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, - pp->linestart_resp, (size_t)perline, conn); - - /* - * We pass all response-lines to the callback function registered - * for "headers". The response lines can be seen as a kind of - * headers. - */ - result = Curl_client_write(conn, CLIENTWRITE_HEADER, - pp->linestart_resp, perline); - if(result) - return result; - - if(pp->endofresp(pp, code)) { - /* This is the end of the last line, copy the last line to the - start of the buffer and zero terminate, for old times sake (and - krb4)! */ - char *meow; - int n; - for(meow=pp->linestart_resp, n=0; meowlinestart_resp = ptr+1; /* advance pointer */ - i++; /* skip this before getting out */ - - *size = pp->nread_resp; /* size of the response */ - pp->nread_resp = 0; /* restart */ - break; - } - perline=0; /* line starts over here */ - pp->linestart_resp = ptr+1; - } - } - - if(!keepon && (i != gotbytes)) { - /* We found the end of the response lines, but we didn't parse the - full chunk of data we have read from the server. We therefore need - to store the rest of the data to be checked on the next invoke as - it may actually contain another end of response already! */ - clipamount = gotbytes - i; - restart = TRUE; - DEBUGF(infof(data, "Curl_pp_readresp_ %d bytes of trailing " - "server response left\n", - (int)clipamount)); - } - else if(keepon) { - - if((perline == gotbytes) && (gotbytes > BUFSIZE/2)) { - /* We got an excessive line without newlines and we need to deal - with it. We keep the first bytes of the line then we throw - away the rest. */ - infof(data, "Excessive server response line length received, " - "%zd bytes. Stripping\n", gotbytes); - restart = TRUE; - - /* we keep 40 bytes since all our pingpong protocols are only - interested in the first piece */ - clipamount = 40; - } - else if(pp->nread_resp > BUFSIZE/2) { - /* We got a large chunk of data and there's potentially still - trailing data to take care of, so we put any such part in the - "cache", clear the buffer to make space and restart. */ - clipamount = perline; - restart = TRUE; - } - } - else if(i == gotbytes) - restart = TRUE; - - if(clipamount) { - pp->cache_size = clipamount; - pp->cache = malloc(pp->cache_size); - if(pp->cache) - memcpy(pp->cache, pp->linestart_resp, pp->cache_size); - else - return CURLE_OUT_OF_MEMORY; - } - if(restart) { - /* now reset a few variables to start over nicely from the start of - the big buffer */ - pp->nread_resp = 0; /* start over from scratch in the buffer */ - ptr = pp->linestart_resp = buf; - perline = 0; - } - - } /* there was data */ - - } /* while there's buffer left and loop is requested */ - - pp->pending_resp = FALSE; - - return result; -} - -int Curl_pp_getsock(struct pingpong *pp, - curl_socket_t *socks, - int numsocks) -{ - struct connectdata *conn = pp->conn; - - if(!numsocks) - return GETSOCK_BLANK; - - socks[0] = conn->sock[FIRSTSOCKET]; - - if(pp->sendleft) { - /* write mode */ - return GETSOCK_WRITESOCK(0); - } - - /* read mode */ - return GETSOCK_READSOCK(0); -} - -CURLcode Curl_pp_flushsend(struct pingpong *pp) -{ - /* we have a piece of a command still left to send */ - struct connectdata *conn = pp->conn; - ssize_t written; - CURLcode result = CURLE_OK; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - - result = Curl_write(conn, sock, pp->sendthis + pp->sendsize - - pp->sendleft, pp->sendleft, &written); - if(result) - return result; - - if(written != (ssize_t)pp->sendleft) { - /* only a fraction was sent */ - pp->sendleft -= written; - } - else { - free(pp->sendthis); - pp->sendthis=NULL; - pp->sendleft = pp->sendsize = 0; - pp->response = Curl_tvnow(); - } - return CURLE_OK; -} - -CURLcode Curl_pp_disconnect(struct pingpong *pp) -{ - if(pp->cache) { - free(pp->cache); - pp->cache = NULL; - } - return CURLE_OK; -} - - - -#endif diff --git a/lib/polarssl.c b/lib/polarssl.c deleted file mode 100644 index 81c70264f..000000000 --- a/lib/polarssl.c +++ /dev/null @@ -1,596 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2010, 2011, Hoi-Ho Chan, - * Copyright (C) 2012 - 2013, 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 - * 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. - * - ***************************************************************************/ - -/* - * Source file for all PolarSSL-specific code for the TLS/SSL layer. No code - * but curl_sslgen.c should ever call or use these functions. - * - */ - -#include "curl_setup.h" - -#ifdef USE_POLARSSL - -#include -#include -#include -#include -#include -#include - -#include -#include - -#if POLARSSL_VERSION_NUMBER<0x01000000 -/* - Earlier versions of polarssl had no WANT_READ or WANT_WRITE, only TRY_AGAIN -*/ -#define POLARSSL_ERR_NET_WANT_READ POLARSSL_ERR_NET_TRY_AGAIN -#define POLARSSL_ERR_NET_WANT_WRITE POLARSSL_ERR_NET_TRY_AGAIN -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_inet_pton.h" -#include "curl_polarssl.h" -#include "curl_sslgen.h" -#include "curl_parsedate.h" -#include "curl_connect.h" /* for the connect timeout */ -#include "curl_select.h" -#include "curl_rawstr.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* version dependent differences */ -#if POLARSSL_VERSION_NUMBER < 0x01010000 -/* the old way */ -#define HAVEGE_RANDOM havege_rand -#else -/* from 1.1.0 */ -#define HAVEGE_RANDOM havege_random -#endif - -/* Define this to enable lots of debugging for PolarSSL */ -#undef POLARSSL_DEBUG - -#ifdef POLARSSL_DEBUG -static void polarssl_debug(void *context, int level, char *line) -{ - struct SessionHandle *data = NULL; - - if(!context) - return; - - data = (struct SessionHandle *)context; - - infof(data, "%s\n", line); -} -#else -#endif - -static Curl_recv polarssl_recv; -static Curl_send polarssl_send; - - -static CURLcode -polarssl_connect_step1(struct connectdata *conn, - int sockindex) -{ - struct SessionHandle *data = conn->data; - struct ssl_connect_data* connssl = &conn->ssl[sockindex]; - - bool sni = TRUE; /* default is SNI enabled */ - int ret = -1; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - void *old_session = NULL; - size_t old_session_size = 0; - - /* PolarSSL only supports SSLv3 and TLSv1 */ - if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) { - failf(data, "PolarSSL does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - else if(data->set.ssl.version == CURL_SSLVERSION_SSLv3) - sni = FALSE; /* SSLv3 has no SNI */ - - havege_init(&connssl->hs); - - /* Load the trusted CA */ - memset(&connssl->cacert, 0, sizeof(x509_cert)); - - if(data->set.str[STRING_SSL_CAFILE]) { - ret = x509parse_crtfile(&connssl->cacert, - data->set.str[STRING_SSL_CAFILE]); - - if(ret<0) { - failf(data, "Error reading ca cert file %s: -0x%04X", - data->set.str[STRING_SSL_CAFILE], ret); - - if(data->set.ssl.verifypeer) - return CURLE_SSL_CACERT_BADFILE; - } - } - - /* Load the client certificate */ - memset(&connssl->clicert, 0, sizeof(x509_cert)); - - if(data->set.str[STRING_CERT]) { - ret = x509parse_crtfile(&connssl->clicert, - data->set.str[STRING_CERT]); - - if(ret) { - failf(data, "Error reading client cert file %s: -0x%04X", - data->set.str[STRING_CERT], -ret); - return CURLE_SSL_CERTPROBLEM; - } - } - - /* Load the client private key */ - if(data->set.str[STRING_KEY]) { - ret = x509parse_keyfile(&connssl->rsa, - data->set.str[STRING_KEY], - data->set.str[STRING_KEY_PASSWD]); - - if(ret) { - failf(data, "Error reading private key %s: -0x%04X", - data->set.str[STRING_KEY], -ret); - return CURLE_SSL_CERTPROBLEM; - } - } - - /* Load the CRL */ - memset(&connssl->crl, 0, sizeof(x509_crl)); - - if(data->set.str[STRING_SSL_CRLFILE]) { - ret = x509parse_crlfile(&connssl->crl, - data->set.str[STRING_SSL_CRLFILE]); - - if(ret) { - failf(data, "Error reading CRL file %s: -0x%04X", - data->set.str[STRING_SSL_CRLFILE], -ret); - return CURLE_SSL_CRL_BADFILE; - } - } - - infof(data, "PolarSSL: Connecting to %s:%d\n", - conn->host.name, conn->remote_port); - - if(ssl_init(&connssl->ssl)) { - failf(data, "PolarSSL: ssl_init failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - ssl_set_endpoint(&connssl->ssl, SSL_IS_CLIENT); - ssl_set_authmode(&connssl->ssl, SSL_VERIFY_OPTIONAL); - - ssl_set_rng(&connssl->ssl, HAVEGE_RANDOM, - &connssl->hs); - ssl_set_bio(&connssl->ssl, - net_recv, &conn->sock[sockindex], - net_send, &conn->sock[sockindex]); - - -#if POLARSSL_VERSION_NUMBER<0x01000000 - ssl_set_ciphers(&connssl->ssl, ssl_default_ciphers); -#else - ssl_set_ciphersuites(&connssl->ssl, ssl_default_ciphersuites); -#endif - if(!Curl_ssl_getsessionid(conn, &old_session, &old_session_size)) { - memcpy(&connssl->ssn, old_session, old_session_size); - infof(data, "PolarSSL re-using session\n"); - } - -/* PolarSSL SVN revision r1316 to r1317, matching <1.2.0 is to cover Ubuntu's - 1.1.4 version and the like */ -#if POLARSSL_VERSION_NUMBER<0x01020000 - ssl_set_session(&connssl->ssl, 1, 600, - &connssl->ssn); -#else - ssl_set_session(&connssl->ssl, - &connssl->ssn); -#endif - - ssl_set_ca_chain(&connssl->ssl, - &connssl->cacert, - &connssl->crl, - conn->host.name); - - ssl_set_own_cert(&connssl->ssl, - &connssl->clicert, &connssl->rsa); - - if(!Curl_inet_pton(AF_INET, conn->host.name, &addr) && -#ifdef ENABLE_IPV6 - !Curl_inet_pton(AF_INET6, conn->host.name, &addr) && -#endif - sni && ssl_set_hostname(&connssl->ssl, conn->host.name)) { - infof(data, "WARNING: failed to configure " - "server name indication (SNI) TLS extension\n"); - } - -#ifdef POLARSSL_DEBUG - ssl_set_dbg(&connssl->ssl, polarssl_debug, data); -#endif - - connssl->connecting_state = ssl_connect_2; - - return CURLE_OK; -} - -static CURLcode -polarssl_connect_step2(struct connectdata *conn, - int sockindex) -{ - int ret; - struct SessionHandle *data = conn->data; - struct ssl_connect_data* connssl = &conn->ssl[sockindex]; - char buffer[1024]; - - conn->recv[sockindex] = polarssl_recv; - conn->send[sockindex] = polarssl_send; - - for(;;) { - if(!(ret = ssl_handshake(&connssl->ssl))) - break; - else if(ret != POLARSSL_ERR_NET_WANT_READ && - ret != POLARSSL_ERR_NET_WANT_WRITE) { - failf(data, "ssl_handshake returned -0x%04X", -ret); - return CURLE_SSL_CONNECT_ERROR; - } - else { - if(ret == POLARSSL_ERR_NET_WANT_READ) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - if(ret == POLARSSL_ERR_NET_WANT_WRITE) { - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; - } - failf(data, "SSL_connect failed with error %d.", ret); - return CURLE_SSL_CONNECT_ERROR; - - } - } - - infof(data, "PolarSSL: Handshake complete, cipher is %s\n", -#if POLARSSL_VERSION_NUMBER<0x01000000 - ssl_get_cipher(&conn->ssl[sockindex].ssl) -#elif POLARSSL_VERSION_NUMBER >= 0x01010000 - ssl_get_ciphersuite(&conn->ssl[sockindex].ssl) -#else - ssl_get_ciphersuite_name(&conn->ssl[sockindex].ssl) -#endif - ); - - ret = ssl_get_verify_result(&conn->ssl[sockindex].ssl); - - if(ret && data->set.ssl.verifypeer) { - if(ret & BADCERT_EXPIRED) - failf(data, "Cert verify failed: BADCERT_EXPIRED"); - - if(ret & BADCERT_REVOKED) { - failf(data, "Cert verify failed: BADCERT_REVOKED"); - return CURLE_SSL_CACERT; - } - - if(ret & BADCERT_CN_MISMATCH) - failf(data, "Cert verify failed: BADCERT_CN_MISMATCH"); - - if(ret & BADCERT_NOT_TRUSTED) - failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED"); - - return CURLE_PEER_FAILED_VERIFICATION; - } - -/* PolarSSL SVN revision r1316 to r1317, matching <1.2.0 is to cover Ubuntu's - 1.1.4 version and the like */ -#if POLARSSL_VERSION_NUMBER<0x01020000 - if(conn->ssl[sockindex].ssl.peer_cert) { -#else - if(ssl_get_peer_cert(&(connssl->ssl))) { -#endif - /* If the session was resumed, there will be no peer certs */ - memset(buffer, 0, sizeof(buffer)); - -/* PolarSSL SVN revision r1316 to r1317, matching <1.2.0 is to cover Ubuntu's - 1.1.4 version and the like */ -#if POLARSSL_VERSION_NUMBER<0x01020000 - if(x509parse_cert_info(buffer, sizeof(buffer), (char *)"* ", - conn->ssl[sockindex].ssl.peer_cert) != -1) -#else - if(x509parse_cert_info(buffer, sizeof(buffer), (char *)"* ", - ssl_get_peer_cert(&(connssl->ssl))) != -1) -#endif - infof(data, "Dumping cert info:\n%s\n", buffer); - } - - connssl->connecting_state = ssl_connect_3; - infof(data, "SSL connected\n"); - - return CURLE_OK; -} - -static CURLcode -polarssl_connect_step3(struct connectdata *conn, - int sockindex) -{ - CURLcode retcode = CURLE_OK; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct SessionHandle *data = conn->data; - void *old_ssl_sessionid = NULL; - ssl_session *our_ssl_sessionid = &conn->ssl[sockindex].ssn ; - int incache; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - - /* Save the current session data for possible re-use */ - incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL)); - if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing\n"); - Curl_ssl_delsessionid(conn, old_ssl_sessionid); - incache = FALSE; - } - } - if(!incache) { - void *new_session = malloc(sizeof(ssl_session)); - - if(new_session) { - memcpy(new_session, our_ssl_sessionid, - sizeof(ssl_session)); - - retcode = Curl_ssl_addsessionid(conn, new_session, - sizeof(ssl_session)); - } - else { - retcode = CURLE_OUT_OF_MEMORY; - } - - if(retcode) { - failf(data, "failed to store ssl session"); - return retcode; - } - } - - connssl->connecting_state = ssl_connect_done; - - return CURLE_OK; -} - -static ssize_t polarssl_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - int ret = -1; - - ret = ssl_write(&conn->ssl[sockindex].ssl, - (unsigned char *)mem, len); - - if(ret < 0) { - *curlcode = (ret == POLARSSL_ERR_NET_WANT_WRITE) ? - CURLE_AGAIN : CURLE_SEND_ERROR; - ret = -1; - } - - return ret; -} - -void Curl_polarssl_close_all(struct SessionHandle *data) -{ - (void)data; -} - -void Curl_polarssl_close(struct connectdata *conn, int sockindex) -{ - rsa_free(&conn->ssl[sockindex].rsa); - x509_free(&conn->ssl[sockindex].clicert); - x509_free(&conn->ssl[sockindex].cacert); - x509_crl_free(&conn->ssl[sockindex].crl); - ssl_free(&conn->ssl[sockindex].ssl); -} - -static ssize_t polarssl_recv(struct connectdata *conn, - int num, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - int ret = -1; - ssize_t len = -1; - - memset(buf, 0, buffersize); - ret = ssl_read(&conn->ssl[num].ssl, (unsigned char *)buf, buffersize); - - if(ret <= 0) { - if(ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) - return 0; - - *curlcode = (ret == POLARSSL_ERR_NET_WANT_READ) ? - CURLE_AGAIN : CURLE_RECV_ERROR; - return -1; - } - - len = ret; - - return len; -} - -void Curl_polarssl_session_free(void *ptr) -{ - free(ptr); -} - -size_t Curl_polarssl_version(char *buffer, size_t size) -{ - unsigned int version = version_get_number(); - return snprintf(buffer, size, "PolarSSL/%d.%d.%d", version>>24, - (version>>16)&0xff, (version>>8)&0xff); -} - -static CURLcode -polarssl_connect_common(struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode retcode; - struct SessionHandle *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - long timeout_ms; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1==connssl->connecting_state) { - /* Find out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - retcode = polarssl_connect_step1(conn, sockindex); - if(retcode) - return retcode; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing== - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading== - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ - retcode = polarssl_connect_step2(conn, sockindex); - if(retcode || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return retcode; - - } /* repeat step2 until all transactions are done. */ - - if(ssl_connect_3==connssl->connecting_state) { - retcode = polarssl_connect_step3(conn, sockindex); - if(retcode) - return retcode; - } - - if(ssl_connect_done==connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = polarssl_recv; - conn->send[sockindex] = polarssl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -CURLcode -Curl_polarssl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) -{ - return polarssl_connect_common(conn, sockindex, TRUE, done); -} - - -CURLcode -Curl_polarssl_connect(struct connectdata *conn, - int sockindex) -{ - CURLcode retcode; - bool done = FALSE; - - retcode = polarssl_connect_common(conn, sockindex, FALSE, &done); - if(retcode) - return retcode; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -#endif diff --git a/lib/pop3.c b/lib/pop3.c deleted file mode 100644 index 0d157f00b..000000000 --- a/lib/pop3.c +++ /dev/null @@ -1,1764 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - * RFC1734 POP3 Authentication - * RFC1939 POP3 protocol - * RFC2195 CRAM-MD5 authentication - * RFC2384 POP URL Scheme - * RFC2449 POP3 Extension Mechanism - * RFC2595 Using TLS with IMAP, POP3 and ACAP - * RFC2831 DIGEST-MD5 authentication - * RFC4616 PLAIN authentication - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_POP3 - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_UTSNAME_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -#include -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_if2ip.h" -#include "curl_hostip.h" -#include "curl_progress.h" -#include "curl_transfer.h" -#include "curl_escape.h" -#include "curl_http.h" /* for HTTP proxy tunnel stuff */ -#include "curl_socks.h" -#include "curl_pop3.h" - -#include "curl_strtoofft.h" -#include "curl_strequal.h" -#include "curl_sslgen.h" -#include "curl_connect.h" -#include "curl_strerror.h" -#include "curl_select.h" -#include "curl_multiif.h" -#include "curl_url.h" -#include "curl_rawstr.h" -#include "curl_sasl.h" -#include "curl_md5.h" -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* Local API functions */ -static CURLcode pop3_parse_url_path(struct connectdata *conn); -static CURLcode pop3_parse_custom_request(struct connectdata *conn); -static CURLcode pop3_regular_transfer(struct connectdata *conn, bool *done); -static CURLcode pop3_do(struct connectdata *conn, bool *done); -static CURLcode pop3_done(struct connectdata *conn, CURLcode status, - bool premature); -static CURLcode pop3_connect(struct connectdata *conn, bool *done); -static CURLcode pop3_disconnect(struct connectdata *conn, bool dead); -static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done); -static int pop3_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -static CURLcode pop3_doing(struct connectdata *conn, bool *dophase_done); -static CURLcode pop3_setup_connection(struct connectdata *conn); - -/* - * POP3 protocol handler. - */ - -const struct Curl_handler Curl_handler_pop3 = { - "POP3", /* scheme */ - pop3_setup_connection, /* setup_connection */ - pop3_do, /* do_it */ - pop3_done, /* done */ - ZERO_NULL, /* do_more */ - pop3_connect, /* connect_it */ - pop3_multi_statemach, /* connecting */ - pop3_doing, /* doing */ - pop3_getsock, /* proto_getsock */ - pop3_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_POP3, /* defport */ - CURLPROTO_POP3, /* protocol */ - PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY /* flags */ -}; - -#ifdef USE_SSL -/* - * POP3S protocol handler. - */ - -const struct Curl_handler Curl_handler_pop3s = { - "POP3S", /* scheme */ - pop3_setup_connection, /* setup_connection */ - pop3_do, /* do_it */ - pop3_done, /* done */ - ZERO_NULL, /* do_more */ - pop3_connect, /* connect_it */ - pop3_multi_statemach, /* connecting */ - pop3_doing, /* doing */ - pop3_getsock, /* proto_getsock */ - pop3_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_POP3S, /* defport */ - CURLPROTO_POP3 | CURLPROTO_POP3S, /* protocol */ - PROTOPT_CLOSEACTION | PROTOPT_SSL - | PROTOPT_NOURLQUERY /* flags */ -}; -#endif - -#ifndef CURL_DISABLE_HTTP -/* - * HTTP-proxyed POP3 protocol handler. - */ - -static const struct Curl_handler Curl_handler_pop3_proxy = { - "POP3", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_POP3, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -#ifdef USE_SSL -/* - * HTTP-proxyed POP3S protocol handler. - */ - -static const struct Curl_handler Curl_handler_pop3s_proxy = { - "POP3S", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_POP3S, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; -#endif -#endif - -/* Function that checks for an ending pop3 status code at the start of the - given string, but also detects the APOP timestamp from the server greeting - as well as the supported authentication types and allowed SASL mechanisms - from the CAPA response. */ -static int pop3_endofresp(struct pingpong *pp, int *resp) -{ - char *line = pp->linestart_resp; - size_t len = strlen(pp->linestart_resp); - struct connectdata *conn = pp->conn; - struct pop3_conn *pop3c = &conn->proto.pop3c; - size_t wordlen; - size_t i; - - /* Do we have an error response? */ - if(len >= 4 && !memcmp("-ERR", line, 4)) { - *resp = '-'; - - return FALSE; - } - - /* Are we processing servergreet responses */ - if(pop3c->state == POP3_SERVERGREET) { - /* Look for the APOP timestamp */ - if(len >= 3 && line[len - 3] == '>') { - for(i = 0; i < len - 3; ++i) { - if(line[i] == '<') { - /* Calculate the length of the timestamp */ - size_t timestamplen = len - 2 - i; - - /* Allocate some memory for the timestamp */ - pop3c->apoptimestamp = (char *)calloc(1, timestamplen + 1); - - if(!pop3c->apoptimestamp) - break; - - /* Copy the timestamp */ - memcpy(pop3c->apoptimestamp, line + i, timestamplen); - pop3c->apoptimestamp[timestamplen] = '\0'; - break; - } - } - } - } - /* Are we processing CAPA command responses? */ - else if(pop3c->state == POP3_CAPA) { - - /* Do we have the terminating character? */ - if(len >= 1 && !memcmp(line, ".", 1)) { - *resp = '+'; - - return TRUE; - } - - /* Does the server support clear text? */ - if(len >= 4 && !memcmp(line, "USER", 4)) { - pop3c->authtypes |= POP3_TYPE_CLEARTEXT; - return FALSE; - } - - /* Does the server support APOP? */ - if(len >= 4 && !memcmp(line, "APOP", 4)) { - pop3c->authtypes |= POP3_TYPE_APOP; - return FALSE; - } - - /* Does the server support SASL? */ - if(len < 4 || memcmp(line, "SASL", 4)) - return FALSE; - - pop3c->authtypes |= POP3_TYPE_SASL; - - /* Advance past the SASL keyword */ - line += 4; - len -= 4; - - /* Loop through the data line */ - for(;;) { - while(len && - (*line == ' ' || *line == '\t' || - *line == '\r' || *line == '\n')) { - - if(*line == '\n') - return FALSE; - - line++; - len--; - } - - if(!len) - break; - - /* Extract the word */ - for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && - line[wordlen] != '\t' && line[wordlen] != '\r' && - line[wordlen] != '\n';) - wordlen++; - - /* Test the word for a matching authentication mechanism */ - if(wordlen == 5 && !memcmp(line, "LOGIN", 5)) - pop3c->authmechs |= SASL_MECH_LOGIN; - else if(wordlen == 5 && !memcmp(line, "PLAIN", 5)) - pop3c->authmechs |= SASL_MECH_PLAIN; - else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8)) - pop3c->authmechs |= SASL_MECH_CRAM_MD5; - else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10)) - pop3c->authmechs |= SASL_MECH_DIGEST_MD5; - else if(wordlen == 6 && !memcmp(line, "GSSAPI", 6)) - pop3c->authmechs |= SASL_MECH_GSSAPI; - else if(wordlen == 8 && !memcmp(line, "EXTERNAL", 8)) - pop3c->authmechs |= SASL_MECH_EXTERNAL; - else if(wordlen == 4 && !memcmp(line, "NTLM", 4)) - pop3c->authmechs |= SASL_MECH_NTLM; - - line += wordlen; - len -= wordlen; - } - } - - if((len < 1 || memcmp("+", line, 1)) && - (len < 3 || memcmp("+OK", line, 3))) - return FALSE; /* Nothing for us */ - - /* Otherwise it's a positive response */ - *resp = '+'; - - return TRUE; -} - -/* This is the ONLY way to change POP3 state! */ -static void state(struct connectdata *conn, pop3state newstate) -{ - struct pop3_conn *pop3c = &conn->proto.pop3c; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ - static const char * const names[] = { - "STOP", - "SERVERGREET", - "STARTTLS", - "CAPA", - "AUTH_PLAIN", - "AUTH_LOGIN", - "AUTH_LOGIN_PASSWD", - "AUTH_CRAMMD5", - "AUTH_DIGESTMD5", - "AUTH_DIGESTMD5_RESP", - "AUTH_NTLM", - "AUTH_NTLM_TYPE2MSG", - "AUTH", - "APOP", - "USER", - "PASS", - "COMMAND", - "QUIT", - /* LAST */ - }; - - if(pop3c->state != newstate) - infof(conn->data, "POP3 %p state change from %s to %s\n", - pop3c, names[pop3c->state], names[newstate]); -#endif - - pop3c->state = newstate; -} - -static CURLcode pop3_state_capa(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; - - pop3c->authmechs = 0; /* No known authentication mechanisms yet */ - pop3c->authused = 0; /* Clear the authentication mechanism used */ - - /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ - if(!conn->bits.user_passwd) { - state(conn, POP3_STOP); - - return result; - } - - /* Send the CAPA command */ - result = Curl_pp_sendf(&pop3c->pp, "CAPA"); - - if(result) - return result; - - state(conn, POP3_CAPA); - - return CURLE_OK; -} - -static CURLcode pop3_state_user(struct connectdata *conn) -{ - CURLcode result; - struct FTP *pop3 = conn->data->state.proto.pop3; - - /* Send the USER command */ - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "USER %s", - pop3->user ? pop3->user : ""); - if(result) - return result; - - state(conn, POP3_USER); - - return CURLE_OK; -} - -#ifndef CURL_DISABLE_CRYPTO_AUTH -static CURLcode pop3_state_apop(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; - size_t i; - MD5_context *ctxt; - unsigned char digest[MD5_DIGEST_LEN]; - char secret[2 * MD5_DIGEST_LEN + 1]; - - ctxt = Curl_MD5_init(Curl_DIGEST_MD5); - if(!ctxt) - return CURLE_OUT_OF_MEMORY; - - Curl_MD5_update(ctxt, (const unsigned char *) pop3c->apoptimestamp, - curlx_uztoui(strlen(pop3c->apoptimestamp))); - - Curl_MD5_update(ctxt, (const unsigned char *) conn->passwd, - curlx_uztoui(strlen(conn->passwd))); - - /* Finalise the digest */ - Curl_MD5_final(ctxt, digest); - - /* Convert the calculated 16 octet digest into a 32 byte hex string */ - for(i = 0; i < MD5_DIGEST_LEN; i++) - snprintf(&secret[2 * i], 3, "%02x", digest[i]); - - result = Curl_pp_sendf(&pop3c->pp, "APOP %s %s", conn->user, secret); - - if(!result) - state(conn, POP3_APOP); - - return result; -} -#endif - -static CURLcode pop3_authenticate(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *mech = NULL; - pop3state authstate = POP3_STOP; - - /* Check supported authentication mechanisms by decreasing order of - security */ -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(pop3c->authmechs & SASL_MECH_DIGEST_MD5) { - mech = "DIGEST-MD5"; - authstate = POP3_AUTH_DIGESTMD5; - pop3c->authused = SASL_MECH_DIGEST_MD5; - } - else if(pop3c->authmechs & SASL_MECH_CRAM_MD5) { - mech = "CRAM-MD5"; - authstate = POP3_AUTH_CRAMMD5; - pop3c->authused = SASL_MECH_CRAM_MD5; - } - else -#endif -#ifdef USE_NTLM - if(pop3c->authmechs & SASL_MECH_NTLM) { - mech = "NTLM"; - authstate = POP3_AUTH_NTLM; - pop3c->authused = SASL_MECH_NTLM; - } - else -#endif - if(pop3c->authmechs & SASL_MECH_LOGIN) { - mech = "LOGIN"; - authstate = POP3_AUTH_LOGIN; - pop3c->authused = SASL_MECH_LOGIN; - } - else if(pop3c->authmechs & SASL_MECH_PLAIN) { - mech = "PLAIN"; - authstate = POP3_AUTH_PLAIN; - pop3c->authused = SASL_MECH_PLAIN; - } - else { - infof(conn->data, "No known SASL authentication mechanisms supported!\n"); - result = CURLE_LOGIN_DENIED; /* Other mechanisms not supported */ - } - - if(!result) { - result = Curl_pp_sendf(&pop3c->pp, "AUTH %s", mech); - - if(!result) - state(conn, authstate); - } - - return result; -} - -/* For the POP3 "protocol connect" and "doing" phases only */ -static int pop3_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) -{ - return Curl_pp_getsock(&conn->proto.pop3c.pp, socks, numsocks); -} - -#ifdef USE_SSL -static void pop3_to_pop3s(struct connectdata *conn) -{ - conn->handler = &Curl_handler_pop3s; -} -#else -#define pop3_to_pop3s(x) Curl_nop_stmt -#endif - -/* For the initial server greeting */ -static CURLcode pop3_state_servergreet_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct pop3_conn *pop3c = &conn->proto.pop3c; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Got unexpected pop3-server response"); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - - if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch - to TLS connection now */ - result = Curl_pp_sendf(&pop3c->pp, "STLS"); - state(conn, POP3_STARTTLS); - } - else - result = pop3_state_capa(conn); - - return result; -} - -/* For STARTTLS responses */ -static CURLcode pop3_state_starttls_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - if(data->set.use_ssl != CURLUSESSL_TRY) { - failf(data, "STARTTLS denied. %c", pop3code); - result = CURLE_USE_SSL_FAILED; - } - else - result = pop3_state_capa(conn); - } - else { - /* Curl_ssl_connect is BLOCKING */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(CURLE_OK == result) { - pop3_to_pop3s(conn); - result = pop3_state_capa(conn); - } - } - - return result; -} - -/* For CAPA responses */ -static CURLcode pop3_state_capa_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') - result = pop3_state_user(conn); - else { - /* Check supported authentication types by decreasing order of security */ - if(conn->proto.pop3c.authtypes & POP3_TYPE_SASL) - result = pop3_authenticate(conn); -#ifndef CURL_DISABLE_CRYPTO_AUTH - else if(conn->proto.pop3c.authtypes & POP3_TYPE_APOP) - result = pop3_state_apop(conn); -#endif - else if(conn->proto.pop3c.authtypes & POP3_TYPE_CLEARTEXT) - result = pop3_state_user(conn); - else { - infof(conn->data, "No known authentication types supported!\n"); - result = CURLE_LOGIN_DENIED; /* Other types not supported */ - } - } - - return result; -} - -/* For AUTH PLAIN responses */ -static CURLcode pop3_state_auth_plain_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - size_t len = 0; - char *plainauth = NULL; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied. %c", pop3code); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the authorisation message */ - result = Curl_sasl_create_plain_message(data, conn->user, conn->passwd, - &plainauth, &len); - - /* Send the message */ - if(!result) { - if(plainauth) { - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", plainauth); - - if(!result) - state(conn, POP3_AUTH); - } - - Curl_safefree(plainauth); - } - } - - return result; -} - -/* For AUTH LOGIN responses */ -static CURLcode pop3_state_auth_login_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - size_t len = 0; - char *authuser = NULL; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied: %d", pop3code); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the user message */ - result = Curl_sasl_create_login_message(data, conn->user, - &authuser, &len); - - /* Send the user */ - if(!result) { - if(authuser) { - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", authuser); - - if(!result) - state(conn, POP3_AUTH_LOGIN_PASSWD); - } - - Curl_safefree(authuser); - } - } - - return result; -} - -/* For AUTH LOGIN user entry responses */ -static CURLcode pop3_state_auth_login_password_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - size_t len = 0; - char *authpasswd = NULL; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied: %d", pop3code); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the password message */ - result = Curl_sasl_create_login_message(data, conn->passwd, - &authpasswd, &len); - - /* Send the password */ - if(!result) { - if(authpasswd) { - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", authpasswd); - - if(!result) - state(conn, POP3_AUTH); - } - - Curl_safefree(authpasswd); - } - } - - return result; -} - -#ifndef CURL_DISABLE_CRYPTO_AUTH -/* For AUTH CRAM-MD5 responses */ -static CURLcode pop3_state_auth_cram_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - char *chlg64 = data->state.buffer; - size_t len = 0; - char *rplyb64 = NULL; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied: %d", pop3code); - return CURLE_LOGIN_DENIED; - } - - /* Get the challenge */ - for(chlg64 += 2; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++) - ; - - /* Terminate the challenge */ - if(*chlg64 != '=') { - for(len = strlen(chlg64); len--;) - if(chlg64[len] != '\r' && chlg64[len] != '\n' && chlg64[len] != ' ' && - chlg64[len] != '\t') - break; - - if(++len) { - chlg64[len] = '\0'; - } - } - - /* Create the response message */ - result = Curl_sasl_create_cram_md5_message(data, chlg64, conn->user, - conn->passwd, &rplyb64, &len); - - /* Send the response */ - if(!result) { - if(rplyb64) { - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", rplyb64); - - if(!result) - state(conn, POP3_AUTH); - } - - Curl_safefree(rplyb64); - } - - return result; -} - -/* For AUTH DIGEST-MD5 challenge responses */ -static CURLcode pop3_state_auth_digest_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - char *chlg64 = data->state.buffer; - size_t len = 0; - char *rplyb64 = NULL; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied: %d", pop3code); - return CURLE_LOGIN_DENIED; - } - - /* Get the challenge */ - for(chlg64 += 2; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++) - ; - - /* Create the response message */ - result = Curl_sasl_create_digest_md5_message(data, chlg64, conn->user, - conn->passwd, "pop", - &rplyb64, &len); - - /* Send the response */ - if(!result) { - if(rplyb64) { - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", rplyb64); - - if(!result) - state(conn, POP3_AUTH_DIGESTMD5_RESP); - } - - Curl_safefree(rplyb64); - } - - return result; -} - -/* For AUTH DIGEST-MD5 challenge-response responses */ -static CURLcode pop3_state_auth_digest_resp_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Authentication failed: %d", pop3code); - result = CURLE_LOGIN_DENIED; - } - else { - /* Send an empty response */ - result = Curl_pp_sendf(&conn->proto.pop3c.pp, ""); - - if(!result) - state(conn, POP3_AUTH); - } - - return result; -} -#endif - -#ifdef USE_NTLM -/* For AUTH NTLM responses */ -static CURLcode pop3_state_auth_ntlm_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - size_t len = 0; - char *type1msg = NULL; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied: %d", pop3code); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the type-1 message */ - result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, - &conn->ntlm, - &type1msg, &len); - - /* Send the message */ - if(!result) { - if(type1msg) { - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", type1msg); - - if(!result) - state(conn, POP3_AUTH_NTLM_TYPE2MSG); - } - - Curl_safefree(type1msg); - } - } - - return result; -} - -/* For NTLM type-2 responses (sent in reponse to our type-1 message) */ -static CURLcode pop3_state_auth_ntlm_type2msg_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - size_t len = 0; - char *type3msg = NULL; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied: %d", pop3code); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the type-3 message */ - result = Curl_sasl_create_ntlm_type3_message(data, - data->state.buffer + 2, - conn->user, conn->passwd, - &conn->ntlm, - &type3msg, &len); - - /* Send the message */ - if(!result) { - if(type3msg) { - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", type3msg); - - if(!result) - state(conn, POP3_AUTH); - } - - Curl_safefree(type3msg); - } - } - - return result; -} -#endif - -/* For final responses to the AUTH sequence */ -static CURLcode pop3_state_auth_final_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Authentication failed: %d", pop3code); - result = CURLE_LOGIN_DENIED; - } - - /* End of connect phase */ - state(conn, POP3_STOP); - - return result; -} - -static CURLcode pop3_state_apop_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Authentication failed: %d", pop3code); - result = CURLE_LOGIN_DENIED; - } - - /* End of connect phase */ - state(conn, POP3_STOP); - - return result; -} - -/* For USER responses */ -static CURLcode pop3_state_user_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct FTP *pop3 = data->state.proto.pop3; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied. %c", pop3code); - result = CURLE_LOGIN_DENIED; - } - else - /* Send the PASS command */ - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "PASS %s", - pop3->passwd ? pop3->passwd : ""); - if(result) - return result; - - state(conn, POP3_PASS); - - return result; -} - -/* For PASS responses */ -static CURLcode pop3_state_pass_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - failf(data, "Access denied. %c", pop3code); - result = CURLE_LOGIN_DENIED; - } - - /* End of connect phase */ - state(conn, POP3_STOP); - - return result; -} - -/* Start the DO phase for the command */ -static CURLcode pop3_command(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *command = NULL; - - /* Calculate the default command */ - if(pop3c->mailbox[0] == '\0' || conn->data->set.ftp_list_only) { - command = "LIST"; - - if(pop3c->mailbox[0] != '\0') { - /* Message specific LIST so skip the BODY transfer */ - struct FTP *pop3 = conn->data->state.proto.pop3; - pop3->transfer = FTPTRANSFER_INFO; - } - } - else - command = "RETR"; - - /* Send the command */ - if(pop3c->mailbox[0] != '\0') - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s %s", - (pop3c->custom && pop3c->custom[0] != '\0' ? - pop3c->custom : command), pop3c->mailbox); - else - result = Curl_pp_sendf(&conn->proto.pop3c.pp, - (pop3c->custom && pop3c->custom[0] != '\0' ? - pop3c->custom : command)); - - if(result) - return result; - - state(conn, POP3_COMMAND); - - return result; -} - -/* For command responses */ -static CURLcode pop3_state_command_resp(struct connectdata *conn, - int pop3code, - pop3state instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct FTP *pop3 = data->state.proto.pop3; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; - - (void)instate; /* no use for this yet */ - - if(pop3code != '+') { - state(conn, POP3_STOP); - return CURLE_RECV_ERROR; - } - - /* This 'OK' line ends with a CR LF pair which is the two first bytes of the - EOB string so count this is two matching bytes. This is necessary to make - the code detect the EOB if the only data than comes now is %2e CR LF like - when there is no body to return. */ - pop3c->eob = 2; - - /* But since this initial CR LF pair is not part of the actual body, we set - the strip counter here so that these bytes won't be delivered. */ - pop3c->strip = 2; - - /* POP3 download */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, pop3->bytecountp, - -1, NULL); /* no upload here */ - - if(pp->cache) { - /* The header "cache" contains a bunch of data that is actually body - content so send it as such. Note that there may even be additional - "headers" after the body */ - - if(!data->set.opt_no_body) { - result = Curl_pop3_write(conn, pp->cache, pp->cache_size); - if(result) - return result; - } - - /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; - } - - /* End of do phase */ - state(conn, POP3_STOP); - - return result; -} - -static CURLcode pop3_statemach_act(struct connectdata *conn) -{ - CURLcode result; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int pop3code; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; - size_t nread = 0; - - /* Flush any data that needs to be sent */ - if(pp->sendleft) - return Curl_pp_flushsend(pp); - - /* Read the response from the server */ - result = Curl_pp_readresp(sock, pp, &pop3code, &nread); - if(result) - return result; - - if(pop3code) { - /* We have now received a full POP3 server response */ - switch(pop3c->state) { - case POP3_SERVERGREET: - result = pop3_state_servergreet_resp(conn, pop3code, pop3c->state); - break; - - case POP3_STARTTLS: - result = pop3_state_starttls_resp(conn, pop3code, pop3c->state); - break; - - case POP3_CAPA: - result = pop3_state_capa_resp(conn, pop3code, pop3c->state); - break; - - case POP3_AUTH_PLAIN: - result = pop3_state_auth_plain_resp(conn, pop3code, pop3c->state); - break; - - case POP3_AUTH_LOGIN: - result = pop3_state_auth_login_resp(conn, pop3code, pop3c->state); - break; - - case POP3_AUTH_LOGIN_PASSWD: - result = pop3_state_auth_login_password_resp(conn, pop3code, - pop3c->state); - break; - -#ifndef CURL_DISABLE_CRYPTO_AUTH - case POP3_AUTH_CRAMMD5: - result = pop3_state_auth_cram_resp(conn, pop3code, pop3c->state); - break; - - case POP3_AUTH_DIGESTMD5: - result = pop3_state_auth_digest_resp(conn, pop3code, pop3c->state); - break; - - case POP3_AUTH_DIGESTMD5_RESP: - result = pop3_state_auth_digest_resp_resp(conn, pop3code, pop3c->state); - break; -#endif - -#ifdef USE_NTLM - case POP3_AUTH_NTLM: - result = pop3_state_auth_ntlm_resp(conn, pop3code, pop3c->state); - break; - - case POP3_AUTH_NTLM_TYPE2MSG: - result = pop3_state_auth_ntlm_type2msg_resp(conn, pop3code, - pop3c->state); - break; -#endif - - case POP3_AUTH: - result = pop3_state_auth_final_resp(conn, pop3code, pop3c->state); - break; - - case POP3_APOP: - result = pop3_state_apop_resp(conn, pop3code, pop3c->state); - break; - - case POP3_USER: - result = pop3_state_user_resp(conn, pop3code, pop3c->state); - break; - - case POP3_PASS: - result = pop3_state_pass_resp(conn, pop3code, pop3c->state); - break; - - case POP3_COMMAND: - result = pop3_state_command_resp(conn, pop3code, pop3c->state); - break; - - case POP3_QUIT: - /* fallthrough, just stop! */ - default: - /* internal error */ - state(conn, POP3_STOP); - break; - } - } - - return result; -} - -/* Called repeatedly until done from curl_multi.c */ -static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done) -{ - struct pop3_conn *pop3c = &conn->proto.pop3c; - CURLcode result = Curl_pp_multi_statemach(&pop3c->pp); - - *done = (pop3c->state == POP3_STOP) ? TRUE : FALSE; - - return result; -} - -static CURLcode pop3_easy_statemach(struct connectdata *conn) -{ - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct pingpong *pp = &pop3c->pp; - CURLcode result = CURLE_OK; - - while(pop3c->state != POP3_STOP) { - result = Curl_pp_easy_statemach(pp); - if(result) - break; - } - - return result; -} - -/* Allocate and initialize the POP3 struct for the current SessionHandle if - required */ -static CURLcode pop3_init(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - struct FTP *pop3 = data->state.proto.pop3; - - if(!pop3) { - pop3 = data->state.proto.pop3 = calloc(sizeof(struct FTP), 1); - if(!pop3) - return CURLE_OUT_OF_MEMORY; - } - - /* Get some initial data into the pop3 struct */ - pop3->bytecountp = &data->req.bytecount; - - /* No need to duplicate user+password, the connectdata struct won't change - during a session, but we re-init them here since on subsequent inits - since the conn struct may have changed or been replaced. - */ - pop3->user = conn->user; - pop3->passwd = conn->passwd; - - return CURLE_OK; -} - -/*********************************************************************** - * - * pop3_connect() - * - * This function should do everything that is to be considered a part of the - * connection phase. - * - * The variable 'done' points to will be TRUE if the protocol-layer connect - * phase is done when this function returns, or FALSE is not. When called as - * a part of the easy interface, it will always be TRUE. - */ -static CURLcode pop3_connect(struct connectdata *conn, bool *done) -{ - CURLcode result; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct SessionHandle *data = conn->data; - struct pingpong *pp = &pop3c->pp; - - *done = FALSE; /* default to not done yet */ - - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - result = pop3_init(conn); - if(CURLE_OK != result) - return result; - - /* We always support persistent connections on pop3 */ - conn->bits.close = FALSE; - - pp->response_time = RESP_TIMEOUT; /* set default response time-out */ - pp->statemach_act = pop3_statemach_act; - pp->endofresp = pop3_endofresp; - pp->conn = conn; - - if(conn->handler->flags & PROTOPT_SSL) { - /* POP3S is simply pop3 with SSL for the control channel */ - /* so perform the SSL initialization for this socket */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(result) - return result; - } - - /* Initialise the response reader stuff */ - Curl_pp_init(pp); - - /* Start off waiting for the server greeting response */ - state(conn, POP3_SERVERGREET); - - if(data->state.used_interface == Curl_if_multi) - result = pop3_multi_statemach(conn, done); - else { - result = pop3_easy_statemach(conn); - if(!result) - *done = TRUE; - } - - return result; -} - -/*********************************************************************** - * - * pop3_done() - * - * The DONE function. This does what needs to be done after a single DO has - * performed. - * - * Input argument is already checked for validity. - */ -static CURLcode pop3_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - struct SessionHandle *data = conn->data; - struct FTP *pop3 = data->state.proto.pop3; - struct pop3_conn *pop3c = &conn->proto.pop3c; - CURLcode result = CURLE_OK; - - (void)premature; - - if(!pop3) - /* When the easy handle is removed from the multi while libcurl is still - * trying to resolve the host name, it seems that the pop3 struct is not - * yet initialized, but the removal action calls Curl_done() which calls - * this function. So we simply return success if no pop3 pointer is set. - */ - return CURLE_OK; - - if(status) { - conn->bits.close = TRUE; /* marked for closure */ - result = status; /* use the already set error code */ - } - - /* Cleanup our do based variables */ - Curl_safefree(pop3c->mailbox); - Curl_safefree(pop3c->custom); - - /* Clear the transfer mode for the next connection */ - pop3->transfer = FTPTRANSFER_BODY; - - return result; -} - -/*********************************************************************** - * - * pop3_perform() - * - * This is the actual DO function for POP3. Get a file/directory according to - * the options previously setup. - */ -static CURLcode pop3_perform(struct connectdata *conn, bool *connected, - bool *dophase_done) -{ - /* This is POP3 and no proxy */ - CURLcode result = CURLE_OK; - - DEBUGF(infof(conn->data, "DO phase starts\n")); - - if(conn->data->set.opt_no_body) { - /* Requested no body means no transfer */ - struct FTP *pop3 = conn->data->state.proto.pop3; - pop3->transfer = FTPTRANSFER_INFO; - } - - *dophase_done = FALSE; /* not done yet */ - - /* Start the first command in the DO phase */ - result = pop3_command(conn); - if(result) - return result; - - /* Run the state-machine */ - if(conn->data->state.used_interface == Curl_if_multi) - result = pop3_multi_statemach(conn, dophase_done); - else { - result = pop3_easy_statemach(conn); - *dophase_done = TRUE; /* with the easy interface we are done here */ - } - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); - - return result; -} - -/*********************************************************************** - * - * pop3_do() - * - * This function is registered as 'curl_do' function. It decodes the path - * parts etc as a wrapper to the actual DO function (pop3_perform). - * - * The input argument is already checked for validity. - */ -static CURLcode pop3_do(struct connectdata *conn, bool *done) -{ - CURLcode retcode = CURLE_OK; - - *done = FALSE; /* default to false */ - - /* - Since connections can be re-used between SessionHandles, this might be a - connection already existing but on a fresh SessionHandle struct so we must - make sure we have a good 'struct POP3' to play with. For new connections, - the struct POP3 is allocated and setup in the pop3_connect() function. - */ - Curl_reset_reqproto(conn); - retcode = pop3_init(conn); - if(retcode) - return retcode; - - /* Parse the URL path */ - retcode = pop3_parse_url_path(conn); - if(retcode) - return retcode; - - /* Parse the custom request */ - retcode = pop3_parse_custom_request(conn); - if(retcode) - return retcode; - - retcode = pop3_regular_transfer(conn, done); - - return retcode; -} - -/*********************************************************************** - * - * pop3_quit() - * - * This should be called before calling sclose(). We should then wait for the - * response from the server before returning. The calling code should then try - * to close the connection. - */ -static CURLcode pop3_quit(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "QUIT", NULL); - if(result) - return result; - - state(conn, POP3_QUIT); - - result = pop3_easy_statemach(conn); - - return result; -} - -/*********************************************************************** - * - * pop3_disconnect() - * - * Disconnect from an POP3 server. Cleanup protocol-specific per-connection - * resources. BLOCKING. - */ -static CURLcode pop3_disconnect(struct connectdata *conn, - bool dead_connection) -{ - struct pop3_conn *pop3c = &conn->proto.pop3c; - - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to */ - - /* The POP3 session may or may not have been allocated/setup at this - point! */ - if(!dead_connection && pop3c->pp.conn) - (void)pop3_quit(conn); /* ignore errors on the LOGOUT */ - - /* Disconnect from the server */ - Curl_pp_disconnect(&pop3c->pp); - - /* Cleanup the SASL module */ - Curl_sasl_cleanup(conn, pop3c->authused); - - /* Cleanup our connection based variables */ - Curl_safefree(pop3c->apoptimestamp); - - return CURLE_OK; -} - -/*********************************************************************** - * - * pop3_parse_url_path() - * - * Parse the URL path into separate path components. - */ -static CURLcode pop3_parse_url_path(struct connectdata *conn) -{ - /* The POP3 struct is already initialised in pop3_connect() */ - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct SessionHandle *data = conn->data; - const char *path = data->state.path; - - /* URL decode the path and use this mailbox */ - return Curl_urldecode(data, path, 0, &pop3c->mailbox, NULL, TRUE); -} - -static CURLcode pop3_parse_custom_request(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; - struct SessionHandle *data = conn->data; - const char *custom = conn->data->set.str[STRING_CUSTOMREQUEST]; - - /* URL decode the custom request */ - if(custom) - result = Curl_urldecode(data, custom, 0, &pop3c->custom, NULL, TRUE); - - return result; -} - -/* Call this when the DO phase has completed */ -static CURLcode pop3_dophase_done(struct connectdata *conn, bool connected) -{ - struct FTP *pop3 = conn->data->state.proto.pop3; - - (void)connected; - - if(pop3->transfer != FTPTRANSFER_BODY) - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - - return CURLE_OK; -} - -/* Called from curl_multi.c while DOing */ -static CURLcode pop3_doing(struct connectdata *conn, bool *dophase_done) -{ - CURLcode result = pop3_multi_statemach(conn, dophase_done); - - if(result) - DEBUGF(infof(conn->data, "DO phase failed\n")); - else { - if(*dophase_done) { - result = pop3_dophase_done(conn, FALSE /* not connected */); - - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - } - - return result; -} - -/*********************************************************************** - * - * pop3_regular_transfer() - * - * The input argument is already checked for validity. - * - * Performs all commands done before a regular transfer between a local and a - * remote host. - */ -static CURLcode pop3_regular_transfer(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - bool connected = FALSE; - struct SessionHandle *data = conn->data; - - /* Make sure size is unknown at this point */ - data->req.size = -1; - - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - Curl_pgrsSetUploadSize(data, 0); - Curl_pgrsSetDownloadSize(data, 0); - - result = pop3_perform(conn, &connected, dophase_done); - - if(CURLE_OK == result) { - if(!*dophase_done) - /* The DO phase has not completed yet */ - return CURLE_OK; - - result = pop3_dophase_done(conn, connected); - if(result) - return result; - } - - return result; -} - -static CURLcode pop3_setup_connection(struct connectdata * conn) -{ - struct SessionHandle *data = conn->data; - - if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel pop3 operations through the proxy, we - switch and use HTTP operations only */ -#ifndef CURL_DISABLE_HTTP - if(conn->handler == &Curl_handler_pop3) - conn->handler = &Curl_handler_pop3_proxy; - else { -#ifdef USE_SSL - conn->handler = &Curl_handler_pop3s_proxy; -#else - failf(data, "POP3S not supported!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - /* We explicitly mark this connection as persistent here as we're doing - POP3 over HTTP and thus we accidentally avoid setting this value - otherwise */ - conn->bits.close = FALSE; -#else - failf(data, "POP3 over http proxy requires HTTP support built-in!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - data->state.path++; /* don't include the initial slash */ - - return CURLE_OK; -} - -/* This function scans the body after the end-of-body and writes everything - until the end is found */ -CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) -{ - /* This code could be made into a special function in the handler struct */ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct SingleRequest *k = &data->req; - - struct pop3_conn *pop3c = &conn->proto.pop3c; - bool strip_dot = FALSE; - size_t last = 0; - size_t i; - - /* Search through the buffer looking for the end-of-body marker which is - 5 bytes (0d 0a 2e 0d 0a). Note that a line starting with a dot matches - the eob so the server will have prefixed it with an extra dot which we - need to strip out. Additionally the marker could of course be spread out - over 5 different data chunks */ - for(i = 0; i < nread; i++) { - size_t prev = pop3c->eob; - - switch(str[i]) { - case 0x0d: - if(pop3c->eob == 0) { - pop3c->eob++; - - if(i) { - /* Write out the body part that didn't match */ - result = Curl_client_write(conn, CLIENTWRITE_BODY, &str[last], - i - last); - - if(result) - return result; - - last = i; - } - } - else if(pop3c->eob == 3) - pop3c->eob++; - else - /* If the character match wasn't at position 0 or 3 then restart the - pattern matching */ - pop3c->eob = 1; - break; - - case 0x0a: - if(pop3c->eob == 1 || pop3c->eob == 4) - pop3c->eob++; - else - /* If the character match wasn't at position 1 or 4 then start the - search again */ - pop3c->eob = 0; - break; - - case 0x2e: - if(pop3c->eob == 2) - pop3c->eob++; - else if(pop3c->eob == 3) { - /* We have an extra dot after the CRLF which we need to strip off */ - strip_dot = TRUE; - pop3c->eob = 0; - } - else - /* If the character match wasn't at position 2 then start the search - again */ - pop3c->eob = 0; - break; - - default: - pop3c->eob = 0; - break; - } - - /* Did we have a partial match which has subsequently failed? */ - if(prev && prev >= pop3c->eob) { - /* Strip can only be non-zero for the very first mismatch after CRLF - and then both prev and strip are equal and nothing will be output - below */ - while(prev && pop3c->strip) { - prev--; - pop3c->strip--; - } - - if(prev) { - /* If the partial match was the CRLF and dot then only write the CRLF - as the server would have inserted the dot */ - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char*)POP3_EOB, - strip_dot ? prev - 1 : prev); - - if(result) - return result; - - last = i; - strip_dot = FALSE; - } - } - } - - if(pop3c->eob == POP3_EOB_LEN) { - /* We have a full match so the transfer is done, however we must transfer - the CRLF at the start of the EOB as this is considered to be part of the - message as per RFC-1939, sect. 3 */ - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)POP3_EOB, 2); - - k->keepon &= ~KEEP_RECV; - pop3c->eob = 0; - - return result; - } - - if(pop3c->eob) - /* While EOB is matching nothing should be output */ - return CURLE_OK; - - if(nread - last) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, &str[last], - nread - last); - } - - return result; -} - -#endif /* CURL_DISABLE_POP3 */ diff --git a/lib/progress.c b/lib/progress.c deleted file mode 100644 index 88f802d0a..000000000 --- a/lib/progress.c +++ /dev/null @@ -1,474 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_progress.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* Provide a string that is 2 + 1 + 2 + 1 + 2 = 8 letters long (plus the zero - byte) */ -static void time2str(char *r, curl_off_t seconds) -{ - curl_off_t d, h, m, s; - if(seconds <= 0) { - strcpy(r, "--:--:--"); - return; - } - h = seconds / CURL_OFF_T_C(3600); - if(h <= CURL_OFF_T_C(99)) { - m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); - s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); - snprintf(r, 9, "%2" FORMAT_OFF_T ":%02" FORMAT_OFF_T ":%02" FORMAT_OFF_T, - h, m, s); - } - else { - /* this equals to more than 99 hours, switch to a more suitable output - format to fit within the limits. */ - d = seconds / CURL_OFF_T_C(86400); - h = (seconds - (d*CURL_OFF_T_C(86400))) / CURL_OFF_T_C(3600); - if(d <= CURL_OFF_T_C(999)) - snprintf(r, 9, "%3" FORMAT_OFF_T "d %02" FORMAT_OFF_T "h", d, h); - else - snprintf(r, 9, "%7" FORMAT_OFF_T "d", d); - } -} - -/* The point of this function would be to return a string of the input data, - but never longer than 5 columns (+ one zero byte). - Add suffix k, M, G when suitable... */ -static char *max5data(curl_off_t bytes, char *max5) -{ -#define ONE_KILOBYTE CURL_OFF_T_C(1024) -#define ONE_MEGABYTE (CURL_OFF_T_C(1024) * ONE_KILOBYTE) -#define ONE_GIGABYTE (CURL_OFF_T_C(1024) * ONE_MEGABYTE) -#define ONE_TERABYTE (CURL_OFF_T_C(1024) * ONE_GIGABYTE) -#define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) - - if(bytes < CURL_OFF_T_C(100000)) - snprintf(max5, 6, "%5" FORMAT_OFF_T, bytes); - - else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) - snprintf(max5, 6, "%4" FORMAT_OFF_T "k", bytes/ONE_KILOBYTE); - - else if(bytes < CURL_OFF_T_C(100) * ONE_MEGABYTE) - /* 'XX.XM' is good as long as we're less than 100 megs */ - snprintf(max5, 6, "%2" FORMAT_OFF_T ".%0" FORMAT_OFF_T "M", - bytes/ONE_MEGABYTE, - (bytes%ONE_MEGABYTE) / (ONE_MEGABYTE/CURL_OFF_T_C(10)) ); - -#if (CURL_SIZEOF_CURL_OFF_T > 4) - - else if(bytes < CURL_OFF_T_C(10000) * ONE_MEGABYTE) - /* 'XXXXM' is good until we're at 10000MB or above */ - snprintf(max5, 6, "%4" FORMAT_OFF_T "M", bytes/ONE_MEGABYTE); - - else if(bytes < CURL_OFF_T_C(100) * ONE_GIGABYTE) - /* 10000 MB - 100 GB, we show it as XX.XG */ - snprintf(max5, 6, "%2" FORMAT_OFF_T ".%0" FORMAT_OFF_T "G", - bytes/ONE_GIGABYTE, - (bytes%ONE_GIGABYTE) / (ONE_GIGABYTE/CURL_OFF_T_C(10)) ); - - else if(bytes < CURL_OFF_T_C(10000) * ONE_GIGABYTE) - /* up to 10000GB, display without decimal: XXXXG */ - snprintf(max5, 6, "%4" FORMAT_OFF_T "G", bytes/ONE_GIGABYTE); - - else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) - /* up to 10000TB, display without decimal: XXXXT */ - snprintf(max5, 6, "%4" FORMAT_OFF_T "T", bytes/ONE_TERABYTE); - - else - /* up to 10000PB, display without decimal: XXXXP */ - snprintf(max5, 6, "%4" FORMAT_OFF_T "P", bytes/ONE_PETABYTE); - - /* 16384 petabytes (16 exabytes) is the maximum a 64 bit unsigned number - can hold, but our data type is signed so 8192PB will be the maximum. */ - -#else - - else - snprintf(max5, 6, "%4" FORMAT_OFF_T "M", bytes/ONE_MEGABYTE); - -#endif - - return max5; -} - -/* - - New proposed interface, 9th of February 2000: - - pgrsStartNow() - sets start time - pgrsSetDownloadSize(x) - known expected download size - pgrsSetUploadSize(x) - known expected upload size - pgrsSetDownloadCounter() - amount of data currently downloaded - pgrsSetUploadCounter() - amount of data currently uploaded - pgrsUpdate() - show progress - pgrsDone() - transfer complete - -*/ - -int Curl_pgrsDone(struct connectdata *conn) -{ - int rc; - struct SessionHandle *data = conn->data; - data->progress.lastshow=0; - rc = Curl_pgrsUpdate(conn); /* the final (forced) update */ - if(rc) - return rc; - - if(!(data->progress.flags & PGRS_HIDE) && - !data->progress.callback) - /* only output if we don't use a progress callback and we're not - * hidden */ - fprintf(data->set.err, "\n"); - - data->progress.speeder_c = 0; /* reset the progress meter display */ - return 0; -} - -/* reset all times except redirect, and reset the known transfer sizes */ -void Curl_pgrsResetTimesSizes(struct SessionHandle *data) -{ - data->progress.t_nslookup = 0.0; - data->progress.t_connect = 0.0; - data->progress.t_pretransfer = 0.0; - data->progress.t_starttransfer = 0.0; - - Curl_pgrsSetDownloadSize(data, 0); - Curl_pgrsSetUploadSize(data, 0); -} - -void Curl_pgrsTime(struct SessionHandle *data, timerid timer) -{ - struct timeval now = Curl_tvnow(); - - switch(timer) { - default: - case TIMER_NONE: - /* mistake filter */ - break; - case TIMER_STARTSINGLE: - /* This is set at the start of a single fetch */ - data->progress.t_startsingle = now; - break; - - case TIMER_STARTACCEPT: - data->progress.t_acceptdata = Curl_tvnow(); - break; - - case TIMER_NAMELOOKUP: - data->progress.t_nslookup = - Curl_tvdiff_secs(now, data->progress.t_startsingle); - break; - case TIMER_CONNECT: - data->progress.t_connect = - Curl_tvdiff_secs(now, data->progress.t_startsingle); - break; - case TIMER_APPCONNECT: - data->progress.t_appconnect = - Curl_tvdiff_secs(now, data->progress.t_startsingle); - break; - case TIMER_PRETRANSFER: - data->progress.t_pretransfer = - Curl_tvdiff_secs(now, data->progress.t_startsingle); - break; - case TIMER_STARTTRANSFER: - data->progress.t_starttransfer = - Curl_tvdiff_secs(now, data->progress.t_startsingle); - break; - case TIMER_POSTRANSFER: - /* this is the normal end-of-transfer thing */ - break; - case TIMER_REDIRECT: - data->progress.t_redirect = Curl_tvdiff_secs(now, data->progress.start); - break; - } -} - -void Curl_pgrsStartNow(struct SessionHandle *data) -{ - data->progress.speeder_c = 0; /* reset the progress meter display */ - data->progress.start = Curl_tvnow(); - /* clear all bits except HIDE and HEADERS_OUT */ - data->progress.flags &= PGRS_HIDE|PGRS_HEADERS_OUT; -} - -void Curl_pgrsSetDownloadCounter(struct SessionHandle *data, curl_off_t size) -{ - data->progress.downloaded = size; -} - -void Curl_pgrsSetUploadCounter(struct SessionHandle *data, curl_off_t size) -{ - data->progress.uploaded = size; -} - -void Curl_pgrsSetDownloadSize(struct SessionHandle *data, curl_off_t size) -{ - data->progress.size_dl = size; - if(size >= 0) - data->progress.flags |= PGRS_DL_SIZE_KNOWN; - else - data->progress.flags &= ~PGRS_DL_SIZE_KNOWN; -} - -void Curl_pgrsSetUploadSize(struct SessionHandle *data, curl_off_t size) -{ - data->progress.size_ul = size; - if(size >= 0) - data->progress.flags |= PGRS_UL_SIZE_KNOWN; - else - data->progress.flags &= ~PGRS_UL_SIZE_KNOWN; -} - -/* - * Curl_pgrsUpdate() returns 0 for success or the value returned by the - * progress callback! - */ -int Curl_pgrsUpdate(struct connectdata *conn) -{ - struct timeval now; - int result; - char max5[6][10]; - curl_off_t dlpercen=0; - curl_off_t ulpercen=0; - curl_off_t total_percen=0; - curl_off_t total_transfer; - curl_off_t total_expected_transfer; - curl_off_t timespent; - struct SessionHandle *data = conn->data; - int nowindex = data->progress.speeder_c% CURR_TIME; - int checkindex; - int countindex; /* amount of seconds stored in the speeder array */ - char time_left[10]; - char time_total[10]; - char time_spent[10]; - curl_off_t ulestimate=0; - curl_off_t dlestimate=0; - curl_off_t total_estimate; - bool shownow=FALSE; - - now = Curl_tvnow(); /* what time is it */ - - /* The time spent so far (from the start) */ - data->progress.timespent = - (double)(now.tv_sec - data->progress.start.tv_sec) + - (double)(now.tv_usec - data->progress.start.tv_usec)/1000000.0; - timespent = (curl_off_t)data->progress.timespent; - - /* The average download speed this far */ - data->progress.dlspeed = (curl_off_t) - ((double)data->progress.downloaded/ - (data->progress.timespent>0?data->progress.timespent:1)); - - /* The average upload speed this far */ - data->progress.ulspeed = (curl_off_t) - ((double)data->progress.uploaded/ - (data->progress.timespent>0?data->progress.timespent:1)); - - /* Calculations done at most once a second, unless end is reached */ - if(data->progress.lastshow != (long)now.tv_sec) { - shownow = TRUE; - - data->progress.lastshow = now.tv_sec; - - /* Let's do the "current speed" thing, which should use the fastest - of the dl/ul speeds. Store the faster speed at entry 'nowindex'. */ - data->progress.speeder[ nowindex ] = - data->progress.downloaded>data->progress.uploaded? - data->progress.downloaded:data->progress.uploaded; - - /* remember the exact time for this moment */ - data->progress.speeder_time [ nowindex ] = now; - - /* advance our speeder_c counter, which is increased every time we get - here and we expect it to never wrap as 2^32 is a lot of seconds! */ - data->progress.speeder_c++; - - /* figure out how many index entries of data we have stored in our speeder - array. With N_ENTRIES filled in, we have about N_ENTRIES-1 seconds of - transfer. Imagine, after one second we have filled in two entries, - after two seconds we've filled in three entries etc. */ - countindex = ((data->progress.speeder_c>=CURR_TIME)? - CURR_TIME:data->progress.speeder_c) - 1; - - /* first of all, we don't do this if there's no counted seconds yet */ - if(countindex) { - long span_ms; - - /* Get the index position to compare with the 'nowindex' position. - Get the oldest entry possible. While we have less than CURR_TIME - entries, the first entry will remain the oldest. */ - checkindex = (data->progress.speeder_c>=CURR_TIME)? - data->progress.speeder_c%CURR_TIME:0; - - /* Figure out the exact time for the time span */ - span_ms = Curl_tvdiff(now, - data->progress.speeder_time[checkindex]); - if(0 == span_ms) - span_ms=1; /* at least one millisecond MUST have passed */ - - /* Calculate the average speed the last 'span_ms' milliseconds */ - { - curl_off_t amount = data->progress.speeder[nowindex]- - data->progress.speeder[checkindex]; - - if(amount > CURL_OFF_T_C(4294967) /* 0xffffffff/1000 */) - /* the 'amount' value is bigger than would fit in 32 bits if - multiplied with 1000, so we use the double math for this */ - data->progress.current_speed = (curl_off_t) - ((double)amount/((double)span_ms/1000.0)); - else - /* the 'amount' value is small enough to fit within 32 bits even - when multiplied with 1000 */ - data->progress.current_speed = amount*CURL_OFF_T_C(1000)/span_ms; - } - } - else - /* the first second we use the main average */ - data->progress.current_speed = - (data->progress.ulspeed>data->progress.dlspeed)? - data->progress.ulspeed:data->progress.dlspeed; - - } /* Calculations end */ - - if(!(data->progress.flags & PGRS_HIDE)) { - - /* progress meter has not been shut off */ - - if(data->set.fprogress) { - /* There's a callback set, so we call that instead of writing - anything ourselves. This really is the way to go. */ - result= data->set.fprogress(data->set.progress_client, - (double)data->progress.size_dl, - (double)data->progress.downloaded, - (double)data->progress.size_ul, - (double)data->progress.uploaded); - if(result) - failf(data, "Callback aborted"); - return result; - } - - if(!shownow) - /* only show the internal progress meter once per second */ - return 0; - - /* If there's no external callback set, use internal code to show - progress */ - - if(!(data->progress.flags & PGRS_HEADERS_OUT)) { - if(data->state.resume_from) { - fprintf(data->set.err, - "** Resuming transfer from byte position %" FORMAT_OFF_T "\n", - data->state.resume_from); - } - fprintf(data->set.err, - " %% Total %% Received %% Xferd Average Speed " - "Time Time Time Current\n" - " Dload Upload " - "Total Spent Left Speed\n"); - data->progress.flags |= PGRS_HEADERS_OUT; /* headers are shown */ - } - - /* Figure out the estimated time of arrival for the upload */ - if((data->progress.flags & PGRS_UL_SIZE_KNOWN) && - (data->progress.ulspeed > CURL_OFF_T_C(0))) { - ulestimate = data->progress.size_ul / data->progress.ulspeed; - - if(data->progress.size_ul > CURL_OFF_T_C(10000)) - ulpercen = data->progress.uploaded / - (data->progress.size_ul/CURL_OFF_T_C(100)); - else if(data->progress.size_ul > CURL_OFF_T_C(0)) - ulpercen = (data->progress.uploaded*100) / - data->progress.size_ul; - } - - /* ... and the download */ - if((data->progress.flags & PGRS_DL_SIZE_KNOWN) && - (data->progress.dlspeed > CURL_OFF_T_C(0))) { - dlestimate = data->progress.size_dl / data->progress.dlspeed; - - if(data->progress.size_dl > CURL_OFF_T_C(10000)) - dlpercen = data->progress.downloaded / - (data->progress.size_dl/CURL_OFF_T_C(100)); - else if(data->progress.size_dl > CURL_OFF_T_C(0)) - dlpercen = (data->progress.downloaded*100) / - data->progress.size_dl; - } - - /* Now figure out which of them is slower and use that one for the - total estimate! */ - total_estimate = ulestimate>dlestimate?ulestimate:dlestimate; - - /* create the three time strings */ - time2str(time_left, total_estimate > 0?(total_estimate - timespent):0); - time2str(time_total, total_estimate); - time2str(time_spent, timespent); - - /* Get the total amount of data expected to get transferred */ - total_expected_transfer = - (data->progress.flags & PGRS_UL_SIZE_KNOWN? - data->progress.size_ul:data->progress.uploaded)+ - (data->progress.flags & PGRS_DL_SIZE_KNOWN? - data->progress.size_dl:data->progress.downloaded); - - /* We have transferred this much so far */ - total_transfer = data->progress.downloaded + data->progress.uploaded; - - /* Get the percentage of data transferred so far */ - if(total_expected_transfer > CURL_OFF_T_C(10000)) - total_percen = total_transfer / - (total_expected_transfer/CURL_OFF_T_C(100)); - else if(total_expected_transfer > CURL_OFF_T_C(0)) - total_percen = (total_transfer*100) / total_expected_transfer; - - fprintf(data->set.err, - "\r" - "%3" FORMAT_OFF_T " %s " - "%3" FORMAT_OFF_T " %s " - "%3" FORMAT_OFF_T " %s %s %s %s %s %s %s", - total_percen, /* 3 letters */ /* total % */ - max5data(total_expected_transfer, max5[2]), /* total size */ - dlpercen, /* 3 letters */ /* rcvd % */ - max5data(data->progress.downloaded, max5[0]), /* rcvd size */ - ulpercen, /* 3 letters */ /* xfer % */ - max5data(data->progress.uploaded, max5[1]), /* xfer size */ - max5data(data->progress.dlspeed, max5[3]), /* avrg dl speed */ - max5data(data->progress.ulspeed, max5[4]), /* avrg ul speed */ - time_total, /* 8 letters */ /* total time */ - time_spent, /* 8 letters */ /* time spent */ - time_left, /* 8 letters */ /* time left */ - max5data(data->progress.current_speed, max5[5]) /* current speed */ - ); - - /* we flush the output stream to make it appear as soon as possible */ - fflush(data->set.err); - - } /* !(data->progress.flags & PGRS_HIDE) */ - - return 0; -} diff --git a/lib/qssl.c b/lib/qssl.c deleted file mode 100644 index d140dc9eb..000000000 --- a/lib/qssl.c +++ /dev/null @@ -1,501 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef USE_QSOSSL - -#include - -#ifdef HAVE_LIMITS_H -# include -#endif - -#include -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_qssl.h" -#include "curl_sslgen.h" -#include "curl_connect.h" /* for the connect timeout */ -#include "curl_select.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - - -int Curl_qsossl_init(void) - -{ - /* Nothing to do here. We must have connection data to initialize ssl, so - * defer. - */ - - return 1; -} - - -void Curl_qsossl_cleanup(void) - -{ - /* Nothing to do. */ -} - - -static CURLcode Curl_qsossl_init_session(struct SessionHandle * data) - -{ - int rc; - char * certname; - SSLInit initstr; - SSLInitApp initappstr; - - /* Initialize the job for SSL according to the current parameters. - * QsoSSL offers two ways to do it: SSL_Init_Application() that uses an - * application identifier to select certificates in the main certificate - * store, and SSL_Init() that uses named keyring files and a password. - * It is not possible to have different keyrings for the CAs and the - * local certificate. We thus use the certificate name to identify the - * keyring if given, else the CA file name. - * If the key file name is given, it is taken as the password for the - * keyring in certificate file. - * We first try to SSL_Init_Application(), then SSL_Init() if it failed. - */ - - certname = data->set.str[STRING_CERT]; - - if(!certname) { - certname = data->set.str[STRING_SSL_CAFILE]; - - if(!certname) - return CURLE_OK; /* Use previous setup. */ - } - - memset((char *) &initappstr, 0, sizeof initappstr); - initappstr.applicationID = certname; - initappstr.applicationIDLen = strlen(certname); - initappstr.protocol = SSL_VERSION_CURRENT; /* TLSV1 compat. SSLV[23]. */ - initappstr.sessionType = SSL_REGISTERED_AS_CLIENT; - rc = SSL_Init_Application(&initappstr); - - if(rc == SSL_ERROR_NOT_REGISTERED) { - initstr.keyringFileName = certname; - initstr.keyringPassword = data->set.str[STRING_KEY]; - initstr.cipherSuiteList = NULL; /* Use default. */ - initstr.cipherSuiteListLen = 0; - rc = SSL_Init(&initstr); - } - - switch (rc) { - - case 0: /* No error. */ - break; - - case SSL_ERROR_IO: - failf(data, "SSL_Init() I/O error: %s", strerror(errno)); - return CURLE_SSL_CONNECT_ERROR; - - case SSL_ERROR_BAD_CIPHER_SUITE: - return CURLE_SSL_CIPHER; - - case SSL_ERROR_KEYPASSWORD_EXPIRED: - case SSL_ERROR_NOT_REGISTERED: - return CURLE_SSL_CONNECT_ERROR; - - case SSL_ERROR_NO_KEYRING: - return CURLE_SSL_CACERT; - - case SSL_ERROR_CERT_EXPIRED: - return CURLE_SSL_CERTPROBLEM; - - default: - failf(data, "SSL_Init(): %s", SSL_Strerror(rc, NULL)); - return CURLE_SSL_CONNECT_ERROR; - } - - return CURLE_OK; -} - - -static CURLcode Curl_qsossl_create(struct connectdata * conn, int sockindex) - -{ - SSLHandle * h; - struct ssl_connect_data * connssl = &conn->ssl[sockindex]; - - h = SSL_Create(conn->sock[sockindex], SSL_ENCRYPT); - - if(!h) { - failf(conn->data, "SSL_Create() I/O error: %s", strerror(errno)); - return CURLE_SSL_CONNECT_ERROR; - } - - connssl->handle = h; - return CURLE_OK; -} - - -static int Curl_qsossl_trap_cert(SSLHandle * h) - -{ - return 1; /* Accept certificate. */ -} - - -static CURLcode Curl_qsossl_handshake(struct connectdata * conn, int sockindex) - -{ - int rc; - struct SessionHandle * data = conn->data; - struct ssl_connect_data * connssl = &conn->ssl[sockindex]; - SSLHandle * h = connssl->handle; - long timeout_ms; - - h->exitPgm = NULL; - - if(!data->set.ssl.verifyhost) - h->exitPgm = Curl_qsossl_trap_cert; - - /* figure out how long time we should wait at maximum */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* SSL_Handshake() timeout resolution is second, so round up. */ - h->timeout = (timeout_ms + 1000 - 1) / 1000; - - /* Set-up protocol. */ - - switch (data->set.ssl.version) { - - default: - case CURL_SSLVERSION_DEFAULT: - h->protocol = SSL_VERSION_CURRENT; /* TLSV1 compat. SSLV[23]. */ - break; - - case CURL_SSLVERSION_TLSv1: - h->protocol = TLS_VERSION_1; - break; - - case CURL_SSLVERSION_SSLv2: - h->protocol = SSL_VERSION_2; - break; - - case CURL_SSLVERSION_SSLv3: - h->protocol = SSL_VERSION_3; - break; - } - - rc = SSL_Handshake(h, SSL_HANDSHAKE_AS_CLIENT); - - switch (rc) { - - case 0: /* No error. */ - break; - - case SSL_ERROR_BAD_CERTIFICATE: - case SSL_ERROR_BAD_CERT_SIG: - case SSL_ERROR_NOT_TRUSTED_ROOT: - return CURLE_PEER_FAILED_VERIFICATION; - - case SSL_ERROR_BAD_CIPHER_SUITE: - case SSL_ERROR_NO_CIPHERS: - return CURLE_SSL_CIPHER; - - case SSL_ERROR_CERTIFICATE_REJECTED: - case SSL_ERROR_CERT_EXPIRED: - case SSL_ERROR_NO_CERTIFICATE: - return CURLE_SSL_CERTPROBLEM; - - case SSL_ERROR_IO: - failf(data, "SSL_Handshake() I/O error: %s", strerror(errno)); - return CURLE_SSL_CONNECT_ERROR; - - default: - failf(data, "SSL_Handshake(): %s", SSL_Strerror(rc, NULL)); - return CURLE_SSL_CONNECT_ERROR; - } - - return CURLE_OK; -} - - -static Curl_recv qsossl_recv; -static Curl_send qsossl_send; - -CURLcode Curl_qsossl_connect(struct connectdata * conn, int sockindex) - -{ - struct SessionHandle * data = conn->data; - struct ssl_connect_data * connssl = &conn->ssl[sockindex]; - int rc; - - rc = Curl_qsossl_init_session(data); - - if(rc == CURLE_OK) { - rc = Curl_qsossl_create(conn, sockindex); - - if(rc == CURLE_OK) - rc = Curl_qsossl_handshake(conn, sockindex); - else { - SSL_Destroy(connssl->handle); - connssl->handle = NULL; - connssl->use = FALSE; - connssl->state = ssl_connection_none; - } - } - if(rc == CURLE_OK) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = qsossl_recv; - conn->send[sockindex] = qsossl_send; - } - - return rc; -} - - -static int Curl_qsossl_close_one(struct ssl_connect_data * conn, - struct SessionHandle * data) - -{ - int rc; - - if(!conn->handle) - return 0; - - rc = SSL_Destroy(conn->handle); - - if(rc) { - if(rc == SSL_ERROR_IO) { - failf(data, "SSL_Destroy() I/O error: %s", strerror(errno)); - return -1; - } - - /* An SSL error. */ - failf(data, "SSL_Destroy() returned error %s", SSL_Strerror(rc, NULL)); - return -1; - } - - conn->handle = NULL; - return 0; -} - - -void Curl_qsossl_close(struct connectdata *conn, int sockindex) - -{ - struct SessionHandle *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - if(connssl->use) - (void) Curl_qsossl_close_one(connssl, data); -} - - -int Curl_qsossl_close_all(struct SessionHandle * data) - -{ - /* Unimplemented. */ - (void) data; - return 0; -} - - -int Curl_qsossl_shutdown(struct connectdata * conn, int sockindex) - -{ - struct ssl_connect_data * connssl = &conn->ssl[sockindex]; - struct SessionHandle *data = conn->data; - ssize_t nread; - int what; - int rc; - char buf[120]; - - if(!connssl->handle) - return 0; - - if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) - return 0; - - if(Curl_qsossl_close_one(connssl, data)) - return -1; - - rc = 0; - - what = Curl_socket_ready(conn->sock[sockindex], - CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); - - for(;;) { - if(what < 0) { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - rc = -1; - break; - } - - if(!what) { /* timeout */ - failf(data, "SSL shutdown timeout"); - break; - } - - /* Something to read, let's do it and hope that it is the close - notify alert from the server. No way to SSL_Read now, so use read(). */ - - nread = read(conn->sock[sockindex], buf, sizeof(buf)); - - if(nread < 0) { - failf(data, "read: %s", strerror(errno)); - rc = -1; - } - - if(nread <= 0) - break; - - what = Curl_socket_ready(conn->sock[sockindex], CURL_SOCKET_BAD, 0); - } - - return rc; -} - - -static ssize_t qsossl_send(struct connectdata * conn, int sockindex, - const void * mem, size_t len, CURLcode * curlcode) - -{ - /* SSL_Write() is said to return 'int' while write() and send() returns - 'size_t' */ - int rc; - - rc = SSL_Write(conn->ssl[sockindex].handle, (void *) mem, (int) len); - - if(rc < 0) { - switch(rc) { - - case SSL_ERROR_BAD_STATE: - /* The operation did not complete; the same SSL I/O function - should be called again later. This is basically an EWOULDBLOCK - equivalent. */ - *curlcode = CURLE_AGAIN; - return -1; - - case SSL_ERROR_IO: - switch (errno) { - case EWOULDBLOCK: - case EINTR: - *curlcode = CURLE_AGAIN; - return -1; - } - - failf(conn->data, "SSL_Write() I/O error: %s", strerror(errno)); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - /* An SSL error. */ - failf(conn->data, "SSL_Write() returned error %s", - SSL_Strerror(rc, NULL)); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - - return (ssize_t) rc; /* number of bytes */ -} - - -static ssize_t qsossl_recv(struct connectdata * conn, int num, char * buf, - size_t buffersize, CURLcode * curlcode) - -{ - char error_buffer[120]; /* OpenSSL documents that this must be at - least 120 bytes long. */ - unsigned long sslerror; - int buffsize; - int nread; - - buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - nread = SSL_Read(conn->ssl[num].handle, buf, buffsize); - - if(nread < 0) { - /* failed SSL_read */ - - switch (nread) { - - case SSL_ERROR_BAD_STATE: - /* there's data pending, re-invoke SSL_Read(). */ - *curlcode = CURLE_AGAIN; - return -1; - - case SSL_ERROR_IO: - switch (errno) { - case EWOULDBLOCK: - *curlcode = CURLE_AGAIN; - return -1; - } - - failf(conn->data, "SSL_Read() I/O error: %s", strerror(errno)); - *curlcode = CURLE_RECV_ERROR; - return -1; - - default: - failf(conn->data, "SSL read error: %s", SSL_Strerror(nread, NULL)); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - } - return (ssize_t) nread; -} - - -size_t Curl_qsossl_version(char * buffer, size_t size) - -{ - strncpy(buffer, "IBM OS/400 SSL", size); - return strlen(buffer); -} - - -int Curl_qsossl_check_cxn(struct connectdata * cxn) - -{ - int err; - int errlen; - - /* The only thing that can be tested here is at the socket level. */ - - if(!cxn->ssl[FIRSTSOCKET].handle) - return 0; /* connection has been closed */ - - err = 0; - errlen = sizeof err; - - if(getsockopt(cxn->sock[FIRSTSOCKET], SOL_SOCKET, SO_ERROR, - (unsigned char *) &err, &errlen) || - errlen != sizeof err || err) - return 0; /* connection has been closed */ - - return -1; /* connection status unknown */ -} - -#endif /* USE_QSOSSL */ diff --git a/lib/rawstr.c b/lib/rawstr.c deleted file mode 100644 index 17fd1f3f5..000000000 --- a/lib/rawstr.c +++ /dev/null @@ -1,142 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_rawstr.h" - -/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because - its behavior is altered by the current locale. */ -char Curl_raw_toupper(char in) -{ - switch (in) { - case 'a': - return 'A'; - case 'b': - return 'B'; - case 'c': - return 'C'; - case 'd': - return 'D'; - case 'e': - return 'E'; - case 'f': - return 'F'; - case 'g': - return 'G'; - case 'h': - return 'H'; - case 'i': - return 'I'; - case 'j': - return 'J'; - case 'k': - return 'K'; - case 'l': - return 'L'; - case 'm': - return 'M'; - case 'n': - return 'N'; - case 'o': - return 'O'; - case 'p': - return 'P'; - case 'q': - return 'Q'; - case 'r': - return 'R'; - case 's': - return 'S'; - case 't': - return 'T'; - case 'u': - return 'U'; - case 'v': - return 'V'; - case 'w': - return 'W'; - case 'x': - return 'X'; - case 'y': - return 'Y'; - case 'z': - return 'Z'; - } - return in; -} - -/* - * Curl_raw_equal() is for doing "raw" case insensitive strings. This is meant - * to be locale independent and only compare strings we know are safe for - * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for - * some further explanation to why this function is necessary. - * - * The function is capable of comparing a-z case insensitively even for - * non-ascii. - */ - -int Curl_raw_equal(const char *first, const char *second) -{ - while(*first && *second) { - if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) - /* get out of the loop as soon as they don't match */ - break; - first++; - second++; - } - /* we do the comparison here (possibly again), just to make sure that if the - loop above is skipped because one of the strings reached zero, we must not - return this as a successful match */ - return (Curl_raw_toupper(*first) == Curl_raw_toupper(*second)); -} - -int Curl_raw_nequal(const char *first, const char *second, size_t max) -{ - while(*first && *second && max) { - if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) { - break; - } - max--; - first++; - second++; - } - if(0 == max) - return 1; /* they are equal this far */ - - return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); -} - -/* Copy an upper case version of the string from src to dest. The - * strings may overlap. No more than n characters of the string are copied - * (including any NUL) and the destination string will NOT be - * NUL-terminated if that limit is reached. - */ -void Curl_strntoupper(char *dest, const char *src, size_t n) -{ - if(n < 1) - return; - - do { - *dest++ = Curl_raw_toupper(*src); - } while(*src++ && --n); -} diff --git a/lib/rtsp.c b/lib/rtsp.c deleted file mode 100644 index 71e434c47..000000000 --- a/lib/rtsp.c +++ /dev/null @@ -1,807 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_RTSP - -#include "curl_urldata.h" -#include -#include "curl_transfer.h" -#include "curl_sendf.h" -#include "curl_multiif.h" -#include "curl_http.h" -#include "curl_url.h" -#include "curl_progress.h" -#include "curl_rtsp.h" -#include "curl_rawstr.h" -#include "curl_memory.h" -#include "curl_select.h" -#include "curl_connect.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - * TODO (general) - * -incoming server requests - * -server CSeq counter - * -digest authentication - * -connect thru proxy - * -pipelining? - */ - - -#define RTP_PKT_CHANNEL(p) ((int)((unsigned char)((p)[1]))) - -#define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \ - ((int)((unsigned char)((p)[3])))) - -/* protocol-specific functions set up to be called by the main engine */ -static CURLcode rtsp_do(struct connectdata *conn, bool *done); -static CURLcode rtsp_done(struct connectdata *conn, CURLcode, bool premature); -static CURLcode rtsp_connect(struct connectdata *conn, bool *done); -static CURLcode rtsp_disconnect(struct connectdata *conn, bool dead); - -static int rtsp_getsock_do(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); - -/* - * Parse and write out any available RTP data. - * - * nread: amount of data left after k->str. will be modified if RTP - * data is parsed and k->str is moved up - * readmore: whether or not the RTP parser needs more data right away - */ -static CURLcode rtsp_rtp_readwrite(struct SessionHandle *data, - struct connectdata *conn, - ssize_t *nread, - bool *readmore); - - -/* this returns the socket to wait for in the DO and DOING state for the multi - interface and then we're always _sending_ a request and thus we wait for - the single socket to become writable only */ -static int rtsp_getsock_do(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - /* write mode */ - (void)numsocks; /* unused, we trust it to be at least 1 */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_WRITESOCK(0); -} - -static -CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len); - - -/* - * RTSP handler interface. - */ -const struct Curl_handler Curl_handler_rtsp = { - "RTSP", /* scheme */ - ZERO_NULL, /* setup_connection */ - rtsp_do, /* do_it */ - rtsp_done, /* done */ - ZERO_NULL, /* do_more */ - rtsp_connect, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - rtsp_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - rtsp_disconnect, /* disconnect */ - rtsp_rtp_readwrite, /* readwrite */ - PORT_RTSP, /* defport */ - CURLPROTO_RTSP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -/* - * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not - * want to block the application forever while receiving a stream. Therefore, - * we cannot assume that an RTSP socket is dead just because it is readable. - * - * Instead, if it is readable, run Curl_getconnectinfo() to peek at the socket - * and distinguish between closed and data. - */ -bool Curl_rtsp_connisdead(struct connectdata *check) -{ - int sval; - bool ret_val = TRUE; - - sval = Curl_socket_ready(check->sock[FIRSTSOCKET], CURL_SOCKET_BAD, 0); - if(sval == 0) { - /* timeout */ - ret_val = FALSE; - } - else if(sval & CURL_CSELECT_ERR) { - /* socket is in an error state */ - ret_val = TRUE; - } - else if((sval & CURL_CSELECT_IN) && check->data) { - /* readable with no error. could be closed or could be alive but we can - only check if we have a proper SessionHandle for the connection */ - curl_socket_t connectinfo = Curl_getconnectinfo(check->data, &check); - if(connectinfo != CURL_SOCKET_BAD) - ret_val = FALSE; - } - - return ret_val; -} - -static CURLcode rtsp_connect(struct connectdata *conn, bool *done) -{ - CURLcode httpStatus; - struct SessionHandle *data = conn->data; - - httpStatus = Curl_http_connect(conn, done); - - /* Initialize the CSeq if not already done */ - if(data->state.rtsp_next_client_CSeq == 0) - data->state.rtsp_next_client_CSeq = 1; - if(data->state.rtsp_next_server_CSeq == 0) - data->state.rtsp_next_server_CSeq = 1; - - conn->proto.rtspc.rtp_channel = -1; - - return httpStatus; -} - -static CURLcode rtsp_disconnect(struct connectdata *conn, bool dead) -{ - (void) dead; - Curl_safefree(conn->proto.rtspc.rtp_buf); - return CURLE_OK; -} - - -static CURLcode rtsp_done(struct connectdata *conn, - CURLcode status, bool premature) -{ - struct SessionHandle *data = conn->data; - struct RTSP *rtsp = data->state.proto.rtsp; - CURLcode httpStatus; - long CSeq_sent; - long CSeq_recv; - - /* Bypass HTTP empty-reply checks on receive */ - if(data->set.rtspreq == RTSPREQ_RECEIVE) - premature = TRUE; - - httpStatus = Curl_http_done(conn, status, premature); - - if(rtsp) { - /* Check the sequence numbers */ - CSeq_sent = rtsp->CSeq_sent; - CSeq_recv = rtsp->CSeq_recv; - if((data->set.rtspreq != RTSPREQ_RECEIVE) && (CSeq_sent != CSeq_recv)) { - failf(data, - "The CSeq of this request %ld did not match the response %ld", - CSeq_sent, CSeq_recv); - return CURLE_RTSP_CSEQ_ERROR; - } - else if(data->set.rtspreq == RTSPREQ_RECEIVE && - (conn->proto.rtspc.rtp_channel == -1)) { - infof(data, "Got an RTP Receive with a CSeq of %ld\n", CSeq_recv); - /* TODO CPC: Server -> Client logic here */ - } - } - - return httpStatus; -} - -static CURLcode rtsp_do(struct connectdata *conn, bool *done) -{ - struct SessionHandle *data = conn->data; - CURLcode result=CURLE_OK; - Curl_RtspReq rtspreq = data->set.rtspreq; - struct RTSP *rtsp; - struct HTTP *http; - Curl_send_buffer *req_buffer; - curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ - curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ - - const char *p_request = NULL; - const char *p_session_id = NULL; - const char *p_accept = NULL; - const char *p_accept_encoding = NULL; - const char *p_range = NULL; - const char *p_referrer = NULL; - const char *p_stream_uri = NULL; - const char *p_transport = NULL; - const char *p_uagent = NULL; - - *done = TRUE; - - Curl_reset_reqproto(conn); - - if(!data->state.proto.rtsp) { - /* Only allocate this struct if we don't already have it! */ - - rtsp = calloc(1, sizeof(struct RTSP)); - if(!rtsp) - return CURLE_OUT_OF_MEMORY; - data->state.proto.rtsp = rtsp; - } - else { - rtsp = data->state.proto.rtsp; - } - - http = &(rtsp->http_wrapper); - /* Assert that no one has changed the RTSP struct in an evil way */ - DEBUGASSERT((void *)http == (void *)rtsp); - - rtsp->CSeq_sent = data->state.rtsp_next_client_CSeq; - rtsp->CSeq_recv = 0; - - /* Setup the 'p_request' pointer to the proper p_request string - * Since all RTSP requests are included here, there is no need to - * support custom requests like HTTP. - **/ - DEBUGASSERT((rtspreq > RTSPREQ_NONE && rtspreq < RTSPREQ_LAST)); - data->set.opt_no_body = TRUE; /* most requests don't contain a body */ - switch(rtspreq) { - case RTSPREQ_NONE: - failf(data, "Got invalid RTSP request: RTSPREQ_NONE"); - return CURLE_BAD_FUNCTION_ARGUMENT; - case RTSPREQ_OPTIONS: - p_request = "OPTIONS"; - break; - case RTSPREQ_DESCRIBE: - p_request = "DESCRIBE"; - data->set.opt_no_body = FALSE; - break; - case RTSPREQ_ANNOUNCE: - p_request = "ANNOUNCE"; - break; - case RTSPREQ_SETUP: - p_request = "SETUP"; - break; - case RTSPREQ_PLAY: - p_request = "PLAY"; - break; - case RTSPREQ_PAUSE: - p_request = "PAUSE"; - break; - case RTSPREQ_TEARDOWN: - p_request = "TEARDOWN"; - break; - case RTSPREQ_GET_PARAMETER: - /* GET_PARAMETER's no_body status is determined later */ - p_request = "GET_PARAMETER"; - data->set.opt_no_body = FALSE; - break; - case RTSPREQ_SET_PARAMETER: - p_request = "SET_PARAMETER"; - break; - case RTSPREQ_RECORD: - p_request = "RECORD"; - break; - case RTSPREQ_RECEIVE: - p_request = ""; - /* Treat interleaved RTP as body*/ - data->set.opt_no_body = FALSE; - break; - case RTSPREQ_LAST: - failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - if(rtspreq == RTSPREQ_RECEIVE) { - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, -1, NULL); - - return result; - } - - p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; - if(!p_session_id && - (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { - failf(data, "Refusing to issue an RTSP request [%s] without a session ID.", - p_request ? p_request : ""); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - /* TODO: auth? */ - /* TODO: proxy? */ - - /* Stream URI. Default to server '*' if not specified */ - if(data->set.str[STRING_RTSP_STREAM_URI]) { - p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; - } - else { - p_stream_uri = "*"; - } - - /* Transport Header for SETUP requests */ - p_transport = Curl_checkheaders(data, "Transport:"); - if(rtspreq == RTSPREQ_SETUP && !p_transport) { - /* New Transport: setting? */ - if(data->set.str[STRING_RTSP_TRANSPORT]) { - Curl_safefree(conn->allocptr.rtsp_transport); - - conn->allocptr.rtsp_transport = - aprintf("Transport: %s\r\n", - data->set.str[STRING_RTSP_TRANSPORT]); - if(!conn->allocptr.rtsp_transport) - return CURLE_OUT_OF_MEMORY; - } - else { - failf(data, - "Refusing to issue an RTSP SETUP without a Transport: header."); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - p_transport = conn->allocptr.rtsp_transport; - } - - /* Accept Headers for DESCRIBE requests */ - if(rtspreq == RTSPREQ_DESCRIBE) { - /* Accept Header */ - p_accept = Curl_checkheaders(data, "Accept:")? - NULL:"Accept: application/sdp\r\n"; - - /* Accept-Encoding header */ - if(!Curl_checkheaders(data, "Accept-Encoding:") && - data->set.str[STRING_ENCODING]) { - Curl_safefree(conn->allocptr.accept_encoding); - conn->allocptr.accept_encoding = - aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); - - if(!conn->allocptr.accept_encoding) - return CURLE_OUT_OF_MEMORY; - - p_accept_encoding = conn->allocptr.accept_encoding; - } - } - - /* The User-Agent string might have been allocated in curl_url.c already, - because it might have been used in the proxy connect, but if we have - got a header with the user-agent string specified, we erase the - previously made string here. */ - if(Curl_checkheaders(data, "User-Agent:") && conn->allocptr.uagent) { - Curl_safefree(conn->allocptr.uagent); - conn->allocptr.uagent = NULL; - } - else if(!Curl_checkheaders(data, "User-Agent:") && - data->set.str[STRING_USERAGENT]) { - p_uagent = conn->allocptr.uagent; - } - - /* Referrer */ - Curl_safefree(conn->allocptr.ref); - if(data->change.referer && !Curl_checkheaders(data, "Referer:")) - conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); - else - conn->allocptr.ref = NULL; - - p_referrer = conn->allocptr.ref; - - /* - * Range Header - * Only applies to PLAY, PAUSE, RECORD - * - * Go ahead and use the Range stuff supplied for HTTP - */ - if(data->state.use_range && - (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { - - /* Check to see if there is a range set in the custom headers */ - if(!Curl_checkheaders(data, "Range:") && data->state.range) { - Curl_safefree(conn->allocptr.rangeline); - conn->allocptr.rangeline = aprintf("Range: %s\r\n", data->state.range); - p_range = conn->allocptr.rangeline; - } - } - - /* - * Sanity check the custom headers - */ - if(Curl_checkheaders(data, "CSeq:")) { - failf(data, "CSeq cannot be set as a custom header."); - return CURLE_RTSP_CSEQ_ERROR; - } - if(Curl_checkheaders(data, "Session:")) { - failf(data, "Session ID cannot be set as a custom header."); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - /* Initialize a dynamic send buffer */ - req_buffer = Curl_add_buffer_init(); - - if(!req_buffer) - return CURLE_OUT_OF_MEMORY; - - result = - Curl_add_bufferf(req_buffer, - "%s %s RTSP/1.0\r\n" /* Request Stream-URI RTSP/1.0 */ - "CSeq: %ld\r\n", /* CSeq */ - (p_request ? p_request : ""), p_stream_uri, - rtsp->CSeq_sent); - if(result) - return result; - - /* - * Rather than do a normal alloc line, keep the session_id unformatted - * to make comparison easier - */ - if(p_session_id) { - result = Curl_add_bufferf(req_buffer, "Session: %s\r\n", p_session_id); - if(result) - return result; - } - - /* - * Shared HTTP-like options - */ - result = Curl_add_bufferf(req_buffer, - "%s" /* transport */ - "%s" /* accept */ - "%s" /* accept-encoding */ - "%s" /* range */ - "%s" /* referrer */ - "%s" /* user-agent */ - , - p_transport ? p_transport : "", - p_accept ? p_accept : "", - p_accept_encoding ? p_accept_encoding : "", - p_range ? p_range : "", - p_referrer ? p_referrer : "", - p_uagent ? p_uagent : ""); - if(result) - return result; - - if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { - result = Curl_add_timecondition(data, req_buffer); - if(result) - return result; - } - - result = Curl_add_custom_headers(conn, req_buffer); - if(result) - return result; - - if(rtspreq == RTSPREQ_ANNOUNCE || - rtspreq == RTSPREQ_SET_PARAMETER || - rtspreq == RTSPREQ_GET_PARAMETER) { - - if(data->set.upload) { - putsize = data->set.infilesize; - data->set.httpreq = HTTPREQ_PUT; - - } - else { - postsize = (data->set.postfieldsize != -1)? - data->set.postfieldsize: - (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); - data->set.httpreq = HTTPREQ_POST; - } - - if(putsize > 0 || postsize > 0) { - /* As stated in the http comments, it is probably not wise to - * actually set a custom Content-Length in the headers */ - if(!Curl_checkheaders(data, "Content-Length:")) { - result = Curl_add_bufferf(req_buffer, - "Content-Length: %" FORMAT_OFF_T"\r\n", - (data->set.upload ? putsize : postsize)); - if(result) - return result; - } - - if(rtspreq == RTSPREQ_SET_PARAMETER || - rtspreq == RTSPREQ_GET_PARAMETER) { - if(!Curl_checkheaders(data, "Content-Type:")) { - result = Curl_add_bufferf(req_buffer, - "Content-Type: text/parameters\r\n"); - if(result) - return result; - } - } - - if(rtspreq == RTSPREQ_ANNOUNCE) { - if(!Curl_checkheaders(data, "Content-Type:")) { - result = Curl_add_bufferf(req_buffer, - "Content-Type: application/sdp\r\n"); - if(result) - return result; - } - } - - data->state.expect100header = FALSE; /* RTSP posts are simple/small */ - } - else if(rtspreq == RTSPREQ_GET_PARAMETER) { - /* Check for an empty GET_PARAMETER (heartbeat) request */ - data->set.httpreq = HTTPREQ_HEAD; - data->set.opt_no_body = TRUE; - } - } - - /* RTSP never allows chunked transfer */ - data->req.forbidchunk = TRUE; - /* Finish the request buffer */ - result = Curl_add_buffer(req_buffer, "\r\n", 2); - if(result) - return result; - - if(postsize > 0) { - result = Curl_add_buffer(req_buffer, data->set.postfields, - (size_t)postsize); - if(result) - return result; - } - - /* issue the request */ - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - if(result) { - failf(data, "Failed sending RTSP request"); - return result; - } - - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, - putsize?FIRSTSOCKET:-1, - putsize?&http->writebytecount:NULL); - - /* Increment the CSeq on success */ - data->state.rtsp_next_client_CSeq++; - - if(http->writebytecount) { - /* if a request-body has been sent off, we make sure this progress is - noted properly */ - Curl_pgrsSetUploadCounter(data, http->writebytecount); - if(Curl_pgrsUpdate(conn)) - result = CURLE_ABORTED_BY_CALLBACK; - } - - return result; -} - - -static CURLcode rtsp_rtp_readwrite(struct SessionHandle *data, - struct connectdata *conn, - ssize_t *nread, - bool *readmore) { - struct SingleRequest *k = &data->req; - struct rtsp_conn *rtspc = &(conn->proto.rtspc); - - char *rtp; /* moving pointer to rtp data */ - ssize_t rtp_dataleft; /* how much data left to parse in this round */ - char *scratch; - CURLcode result; - - if(rtspc->rtp_buf) { - /* There was some leftover data the last time. Merge buffers */ - char *newptr = realloc(rtspc->rtp_buf, rtspc->rtp_bufsize + *nread); - if(!newptr) { - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; - return CURLE_OUT_OF_MEMORY; - } - rtspc->rtp_buf = newptr; - memcpy(rtspc->rtp_buf + rtspc->rtp_bufsize, k->str, *nread); - rtspc->rtp_bufsize += *nread; - rtp = rtspc->rtp_buf; - rtp_dataleft = rtspc->rtp_bufsize; - } - else { - /* Just parse the request buffer directly */ - rtp = k->str; - rtp_dataleft = *nread; - } - - while((rtp_dataleft > 0) && - (rtp[0] == '$')) { - if(rtp_dataleft > 4) { - int rtp_length; - - /* Parse the header */ - /* The channel identifier immediately follows and is 1 byte */ - rtspc->rtp_channel = RTP_PKT_CHANNEL(rtp); - - /* The length is two bytes */ - rtp_length = RTP_PKT_LENGTH(rtp); - - if(rtp_dataleft < rtp_length + 4) { - /* Need more - incomplete payload*/ - *readmore = TRUE; - break; - } - else { - /* We have the full RTP interleaved packet - * Write out the header including the leading '$' */ - DEBUGF(infof(data, "RTP write channel %d rtp_length %d\n", - rtspc->rtp_channel, rtp_length)); - result = rtp_client_write(conn, &rtp[0], rtp_length + 4); - if(result) { - failf(data, "Got an error writing an RTP packet"); - *readmore = FALSE; - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; - return result; - } - - /* Move forward in the buffer */ - rtp_dataleft -= rtp_length + 4; - rtp += rtp_length + 4; - - if(data->set.rtspreq == RTSPREQ_RECEIVE) { - /* If we are in a passive receive, give control back - * to the app as often as we can. - */ - k->keepon &= ~KEEP_RECV; - } - } - } - else { - /* Need more - incomplete header */ - *readmore = TRUE; - break; - } - } - - if(rtp_dataleft != 0 && rtp[0] == '$') { - DEBUGF(infof(data, "RTP Rewinding %zu %s\n", rtp_dataleft, - *readmore ? "(READMORE)" : "")); - - /* Store the incomplete RTP packet for a "rewind" */ - scratch = malloc(rtp_dataleft); - if(!scratch) { - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; - return CURLE_OUT_OF_MEMORY; - } - memcpy(scratch, rtp, rtp_dataleft); - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = scratch; - rtspc->rtp_bufsize = rtp_dataleft; - - /* As far as the transfer is concerned, this data is consumed */ - *nread = 0; - return CURLE_OK; - } - else { - /* Fix up k->str to point just after the last RTP packet */ - k->str += *nread - rtp_dataleft; - - /* either all of the data has been read or... - * rtp now points at the next byte to parse - */ - if(rtp_dataleft > 0) - DEBUGASSERT(k->str[0] == rtp[0]); - - DEBUGASSERT(rtp_dataleft <= *nread); /* sanity check */ - - *nread = rtp_dataleft; - } - - /* If we get here, we have finished with the leftover/merge buffer */ - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; - - return CURLE_OK; -} - -static -CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len) -{ - struct SessionHandle *data = conn->data; - size_t wrote; - curl_write_callback writeit; - - if(len == 0) { - failf (data, "Cannot write a 0 size RTP packet."); - return CURLE_WRITE_ERROR; - } - - writeit = data->set.fwrite_rtp?data->set.fwrite_rtp:data->set.fwrite_func; - wrote = writeit(ptr, 1, len, data->set.rtp_out); - - if(CURL_WRITEFUNC_PAUSE == wrote) { - failf (data, "Cannot pause RTP"); - return CURLE_WRITE_ERROR; - } - - if(wrote != len) { - failf (data, "Failed writing RTP data"); - return CURLE_WRITE_ERROR; - } - - return CURLE_OK; -} - -CURLcode Curl_rtsp_parseheader(struct connectdata *conn, - char *header) -{ - struct SessionHandle *data = conn->data; - long CSeq = 0; - - if(checkprefix("CSeq:", header)) { - /* Store the received CSeq. Match is verified in rtsp_done */ - int nc = sscanf(&header[4], ": %ld", &CSeq); - if(nc == 1) { - data->state.proto.rtsp->CSeq_recv = CSeq; /* mark the request */ - data->state.rtsp_CSeq_recv = CSeq; /* update the handle */ - } - else { - failf(data, "Unable to read the CSeq header: [%s]", header); - return CURLE_RTSP_CSEQ_ERROR; - } - } - else if(checkprefix("Session:", header)) { - char *start; - - /* Find the first non-space letter */ - start = header + 9; - while(*start && ISSPACE(*start)) - start++; - - if(!*start) { - failf(data, "Got a blank Session ID"); - } - else if(data->set.str[STRING_RTSP_SESSION_ID]) { - /* If the Session ID is set, then compare */ - if(strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], - strlen(data->set.str[STRING_RTSP_SESSION_ID])) != 0) { - failf(data, "Got RTSP Session ID Line [%s], but wanted ID [%s]", - start, data->set.str[STRING_RTSP_SESSION_ID]); - return CURLE_RTSP_SESSION_ERROR; - } - } - else { - /* If the Session ID is not set, and we find it in a response, then - set it */ - - /* The session ID can be an alphanumeric or a 'safe' character - * - * RFC 2326 15.1 Base Syntax: - * safe = "\$" | "-" | "_" | "." | "+" - * */ - char *end = start; - while(*end && - (ISALNUM(*end) || *end == '-' || *end == '_' || *end == '.' || - *end == '+' || - (*end == '\\' && *(end + 1) && *(end + 1) == '$' && (++end, 1)))) - end++; - - /* Copy the id substring into a new buffer */ - data->set.str[STRING_RTSP_SESSION_ID] = malloc(end - start + 1); - if(data->set.str[STRING_RTSP_SESSION_ID] == NULL) - return CURLE_OUT_OF_MEMORY; - memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, end - start); - (data->set.str[STRING_RTSP_SESSION_ID])[end - start] = '\0'; - } - } - return CURLE_OK; -} - -#endif /* CURL_DISABLE_RTSP */ diff --git a/lib/security.c b/lib/security.c deleted file mode 100644 index b7544ffbd..000000000 --- a/lib/security.c +++ /dev/null @@ -1,604 +0,0 @@ -/* 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 and modified a lot 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) 1998, 1999 Kungliga Tekniska Högskolan - * (Royal Institute of Technology, Stockholm, Sweden). - * - * Copyright (C) 2001 - 2011, Daniel Stenberg, , et al. - * - * 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 "curl_setup.h" - -#ifndef CURL_DISABLE_FTP -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - -#ifdef HAVE_NETDB_H -#include -#endif - -#ifdef HAVE_LIMITS_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_base64.h" -#include "curl_memory.h" -#include "curl_krb4.h" -#include "curl_ftp.h" -#include "curl_sendf.h" -#include "curl_rawstr.h" -#include "curl_warnless.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -static const struct { - enum protection_level level; - const char *name; -} level_names[] = { - { PROT_CLEAR, "clear" }, - { PROT_SAFE, "safe" }, - { PROT_CONFIDENTIAL, "confidential" }, - { PROT_PRIVATE, "private" } -}; - -static enum protection_level -name_to_level(const char *name) -{ - int i; - for(i = 0; i < (int)sizeof(level_names)/(int)sizeof(level_names[0]); i++) - if(checkprefix(name, level_names[i].name)) - return level_names[i].level; - return PROT_NONE; -} - -/* Convert a protocol |level| to its char representation. - We take an int to catch programming mistakes. */ -static char level_to_char(int level) { - switch(level) { - case PROT_CLEAR: - return 'C'; - case PROT_SAFE: - return 'S'; - case PROT_CONFIDENTIAL: - return 'E'; - case PROT_PRIVATE: - return 'P'; - case PROT_CMD: - /* Fall through */ - default: - /* Those 2 cases should not be reached! */ - break; - } - DEBUGASSERT(0); - /* Default to the most secure alternative. */ - return 'P'; -} - -static const struct Curl_sec_client_mech * const mechs[] = { -#if defined(HAVE_GSSAPI) - &Curl_krb5_client_mech, -#endif -#if defined(HAVE_KRB4) - &Curl_krb4_client_mech, -#endif - NULL -}; - -/* Send an FTP command defined by |message| and the optional arguments. The - function returns the ftp_code. If an error occurs, -1 is returned. */ -static int ftp_send_command(struct connectdata *conn, const char *message, ...) -{ - int ftp_code; - ssize_t nread; - va_list args; - char print_buffer[50]; - - va_start(args, message); - vsnprintf(print_buffer, sizeof(print_buffer), message, args); - va_end(args); - - if(Curl_ftpsendf(conn, print_buffer) != CURLE_OK) { - ftp_code = -1; - } - else { - if(Curl_GetFTPResponse(&nread, conn, &ftp_code) != CURLE_OK) - ftp_code = -1; - } - - (void)nread; /* Unused */ - return ftp_code; -} - -/* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode - saying whether an error occurred or CURLE_OK if |len| was read. */ -static CURLcode -socket_read(curl_socket_t fd, void *to, size_t len) -{ - char *to_p = to; - CURLcode code; - ssize_t nread; - - while(len > 0) { - code = Curl_read_plain(fd, to_p, len, &nread); - if(code == CURLE_OK) { - len -= nread; - to_p += nread; - } - else { - /* FIXME: We are doing a busy wait */ - if(code == CURLE_AGAIN) - continue; - return code; - } - } - return CURLE_OK; -} - - -/* Write |len| bytes from the buffer |to| to the socket |fd|. Return a - CURLcode saying whether an error occurred or CURLE_OK if |len| was - written. */ -static CURLcode -socket_write(struct connectdata *conn, curl_socket_t fd, const void *to, - size_t len) -{ - const char *to_p = to; - CURLcode code; - ssize_t written; - - while(len > 0) { - code = Curl_write_plain(conn, fd, to_p, len, &written); - if(code == CURLE_OK) { - len -= written; - to_p += written; - } - else { - /* FIXME: We are doing a busy wait */ - if(code == CURLE_AGAIN) - continue; - return code; - } - } - return CURLE_OK; -} - -static CURLcode read_data(struct connectdata *conn, - curl_socket_t fd, - struct krb4buffer *buf) -{ - int len; - void* tmp; - CURLcode ret; - - ret = socket_read(fd, &len, sizeof(len)); - if(ret != CURLE_OK) - return ret; - - len = ntohl(len); - tmp = realloc(buf->data, len); - if(tmp == NULL) - return CURLE_OUT_OF_MEMORY; - - buf->data = tmp; - ret = socket_read(fd, buf->data, len); - if(ret != CURLE_OK) - return ret; - buf->size = conn->mech->decode(conn->app_data, buf->data, len, - conn->data_prot, conn); - buf->index = 0; - return CURLE_OK; -} - -static size_t -buffer_read(struct krb4buffer *buf, void *data, size_t len) -{ - if(buf->size - buf->index < len) - len = buf->size - buf->index; - memcpy(data, (char*)buf->data + buf->index, len); - buf->index += len; - return len; -} - -/* Matches Curl_recv signature */ -static ssize_t sec_recv(struct connectdata *conn, int sockindex, - char *buffer, size_t len, CURLcode *err) -{ - size_t bytes_read; - size_t total_read = 0; - curl_socket_t fd = conn->sock[sockindex]; - - *err = CURLE_OK; - - /* Handle clear text response. */ - if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) - return read(fd, buffer, len); - - if(conn->in_buffer.eof_flag) { - conn->in_buffer.eof_flag = 0; - return 0; - } - - bytes_read = buffer_read(&conn->in_buffer, buffer, len); - len -= bytes_read; - total_read += bytes_read; - buffer += bytes_read; - - while(len > 0) { - if(read_data(conn, fd, &conn->in_buffer) != CURLE_OK) - return -1; - if(conn->in_buffer.size == 0) { - if(bytes_read > 0) - conn->in_buffer.eof_flag = 1; - return bytes_read; - } - bytes_read = buffer_read(&conn->in_buffer, buffer, len); - len -= bytes_read; - total_read += bytes_read; - buffer += bytes_read; - } - /* FIXME: Check for overflow */ - return total_read; -} - -/* Send |length| bytes from |from| to the |fd| socket taking care of encoding - and negociating with the server. |from| can be NULL. */ -/* FIXME: We don't check for errors nor report any! */ -static void do_sec_send(struct connectdata *conn, curl_socket_t fd, - const char *from, int length) -{ - int bytes, htonl_bytes; /* 32-bit integers for htonl */ - char *buffer = NULL; - char *cmd_buffer; - size_t cmd_size = 0; - CURLcode error; - enum protection_level prot_level = conn->data_prot; - bool iscmd = (prot_level == PROT_CMD)?TRUE:FALSE; - - DEBUGASSERT(prot_level > PROT_NONE && prot_level < PROT_LAST); - - if(iscmd) { - if(!strncmp(from, "PASS ", 5) || !strncmp(from, "ACCT ", 5)) - prot_level = PROT_PRIVATE; - else - prot_level = conn->command_prot; - } - bytes = conn->mech->encode(conn->app_data, from, length, prot_level, - (void**)&buffer, conn); - if(!buffer || bytes <= 0) - return; /* error */ - - if(iscmd) { - error = Curl_base64_encode(conn->data, buffer, curlx_sitouz(bytes), - &cmd_buffer, &cmd_size); - if(error) { - free(buffer); - return; /* error */ - } - if(cmd_size > 0) { - static const char *enc = "ENC "; - static const char *mic = "MIC "; - if(prot_level == PROT_PRIVATE) - socket_write(conn, fd, enc, 4); - else - socket_write(conn, fd, mic, 4); - - socket_write(conn, fd, cmd_buffer, cmd_size); - socket_write(conn, fd, "\r\n", 2); - infof(conn->data, "Send: %s%s\n", prot_level == PROT_PRIVATE?enc:mic, - cmd_buffer); - free(cmd_buffer); - } - } - else { - htonl_bytes = htonl(bytes); - socket_write(conn, fd, &htonl_bytes, sizeof(htonl_bytes)); - socket_write(conn, fd, buffer, curlx_sitouz(bytes)); - } - free(buffer); -} - -static ssize_t sec_write(struct connectdata *conn, curl_socket_t fd, - const char *buffer, size_t length) -{ - /* FIXME: Check for overflow */ - ssize_t tx = 0, len = conn->buffer_size; - - len -= conn->mech->overhead(conn->app_data, conn->data_prot, - curlx_sztosi(len)); - if(len <= 0) - len = length; - while(length) { - if(len >= 0 || length < (size_t)len) { - /* FIXME: Check for overflow. */ - len = length; - } - do_sec_send(conn, fd, buffer, curlx_sztosi(len)); - length -= len; - buffer += len; - tx += len; - } - return tx; -} - -/* Matches Curl_send signature */ -static ssize_t sec_send(struct connectdata *conn, int sockindex, - const void *buffer, size_t len, CURLcode *err) -{ - curl_socket_t fd = conn->sock[sockindex]; - *err = CURLE_OK; - return sec_write(conn, fd, buffer, len); -} - -int Curl_sec_read_msg(struct connectdata *conn, char *buffer, - enum protection_level level) -{ - /* decoded_len should be size_t or ssize_t but conn->mech->decode returns an - int */ - int decoded_len; - char *buf; - int ret_code; - size_t decoded_sz = 0; - CURLcode error; - - DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); - - error = Curl_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); - if(error || decoded_sz == 0) - return -1; - - if(decoded_sz > (size_t)INT_MAX) { - free(buf); - return -1; - } - decoded_len = curlx_uztosi(decoded_sz); - - decoded_len = conn->mech->decode(conn->app_data, buf, decoded_len, - level, conn); - if(decoded_len <= 0) { - free(buf); - return -1; - } - - if(conn->data->set.verbose) { - buf[decoded_len] = '\n'; - Curl_debug(conn->data, CURLINFO_HEADER_IN, buf, decoded_len + 1, conn); - } - - buf[decoded_len] = '\0'; - DEBUGASSERT(decoded_len > 3); - if(buf[3] == '-') - ret_code = 0; - else { - /* Check for error? */ - sscanf(buf, "%d", &ret_code); - } - - if(buf[decoded_len - 1] == '\n') - buf[decoded_len - 1] = '\0'; - /* FIXME: Is |buffer| length always greater than |decoded_len|? */ - strcpy(buffer, buf); - free(buf); - return ret_code; -} - -/* FIXME: The error code returned here is never checked. */ -static int sec_set_protection_level(struct connectdata *conn) -{ - int code; - char* pbsz; - static unsigned int buffer_size = 1 << 20; /* 1048576 */ - enum protection_level level = conn->request_data_prot; - - DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); - - if(!conn->sec_complete) { - infof(conn->data, "Trying to change the protection level after the" - "completion of the data exchange.\n"); - return -1; - } - - /* Bail out if we try to set up the same level */ - if(conn->data_prot == level) - return 0; - - if(level) { - code = ftp_send_command(conn, "PBSZ %u", buffer_size); - if(code < 0) - return -1; - - if(code/100 != 2) { - failf(conn->data, "Failed to set the protection's buffer size."); - return -1; - } - conn->buffer_size = buffer_size; - - pbsz = strstr(conn->data->state.buffer, "PBSZ="); - if(pbsz) { - /* FIXME: Checks for errors in sscanf? */ - sscanf(pbsz, "PBSZ=%u", &buffer_size); - if(buffer_size < conn->buffer_size) - conn->buffer_size = buffer_size; - } - } - - /* Now try to negiociate the protection level. */ - code = ftp_send_command(conn, "PROT %c", level_to_char(level)); - - if(code < 0) - return -1; - - if(code/100 != 2) { - failf(conn->data, "Failed to set the protection level."); - return -1; - } - - conn->data_prot = level; - if(level == PROT_PRIVATE) - conn->command_prot = level; - - return 0; -} - -int -Curl_sec_request_prot(struct connectdata *conn, const char *level) -{ - enum protection_level l = name_to_level(level); - if(l == PROT_NONE) - return -1; - DEBUGASSERT(l > PROT_NONE && l < PROT_LAST); - conn->request_data_prot = l; - return 0; -} - -static CURLcode choose_mech(struct connectdata *conn) -{ - int ret; - struct SessionHandle *data = conn->data; - const struct Curl_sec_client_mech * const *mech; - void *tmp_allocation; - const char *mech_name; - - for(mech = mechs; (*mech); ++mech) { - mech_name = (*mech)->name; - /* We have no mechanism with a NULL name but keep this check */ - DEBUGASSERT(mech_name != NULL); - if(mech_name == NULL) { - infof(data, "Skipping mechanism with empty name (%p)\n", mech); - continue; - } - tmp_allocation = realloc(conn->app_data, (*mech)->size); - if(tmp_allocation == NULL) { - failf(data, "Failed realloc of size %u", (*mech)->size); - mech = NULL; - return CURLE_OUT_OF_MEMORY; - } - conn->app_data = tmp_allocation; - - if((*mech)->init) { - ret = (*mech)->init(conn->app_data); - if(ret != 0) { - infof(data, "Failed initialization for %s. Skipping it.\n", mech_name); - continue; - } - } - - infof(data, "Trying mechanism %s...\n", mech_name); - ret = ftp_send_command(conn, "AUTH %s", mech_name); - if(ret < 0) - /* FIXME: This error is too generic but it is OK for now. */ - return CURLE_COULDNT_CONNECT; - - if(ret/100 != 3) { - switch(ret) { - case 504: - infof(data, "Mechanism %s is not supported by the server (server " - "returned ftp code: 504).\n", mech_name); - break; - case 534: - infof(data, "Mechanism %s was rejected by the server (server returned " - "ftp code: 534).\n", mech_name); - break; - default: - if(ret/100 == 5) { - infof(data, "server does not support the security extensions\n"); - return CURLE_USE_SSL_FAILED; - } - break; - } - continue; - } - - /* Authenticate */ - ret = (*mech)->auth(conn->app_data, conn); - - if(ret == AUTH_CONTINUE) - continue; - else if(ret != AUTH_OK) { - /* Mechanism has dumped the error to stderr, don't error here. */ - return -1; - } - DEBUGASSERT(ret == AUTH_OK); - - conn->mech = *mech; - conn->sec_complete = 1; - conn->recv[FIRSTSOCKET] = sec_recv; - conn->send[FIRSTSOCKET] = sec_send; - conn->recv[SECONDARYSOCKET] = sec_recv; - conn->send[SECONDARYSOCKET] = sec_send; - conn->command_prot = PROT_SAFE; - /* Set the requested protection level */ - /* BLOCKING */ - (void)sec_set_protection_level(conn); - break; - } - - return mech != NULL ? CURLE_OK : CURLE_FAILED_INIT; -} - -CURLcode -Curl_sec_login(struct connectdata *conn) -{ - return choose_mech(conn); -} - - -void -Curl_sec_end(struct connectdata *conn) -{ - if(conn->mech != NULL && conn->mech->end) - conn->mech->end(conn->app_data); - if(conn->app_data) { - free(conn->app_data); - conn->app_data = NULL; - } - if(conn->in_buffer.data) { - free(conn->in_buffer.data); - conn->in_buffer.data = NULL; - conn->in_buffer.size = 0; - conn->in_buffer.index = 0; - /* FIXME: Is this really needed? */ - conn->in_buffer.eof_flag = 0; - } - conn->sec_complete = 0; - conn->data_prot = PROT_CLEAR; - conn->mech = NULL; -} - -#endif /* HAVE_KRB4 || HAVE_GSSAPI */ - -#endif /* CURL_DISABLE_FTP */ diff --git a/lib/select.c b/lib/select.c deleted file mode 100644 index d4519d39e..000000000 --- a/lib/select.c +++ /dev/null @@ -1,529 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#if !defined(HAVE_SELECT) && !defined(HAVE_POLL_FINE) -#error "We can't compile without select() or poll() support." -#endif - -#if defined(__BEOS__) && !defined(__HAIKU__) -/* BeOS has FD_SET defined in socket.h */ -#include -#endif - -#ifdef MSDOS -#include /* delay() */ -#endif - -#include - -#include "curl_urldata.h" -#include "curl_connect.h" -#include "curl_select.h" -#include "curl_warnless.h" - -/* Convenience local macros */ - -#define elapsed_ms (int)curlx_tvdiff(curlx_tvnow(), initial_tv) - -#ifdef CURL_ACKNOWLEDGE_EINTR -#define error_not_EINTR (1) -#else -#define error_not_EINTR (error != EINTR) -#endif - -/* - * Internal function used for waiting a specific amount of ms - * in Curl_socket_ready() and Curl_poll() when no file descriptor - * is provided to wait on, just being used to delay execution. - * WinSock select() and poll() timeout mechanisms need a valid - * socket descriptor in a not null file descriptor set to work. - * Waiting indefinitely with this function is not allowed, a - * zero or negative timeout value will return immediately. - * Timeout resolution, accuracy, as well as maximum supported - * value is system dependent, neither factor is a citical issue - * for the intended use of this function in the library. - * On non-DOS and non-Winsock platforms, when compiled with - * CURL_ACKNOWLEDGE_EINTR defined, EINTR condition is honored - * and function might exit early without awaiting full timeout, - * otherwise EINTR will be ignored and full timeout will elapse. - * - * Return values: - * -1 = system call error, invalid timeout value, or interrupted - * 0 = specified timeout has elapsed - */ -int Curl_wait_ms(int timeout_ms) -{ -#if !defined(MSDOS) && !defined(USE_WINSOCK) -#ifndef HAVE_POLL_FINE - struct timeval pending_tv; -#endif - struct timeval initial_tv; - int pending_ms; - int error; -#endif - int r = 0; - - if(!timeout_ms) - return 0; - if(timeout_ms < 0) { - SET_SOCKERRNO(EINVAL); - return -1; - } -#if defined(MSDOS) - delay(timeout_ms); -#elif defined(USE_WINSOCK) - Sleep(timeout_ms); -#else - pending_ms = timeout_ms; - initial_tv = curlx_tvnow(); - do { -#if defined(HAVE_POLL_FINE) - r = poll(NULL, 0, pending_ms); -#else - pending_tv.tv_sec = pending_ms / 1000; - pending_tv.tv_usec = (pending_ms % 1000) * 1000; - r = select(0, NULL, NULL, NULL, &pending_tv); -#endif /* HAVE_POLL_FINE */ - if(r != -1) - break; - error = SOCKERRNO; - if(error && error_not_EINTR) - break; - pending_ms = timeout_ms - elapsed_ms; - if(pending_ms <= 0) - break; - } while(r == -1); -#endif /* USE_WINSOCK */ - if(r) - r = -1; - return r; -} - -/* - * Wait for read or write events on a set of file descriptors. It uses poll() - * when a fine poll() is available, in order to avoid limits with FD_SETSIZE, - * otherwise select() is used. An error is returned if select() is being used - * and a file descriptor is too large for FD_SETSIZE. - * - * A negative timeout value makes this function wait indefinitely, - * unles no valid file descriptor is given, when this happens the - * negative timeout is ignored and the function times out immediately. - * When compiled with CURL_ACKNOWLEDGE_EINTR defined, EINTR condition - * is honored and function might exit early without awaiting timeout, - * otherwise EINTR will be ignored. - * - * Return values: - * -1 = system call error or fd >= FD_SETSIZE - * 0 = timeout - * [bitmask] = action as described below - * - * CURL_CSELECT_IN - first socket is readable - * CURL_CSELECT_IN2 - second socket is readable - * CURL_CSELECT_OUT - write socket is writable - * CURL_CSELECT_ERR - an error condition occurred - */ -int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ - curl_socket_t readfd1, - curl_socket_t writefd, /* socket to write to */ - long timeout_ms) /* milliseconds to wait */ -{ -#ifdef HAVE_POLL_FINE - struct pollfd pfd[3]; - int num; -#else - struct timeval pending_tv; - struct timeval *ptimeout; - fd_set fds_read; - fd_set fds_write; - fd_set fds_err; - curl_socket_t maxfd; -#endif - struct timeval initial_tv = {0,0}; - int pending_ms = 0; - int error; - int r; - int ret; - - if((readfd0 == CURL_SOCKET_BAD) && (readfd1 == CURL_SOCKET_BAD) && - (writefd == CURL_SOCKET_BAD)) { - /* no sockets, just wait */ - r = Curl_wait_ms((int)timeout_ms); - return r; - } - - /* Avoid initial timestamp, avoid curlx_tvnow() call, when elapsed - time in this function does not need to be measured. This happens - when function is called with a zero timeout or a negative timeout - value indicating a blocking call should be performed. */ - - if(timeout_ms > 0) { - pending_ms = (int)timeout_ms; - initial_tv = curlx_tvnow(); - } - -#ifdef HAVE_POLL_FINE - - num = 0; - if(readfd0 != CURL_SOCKET_BAD) { - pfd[num].fd = readfd0; - pfd[num].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI; - pfd[num].revents = 0; - num++; - } - if(readfd1 != CURL_SOCKET_BAD) { - pfd[num].fd = readfd1; - pfd[num].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI; - pfd[num].revents = 0; - num++; - } - if(writefd != CURL_SOCKET_BAD) { - pfd[num].fd = writefd; - pfd[num].events = POLLWRNORM|POLLOUT; - pfd[num].revents = 0; - num++; - } - - do { - if(timeout_ms < 0) - pending_ms = -1; - else if(!timeout_ms) - pending_ms = 0; - r = poll(pfd, num, pending_ms); - if(r != -1) - break; - error = SOCKERRNO; - if(error && error_not_EINTR) - break; - if(timeout_ms > 0) { - pending_ms = (int)(timeout_ms - elapsed_ms); - if(pending_ms <= 0) { - r = 0; /* Simulate a "call timed out" case */ - break; - } - } - } while(r == -1); - - if(r < 0) - return -1; - if(r == 0) - return 0; - - ret = 0; - num = 0; - if(readfd0 != CURL_SOCKET_BAD) { - if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP)) - ret |= CURL_CSELECT_IN; - if(pfd[num].revents & (POLLRDBAND|POLLPRI|POLLNVAL)) - ret |= CURL_CSELECT_ERR; - num++; - } - if(readfd1 != CURL_SOCKET_BAD) { - if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP)) - ret |= CURL_CSELECT_IN2; - if(pfd[num].revents & (POLLRDBAND|POLLPRI|POLLNVAL)) - ret |= CURL_CSELECT_ERR; - num++; - } - if(writefd != CURL_SOCKET_BAD) { - if(pfd[num].revents & (POLLWRNORM|POLLOUT)) - ret |= CURL_CSELECT_OUT; - if(pfd[num].revents & (POLLERR|POLLHUP|POLLNVAL)) - ret |= CURL_CSELECT_ERR; - } - - return ret; - -#else /* HAVE_POLL_FINE */ - - FD_ZERO(&fds_err); - maxfd = (curl_socket_t)-1; - - FD_ZERO(&fds_read); - if(readfd0 != CURL_SOCKET_BAD) { - VERIFY_SOCK(readfd0); - FD_SET(readfd0, &fds_read); - FD_SET(readfd0, &fds_err); - maxfd = readfd0; - } - if(readfd1 != CURL_SOCKET_BAD) { - VERIFY_SOCK(readfd1); - FD_SET(readfd1, &fds_read); - FD_SET(readfd1, &fds_err); - if(readfd1 > maxfd) - maxfd = readfd1; - } - - FD_ZERO(&fds_write); - if(writefd != CURL_SOCKET_BAD) { - VERIFY_SOCK(writefd); - FD_SET(writefd, &fds_write); - FD_SET(writefd, &fds_err); - if(writefd > maxfd) - maxfd = writefd; - } - - ptimeout = (timeout_ms < 0) ? NULL : &pending_tv; - - do { - if(timeout_ms > 0) { - pending_tv.tv_sec = pending_ms / 1000; - pending_tv.tv_usec = (pending_ms % 1000) * 1000; - } - else if(!timeout_ms) { - pending_tv.tv_sec = 0; - pending_tv.tv_usec = 0; - } - r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout); - if(r != -1) - break; - error = SOCKERRNO; - if(error && error_not_EINTR) - break; - if(timeout_ms > 0) { - pending_ms = timeout_ms - elapsed_ms; - if(pending_ms <= 0) { - r = 0; /* Simulate a "call timed out" case */ - break; - } - } - } while(r == -1); - - if(r < 0) - return -1; - if(r == 0) - return 0; - - ret = 0; - if(readfd0 != CURL_SOCKET_BAD) { - if(FD_ISSET(readfd0, &fds_read)) - ret |= CURL_CSELECT_IN; - if(FD_ISSET(readfd0, &fds_err)) - ret |= CURL_CSELECT_ERR; - } - if(readfd1 != CURL_SOCKET_BAD) { - if(FD_ISSET(readfd1, &fds_read)) - ret |= CURL_CSELECT_IN2; - if(FD_ISSET(readfd1, &fds_err)) - ret |= CURL_CSELECT_ERR; - } - if(writefd != CURL_SOCKET_BAD) { - if(FD_ISSET(writefd, &fds_write)) - ret |= CURL_CSELECT_OUT; - if(FD_ISSET(writefd, &fds_err)) - ret |= CURL_CSELECT_ERR; - } - - return ret; - -#endif /* HAVE_POLL_FINE */ - -} - -/* - * This is a wrapper around poll(). If poll() does not exist, then - * select() is used instead. An error is returned if select() is - * being used and a file descriptor is too large for FD_SETSIZE. - * A negative timeout value makes this function wait indefinitely, - * unles no valid file descriptor is given, when this happens the - * negative timeout is ignored and the function times out immediately. - * When compiled with CURL_ACKNOWLEDGE_EINTR defined, EINTR condition - * is honored and function might exit early without awaiting timeout, - * otherwise EINTR will be ignored. - * - * Return values: - * -1 = system call error or fd >= FD_SETSIZE - * 0 = timeout - * N = number of structures with non zero revent fields - */ -int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms) -{ -#ifndef HAVE_POLL_FINE - struct timeval pending_tv; - struct timeval *ptimeout; - fd_set fds_read; - fd_set fds_write; - fd_set fds_err; - curl_socket_t maxfd; -#endif - struct timeval initial_tv = {0,0}; - bool fds_none = TRUE; - unsigned int i; - int pending_ms = 0; - int error; - int r; - - if(ufds) { - for(i = 0; i < nfds; i++) { - if(ufds[i].fd != CURL_SOCKET_BAD) { - fds_none = FALSE; - break; - } - } - } - if(fds_none) { - r = Curl_wait_ms(timeout_ms); - return r; - } - - /* Avoid initial timestamp, avoid curlx_tvnow() call, when elapsed - time in this function does not need to be measured. This happens - when function is called with a zero timeout or a negative timeout - value indicating a blocking call should be performed. */ - - if(timeout_ms > 0) { - pending_ms = timeout_ms; - initial_tv = curlx_tvnow(); - } - -#ifdef HAVE_POLL_FINE - - do { - if(timeout_ms < 0) - pending_ms = -1; - else if(!timeout_ms) - pending_ms = 0; - r = poll(ufds, nfds, pending_ms); - if(r != -1) - break; - error = SOCKERRNO; - if(error && error_not_EINTR) - break; - if(timeout_ms > 0) { - pending_ms = timeout_ms - elapsed_ms; - if(pending_ms <= 0) - break; - } - } while(r == -1); - - if(r < 0) - return -1; - if(r == 0) - return 0; - - for(i = 0; i < nfds; i++) { - if(ufds[i].fd == CURL_SOCKET_BAD) - continue; - if(ufds[i].revents & POLLHUP) - ufds[i].revents |= POLLIN; - if(ufds[i].revents & POLLERR) - ufds[i].revents |= (POLLIN|POLLOUT); - } - -#else /* HAVE_POLL_FINE */ - - FD_ZERO(&fds_read); - FD_ZERO(&fds_write); - FD_ZERO(&fds_err); - maxfd = (curl_socket_t)-1; - - for(i = 0; i < nfds; i++) { - ufds[i].revents = 0; - if(ufds[i].fd == CURL_SOCKET_BAD) - continue; - VERIFY_SOCK(ufds[i].fd); - if(ufds[i].events & (POLLIN|POLLOUT|POLLPRI| - POLLRDNORM|POLLWRNORM|POLLRDBAND)) { - if(ufds[i].fd > maxfd) - maxfd = ufds[i].fd; - if(ufds[i].events & (POLLRDNORM|POLLIN)) - FD_SET(ufds[i].fd, &fds_read); - if(ufds[i].events & (POLLWRNORM|POLLOUT)) - FD_SET(ufds[i].fd, &fds_write); - if(ufds[i].events & (POLLRDBAND|POLLPRI)) - FD_SET(ufds[i].fd, &fds_err); - } - } - - ptimeout = (timeout_ms < 0) ? NULL : &pending_tv; - - do { - if(timeout_ms > 0) { - pending_tv.tv_sec = pending_ms / 1000; - pending_tv.tv_usec = (pending_ms % 1000) * 1000; - } - else if(!timeout_ms) { - pending_tv.tv_sec = 0; - pending_tv.tv_usec = 0; - } - r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout); - if(r != -1) - break; - error = SOCKERRNO; - if(error && error_not_EINTR) - break; - if(timeout_ms > 0) { - pending_ms = timeout_ms - elapsed_ms; - if(pending_ms <= 0) - break; - } - } while(r == -1); - - if(r < 0) - return -1; - if(r == 0) - return 0; - - r = 0; - for(i = 0; i < nfds; i++) { - ufds[i].revents = 0; - if(ufds[i].fd == CURL_SOCKET_BAD) - continue; - if(FD_ISSET(ufds[i].fd, &fds_read)) - ufds[i].revents |= POLLIN; - if(FD_ISSET(ufds[i].fd, &fds_write)) - ufds[i].revents |= POLLOUT; - if(FD_ISSET(ufds[i].fd, &fds_err)) - ufds[i].revents |= POLLPRI; - if(ufds[i].revents != 0) - r++; - } - -#endif /* HAVE_POLL_FINE */ - - return r; -} - -#ifdef TPF -/* - * This is a replacement for select() on the TPF platform. - * It is used whenever libcurl calls select(). - * The call below to tpf_process_signals() is required because - * TPF's select calls are not signal interruptible. - * - * Return values are the same as select's. - */ -int tpf_select_libcurl(int maxfds, fd_set* reads, fd_set* writes, - fd_set* excepts, struct timeval* tv) -{ - int rc; - - rc = tpf_select_bsd(maxfds, reads, writes, excepts, tv); - tpf_process_signals(); - return(rc); -} -#endif /* TPF */ diff --git a/lib/sendf.c b/lib/sendf.c deleted file mode 100644 index a1ec50977..000000000 --- a/lib/sendf.c +++ /dev/null @@ -1,687 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_connect.h" -#include "curl_sslgen.h" -#include "curl_ssh.h" -#include "curl_multiif.h" -#include "curl_non_ascii.h" - -#define _MPRINTF_REPLACE /* use the internal *printf() functions */ -#include - -/* the krb4 functions only exists for FTP and if krb4 or gssapi is defined */ -#if !defined(CURL_DISABLE_FTP) && (defined(HAVE_KRB4) || defined(HAVE_GSSAPI)) -#include "curl_krb4.h" -#else -#define Curl_sec_send(a,b,c,d) -1 -#define Curl_sec_read(a,b,c,d) -1 -#endif - -#include "curl_memory.h" -#include "curl_strerror.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifdef CURL_DO_LINEEND_CONV -/* - * convert_lineends() changes CRLF (\r\n) end-of-line markers to a single LF - * (\n), with special processing for CRLF sequences that are split between two - * blocks of data. Remaining, bare CRs are changed to LFs. The possibly new - * size of the data is returned. - */ -static size_t convert_lineends(struct SessionHandle *data, - char *startPtr, size_t size) -{ - char *inPtr, *outPtr; - - /* sanity check */ - if((startPtr == NULL) || (size < 1)) { - return(size); - } - - if(data->state.prev_block_had_trailing_cr) { - /* The previous block of incoming data - had a trailing CR, which was turned into a LF. */ - if(*startPtr == '\n') { - /* This block of incoming data starts with the - previous block's LF so get rid of it */ - memmove(startPtr, startPtr+1, size-1); - size--; - /* and it wasn't a bare CR but a CRLF conversion instead */ - data->state.crlf_conversions++; - } - data->state.prev_block_had_trailing_cr = FALSE; /* reset the flag */ - } - - /* find 1st CR, if any */ - inPtr = outPtr = memchr(startPtr, '\r', size); - if(inPtr) { - /* at least one CR, now look for CRLF */ - while(inPtr < (startPtr+size-1)) { - /* note that it's size-1, so we'll never look past the last byte */ - if(memcmp(inPtr, "\r\n", 2) == 0) { - /* CRLF found, bump past the CR and copy the NL */ - inPtr++; - *outPtr = *inPtr; - /* keep track of how many CRLFs we converted */ - data->state.crlf_conversions++; - } - else { - if(*inPtr == '\r') { - /* lone CR, move LF instead */ - *outPtr = '\n'; - } - else { - /* not a CRLF nor a CR, just copy whatever it is */ - *outPtr = *inPtr; - } - } - outPtr++; - inPtr++; - } /* end of while loop */ - - if(inPtr < startPtr+size) { - /* handle last byte */ - if(*inPtr == '\r') { - /* deal with a CR at the end of the buffer */ - *outPtr = '\n'; /* copy a NL instead */ - /* note that a CRLF might be split across two blocks */ - data->state.prev_block_had_trailing_cr = TRUE; - } - else { - /* copy last byte */ - *outPtr = *inPtr; - } - outPtr++; - } - if(outPtr < startPtr+size) - /* tidy up by null terminating the now shorter data */ - *outPtr = '\0'; - - return(outPtr - startPtr); - } - return(size); -} -#endif /* CURL_DO_LINEEND_CONV */ - -/* Curl_infof() is for info message along the way */ - -void Curl_infof(struct SessionHandle *data, const char *fmt, ...) -{ - if(data && data->set.verbose) { - va_list ap; - size_t len; - char print_buffer[2048 + 1]; - va_start(ap, fmt); - vsnprintf(print_buffer, sizeof(print_buffer), fmt, ap); - va_end(ap); - len = strlen(print_buffer); - Curl_debug(data, CURLINFO_TEXT, print_buffer, len, NULL); - } -} - -/* Curl_failf() is for messages stating why we failed. - * The message SHALL NOT include any LF or CR. - */ - -void Curl_failf(struct SessionHandle *data, const char *fmt, ...) -{ - va_list ap; - size_t len; - va_start(ap, fmt); - - vsnprintf(data->state.buffer, BUFSIZE, fmt, ap); - - if(data->set.errorbuffer && !data->state.errorbuf) { - snprintf(data->set.errorbuffer, CURL_ERROR_SIZE, "%s", data->state.buffer); - data->state.errorbuf = TRUE; /* wrote error string */ - } - if(data->set.verbose) { - len = strlen(data->state.buffer); - if(len < BUFSIZE - 1) { - data->state.buffer[len] = '\n'; - data->state.buffer[++len] = '\0'; - } - Curl_debug(data, CURLINFO_TEXT, data->state.buffer, len, NULL); - } - - va_end(ap); -} - -/* Curl_sendf() sends formated data to the server */ -CURLcode Curl_sendf(curl_socket_t sockfd, struct connectdata *conn, - const char *fmt, ...) -{ - struct SessionHandle *data = conn->data; - ssize_t bytes_written; - size_t write_len; - CURLcode res = CURLE_OK; - char *s; - char *sptr; - va_list ap; - va_start(ap, fmt); - s = vaprintf(fmt, ap); /* returns an allocated string */ - va_end(ap); - if(!s) - return CURLE_OUT_OF_MEMORY; /* failure */ - - bytes_written=0; - write_len = strlen(s); - sptr = s; - - for(;;) { - /* Write the buffer to the socket */ - res = Curl_write(conn, sockfd, sptr, write_len, &bytes_written); - - if(CURLE_OK != res) - break; - - if(data->set.verbose) - Curl_debug(data, CURLINFO_DATA_OUT, sptr, (size_t)bytes_written, conn); - - if((size_t)bytes_written != write_len) { - /* if not all was written at once, we must advance the pointer, decrease - the size left and try again! */ - write_len -= bytes_written; - sptr += bytes_written; - } - else - break; - } - - free(s); /* free the output string */ - - return res; -} - -/* - * Curl_write() is an internal write function that sends data to the - * server. Works with plain sockets, SCP, SSL or kerberos. - * - * If the write would block (CURLE_AGAIN), we return CURLE_OK and - * (*written == 0). Otherwise we return regular CURLcode value. - */ -CURLcode Curl_write(struct connectdata *conn, - curl_socket_t sockfd, - const void *mem, - size_t len, - ssize_t *written) -{ - ssize_t bytes_written; - CURLcode curlcode = CURLE_OK; - int num = (sockfd == conn->sock[SECONDARYSOCKET]); - - bytes_written = conn->send[num](conn, num, mem, len, &curlcode); - - *written = bytes_written; - if(bytes_written >= 0) - /* we completely ignore the curlcode value when subzero is not returned */ - return CURLE_OK; - - /* handle CURLE_AGAIN or a send failure */ - switch(curlcode) { - case CURLE_AGAIN: - *written = 0; - return CURLE_OK; - - case CURLE_OK: - /* general send failure */ - return CURLE_SEND_ERROR; - - default: - /* we got a specific curlcode, forward it */ - return curlcode; - } -} - -ssize_t Curl_send_plain(struct connectdata *conn, int num, - const void *mem, size_t len, CURLcode *code) -{ - curl_socket_t sockfd = conn->sock[num]; - ssize_t bytes_written = swrite(sockfd, mem, len); - - *code = CURLE_OK; - if(-1 == bytes_written) { - int err = SOCKERRNO; - - if( -#ifdef WSAEWOULDBLOCK - /* This is how Windows does it */ - (WSAEWOULDBLOCK == err) -#else - /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned - due to its inability to send off data without blocking. We therefor - treat both error codes the same here */ - (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) -#endif - ) { - /* this is just a case of EWOULDBLOCK */ - bytes_written=0; - *code = CURLE_AGAIN; - } - else { - failf(conn->data, "Send failure: %s", - Curl_strerror(conn, err)); - conn->data->state.os_errno = err; - *code = CURLE_SEND_ERROR; - } - } - return bytes_written; -} - -/* - * Curl_write_plain() is an internal write function that sends data to the - * server using plain sockets only. Otherwise meant to have the exact same - * proto as Curl_write() - */ -CURLcode Curl_write_plain(struct connectdata *conn, - curl_socket_t sockfd, - const void *mem, - size_t len, - ssize_t *written) -{ - ssize_t bytes_written; - CURLcode retcode; - int num = (sockfd == conn->sock[SECONDARYSOCKET]); - - bytes_written = Curl_send_plain(conn, num, mem, len, &retcode); - - *written = bytes_written; - - return retcode; -} - -ssize_t Curl_recv_plain(struct connectdata *conn, int num, char *buf, - size_t len, CURLcode *code) -{ - curl_socket_t sockfd = conn->sock[num]; - ssize_t nread = sread(sockfd, buf, len); - - *code = CURLE_OK; - if(-1 == nread) { - int err = SOCKERRNO; - - if( -#ifdef WSAEWOULDBLOCK - /* This is how Windows does it */ - (WSAEWOULDBLOCK == err) -#else - /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned - due to its inability to send off data without blocking. We therefor - treat both error codes the same here */ - (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) -#endif - ) { - /* this is just a case of EWOULDBLOCK */ - *code = CURLE_AGAIN; - } - else { - failf(conn->data, "Recv failure: %s", - Curl_strerror(conn, err)); - conn->data->state.os_errno = err; - *code = CURLE_RECV_ERROR; - } - } - return nread; -} - -static CURLcode pausewrite(struct SessionHandle *data, - int type, /* what type of data */ - const char *ptr, - size_t len) -{ - /* signalled to pause sending on this connection, but since we have data - we want to send we need to dup it to save a copy for when the sending - is again enabled */ - struct SingleRequest *k = &data->req; - char *dupl = malloc(len); - if(!dupl) - return CURLE_OUT_OF_MEMORY; - - memcpy(dupl, ptr, len); - - /* store this information in the state struct for later use */ - data->state.tempwrite = dupl; - data->state.tempwritesize = len; - data->state.tempwritetype = type; - - /* mark the connection as RECV paused */ - k->keepon |= KEEP_RECV_PAUSE; - - DEBUGF(infof(data, "Pausing with %zu bytes in buffer for type %02x\n", - len, type)); - - return CURLE_OK; -} - - -/* Curl_client_write() sends data to the write callback(s) - - The bit pattern defines to what "streams" to write to. Body and/or header. - The defines are in curl_sendf.h of course. - - If CURL_DO_LINEEND_CONV is enabled, data is converted IN PLACE to the - local character encoding. This is a problem and should be changed in - the future to leave the original data alone. - */ -CURLcode Curl_client_write(struct connectdata *conn, - int type, - char *ptr, - size_t len) -{ - struct SessionHandle *data = conn->data; - size_t wrote; - - if(0 == len) - len = strlen(ptr); - - /* If reading is actually paused, we're forced to append this chunk of data - to the already held data, but only if it is the same type as otherwise it - can't work and it'll return error instead. */ - if(data->req.keepon & KEEP_RECV_PAUSE) { - size_t newlen; - char *newptr; - if(type != data->state.tempwritetype) - /* major internal confusion */ - return CURLE_RECV_ERROR; - - DEBUGASSERT(data->state.tempwrite); - - /* figure out the new size of the data to save */ - newlen = len + data->state.tempwritesize; - /* allocate the new memory area */ - newptr = realloc(data->state.tempwrite, newlen); - if(!newptr) - return CURLE_OUT_OF_MEMORY; - /* copy the new data to the end of the new area */ - memcpy(newptr + data->state.tempwritesize, ptr, len); - /* update the pointer and the size */ - data->state.tempwrite = newptr; - data->state.tempwritesize = newlen; - - return CURLE_OK; - } - - if(type & CLIENTWRITE_BODY) { - if((conn->handler->protocol&CURLPROTO_FTP) && - conn->proto.ftpc.transfertype == 'A') { - /* convert from the network encoding */ - CURLcode rc = Curl_convert_from_network(data, ptr, len); - /* Curl_convert_from_network calls failf if unsuccessful */ - if(rc) - return rc; - -#ifdef CURL_DO_LINEEND_CONV - /* convert end-of-line markers */ - len = convert_lineends(data, ptr, len); -#endif /* CURL_DO_LINEEND_CONV */ - } - /* If the previous block of data ended with CR and this block of data is - just a NL, then the length might be zero */ - if(len) { - wrote = data->set.fwrite_func(ptr, 1, len, data->set.out); - } - else { - wrote = len; - } - - if(CURL_WRITEFUNC_PAUSE == wrote) - return pausewrite(data, type, ptr, len); - - if(wrote != len) { - failf(data, "Failed writing body (%zu != %zu)", wrote, len); - return CURLE_WRITE_ERROR; - } - } - - if((type & CLIENTWRITE_HEADER) && - (data->set.fwrite_header || data->set.writeheader) ) { - /* - * Write headers to the same callback or to the especially setup - * header callback function (added after version 7.7.1). - */ - curl_write_callback writeit= - data->set.fwrite_header?data->set.fwrite_header:data->set.fwrite_func; - - /* Note: The header is in the host encoding - regardless of the ftp transfer mode (ASCII/Image) */ - - wrote = writeit(ptr, 1, len, data->set.writeheader); - if(CURL_WRITEFUNC_PAUSE == wrote) - /* here we pass in the HEADER bit only since if this was body as well - then it was passed already and clearly that didn't trigger the pause, - so this is saved for later with the HEADER bit only */ - return pausewrite(data, CLIENTWRITE_HEADER, ptr, len); - - if(wrote != len) { - failf (data, "Failed writing header"); - return CURLE_WRITE_ERROR; - } - } - - return CURLE_OK; -} - -CURLcode Curl_read_plain(curl_socket_t sockfd, - char *buf, - size_t bytesfromsocket, - ssize_t *n) -{ - ssize_t nread = sread(sockfd, buf, bytesfromsocket); - - if(-1 == nread) { - int err = SOCKERRNO; -#ifdef USE_WINSOCK - if(WSAEWOULDBLOCK == err) -#else - if((EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err)) -#endif - return CURLE_AGAIN; - else - return CURLE_RECV_ERROR; - } - - /* we only return number of bytes read when we return OK */ - *n = nread; - return CURLE_OK; -} - -/* - * Internal read-from-socket function. This is meant to deal with plain - * sockets, SSL sockets and kerberos sockets. - * - * Returns a regular CURLcode value. - */ -CURLcode Curl_read(struct connectdata *conn, /* connection data */ - curl_socket_t sockfd, /* read from this socket */ - char *buf, /* store read data here */ - size_t sizerequested, /* max amount to read */ - ssize_t *n) /* amount bytes read */ -{ - CURLcode curlcode = CURLE_RECV_ERROR; - ssize_t nread = 0; - size_t bytesfromsocket = 0; - char *buffertofill = NULL; - bool pipelining = (conn->data->multi && - Curl_multi_canPipeline(conn->data->multi)) ? TRUE : FALSE; - - /* Set 'num' to 0 or 1, depending on which socket that has been sent here. - If it is the second socket, we set num to 1. Otherwise to 0. This lets - us use the correct ssl handle. */ - int num = (sockfd == conn->sock[SECONDARYSOCKET]); - - *n=0; /* reset amount to zero */ - - /* If session can pipeline, check connection buffer */ - if(pipelining) { - size_t bytestocopy = CURLMIN(conn->buf_len - conn->read_pos, - sizerequested); - - /* Copy from our master buffer first if we have some unread data there*/ - if(bytestocopy > 0) { - memcpy(buf, conn->master_buffer + conn->read_pos, bytestocopy); - conn->read_pos += bytestocopy; - conn->bits.stream_was_rewound = FALSE; - - *n = (ssize_t)bytestocopy; - return CURLE_OK; - } - /* If we come here, it means that there is no data to read from the buffer, - * so we read from the socket */ - bytesfromsocket = CURLMIN(sizerequested, BUFSIZE * sizeof (char)); - buffertofill = conn->master_buffer; - } - else { - bytesfromsocket = CURLMIN((long)sizerequested, - conn->data->set.buffer_size ? - conn->data->set.buffer_size : BUFSIZE); - buffertofill = buf; - } - - nread = conn->recv[num](conn, num, buffertofill, bytesfromsocket, &curlcode); - if(nread < 0) - return curlcode; - - if(pipelining) { - memcpy(buf, conn->master_buffer, nread); - conn->buf_len = nread; - conn->read_pos = nread; - } - - *n += nread; - - return CURLE_OK; -} - -/* return 0 on success */ -static int showit(struct SessionHandle *data, curl_infotype type, - char *ptr, size_t size) -{ - static const char s_infotype[CURLINFO_END][3] = { - "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; - -#ifdef CURL_DOES_CONVERSIONS - char buf[BUFSIZE+1]; - size_t conv_size = 0; - - switch(type) { - case CURLINFO_HEADER_OUT: - /* assume output headers are ASCII */ - /* copy the data into my buffer so the original is unchanged */ - if(size > BUFSIZE) { - size = BUFSIZE; /* truncate if necessary */ - buf[BUFSIZE] = '\0'; - } - conv_size = size; - memcpy(buf, ptr, size); - /* Special processing is needed for this block if it - * contains both headers and data (separated by CRLFCRLF). - * We want to convert just the headers, leaving the data as-is. - */ - if(size > 4) { - size_t i; - for(i = 0; i < size-4; i++) { - if(memcmp(&buf[i], "\x0d\x0a\x0d\x0a", 4) == 0) { - /* convert everything through this CRLFCRLF but no further */ - conv_size = i + 4; - break; - } - } - } - - Curl_convert_from_network(data, buf, conv_size); - /* Curl_convert_from_network calls failf if unsuccessful */ - /* we might as well continue even if it fails... */ - ptr = buf; /* switch pointer to use my buffer instead */ - break; - default: - /* leave everything else as-is */ - break; - } -#endif /* CURL_DOES_CONVERSIONS */ - - if(data->set.fdebug) - return (*data->set.fdebug)(data, type, ptr, size, - data->set.debugdata); - - switch(type) { - case CURLINFO_TEXT: - case CURLINFO_HEADER_OUT: - case CURLINFO_HEADER_IN: - fwrite(s_infotype[type], 2, 1, data->set.err); - fwrite(ptr, size, 1, data->set.err); -#ifdef CURL_DOES_CONVERSIONS - if(size != conv_size) { - /* we had untranslated data so we need an explicit newline */ - fwrite("\n", 1, 1, data->set.err); - } -#endif - break; - default: /* nada */ - break; - } - return 0; -} - -int Curl_debug(struct SessionHandle *data, curl_infotype type, - char *ptr, size_t size, - struct connectdata *conn) -{ - int rc; - if(data->set.printhost && conn && conn->host.dispname) { - char buffer[160]; - const char *t=NULL; - const char *w="Data"; - switch (type) { - case CURLINFO_HEADER_IN: - w = "Header"; - case CURLINFO_DATA_IN: - t = "from"; - break; - case CURLINFO_HEADER_OUT: - w = "Header"; - case CURLINFO_DATA_OUT: - t = "to"; - break; - default: - break; - } - - if(t) { - snprintf(buffer, sizeof(buffer), "[%s %s %s]", w, t, - conn->host.dispname); - rc = showit(data, CURLINFO_TEXT, buffer, strlen(buffer)); - if(rc) - return rc; - } - } - rc = showit(data, type, ptr, size); - return rc; -} diff --git a/lib/share.c b/lib/share.c deleted file mode 100644 index 182e6e99b..000000000 --- a/lib/share.c +++ /dev/null @@ -1,254 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include -#include "curl_urldata.h" -#include "curl_share.h" -#include "curl_sslgen.h" -#include "curl_memory.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -CURLSH * -curl_share_init(void) -{ - struct Curl_share *share = calloc(1, sizeof(struct Curl_share)); - if(share) - share->specifier |= (1<dirty) - /* don't allow setting options while one or more handles are already - using this share */ - return CURLSHE_IN_USE; - - va_start(param, option); - - switch(option) { - case CURLSHOPT_SHARE: - /* this is a type this share will share */ - type = va_arg(param, int); - share->specifier |= (1<hostcache) { - share->hostcache = Curl_mk_dnscache(); - if(!share->hostcache) - res = CURLSHE_NOMEM; - } - break; - - case CURL_LOCK_DATA_COOKIE: -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(!share->cookies) { - share->cookies = Curl_cookie_init(NULL, NULL, NULL, TRUE ); - if(!share->cookies) - res = CURLSHE_NOMEM; - } -#else /* CURL_DISABLE_HTTP */ - res = CURLSHE_NOT_BUILT_IN; -#endif - break; - - case CURL_LOCK_DATA_SSL_SESSION: -#ifdef USE_SSL - if(!share->sslsession) { - share->max_ssl_sessions = 8; - share->sslsession = calloc(share->max_ssl_sessions, - sizeof(struct curl_ssl_session)); - share->sessionage = 0; - if(!share->sslsession) - res = CURLSHE_NOMEM; - } -#else - res = CURLSHE_NOT_BUILT_IN; -#endif - break; - - case CURL_LOCK_DATA_CONNECT: /* not supported (yet) */ - break; - - default: - res = CURLSHE_BAD_OPTION; - } - break; - - case CURLSHOPT_UNSHARE: - /* this is a type this share will no longer share */ - type = va_arg(param, int); - share->specifier &= ~(1<hostcache) { - Curl_hash_destroy(share->hostcache); - share->hostcache = NULL; - } - break; - - case CURL_LOCK_DATA_COOKIE: -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(share->cookies) { - Curl_cookie_cleanup(share->cookies); - share->cookies = NULL; - } -#else /* CURL_DISABLE_HTTP */ - res = CURLSHE_NOT_BUILT_IN; -#endif - break; - - case CURL_LOCK_DATA_SSL_SESSION: -#ifdef USE_SSL - Curl_safefree(share->sslsession); -#else - res = CURLSHE_NOT_BUILT_IN; -#endif - break; - - case CURL_LOCK_DATA_CONNECT: - break; - - default: - res = CURLSHE_BAD_OPTION; - break; - } - break; - - case CURLSHOPT_LOCKFUNC: - lockfunc = va_arg(param, curl_lock_function); - share->lockfunc = lockfunc; - break; - - case CURLSHOPT_UNLOCKFUNC: - unlockfunc = va_arg(param, curl_unlock_function); - share->unlockfunc = unlockfunc; - break; - - case CURLSHOPT_USERDATA: - ptr = va_arg(param, void *); - share->clientdata = ptr; - break; - - default: - res = CURLSHE_BAD_OPTION; - break; - } - - va_end(param); - - return res; -} - -CURLSHcode -curl_share_cleanup(CURLSH *sh) -{ - struct Curl_share *share = (struct Curl_share *)sh; - - if(share == NULL) - return CURLSHE_INVALID; - - if(share->lockfunc) - share->lockfunc(NULL, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE, - share->clientdata); - - if(share->dirty) { - if(share->unlockfunc) - share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata); - return CURLSHE_IN_USE; - } - - if(share->hostcache) { - Curl_hash_destroy(share->hostcache); - share->hostcache = NULL; - } - -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(share->cookies) - Curl_cookie_cleanup(share->cookies); -#endif - -#ifdef USE_SSL - if(share->sslsession) { - size_t i; - for(i = 0; i < share->max_ssl_sessions; i++) - Curl_ssl_kill_session(&(share->sslsession[i])); - free(share->sslsession); - } -#endif - - if(share->unlockfunc) - share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata); - free(share); - - return CURLSHE_OK; -} - - -CURLSHcode -Curl_share_lock(struct SessionHandle *data, curl_lock_data type, - curl_lock_access accesstype) -{ - struct Curl_share *share = data->share; - - if(share == NULL) - return CURLSHE_INVALID; - - if(share->specifier & (1<lockfunc) /* only call this if set! */ - share->lockfunc(data, type, accesstype, share->clientdata); - } - /* else if we don't share this, pretend successful lock */ - - return CURLSHE_OK; -} - -CURLSHcode -Curl_share_unlock(struct SessionHandle *data, curl_lock_data type) -{ - struct Curl_share *share = data->share; - - if(share == NULL) - return CURLSHE_INVALID; - - if(share->specifier & (1<unlockfunc) /* only call this if set! */ - share->unlockfunc (data, type, share->clientdata); - } - - return CURLSHE_OK; -} diff --git a/lib/slist.c b/lib/slist.c deleted file mode 100644 index 2a30ea620..000000000 --- a/lib/slist.c +++ /dev/null @@ -1,127 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_memory.h" -#include "curl_slist.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* returns last node in linked list */ -static struct curl_slist *slist_get_last(struct curl_slist *list) -{ - struct curl_slist *item; - - /* if caller passed us a NULL, return now */ - if(!list) - return NULL; - - /* loop through to find the last item */ - item = list; - while(item->next) { - item = item->next; - } - return item; -} - -/* - * curl_slist_append() appends a string to the linked list. It always returns - * the address of the first record, so that you can use this function as an - * initialization function as well as an append function. If you find this - * bothersome, then simply create a separate _init function and call it - * appropriately from within the program. - */ -struct curl_slist *curl_slist_append(struct curl_slist *list, - const char *data) -{ - struct curl_slist *last; - struct curl_slist *new_item; - - new_item = malloc(sizeof(struct curl_slist)); - if(new_item) { - char *dupdata = strdup(data); - if(dupdata) { - new_item->next = NULL; - new_item->data = dupdata; - } - else { - free(new_item); - return NULL; - } - } - else - return NULL; - - if(list) { - last = slist_get_last(list); - last->next = new_item; - return list; - } - - /* if this is the first item, then new_item *is* the list */ - return new_item; -} - -/* - * Curl_slist_duplicate() duplicates a linked list. It always returns the - * address of the first record of the cloned list or NULL in case of an - * error (or if the input list was NULL). - */ -struct curl_slist *Curl_slist_duplicate(struct curl_slist *inlist) -{ - struct curl_slist *outlist = NULL; - struct curl_slist *tmp; - - while(inlist) { - tmp = curl_slist_append(outlist, inlist->data); - - if(!tmp) { - curl_slist_free_all(outlist); - return NULL; - } - - outlist = tmp; - inlist = inlist->next; - } - return outlist; -} - -/* be nice and clean up resources */ -void curl_slist_free_all(struct curl_slist *list) -{ - struct curl_slist *next; - struct curl_slist *item; - - if(!list) - return; - - item = list; - do { - next = item->next; - Curl_safefree(item->data); - free(item); - item = next; - } while(next); -} - diff --git a/lib/smtp.c b/lib/smtp.c deleted file mode 100644 index 5c4c25c6f..000000000 --- a/lib/smtp.c +++ /dev/null @@ -1,1750 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - * RFC2821 SMTP protocol - * RFC3207 SMTP over TLS - * RFC4954 SMTP Authentication - * RFC2195 CRAM-MD5 authentication - * RFC2831 DIGEST-MD5 authentication - * RFC4616 PLAIN authentication - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_SMTP - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_UTSNAME_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -#include -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_if2ip.h" -#include "curl_hostip.h" -#include "curl_progress.h" -#include "curl_transfer.h" -#include "curl_escape.h" -#include "curl_http.h" /* for HTTP proxy tunnel stuff */ -#include "curl_socks.h" -#include "curl_smtp.h" - -#include "curl_strtoofft.h" -#include "curl_strequal.h" -#include "curl_sslgen.h" -#include "curl_connect.h" -#include "curl_strerror.h" -#include "curl_select.h" -#include "curl_multiif.h" -#include "curl_url.h" -#include "curl_rawstr.h" -#include "curl_gethostname.h" -#include "curl_sasl.h" -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* Local API functions */ -static CURLcode smtp_regular_transfer(struct connectdata *conn, bool *done); -static CURLcode smtp_do(struct connectdata *conn, bool *done); -static CURLcode smtp_done(struct connectdata *conn, CURLcode status, - bool premature); -static CURLcode smtp_connect(struct connectdata *conn, bool *done); -static CURLcode smtp_disconnect(struct connectdata *conn, bool dead); -static CURLcode smtp_multi_statemach(struct connectdata *conn, bool *done); -static int smtp_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -static CURLcode smtp_doing(struct connectdata *conn, bool *dophase_done); -static CURLcode smtp_setup_connection(struct connectdata *conn); -static CURLcode smtp_state_upgrade_tls(struct connectdata *conn); - -/* - * SMTP protocol handler. - */ - -const struct Curl_handler Curl_handler_smtp = { - "SMTP", /* scheme */ - smtp_setup_connection, /* setup_connection */ - smtp_do, /* do_it */ - smtp_done, /* done */ - ZERO_NULL, /* do_more */ - smtp_connect, /* connect_it */ - smtp_multi_statemach, /* connecting */ - smtp_doing, /* doing */ - smtp_getsock, /* proto_getsock */ - smtp_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_SMTP, /* defport */ - CURLPROTO_SMTP, /* protocol */ - PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY /* flags */ -}; - -#ifdef USE_SSL -/* - * SMTPS protocol handler. - */ - -const struct Curl_handler Curl_handler_smtps = { - "SMTPS", /* scheme */ - smtp_setup_connection, /* setup_connection */ - smtp_do, /* do_it */ - smtp_done, /* done */ - ZERO_NULL, /* do_more */ - smtp_connect, /* connect_it */ - smtp_multi_statemach, /* connecting */ - smtp_doing, /* doing */ - smtp_getsock, /* proto_getsock */ - smtp_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_SMTPS, /* defport */ - CURLPROTO_SMTP | CURLPROTO_SMTPS, /* protocol */ - PROTOPT_CLOSEACTION | PROTOPT_SSL - | PROTOPT_NOURLQUERY /* flags */ -}; -#endif - -#ifndef CURL_DISABLE_HTTP -/* - * HTTP-proxyed SMTP protocol handler. - */ - -static const struct Curl_handler Curl_handler_smtp_proxy = { - "SMTP", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_SMTP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -#ifdef USE_SSL -/* - * HTTP-proxyed SMTPS protocol handler. - */ - -static const struct Curl_handler Curl_handler_smtps_proxy = { - "SMTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_SMTPS, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; -#endif -#endif - -/* Function that checks for an ending smtp status code at the start of the - given string, but also detects the supported authentication mechanisms - from the EHLO AUTH response. */ -static int smtp_endofresp(struct pingpong *pp, int *resp) -{ - char *line = pp->linestart_resp; - size_t len = pp->nread_resp; - struct connectdata *conn = pp->conn; - struct smtp_conn *smtpc = &conn->proto.smtpc; - int result; - size_t wordlen; - - if(len < 4 || !ISDIGIT(line[0]) || !ISDIGIT(line[1]) || !ISDIGIT(line[2])) - return FALSE; /* Nothing for us */ - - if((result = (line[3] == ' ')) != 0) - *resp = curlx_sltosi(strtol(line, NULL, 10)); - - line += 4; - len -= 4; - - if(smtpc->state == SMTP_EHLO && len >= 4 && !memcmp(line, "SIZE", 4)) { - DEBUGF(infof(conn->data, "Server supports SIZE extension.\n")); - smtpc->size_supported = true; - } - - if(smtpc->state == SMTP_EHLO && len >= 5 && !memcmp(line, "AUTH ", 5)) { - line += 5; - len -= 5; - - for(;;) { - while(len && - (*line == ' ' || *line == '\t' || - *line == '\r' || *line == '\n')) { - line++; - len--; - } - - if(!len) - break; - - for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && - line[wordlen] != '\t' && line[wordlen] != '\r' && - line[wordlen] != '\n';) - wordlen++; - - if(wordlen == 5 && !memcmp(line, "LOGIN", 5)) - smtpc->authmechs |= SASL_MECH_LOGIN; - else if(wordlen == 5 && !memcmp(line, "PLAIN", 5)) - smtpc->authmechs |= SASL_MECH_PLAIN; - else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8)) - smtpc->authmechs |= SASL_MECH_CRAM_MD5; - else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10)) - smtpc->authmechs |= SASL_MECH_DIGEST_MD5; - else if(wordlen == 6 && !memcmp(line, "GSSAPI", 6)) - smtpc->authmechs |= SASL_MECH_GSSAPI; - else if(wordlen == 8 && !memcmp(line, "EXTERNAL", 8)) - smtpc->authmechs |= SASL_MECH_EXTERNAL; - else if(wordlen == 4 && !memcmp(line, "NTLM", 4)) - smtpc->authmechs |= SASL_MECH_NTLM; - - line += wordlen; - len -= wordlen; - } - } - - return result; -} - -/* This is the ONLY way to change SMTP state! */ -static void state(struct connectdata *conn, smtpstate newstate) -{ - struct smtp_conn *smtpc = &conn->proto.smtpc; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ - static const char * const names[] = { - "STOP", - "SERVERGREET", - "EHLO", - "HELO", - "STARTTLS", - "UPGRADETLS", - "AUTH_PLAIN", - "AUTH_LOGIN", - "AUTH_PASSWD", - "AUTH_CRAMMD5", - "AUTH_DIGESTMD5", - "AUTH_DIGESTMD5_RESP", - "AUTH_NTLM", - "AUTH_NTLM_TYPE2MSG", - "AUTH", - "MAIL", - "RCPT", - "DATA", - "POSTDATA", - "QUIT", - /* LAST */ - }; - - if(smtpc->state != newstate) - infof(conn->data, "SMTP %p state change from %s to %s\n", - smtpc, names[smtpc->state], names[newstate]); -#endif - - smtpc->state = newstate; -} - -static CURLcode smtp_state_ehlo(struct connectdata *conn) -{ - CURLcode result; - struct smtp_conn *smtpc = &conn->proto.smtpc; - - smtpc->authmechs = 0; /* No known authentication mechanisms yet */ - smtpc->authused = 0; /* Clear the authentication mechanism used - for esmtp connections */ - - /* send EHLO */ - result = Curl_pp_sendf(&smtpc->pp, "EHLO %s", smtpc->domain); - - if(result) - return result; - - state(conn, SMTP_EHLO); - - return CURLE_OK; -} - -static CURLcode smtp_state_helo(struct connectdata *conn) -{ - CURLcode result; - struct smtp_conn *smtpc = &conn->proto.smtpc; - - smtpc->authused = 0; /* No authentication mechanism used in smtp - connections */ - - /* send HELO */ - result = Curl_pp_sendf(&smtpc->pp, "HELO %s", smtpc->domain); - - if(result) - return result; - - state(conn, SMTP_HELO); - - return CURLE_OK; -} - -static CURLcode smtp_authenticate(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; - char *initresp = NULL; - const char *mech = NULL; - size_t len = 0; - smtpstate state1 = SMTP_STOP; - smtpstate state2 = SMTP_STOP; - - /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ - if(!conn->bits.user_passwd) { - state(conn, SMTP_STOP); - - return result; - } - - /* Check supported authentication mechanisms by decreasing order of - security */ -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(smtpc->authmechs & SASL_MECH_DIGEST_MD5) { - mech = "DIGEST-MD5"; - state1 = SMTP_AUTH_DIGESTMD5; - smtpc->authused = SASL_MECH_DIGEST_MD5; - } - else if(smtpc->authmechs & SASL_MECH_CRAM_MD5) { - mech = "CRAM-MD5"; - state1 = SMTP_AUTH_CRAMMD5; - smtpc->authused = SASL_MECH_CRAM_MD5; - } - else -#endif -#ifdef USE_NTLM - if(smtpc->authmechs & SASL_MECH_NTLM) { - mech = "NTLM"; - state1 = SMTP_AUTH_NTLM; - state2 = SMTP_AUTH_NTLM_TYPE2MSG; - smtpc->authused = SASL_MECH_NTLM; - result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, - &conn->ntlm, - &initresp, &len); - } - else -#endif - if(smtpc->authmechs & SASL_MECH_LOGIN) { - mech = "LOGIN"; - state1 = SMTP_AUTH_LOGIN; - state2 = SMTP_AUTH_PASSWD; - smtpc->authused = SASL_MECH_LOGIN; - result = Curl_sasl_create_login_message(conn->data, conn->user, - &initresp, &len); - } - else if(smtpc->authmechs & SASL_MECH_PLAIN) { - mech = "PLAIN"; - state1 = SMTP_AUTH_PLAIN; - state2 = SMTP_AUTH; - smtpc->authused = SASL_MECH_PLAIN; - result = Curl_sasl_create_plain_message(conn->data, conn->user, - conn->passwd, &initresp, &len); - } - else { - infof(conn->data, "No known authentication mechanisms supported!\n"); - result = CURLE_LOGIN_DENIED; /* Other mechanisms not supported */ - } - - if(!result) { - if(initresp && - strlen(mech) + len <= 512 - 8) { /* AUTH ... */ - result = Curl_pp_sendf(&smtpc->pp, "AUTH %s %s", mech, initresp); - - if(!result) - state(conn, state2); - } - else { - result = Curl_pp_sendf(&smtpc->pp, "AUTH %s", mech); - - if(!result) - state(conn, state1); - } - Curl_safefree(initresp); - } - - return result; -} - -/* For the SMTP "protocol connect" and "doing" phases only */ -static int smtp_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - return Curl_pp_getsock(&conn->proto.smtpc.pp, socks, numsocks); -} - -#ifdef USE_SSL -static void smtp_to_smtps(struct connectdata *conn) -{ - conn->handler = &Curl_handler_smtps; -} -#else -#define smtp_to_smtps(x) Curl_nop_stmt -#endif - -/* For the initial server greeting */ -static CURLcode smtp_state_servergreet_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(smtpcode/100 != 2) { - failf(data, "Got unexpected smtp-server response: %d", smtpcode); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - - result = smtp_state_ehlo(conn); - - return result; -} - -/* For STARTTLS responses */ -static CURLcode smtp_state_starttls_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 220) { - if(data->set.use_ssl != CURLUSESSL_TRY) { - failf(data, "STARTTLS denied. %c", smtpcode); - result = CURLE_USE_SSL_FAILED; - } - else - result = smtp_authenticate(conn); - } - else { - if(data->state.used_interface == Curl_if_multi) { - state(conn, SMTP_UPGRADETLS); - result = smtp_state_upgrade_tls(conn); - } - else { - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(CURLE_OK == result) { - smtp_to_smtps(conn); - result = smtp_state_ehlo(conn); - } - } - } - - return result; -} - -static CURLcode smtp_state_upgrade_tls(struct connectdata *conn) -{ - struct smtp_conn *smtpc = &conn->proto.smtpc; - CURLcode result; - - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &smtpc->ssldone); - - if(smtpc->ssldone) { - smtp_to_smtps(conn); - result = smtp_state_ehlo(conn); - } - - return result; -} - -/* For EHLO responses */ -static CURLcode smtp_state_ehlo_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(smtpcode/100 != 2) { - if((data->set.use_ssl <= CURLUSESSL_TRY || conn->ssl[FIRSTSOCKET].use) && - !conn->bits.user_passwd) - result = smtp_state_helo(conn); - else { - failf(data, "Remote access denied: %d", smtpcode); - result = CURLE_REMOTE_ACCESS_DENIED; - } - } - else if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch - to TLS connection now */ - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "STARTTLS"); - state(conn, SMTP_STARTTLS); - } - else - result = smtp_authenticate(conn); - - return result; -} - -/* For HELO responses */ -static CURLcode smtp_state_helo_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(smtpcode/100 != 2) { - failf(data, "Remote access denied: %d", smtpcode); - result = CURLE_REMOTE_ACCESS_DENIED; - } - else - /* End of connect phase */ - state(conn, SMTP_STOP); - - return result; -} - -/* For AUTH PLAIN (without initial response) responses */ -static CURLcode smtp_state_auth_plain_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - size_t len = 0; - char *plainauth = NULL; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 334) { - failf(data, "Access denied: %d", smtpcode); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the authorisation message */ - result = Curl_sasl_create_plain_message(conn->data, conn->user, - conn->passwd, &plainauth, &len); - - /* Send the message */ - if(!result) { - if(plainauth) { - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", plainauth); - - if(!result) - state(conn, SMTP_AUTH); - } - - Curl_safefree(plainauth); - } - } - - return result; -} - -/* For AUTH LOGIN (without initial response) responses */ -static CURLcode smtp_state_auth_login_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - size_t len = 0; - char *authuser = NULL; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 334) { - failf(data, "Access denied: %d", smtpcode); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the user message */ - result = Curl_sasl_create_login_message(conn->data, conn->user, - &authuser, &len); - - /* Send the user */ - if(!result) { - if(authuser) { - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", authuser); - - if(!result) - state(conn, SMTP_AUTH_PASSWD); - } - - Curl_safefree(authuser); - } - } - - return result; -} - -/* For responses to user entry of AUTH LOGIN */ -static CURLcode smtp_state_auth_passwd_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - size_t len = 0; - char *authpasswd = NULL; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 334) { - failf(data, "Access denied: %d", smtpcode); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the password message */ - result = Curl_sasl_create_login_message(conn->data, conn->passwd, - &authpasswd, &len); - - /* Send the password */ - if(!result) { - if(authpasswd) { - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", authpasswd); - - if(!result) - state(conn, SMTP_AUTH); - } - - Curl_safefree(authpasswd); - } - } - - return result; -} - -#ifndef CURL_DISABLE_CRYPTO_AUTH -/* For AUTH CRAM-MD5 responses */ -static CURLcode smtp_state_auth_cram_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - char *chlg64 = data->state.buffer; - size_t len = 0; - char *rplyb64 = NULL; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 334) { - failf(data, "Access denied: %d", smtpcode); - return CURLE_LOGIN_DENIED; - } - - /* Get the challenge */ - for(chlg64 += 4; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++) - ; - - /* Terminate the challenge */ - if(*chlg64 != '=') { - for(len = strlen(chlg64); len--;) - if(chlg64[len] != '\r' && chlg64[len] != '\n' && chlg64[len] != ' ' && - chlg64[len] != '\t') - break; - - if(++len) { - chlg64[len] = '\0'; - } - } - - /* Create the response message */ - result = Curl_sasl_create_cram_md5_message(data, chlg64, conn->user, - conn->passwd, &rplyb64, &len); - - /* Send the response */ - if(!result) { - if(rplyb64) { - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", rplyb64); - - if(!result) - state(conn, SMTP_AUTH); - } - - Curl_safefree(rplyb64); - } - - return result; -} - -/* For AUTH DIGEST-MD5 challenge responses */ -static CURLcode smtp_state_auth_digest_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - char *chlg64 = data->state.buffer; - size_t len = 0; - char *rplyb64 = NULL; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 334) { - failf(data, "Access denied: %d", smtpcode); - return CURLE_LOGIN_DENIED; - } - - /* Get the challenge */ - for(chlg64 += 4; *chlg64 == ' ' || *chlg64 == '\t'; chlg64++) - ; - - /* Create the response message */ - result = Curl_sasl_create_digest_md5_message(data, chlg64, conn->user, - conn->passwd, "smtp", - &rplyb64, &len); - - /* Send the response */ - if(!result) { - if(rplyb64) { - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", rplyb64); - - if(!result) - state(conn, SMTP_AUTH_DIGESTMD5_RESP); - } - - Curl_safefree(rplyb64); - } - - return result; -} - -/* For AUTH DIGEST-MD5 challenge-response responses */ -static CURLcode smtp_state_auth_digest_resp_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 334) { - failf(data, "Authentication failed: %d", smtpcode); - result = CURLE_LOGIN_DENIED; - } - else { - /* Send an empty response */ - result = Curl_pp_sendf(&conn->proto.smtpc.pp, ""); - - if(!result) - state(conn, SMTP_AUTH); - } - - return result; -} - -#endif - -#ifdef USE_NTLM -/* For AUTH NTLM (without initial response) responses */ -static CURLcode smtp_state_auth_ntlm_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - char *type1msg = NULL; - size_t len = 0; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 334) { - failf(data, "Access denied: %d", smtpcode); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the type-1 message */ - result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, - &conn->ntlm, - &type1msg, &len); - - /* Send the message */ - if(!result) { - if(type1msg) { - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", type1msg); - - if(!result) - state(conn, SMTP_AUTH_NTLM_TYPE2MSG); - } - - Curl_safefree(type1msg); - } - } - - return result; -} - -/* For NTLM type-2 responses (sent in reponse to our type-1 message) */ -static CURLcode smtp_state_auth_ntlm_type2msg_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - char *type3msg = NULL; - size_t len = 0; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 334) { - failf(data, "Access denied: %d", smtpcode); - result = CURLE_LOGIN_DENIED; - } - else { - /* Create the type-3 message */ - result = Curl_sasl_create_ntlm_type3_message(data, - data->state.buffer + 4, - conn->user, conn->passwd, - &conn->ntlm, - &type3msg, &len); - - /* Send the message */ - if(!result) { - if(type3msg) { - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", type3msg); - - if(!result) - state(conn, SMTP_AUTH); - } - - Curl_safefree(type3msg); - } - } - - return result; -} -#endif - -/* For the final responses to the AUTH sequence */ -static CURLcode smtp_state_auth_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 235) { - failf(data, "Authentication failed: %d", smtpcode); - result = CURLE_LOGIN_DENIED; - } - else - /* End of connect phase */ - state(conn, SMTP_STOP); - - return result; -} - -/* Start the DO phase */ -static CURLcode smtp_mail(struct connectdata *conn) -{ - char *from = NULL; - char *auth = NULL; - char *size = NULL; - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - /* Calculate the FROM parameter */ - if(!data->set.str[STRING_MAIL_FROM]) - /* Null reverse-path, RFC-2821, sect. 3.7 */ - from = strdup("<>"); - else if(data->set.str[STRING_MAIL_FROM][0] == '<') - from = aprintf("%s", data->set.str[STRING_MAIL_FROM]); - else - from = aprintf("<%s>", data->set.str[STRING_MAIL_FROM]); - - if(!from) - return CURLE_OUT_OF_MEMORY; - - /* Calculate the optional AUTH parameter */ - if(data->set.str[STRING_MAIL_AUTH] && conn->proto.smtpc.authused) { - if(data->set.str[STRING_MAIL_AUTH][0] != '\0') - auth = aprintf("%s", data->set.str[STRING_MAIL_AUTH]); - else - /* Empty AUTH, RFC-2554, sect. 5 */ - auth = strdup("<>"); - - if(!auth) { - Curl_safefree(from); - - return CURLE_OUT_OF_MEMORY; - } - } - - /* calculate the optional SIZE parameter */ - if(conn->proto.smtpc.size_supported && conn->data->set.infilesize > 0) { - size = aprintf("%" FORMAT_OFF_T, data->set.infilesize); - - if(!size) { - Curl_safefree(from); - Curl_safefree(auth); - - return CURLE_OUT_OF_MEMORY; - } - } - - /* Send the MAIL command */ - if(!auth && !size) - result = Curl_pp_sendf(&conn->proto.smtpc.pp, - "MAIL FROM:%s", from); - else if(auth && !size) - result = Curl_pp_sendf(&conn->proto.smtpc.pp, - "MAIL FROM:%s AUTH=%s", from, auth); - else if(auth && size) - result = Curl_pp_sendf(&conn->proto.smtpc.pp, - "MAIL FROM:%s AUTH=%s SIZE=%s", from, auth, size); - else - result = Curl_pp_sendf(&conn->proto.smtpc.pp, - "MAIL FROM:%s SIZE=%s", from, size); - - Curl_safefree(from); - Curl_safefree(auth); - Curl_safefree(size); - - if(result) - return result; - - state(conn, SMTP_MAIL); - - return result; -} - -static CURLcode smtp_rcpt_to(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; - - /* Send the RCPT TO command */ - if(smtpc->rcpt) { - if(smtpc->rcpt->data[0] == '<') - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "RCPT TO:%s", - smtpc->rcpt->data); - else - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "RCPT TO:<%s>", - smtpc->rcpt->data); - if(!result) - state(conn, SMTP_RCPT); - } - - return result; -} - -/* For MAIL responses */ -static CURLcode smtp_state_mail_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(smtpcode/100 != 2) { - failf(data, "MAIL failed: %d", smtpcode); - result = CURLE_SEND_ERROR; - state(conn, SMTP_STOP); - } - else { - struct smtp_conn *smtpc = &conn->proto.smtpc; - smtpc->rcpt = data->set.mail_rcpt; - - result = smtp_rcpt_to(conn); - } - - return result; -} - -/* For RCPT responses */ -static CURLcode smtp_state_rcpt_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - (void)instate; /* no use for this yet */ - - if(smtpcode/100 != 2) { - failf(data, "RCPT failed: %d", smtpcode); - result = CURLE_SEND_ERROR; - state(conn, SMTP_STOP); - } - else { - struct smtp_conn *smtpc = &conn->proto.smtpc; - - if(smtpc->rcpt) { - smtpc->rcpt = smtpc->rcpt->next; - result = smtp_rcpt_to(conn); - - /* If we failed or still are sending RCPT data then return */ - if(result || smtpc->rcpt) - return result; - } - - /* Send the DATA command */ - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "DATA"); - - if(result) - return result; - - state(conn, SMTP_DATA); - } - - return result; -} - -/* For DATA response */ -static CURLcode smtp_state_data_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - struct SessionHandle *data = conn->data; - struct FTP *smtp = data->state.proto.smtp; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 354) { - state(conn, SMTP_STOP); - return CURLE_SEND_ERROR; - } - - /* SMTP upload */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ - FIRSTSOCKET, smtp->bytecountp); - - /* End of do phase */ - state(conn, SMTP_STOP); - - return CURLE_OK; -} - -/* For POSTDATA responses, which are received after the entire DATA - part has been sent to the server */ -static CURLcode smtp_state_postdata_resp(struct connectdata *conn, - int smtpcode, - smtpstate instate) -{ - CURLcode result = CURLE_OK; - - (void)instate; /* no use for this yet */ - - if(smtpcode != 250) - result = CURLE_RECV_ERROR; - - /* End of done phase */ - state(conn, SMTP_STOP); - - return result; -} - -static CURLcode smtp_statemach_act(struct connectdata *conn) -{ - CURLcode result; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - struct SessionHandle *data = conn->data; - int smtpcode; - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct pingpong *pp = &smtpc->pp; - size_t nread = 0; - - /* Busy upgrading the connection; right now all I/O is SSL/TLS, not SMTP */ - if(smtpc->state == SMTP_UPGRADETLS) - return smtp_state_upgrade_tls(conn); - - /* Flush any data that needs to be sent */ - if(pp->sendleft) - return Curl_pp_flushsend(pp); - - /* Read the response from the server */ - result = Curl_pp_readresp(sock, pp, &smtpcode, &nread); - if(result) - return result; - - /* Store the latest response for later retrieval */ - if(smtpc->state != SMTP_QUIT) - data->info.httpcode = smtpcode; - - if(smtpcode) { - /* We have now received a full SMTP server response */ - switch(smtpc->state) { - case SMTP_SERVERGREET: - result = smtp_state_servergreet_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_EHLO: - result = smtp_state_ehlo_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_HELO: - result = smtp_state_helo_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_STARTTLS: - result = smtp_state_starttls_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_AUTH_PLAIN: - result = smtp_state_auth_plain_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_AUTH_LOGIN: - result = smtp_state_auth_login_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_AUTH_PASSWD: - result = smtp_state_auth_passwd_resp(conn, smtpcode, smtpc->state); - break; - -#ifndef CURL_DISABLE_CRYPTO_AUTH - case SMTP_AUTH_CRAMMD5: - result = smtp_state_auth_cram_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_AUTH_DIGESTMD5: - result = smtp_state_auth_digest_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_AUTH_DIGESTMD5_RESP: - result = smtp_state_auth_digest_resp_resp(conn, smtpcode, smtpc->state); - break; -#endif - -#ifdef USE_NTLM - case SMTP_AUTH_NTLM: - result = smtp_state_auth_ntlm_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_AUTH_NTLM_TYPE2MSG: - result = smtp_state_auth_ntlm_type2msg_resp(conn, smtpcode, - smtpc->state); - break; -#endif - - case SMTP_AUTH: - result = smtp_state_auth_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_MAIL: - result = smtp_state_mail_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_RCPT: - result = smtp_state_rcpt_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_DATA: - result = smtp_state_data_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_POSTDATA: - result = smtp_state_postdata_resp(conn, smtpcode, smtpc->state); - break; - - case SMTP_QUIT: - /* fallthrough, just stop! */ - default: - /* internal error */ - state(conn, SMTP_STOP); - break; - } - } - - return result; -} - -/* Called repeatedly until done from curl_multi.c */ -static CURLcode smtp_multi_statemach(struct connectdata *conn, bool *done) -{ - struct smtp_conn *smtpc = &conn->proto.smtpc; - CURLcode result; - - if((conn->handler->flags & PROTOPT_SSL) && !smtpc->ssldone) - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &smtpc->ssldone); - else - result = Curl_pp_multi_statemach(&smtpc->pp); - - *done = (smtpc->state == SMTP_STOP) ? TRUE : FALSE; - - return result; -} - -static CURLcode smtp_easy_statemach(struct connectdata *conn) -{ - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct pingpong *pp = &smtpc->pp; - CURLcode result = CURLE_OK; - - while(smtpc->state != SMTP_STOP) { - result = Curl_pp_easy_statemach(pp); - if(result) - break; - } - - return result; -} - -/* Allocate and initialize the SMTP struct for the current SessionHandle if - required */ -static CURLcode smtp_init(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - struct FTP *smtp = data->state.proto.smtp; - - if(!smtp) { - smtp = data->state.proto.smtp = calloc(sizeof(struct FTP), 1); - if(!smtp) - return CURLE_OUT_OF_MEMORY; - } - - /* Get some initial data into the smtp struct */ - smtp->bytecountp = &data->req.bytecount; - - /* No need to duplicate user+password, the connectdata struct won't change - during a session, but we re-init them here since on subsequent inits - since the conn struct may have changed or been replaced. - */ - smtp->user = conn->user; - smtp->passwd = conn->passwd; - - return CURLE_OK; -} - -/*********************************************************************** - * - * smtp_connect() - * - * This function should do everything that is to be considered a part of - * the connection phase. - * - * The variable pointed to by 'done' will be TRUE if the protocol-layer - * connect phase is done when this function returns, or FALSE if not. When - * called as a part of the easy interface, it will always be TRUE. - */ -static CURLcode smtp_connect(struct connectdata *conn, bool *done) -{ - CURLcode result; - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct SessionHandle *data = conn->data; - struct pingpong *pp = &smtpc->pp; - const char *path = conn->data->state.path; - char localhost[HOSTNAME_MAX + 1]; - - *done = FALSE; /* default to not done yet */ - - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - result = smtp_init(conn); - if(CURLE_OK != result) - return result; - - /* We always support persistent connections on smtp */ - conn->bits.close = FALSE; - - pp->response_time = RESP_TIMEOUT; /* set default response time-out */ - pp->statemach_act = smtp_statemach_act; - pp->endofresp = smtp_endofresp; - pp->conn = conn; - - if((conn->handler->protocol & CURLPROTO_SMTPS) && - data->state.used_interface != Curl_if_multi) { - /* SMTPS is simply smtp with SSL for the control channel */ - /* so perform the SSL initialization for this socket */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); - if(result) - return result; - } - - /* Initialise the response reader stuff */ - Curl_pp_init(pp); - - /* Set the default response time-out */ - pp->response_time = RESP_TIMEOUT; - pp->statemach_act = smtp_statemach_act; - pp->endofresp = smtp_endofresp; - pp->conn = conn; - - /* Calculate the path if necessary */ - if(!*path) { - if(!Curl_gethostname(localhost, sizeof(localhost))) - path = localhost; - else - path = "localhost"; - } - - /* URL decode the path and use it as the domain in our EHLO */ - result = Curl_urldecode(conn->data, path, 0, &smtpc->domain, NULL, TRUE); - if(result) - return result; - - /* Start off waiting for the server greeting response */ - state(conn, SMTP_SERVERGREET); - - if(data->state.used_interface == Curl_if_multi) - result = smtp_multi_statemach(conn, done); - else { - result = smtp_easy_statemach(conn); - if(!result) - *done = TRUE; - } - - return result; -} - -/*********************************************************************** - * - * smtp_done() - * - * The DONE function. This does what needs to be done after a single DO has - * performed. - * - * Input argument is already checked for validity. - */ -static CURLcode smtp_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - struct SessionHandle *data = conn->data; - struct FTP *smtp = data->state.proto.smtp; - CURLcode result = CURLE_OK; - ssize_t bytes_written; - - (void)premature; - - if(!smtp) - /* When the easy handle is removed from the multi while libcurl is still - * trying to resolve the host name, it seems that the smtp struct is not - * yet initialized, but the removal action calls Curl_done() which calls - * this function. So we simply return success if no smtp pointer is set. - */ - return CURLE_OK; - - if(status) { - conn->bits.close = TRUE; /* marked for closure */ - result = status; /* use the already set error code */ - } - else if(!data->set.connect_only) { - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct pingpong *pp = &smtpc->pp; - - /* Send the end of block data */ - result = Curl_write(conn, - conn->writesockfd, /* socket to send to */ - SMTP_EOB, /* buffer pointer */ - SMTP_EOB_LEN, /* buffer size */ - &bytes_written); /* actually sent away */ - - if(result) - return result; - - if(bytes_written != SMTP_EOB_LEN) { - /* The whole chunk was not sent so keep it around and adjust the - pingpong structure accordingly */ - pp->sendthis = strdup(SMTP_EOB); - pp->sendsize = SMTP_EOB_LEN; - pp->sendleft = SMTP_EOB_LEN - bytes_written; - } - else - /* Successfully sent so adjust the response timeout relative to now */ - pp->response = Curl_tvnow(); - - state(conn, SMTP_POSTDATA); - - /* Run the state-machine - - TODO: when the multi interface is used, this _really_ should be using - the smtp_multi_statemach function but we have no general support for - non-blocking DONE operations, not in the multi state machine and with - Curl_done() invokes on several places in the code! - */ - result = smtp_easy_statemach(conn); - } - - /* Clear the transfer mode for the next connection */ - smtp->transfer = FTPTRANSFER_BODY; - - return result; -} - -/*********************************************************************** - * - * smtp_perform() - * - * This is the actual DO function for SMTP. Get a file/directory according to - * the options previously setup. - */ -static CURLcode smtp_perform(struct connectdata *conn, bool *connected, - bool *dophase_done) -{ - /* This is SMTP and no proxy */ - CURLcode result = CURLE_OK; - - DEBUGF(infof(conn->data, "DO phase starts\n")); - - if(conn->data->set.opt_no_body) { - /* Requested no body means no transfer */ - struct FTP *smtp = conn->data->state.proto.smtp; - smtp->transfer = FTPTRANSFER_INFO; - } - - *dophase_done = FALSE; /* not done yet */ - - /* Start the first command in the DO phase */ - result = smtp_mail(conn); - if(result) - return result; - - /* Run the state-machine */ - if(conn->data->state.used_interface == Curl_if_multi) - result = smtp_multi_statemach(conn, dophase_done); - else { - result = smtp_easy_statemach(conn); - *dophase_done = TRUE; /* with the easy interface we are done here */ - } - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); - - return result; -} - -/*********************************************************************** - * - * smtp_do() - * - * This function is registered as 'curl_do' function. It decodes the path - * parts etc as a wrapper to the actual DO function (smtp_perform). - * - * The input argument is already checked for validity. - */ -static CURLcode smtp_do(struct connectdata *conn, bool *done) -{ - CURLcode retcode = CURLE_OK; - - *done = FALSE; /* default to false */ - - /* - Since connections can be re-used between SessionHandles, this might be a - connection already existing but on a fresh SessionHandle struct so we must - make sure we have a good 'struct SMTP' to play with. For new connections, - the struct SMTP is allocated and setup in the smtp_connect() function. - */ - Curl_reset_reqproto(conn); - retcode = smtp_init(conn); - if(retcode) - return retcode; - - retcode = smtp_regular_transfer(conn, done); - - return retcode; -} - -/*********************************************************************** - * - * smtp_quit() - * - * This should be called before calling sclose(). We should then wait for the - * response from the server before returning. The calling code should then try - * to close the connection. - */ -static CURLcode smtp_quit(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "QUIT"); - if(result) - return result; - - state(conn, SMTP_QUIT); - - result = smtp_easy_statemach(conn); - - return result; -} - -/*********************************************************************** - * - * smtp_disconnect() - * - * Disconnect from an SMTP server. Cleanup protocol-specific per-connection - * resources. BLOCKING. - */ -static CURLcode smtp_disconnect(struct connectdata *conn, - bool dead_connection) -{ - struct smtp_conn *smtpc = &conn->proto.smtpc; - - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to */ - - /* The SMTP session may or may not have been allocated/setup at this - point! */ - if(!dead_connection && smtpc->pp.conn) - (void)smtp_quit(conn); /* ignore errors on the LOGOUT */ - - /* Disconnect from the server */ - Curl_pp_disconnect(&smtpc->pp); - - /* Cleanup the SASL module */ - Curl_sasl_cleanup(conn, smtpc->authused); - - /* Cleanup our connection based variables */ - Curl_safefree(smtpc->domain); - - return CURLE_OK; -} - -/* Call this when the DO phase has completed */ -static CURLcode smtp_dophase_done(struct connectdata *conn, bool connected) -{ - struct FTP *smtp = conn->data->state.proto.smtp; - - (void)connected; - - if(smtp->transfer != FTPTRANSFER_BODY) - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - - return CURLE_OK; -} - -/* Called from curl_multi.c while DOing */ -static CURLcode smtp_doing(struct connectdata *conn, bool *dophase_done) -{ - CURLcode result = smtp_multi_statemach(conn, dophase_done); - - if(result) - DEBUGF(infof(conn->data, "DO phase failed\n")); - else { - if(*dophase_done) { - result = smtp_dophase_done(conn, FALSE /* not connected */); - - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - } - - return result; -} - -/*********************************************************************** - * - * smtp_regular_transfer() - * - * The input argument is already checked for validity. - * - * Performs all commands done before a regular transfer between a local and a - * remote host. - */ -static CURLcode smtp_regular_transfer(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - bool connected = FALSE; - struct SessionHandle *data = conn->data; - - /* Make sure size is unknown at this point */ - data->req.size = -1; - - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - Curl_pgrsSetUploadSize(data, 0); - Curl_pgrsSetDownloadSize(data, 0); - - result = smtp_perform(conn, &connected, dophase_done); - - if(CURLE_OK == result) { - if(!*dophase_done) - /* The DO phase has not completed yet */ - return CURLE_OK; - - result = smtp_dophase_done(conn, connected); - if(result) - return result; - } - - return result; -} - -static CURLcode smtp_setup_connection(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - - if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel smtp operations through the proxy, we - switch and use HTTP operations only */ -#ifndef CURL_DISABLE_HTTP - if(conn->handler == &Curl_handler_smtp) - conn->handler = &Curl_handler_smtp_proxy; - else { -#ifdef USE_SSL - conn->handler = &Curl_handler_smtps_proxy; -#else - failf(data, "SMTPS not supported!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - /* We explicitly mark this connection as persistent here as we're doing - SMTP over HTTP and thus we accidentally avoid setting this value - otherwise */ - conn->bits.close = FALSE; -#else - failf(data, "SMTP over http proxy requires HTTP support built-in!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - data->state.path++; /* don't include the initial slash */ - - return CURLE_OK; -} - -CURLcode Curl_smtp_escape_eob(struct connectdata *conn, ssize_t nread) -{ - /* When sending a SMTP payload we must detect CRLF. sequences making sure - they are sent as CRLF.. instead, as a . on the beginning of a line will - be deleted by the server when not part of an EOB terminator and a - genuine CRLF.CRLF which isn't escaped will wrongly be detected as end of - data by the server. - */ - ssize_t i; - ssize_t si; - struct smtp_conn *smtpc = &conn->proto.smtpc; - struct SessionHandle *data = conn->data; - - /* Do we need to allocate the scatch buffer? */ - if(!data->state.scratch) { - data->state.scratch = malloc(2 * BUFSIZE); - - if(!data->state.scratch) { - failf (data, "Failed to alloc scratch buffer!"); - return CURLE_OUT_OF_MEMORY; - } - } - - /* This loop can be improved by some kind of Boyer-Moore style of - approach but that is saved for later... */ - for(i = 0, si = 0; i < nread; i++) { - if(SMTP_EOB[smtpc->eob] == data->req.upload_fromhere[i]) - smtpc->eob++; - else if(smtpc->eob) { - /* A previous substring matched so output that first */ - memcpy(&data->state.scratch[si], SMTP_EOB, smtpc->eob); - si += smtpc->eob; - - /* Then compare the first byte */ - if(SMTP_EOB[0] == data->req.upload_fromhere[i]) - smtpc->eob = 1; - else - smtpc->eob = 0; - } - - /* Do we have a match for CRLF. as per RFC-2821, sect. 4.5.2 */ - if(SMTP_EOB_FIND_LEN == smtpc->eob) { - /* Copy the replacement data to the target buffer */ - memcpy(&data->state.scratch[si], SMTP_EOB_REPL, SMTP_EOB_REPL_LEN); - si += SMTP_EOB_REPL_LEN; - smtpc->eob = 0; - } - else if(!smtpc->eob) - data->state.scratch[si++] = data->req.upload_fromhere[i]; - } - - if(smtpc->eob) { - /* A substring matched before processing ended so output that now */ - memcpy(&data->state.scratch[si], SMTP_EOB, smtpc->eob); - si += smtpc->eob; - smtpc->eob = 0; - } - - if(si != nread) { - /* Only use the new buffer if we replaced something */ - nread = si; - - /* Upload from the new (replaced) buffer instead */ - data->req.upload_fromhere = data->state.scratch; - - /* Set the new amount too */ - data->req.upload_present = nread; - } - - return CURLE_OK; -} - -#endif /* CURL_DISABLE_SMTP */ diff --git a/lib/socks.c b/lib/socks.c deleted file mode 100644 index 1b70dd629..000000000 --- a/lib/socks.c +++ /dev/null @@ -1,744 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_PROXY) - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_strequal.h" -#include "curl_select.h" -#include "curl_connect.h" -#include "curl_timeval.h" -#include "curl_socks.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - * 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, /* 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 */ -{ - ssize_t nread; - ssize_t allread = 0; - int result; - long timeleft; - *n = 0; - for(;;) { - timeleft = Curl_timeleft(conn->data, NULL, TRUE); - if(timeleft < 0) { - /* we already got the timeout */ - result = CURLE_OPERATION_TIMEDOUT; - break; - } - if(Curl_socket_ready(sockfd, CURL_SOCKET_BAD, timeleft) <= 0) { - result = ~CURLE_OK; - break; - } - result = Curl_read_plain(sockfd, buf, buffersize, &nread); - if(CURLE_AGAIN == result) - continue; - else if(result) - break; - - if(buffersize == nread) { - allread += nread; - *n = allread; - result = CURLE_OK; - break; - } - if(!nread) { - result = ~CURLE_OK; - break; - } - - buffersize -= nread; - buf += nread; - allread += nread; - } - return result; -} - -/* -* This function logs in to a SOCKS4 proxy and sends the specifics to the final -* destination server. -* -* Reference : -* http://socks.permeo.com/protocol/socks4.protocol -* -* Note : -* Set protocol4a=true for "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)" -* Nonsupport "Identification Protocol (RFC1413)" -*/ -CURLcode Curl_SOCKS4(const char *proxy_name, - const char *hostname, - int remote_port, - int sockindex, - struct connectdata *conn, - bool protocol4a) -{ -#define SOCKS4REQLEN 262 - unsigned char socksreq[SOCKS4REQLEN]; /* room for SOCKS4 request incl. user - id */ - int result; - CURLcode code; - curl_socket_t sock = conn->sock[sockindex]; - struct SessionHandle *data = conn->data; - - if(Curl_timeleft(data, NULL, TRUE) < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - curlx_nonblock(sock, FALSE); - - /* - * Compose socks4 request - * - * Request format - * - * +----+----+----+----+----+----+----+----+----+----+....+----+ - * | VN | CD | DSTPORT | DSTIP | USERID |NULL| - * +----+----+----+----+----+----+----+----+----+----+....+----+ - * # of bytes: 1 1 2 4 variable 1 - */ - - socksreq[0] = 4; /* version (SOCKS4) */ - socksreq[1] = 1; /* connect */ - socksreq[2] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ - socksreq[3] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ - - /* DNS resolve only for SOCKS4, not SOCKS4a */ - if(!protocol4a) { - struct Curl_dns_entry *dns; - Curl_addrinfo *hp=NULL; - int rc; - - rc = Curl_resolv(conn, hostname, remote_port, &dns); - - if(rc == CURLRESOLV_ERROR) - return CURLE_COULDNT_RESOLVE_PROXY; - - if(rc == CURLRESOLV_PENDING) - /* ignores the return code, but 'dns' remains NULL on failure */ - (void)Curl_resolver_wait_resolv(conn, &dns); - - /* - * We cannot use 'hostent' as a struct that Curl_resolv() returns. It - * returns a Curl_addrinfo pointer that may not always look the same. - */ - if(dns) - hp=dns->addr; - if(hp) { - char buf[64]; - unsigned short ip[4]; - Curl_printable_address(hp, buf, sizeof(buf)); - - if(4 == sscanf( buf, "%hu.%hu.%hu.%hu", - &ip[0], &ip[1], &ip[2], &ip[3])) { - /* Set DSTIP */ - socksreq[4] = (unsigned char)ip[0]; - socksreq[5] = (unsigned char)ip[1]; - socksreq[6] = (unsigned char)ip[2]; - socksreq[7] = (unsigned char)ip[3]; - } - else - hp = NULL; /* fail! */ - - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ - - } - if(!hp) { - failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", - hostname); - return CURLE_COULDNT_RESOLVE_HOST; - } - } - - /* - * This is currently not supporting "Identification Protocol (RFC1413)". - */ - socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ - if(proxy_name) - strlcat((char*)socksreq + 8, proxy_name, sizeof(socksreq) - 8); - - /* - * Make connection - */ - { - ssize_t actualread; - ssize_t written; - ssize_t hostnamelen = 0; - int packetsize = 9 + - (int)strlen((char*)socksreq + 8); /* size including NUL */ - - /* If SOCKS4a, set special invalid IP address 0.0.0.x */ - if(protocol4a) { - socksreq[4] = 0; - socksreq[5] = 0; - socksreq[6] = 0; - socksreq[7] = 1; - /* If still enough room in buffer, also append hostname */ - hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */ - if(packetsize + hostnamelen <= SOCKS4REQLEN) - strcpy((char*)socksreq + packetsize, hostname); - else - hostnamelen = 0; /* Flag: hostname did not fit in buffer */ - } - - /* Send request */ - code = Curl_write_plain(conn, sock, (char *)socksreq, - packetsize + hostnamelen, - &written); - if((code != CURLE_OK) || (written != packetsize + hostnamelen)) { - failf(data, "Failed to send SOCKS4 connect request."); - return CURLE_COULDNT_CONNECT; - } - if(protocol4a && hostnamelen == 0) { - /* SOCKS4a with very long hostname - send that name separately */ - hostnamelen = (ssize_t)strlen(hostname) + 1; - code = Curl_write_plain(conn, sock, (char *)hostname, hostnamelen, - &written); - if((code != CURLE_OK) || (written != hostnamelen)) { - failf(data, "Failed to send SOCKS4 connect request."); - return CURLE_COULDNT_CONNECT; - } - } - - packetsize = 8; /* receive data size */ - - /* Receive response */ - result = Curl_blockread_all(conn, sock, (char *)socksreq, packetsize, - &actualread); - if((result != CURLE_OK) || (actualread != packetsize)) { - failf(data, "Failed to receive SOCKS4 connect request ack."); - return CURLE_COULDNT_CONNECT; - } - - /* - * Response format - * - * +----+----+----+----+----+----+----+----+ - * | VN | CD | DSTPORT | DSTIP | - * +----+----+----+----+----+----+----+----+ - * # of bytes: 1 1 2 4 - * - * VN is the version of the reply code and should be 0. CD is the result - * code with one of the following values: - * - * 90: request granted - * 91: request rejected or failed - * 92: request rejected because SOCKS server cannot connect to - * identd on the client - * 93: request rejected because the client program and identd - * report different user-ids - */ - - /* wrong version ? */ - if(socksreq[0] != 0) { - failf(data, - "SOCKS4 reply has wrong version, version should be 4."); - return CURLE_COULDNT_CONNECT; - } - - /* Result */ - switch(socksreq[1]) { - case 90: - infof(data, "SOCKS4%s request granted.\n", protocol4a?"a":""); - break; - case 91: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", request rejected or failed.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - ((socksreq[8] << 8) | socksreq[9]), - socksreq[1]); - return CURLE_COULDNT_CONNECT; - case 92: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", request rejected because SOCKS server cannot connect to " - "identd on the client.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - ((socksreq[8] << 8) | socksreq[9]), - socksreq[1]); - return CURLE_COULDNT_CONNECT; - case 93: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", request rejected because the client program and identd " - "report different user-ids.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - ((socksreq[8] << 8) | socksreq[9]), - socksreq[1]); - return CURLE_COULDNT_CONNECT; - default: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", Unknown.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - ((socksreq[8] << 8) | socksreq[9]), - socksreq[1]); - return CURLE_COULDNT_CONNECT; - } - } - - curlx_nonblock(sock, TRUE); - - return CURLE_OK; /* Proxy was successful! */ -} - -/* - * This function logs in to a SOCKS5 proxy and sends the specifics to the final - * destination server. - */ -CURLcode Curl_SOCKS5(const char *proxy_name, - const char *proxy_password, - const char *hostname, - int remote_port, - int sockindex, - struct connectdata *conn) -{ - /* - According to the RFC1928, section "6. Replies". This is what a SOCK5 - replies: - - +----+-----+-------+------+----------+----------+ - |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - +----+-----+-------+------+----------+----------+ - | 1 | 1 | X'00' | 1 | Variable | 2 | - +----+-----+-------+------+----------+----------+ - - Where: - - o VER protocol version: X'05' - o REP Reply field: - o X'00' succeeded - */ - - unsigned char socksreq[600]; /* room for large user/pw (255 max each) */ - ssize_t actualread; - ssize_t written; - int result; - CURLcode code; - curl_socket_t sock = conn->sock[sockindex]; - struct SessionHandle *data = conn->data; - long timeout; - bool socks5_resolve_local = (conn->proxytype == CURLPROXY_SOCKS5)?TRUE:FALSE; - const size_t hostname_len = strlen(hostname); - ssize_t len = 0; - - /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ - if(!socks5_resolve_local && hostname_len > 255) { - infof(conn->data,"SOCKS5: server resolving disabled for hostnames of " - "length > 255 [actual len=%zu]\n", hostname_len); - socks5_resolve_local = TRUE; - } - - /* get timeout */ - timeout = Curl_timeleft(data, NULL, TRUE); - - if(timeout < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - curlx_nonblock(sock, TRUE); - - /* wait until socket gets connected */ - result = Curl_socket_ready(CURL_SOCKET_BAD, sock, timeout); - - if(-1 == result) { - failf(conn->data, "SOCKS5: no connection here"); - return CURLE_COULDNT_CONNECT; - } - else if(0 == result) { - failf(conn->data, "SOCKS5: connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - if(result & CURL_CSELECT_ERR) { - failf(conn->data, "SOCKS5: error occurred during connection"); - return CURLE_COULDNT_CONNECT; - } - - 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 - - curlx_nonblock(sock, FALSE); - - code = Curl_write_plain(conn, sock, (char *)socksreq, (2 + (int)socksreq[1]), - &written); - if((code != CURLE_OK) || (written != (2 + (int)socksreq[1]))) { - failf(data, "Unable to send initial SOCKS5 request."); - return CURLE_COULDNT_CONNECT; - } - - curlx_nonblock(sock, TRUE); - - result = Curl_socket_ready(sock, CURL_SOCKET_BAD, timeout); - - if(-1 == result) { - failf(conn->data, "SOCKS5 nothing to read"); - return CURLE_COULDNT_CONNECT; - } - else if(0 == result) { - failf(conn->data, "SOCKS5 read timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - if(result & CURL_CSELECT_ERR) { - failf(conn->data, "SOCKS5 read error occurred"); - return CURLE_RECV_ERROR; - } - - curlx_nonblock(sock, FALSE); - - result=Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); - if((result != CURLE_OK) || (actualread != 2)) { - failf(data, "Unable to receive initial SOCKS5 response."); - return CURLE_COULDNT_CONNECT; - } - - if(socksreq[0] != 5) { - failf(data, "Received invalid version in initial SOCKS5 response."); - return CURLE_COULDNT_CONNECT; - } - if(socksreq[1] == 0) { - /* 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 proxy_name_len, proxy_password_len; - if(proxy_name && proxy_password) { - proxy_name_len = strlen(proxy_name); - proxy_password_len = strlen(proxy_password); - } - else { - proxy_name_len = 0; - proxy_password_len = 0; - } - - /* username/password request looks like - * +----+------+----------+------+----------+ - * |VER | ULEN | UNAME | PLEN | PASSWD | - * +----+------+----------+------+----------+ - * | 1 | 1 | 1 to 255 | 1 | 1 to 255 | - * +----+------+----------+------+----------+ - */ - len = 0; - socksreq[len++] = 1; /* username/pw subnegotiation version */ - socksreq[len++] = (unsigned char) proxy_name_len; - if(proxy_name && proxy_name_len) - memcpy(socksreq + len, proxy_name, proxy_name_len); - len += proxy_name_len; - socksreq[len++] = (unsigned char) proxy_password_len; - if(proxy_password && proxy_password_len) - memcpy(socksreq + len, proxy_password, proxy_password_len); - len += proxy_password_len; - - code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); - if((code != CURLE_OK) || (len != written)) { - failf(data, "Failed to send SOCKS5 sub-negotiation request."); - return CURLE_COULDNT_CONNECT; - } - - result=Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); - if((result != CURLE_OK) || (actualread != 2)) { - failf(data, "Unable to receive SOCKS5 sub-negotiation response."); - return CURLE_COULDNT_CONNECT; - } - - /* ignore the first (VER) byte */ - if(socksreq[1] != 0) { /* status */ - failf(data, "User was rejected by the SOCKS5 server (%d %d).", - socksreq[0], socksreq[1]); - return CURLE_COULDNT_CONNECT; - } - - /* Everything is good so far, user was authenticated! */ - } - 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" - " that the SOCKS5 server wanted a username/password, since none" - " was supplied to the server on this connection.)"); - } - else { - failf(data, "No authentication method was acceptable."); - } - return CURLE_COULDNT_CONNECT; - } - else { - failf(data, - "Undocumented SOCKS5 mode attempted to be used by server."); - return CURLE_COULDNT_CONNECT; - } - } - - /* Authentication is complete, now specify destination to the proxy */ - len = 0; - socksreq[len++] = 5; /* version (SOCKS5) */ - socksreq[len++] = 1; /* connect */ - socksreq[len++] = 0; /* must be zero */ - - if(!socks5_resolve_local) { - socksreq[len++] = 3; /* ATYP: domain name = 3 */ - socksreq[len++] = (char) hostname_len; /* address length */ - memcpy(&socksreq[len], hostname, hostname_len); /* address str w/o NULL */ - len += hostname_len; - } - else { - struct Curl_dns_entry *dns; - Curl_addrinfo *hp = NULL; - int rc = Curl_resolv(conn, hostname, remote_port, &dns); - - if(rc == CURLRESOLV_ERROR) - return CURLE_COULDNT_RESOLVE_HOST; - - if(rc == CURLRESOLV_PENDING) { - /* this requires that we're in "wait for resolve" state */ - code = Curl_resolver_wait_resolv(conn, &dns); - if(code != CURLE_OK) - return code; - } - - /* - * We cannot use 'hostent' as a struct that Curl_resolv() returns. It - * returns a Curl_addrinfo pointer that may not always look the same. - */ - if(dns) - hp=dns->addr; - if(hp) { - struct sockaddr_in *saddr_in; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 *saddr_in6; -#endif - int i; - - if(hp->ai_family == AF_INET) { - socksreq[len++] = 1; /* ATYP: IPv4 = 1 */ - - saddr_in = (struct sockaddr_in*)hp->ai_addr; - for(i = 0; i < 4; i++) { - socksreq[len++] = ((unsigned char*)&saddr_in->sin_addr.s_addr)[i]; - infof(data, "%d\n", socksreq[len-1]); - } - } -#ifdef ENABLE_IPV6 - else if(hp->ai_family == AF_INET6) { - socksreq[len++] = 4; /* ATYP: IPv6 = 4 */ - - saddr_in6 = (struct sockaddr_in6*)hp->ai_addr; - for(i = 0; i < 16; i++) { - socksreq[len++] = ((unsigned char*)&saddr_in6->sin6_addr.s6_addr)[i]; - } - } -#endif - else - hp = NULL; /* fail! */ - - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ - } - if(!hp) { - failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", - hostname); - return CURLE_COULDNT_RESOLVE_HOST; - } - } - - socksreq[len++] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ - socksreq[len++] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ - -#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, len, &written); - - if((code != CURLE_OK) || (len != written)) { - failf(data, "Failed to send SOCKS5 connect request."); - return CURLE_COULDNT_CONNECT; - } - - len = 10; /* minimum packet size is 10 */ - -#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, - len, &actualread); - - if((result != CURLE_OK) || (len != actualread)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLE_COULDNT_CONNECT; - } - - if(socksreq[0] != 5) { /* version */ - failf(data, - "SOCKS5 reply has wrong version, version should be 5."); - return CURLE_COULDNT_CONNECT; - } - if(socksreq[1] != 0) { /* Anything besides 0 is an error */ - if(socksreq[3] == 1) { - failf(data, - "Can't complete SOCKS5 connection to %d.%d.%d.%d:%d. (%d)", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - ((socksreq[8] << 8) | socksreq[9]), - socksreq[1]); - } - else if(socksreq[3] == 3) { - failf(data, - "Can't complete SOCKS5 connection to %s:%d. (%d)", - hostname, - ((socksreq[8] << 8) | socksreq[9]), - socksreq[1]); - } - else if(socksreq[3] == 4) { - failf(data, - "Can't complete SOCKS5 connection to %02x%02x:%02x%02x:" - "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%d. (%d)", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (unsigned char)socksreq[8], (unsigned char)socksreq[9], - (unsigned char)socksreq[10], (unsigned char)socksreq[11], - (unsigned char)socksreq[12], (unsigned char)socksreq[13], - (unsigned char)socksreq[14], (unsigned char)socksreq[15], - (unsigned char)socksreq[16], (unsigned char)socksreq[17], - (unsigned char)socksreq[18], (unsigned char)socksreq[19], - ((socksreq[8] << 8) | socksreq[9]), - socksreq[1]); - } - return CURLE_COULDNT_CONNECT; - } - - /* Fix: in general, returned BND.ADDR is variable length parameter by RFC - 1928, so the reply packet should be read until the end to avoid errors at - subsequent protocol level. - - +----+-----+-------+------+----------+----------+ - |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - +----+-----+-------+------+----------+----------+ - | 1 | 1 | X'00' | 1 | Variable | 2 | - +----+-----+-------+------+----------+----------+ - - ATYP: - o IP v4 address: X'01', BND.ADDR = 4 byte - o domain name: X'03', BND.ADDR = [ 1 byte length, string ] - o IP v6 address: X'04', BND.ADDR = 16 byte - */ - - /* Calculate real packet size */ - if(socksreq[3] == 3) { - /* domain name */ - int addrlen = (int) socksreq[4]; - len = 5 + addrlen + 2; - } - else if(socksreq[3] == 4) { - /* IPv6 */ - len = 4 + 16 + 2; - } - - /* At this point we already read first 10 bytes */ -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - if(!conn->socks5_gssapi_enctype) { - /* decrypt_gssapi_blockread already read the whole packet */ -#endif - if(len > 10) { - len -= 10; - result = Curl_blockread_all(conn, sock, (char *)&socksreq[10], - len, &actualread); - if((result != CURLE_OK) || (len != actualread)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLE_COULDNT_CONNECT; - } - } -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - } -#endif - - curlx_nonblock(sock, TRUE); - return CURLE_OK; /* Proxy was successful! */ -} - -#endif /* CURL_DISABLE_PROXY */ - diff --git a/lib/socks_gssapi.c b/lib/socks_gssapi.c deleted file mode 100644 index 2bd3d4508..000000000 --- a/lib/socks_gssapi.c +++ /dev/null @@ -1,533 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2009, 2011, Markus Moeller, - * Copyright (C) 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_PROXY - -#ifdef HAVE_GSSAPI -#ifdef HAVE_OLD_GSSMIT -#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name -#define NCOMPAT 1 -#endif -#ifndef gss_nt_service_name -#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE -#endif - -#include "curl_gssapi.h" -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_connect.h" -#include "curl_timeval.h" -#include "curl_socks.h" -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; - -/* - * 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); - gss_release_buffer(&min_stat, &status_string); - break; - } - gss_release_buffer(&min_stat, &status_string); - } - failf(data, "GSSAPI error: %s failed:\n%s", 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; - 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; - unsigned 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]; - - /* 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 = Curl_gss_init_sec_context(data, - &gss_minor_status, - &gss_context, - server, - NULL, - gss_token, - &gss_send_token, - &gss_ret_flags); - - 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); - 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); - - 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); - 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); - - 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 - -#endif /* CURL_DISABLE_PROXY */ diff --git a/lib/socks_sspi.c b/lib/socks_sspi.c deleted file mode 100644 index c57610717..000000000 --- a/lib/socks_sspi.c +++ /dev/null @@ -1,591 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2009, 2011, Markus Moeller, - * Copyright (C) 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_PROXY) - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_connect.h" -#include "curl_strerror.h" -#include "curl_timeval.h" -#include "curl_socks.h" -#include "curl_sspi.h" -#include "curl_multibyte.h" -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use the internal *printf() functions */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* - * Definitions required from ntsecapi.h are directly provided below this point - * to avoid including ntsecapi.h due to a conflict with OpenSSL's safestack.h - */ -#define KERB_WRAP_NO_ENCRYPT 0x80000001 - -/* - * Helper sspi error functions. - */ -static int check_sspi_err(struct connectdata *conn, - SECURITY_STATUS status, - const char* function) -{ - if(status != SEC_E_OK && - status != SEC_I_COMPLETE_AND_CONTINUE && - status != SEC_I_COMPLETE_NEEDED && - status != SEC_I_CONTINUE_NEEDED) { - failf(conn->data, "SSPI error: %s failed: %s", function, - Curl_sspi_strerror(conn, status)); - 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; - /* Needs GSSAPI authentication */ - SECURITY_STATUS status; - unsigned long 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; - unsigned short us_length; - unsigned long qop; - unsigned char socksreq[4]; /* room for gssapi exchange header only */ - char *service = data->set.str[STRING_SOCKS5_GSSAPI_SERVICE]; - - /* 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; - - status = s_pSecFn->AcquireCredentialsHandle(NULL, - (TCHAR *) TEXT("Kerberos"), - SECPKG_CRED_OUTBOUND, - NULL, - NULL, - NULL, - NULL, - &cred_handle, - &expiry); - - if(check_sspi_err(conn, status, "AcquireCredentialsHandle")) { - failf(data, "Failed to acquire credentials."); - Curl_safefree(service_name); - s_pSecFn->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(;;) { - TCHAR *sname; - - sname = Curl_convert_UTF8_to_tchar(service_name); - if(!sname) - return CURLE_OUT_OF_MEMORY; - - status = s_pSecFn->InitializeSecurityContext(&cred_handle, - context_handle, - sname, - 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); - - Curl_unicodefree(sname); - - if(sspi_recv_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - sspi_recv_token.pvBuffer = NULL; - sspi_recv_token.cbBuffer = 0; - } - - if(check_sspi_err(conn, status, "InitializeSecurityContext")) { - Curl_safefree(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); - s_pSecFn->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."); - Curl_safefree(service_name); - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->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 != (size_t)written)) { - failf(data, "Failed to send SSPI authentication token."); - Curl_safefree(service_name); - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; - } - - } - - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - sspi_send_token.pvBuffer = NULL; - sspi_send_token.cbBuffer = 0; - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - sspi_recv_token.pvBuffer = NULL; - sspi_recv_token.cbBuffer = 0; - if(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); - if(result != CURLE_OK || actualread != 4) { - failf(data, "Failed to receive SSPI authentication response."); - Curl_safefree(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->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]); - Curl_safefree(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->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]); - Curl_safefree(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->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) { - Curl_safefree(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_OUT_OF_MEMORY; - } - result = Curl_blockread_all(conn, sock, (char *)sspi_recv_token.pvBuffer, - sspi_recv_token.cbBuffer, &actualread); - - if(result != CURLE_OK || actualread != us_length) { - failf(data, "Failed to receive SSPI authentication token."); - Curl_safefree(service_name); - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; - } - - context_handle = &sspi_context; - } - - Curl_safefree(service_name); - - /* Everything is good so far, user was authenticated! */ - status = s_pSecFn->QueryCredentialsAttributes(&cred_handle, - SECPKG_CRED_ATTR_NAMES, - &names); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - if(check_sspi_err(conn, status, "QueryCredentialAttributes")) { - s_pSecFn->DeleteSecurityContext(&sspi_context); - s_pSecFn->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); - s_pSecFn->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 { - status = s_pSecFn->QueryContextAttributes(&sspi_context, - SECPKG_ATTR_SIZES, - &sspi_sizes); - if(check_sspi_err(conn, status, "QueryContextAttributes")) { - s_pSecFn->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) { - s_pSecFn->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) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->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) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_OUT_OF_MEMORY; - } - status = s_pSecFn->EncryptMessage(&sspi_context, - KERB_WRAP_NO_ENCRYPT, - &wrap_desc, - 0); - if(check_sspi_err(conn, status, "EncryptMessage")) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); - s_pSecFn->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) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); - s_pSecFn->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); - - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - sspi_w_token[0].pvBuffer = NULL; - sspi_w_token[0].cbBuffer = 0; - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - sspi_w_token[1].pvBuffer = NULL; - sspi_w_token[1].cbBuffer = 0; - s_pSecFn->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."); - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - s_pSecFn->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."); - s_pSecFn->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 != (size_t)written)) { - failf(data, "Failed to send SSPI encryption type."); - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; - } - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); - } - - result = Curl_blockread_all(conn, sock, (char *)socksreq, 4, &actualread); - if(result != CURLE_OK || actualread != 4) { - failf(data, "Failed to receive SSPI encryption response."); - s_pSecFn->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]); - s_pSecFn->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]); - s_pSecFn->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) { - s_pSecFn->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); - - if(result != CURLE_OK || actualread != us_length) { - failf(data, "Failed to receive SSPI encryption type."); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->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; - - status = s_pSecFn->DecryptMessage(&sspi_context, - &wrap_desc, - 0, - &qop); - - if(check_sspi_err(conn, status, "DecryptMessage")) { - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->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); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; - } - - memcpy(socksreq,sspi_w_token[1].pvBuffer,sspi_w_token[1].cbBuffer); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->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); - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); - s_pSecFn->DeleteSecurityContext(&sspi_context); - return CURLE_COULDNT_CONNECT; - } - memcpy(socksreq,sspi_w_token[0].pvBuffer,sspi_w_token[0].cbBuffer); - s_pSecFn->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 { - s_pSecFn->DeleteSecurityContext(&sspi_context); - conn->socks5_sspi_context = sspi_context; - } - */ - return CURLE_OK; -} -#endif diff --git a/lib/speedcheck.c b/lib/speedcheck.c deleted file mode 100644 index b9ce77dbf..000000000 --- a/lib/speedcheck.c +++ /dev/null @@ -1,74 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_multiif.h" -#include "curl_speedcheck.h" - -void Curl_speedinit(struct SessionHandle *data) -{ - memset(&data->state.keeps_speed, 0, sizeof(struct timeval)); -} - -CURLcode Curl_speedcheck(struct SessionHandle *data, - struct timeval now) -{ - if((data->progress.current_speed >= 0) && - data->set.low_speed_time && - (Curl_tvlong(data->state.keeps_speed) != 0) && - (data->progress.current_speed < data->set.low_speed_limit)) { - long howlong = Curl_tvdiff(now, data->state.keeps_speed); - long nextcheck = (data->set.low_speed_time * 1000) - howlong; - - /* We are now below the "low speed limit". If we are below it - for "low speed time" seconds we consider that enough reason - to abort the download. */ - if(nextcheck <= 0) { - /* we have been this slow for long enough, now die */ - failf(data, - "Operation too slow. " - "Less than %ld bytes/sec transferred the last %ld seconds", - data->set.low_speed_limit, - data->set.low_speed_time); - return CURLE_OPERATION_TIMEDOUT; - } - else { - /* wait complete low_speed_time */ - Curl_expire(data, nextcheck); - } - } - else { - /* we keep up the required speed all right */ - data->state.keeps_speed = now; - - if(data->set.low_speed_limit) - /* if there is a low speed limit enabled, we set the expire timer to - make this connection's speed get checked again no later than when - this time is up */ - Curl_expire(data, data->set.low_speed_time*1000); - } - return CURLE_OK; -} diff --git a/lib/splay.c b/lib/splay.c deleted file mode 100644 index 21f1d222e..000000000 --- a/lib/splay.c +++ /dev/null @@ -1,288 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1997 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_splay.h" - -/* - * This macro compares two node keys i and j and returns: - * - * negative value: when i is smaller than j - * zero : when i is equal to j - * positive when : when i is larger than j - */ -#define compare(i,j) Curl_splaycomparekeys((i),(j)) - -/* - * Splay using the key i (which may or may not be in the tree.) The starting - * root is t. - */ -struct Curl_tree *Curl_splay(struct timeval i, - struct Curl_tree *t) -{ - struct Curl_tree N, *l, *r, *y; - long comp; - - if(t == NULL) - return t; - N.smaller = N.larger = NULL; - l = r = &N; - - for(;;) { - comp = compare(i, t->key); - if(comp < 0) { - if(t->smaller == NULL) - break; - if(compare(i, t->smaller->key) < 0) { - y = t->smaller; /* rotate smaller */ - t->smaller = y->larger; - y->larger = t; - t = y; - if(t->smaller == NULL) - break; - } - r->smaller = t; /* link smaller */ - r = t; - t = t->smaller; - } - else if(comp > 0) { - if(t->larger == NULL) - break; - if(compare(i, t->larger->key) > 0) { - y = t->larger; /* rotate larger */ - t->larger = y->smaller; - y->smaller = t; - t = y; - if(t->larger == NULL) - break; - } - l->larger = t; /* link larger */ - l = t; - t = t->larger; - } - else - break; - } - - l->larger = t->smaller; /* assemble */ - r->smaller = t->larger; - t->smaller = N.larger; - t->larger = N.smaller; - - return t; -} - -/* Insert key i into the tree t. Return a pointer to the resulting tree or - * NULL if something went wrong. - * - * @unittest: 1309 - */ -struct Curl_tree *Curl_splayinsert(struct timeval i, - struct Curl_tree *t, - struct Curl_tree *node) -{ - static struct timeval KEY_NOTUSED = {-1,-1}; /* will *NEVER* appear */ - - if(node == NULL) - return t; - - if(t != NULL) { - t = Curl_splay(i,t); - if(compare(i, t->key)==0) { - /* There already exists a node in the tree with the very same key. Build - a linked list of nodes. We make the new 'node' struct the new master - node and make the previous node the first one in the 'same' list. */ - - node->same = t; - node->key = i; - node->smaller = t->smaller; - node->larger = t->larger; - - t->smaller = node; /* in the sub node for this same key, we use the - smaller pointer to point back to the master - node */ - - t->key = KEY_NOTUSED; /* and we set the key in the sub node to NOTUSED - to quickly identify this node as a subnode */ - - return node; /* new root node */ - } - } - - if(t == NULL) { - node->smaller = node->larger = NULL; - } - else if(compare(i, t->key) < 0) { - node->smaller = t->smaller; - node->larger = t; - t->smaller = NULL; - - } - else { - node->larger = t->larger; - node->smaller = t; - t->larger = NULL; - } - node->key = i; - - node->same = NULL; /* no identical node (yet) */ - return node; -} - -/* Finds and deletes the best-fit node from the tree. Return a pointer to the - resulting tree. best-fit means the node with the given or lower key */ -struct Curl_tree *Curl_splaygetbest(struct timeval i, - struct Curl_tree *t, - struct Curl_tree **removed) -{ - struct Curl_tree *x; - - if(!t) { - *removed = NULL; /* none removed since there was no root */ - return NULL; - } - - t = Curl_splay(i,t); - if(compare(i, t->key) < 0) { - /* too big node, try the smaller chain */ - if(t->smaller) - t=Curl_splay(t->smaller->key, t); - else { - /* fail */ - *removed = NULL; - return t; - } - } - - if(compare(i, t->key) >= 0) { /* found it */ - /* FIRST! Check if there is a list with identical keys */ - x = t->same; - if(x) { - /* there is, pick one from the list */ - - /* 'x' is the new root node */ - - x->key = t->key; - x->larger = t->larger; - x->smaller = t->smaller; - - *removed = t; - return x; /* new root */ - } - - if(t->smaller == NULL) { - x = t->larger; - } - else { - x = Curl_splay(i, t->smaller); - x->larger = t->larger; - } - *removed = t; - - return x; - } - else { - *removed = NULL; /* no match */ - return t; /* It wasn't there */ - } -} - - -/* Deletes the very node we point out from the tree if it's there. Stores a - * pointer to the new resulting tree in 'newroot'. - * - * Returns zero on success and non-zero on errors! TODO: document error codes. - * When returning error, it does not touch the 'newroot' pointer. - * - * NOTE: when the last node of the tree is removed, there's no tree left so - * 'newroot' will be made to point to NULL. - * - * @unittest: 1309 - */ -int Curl_splayremovebyaddr(struct Curl_tree *t, - struct Curl_tree *removenode, - struct Curl_tree **newroot) -{ - static struct timeval KEY_NOTUSED = {-1,-1}; /* will *NEVER* appear */ - struct Curl_tree *x; - - if(!t || !removenode) - return 1; - - if(compare(KEY_NOTUSED, removenode->key) == 0) { - /* Key set to NOTUSED means it is a subnode within a 'same' linked list - and thus we can unlink it easily. The 'smaller' link of a subnode - links to the parent node. */ - if(removenode->smaller == NULL) - return 3; - - removenode->smaller->same = removenode->same; - if(removenode->same) - removenode->same->smaller = removenode->smaller; - - /* Ensures that double-remove gets caught. */ - removenode->smaller = NULL; - - /* voila, we're done! */ - *newroot = t; /* return the same root */ - return 0; - } - - t = Curl_splay(removenode->key, t); - - /* First make sure that we got the same root node as the one we want - to remove, as otherwise we might be trying to remove a node that - isn't actually in the tree. - - We cannot just compare the keys here as a double remove in quick - succession of a node with key != KEY_NOTUSED && same != NULL - could return the same key but a different node. */ - if(t != removenode) - return 2; - - /* Check if there is a list with identical sizes, as then we're trying to - remove the root node of a list of nodes with identical keys. */ - x = t->same; - if(x) { - /* 'x' is the new root node, we just make it use the root node's - smaller/larger links */ - - x->key = t->key; - x->larger = t->larger; - x->smaller = t->smaller; - } - else { - /* Remove the root node */ - if(t->smaller == NULL) - x = t->larger; - else { - x = Curl_splay(removenode->key, t->smaller); - x->larger = t->larger; - } - } - - *newroot = x; /* store new root pointer */ - - return 0; -} - diff --git a/lib/ssh.c b/lib/ssh.c deleted file mode 100644 index d769a041b..000000000 --- a/lib/ssh.c +++ /dev/null @@ -1,3310 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -/* #define CURL_LIBSSH2_DEBUG */ - -#include "curl_setup.h" - -#ifdef USE_LIBSSH2 - -#ifdef HAVE_LIMITS_H -# include -#endif - -#include -#include - -#ifdef HAVE_FCNTL_H -#include -#endif - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_UTSNAME_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -#include -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_hostip.h" -#include "curl_progress.h" -#include "curl_transfer.h" -#include "curl_escape.h" -#include "curl_http.h" /* for HTTP proxy tunnel stuff */ -#include "curl_ssh.h" -#include "curl_url.h" -#include "curl_speedcheck.h" -#include "curl_getinfo.h" - -#include "curl_strequal.h" -#include "curl_sslgen.h" -#include "curl_connect.h" -#include "curl_strerror.h" -#include "curl_inet_ntop.h" -#include "curl_parsedate.h" /* for the week day and month names */ -#include "curl_sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "curl_strtoofft.h" -#include "curl_multiif.h" -#include "curl_select.h" -#include "curl_warnless.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifdef WIN32 -# undef PATH_MAX -# define PATH_MAX MAX_PATH -#endif - -#ifndef PATH_MAX -#define PATH_MAX 1024 /* just an extra precaution since there are systems that - have their definition hidden well */ -#endif - -#define sftp_libssh2_last_error(s) curlx_ultosi(libssh2_sftp_last_error(s)) - -#define sftp_libssh2_realpath(s,p,t,m) \ - libssh2_sftp_symlink_ex((s), (p), curlx_uztoui(strlen(p)), \ - (t), (m), LIBSSH2_SFTP_REALPATH) - -/* Local functions: */ -static const char *sftp_libssh2_strerror(int err); -static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc); -static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc); -static LIBSSH2_FREE_FUNC(my_libssh2_free); - -static CURLcode get_pathname(const char **cpp, char **path); - -static CURLcode ssh_connect(struct connectdata *conn, bool *done); -static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done); -static CURLcode ssh_do(struct connectdata *conn, bool *done); - -static CURLcode ssh_getworkingpath(struct connectdata *conn, - char *homedir, /* when SFTP is used */ - char **path); - -static CURLcode scp_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode scp_doing(struct connectdata *conn, - bool *dophase_done); -static CURLcode scp_disconnect(struct connectdata *conn, bool dead_connection); - -static CURLcode sftp_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode sftp_doing(struct connectdata *conn, - bool *dophase_done); -static CURLcode sftp_disconnect(struct connectdata *conn, bool dead); -static -CURLcode sftp_perform(struct connectdata *conn, - bool *connected, - bool *dophase_done); - -static int ssh_getsock(struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks number - of sockets */ - int numsocks); - -static int ssh_perform_getsock(const struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks - number of sockets */ - int numsocks); - -/* - * SCP protocol handler. - */ - -const struct Curl_handler Curl_handler_scp = { - "SCP", /* scheme */ - ZERO_NULL, /* setup_connection */ - ssh_do, /* do_it */ - scp_done, /* done */ - ZERO_NULL, /* do_more */ - ssh_connect, /* connect_it */ - ssh_multi_statemach, /* connecting */ - scp_doing, /* doing */ - ssh_getsock, /* proto_getsock */ - ssh_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ssh_perform_getsock, /* perform_getsock */ - scp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_SSH, /* defport */ - CURLPROTO_SCP, /* protocol */ - PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION - | PROTOPT_NOURLQUERY /* flags */ -}; - - -/* - * SFTP protocol handler. - */ - -const struct Curl_handler Curl_handler_sftp = { - "SFTP", /* scheme */ - ZERO_NULL, /* setup_connection */ - ssh_do, /* do_it */ - sftp_done, /* done */ - ZERO_NULL, /* do_more */ - ssh_connect, /* connect_it */ - ssh_multi_statemach, /* connecting */ - sftp_doing, /* doing */ - ssh_getsock, /* proto_getsock */ - ssh_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ssh_perform_getsock, /* perform_getsock */ - sftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_SSH, /* defport */ - CURLPROTO_SFTP, /* protocol */ - PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION - | PROTOPT_NOURLQUERY /* flags */ -}; - - -static void -kbd_callback(const char *name, int name_len, const char *instruction, - int instruction_len, int num_prompts, - const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, - LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, - void **abstract) -{ - struct connectdata *conn = (struct connectdata *)*abstract; - -#ifdef CURL_LIBSSH2_DEBUG - fprintf(stderr, "name=%s\n", name); - fprintf(stderr, "name_len=%d\n", name_len); - fprintf(stderr, "instruction=%s\n", instruction); - fprintf(stderr, "instruction_len=%d\n", instruction_len); - fprintf(stderr, "num_prompts=%d\n", num_prompts); -#else - (void)name; - (void)name_len; - (void)instruction; - (void)instruction_len; -#endif /* CURL_LIBSSH2_DEBUG */ - if(num_prompts == 1) { - responses[0].text = strdup(conn->passwd); - responses[0].length = curlx_uztoui(strlen(conn->passwd)); - } - (void)prompts; - (void)abstract; -} /* kbd_callback */ - -static CURLcode sftp_libssh2_error_to_CURLE(int err) -{ - switch (err) { - case LIBSSH2_FX_OK: - return CURLE_OK; - - case LIBSSH2_FX_NO_SUCH_FILE: - case LIBSSH2_FX_NO_SUCH_PATH: - return CURLE_REMOTE_FILE_NOT_FOUND; - - case LIBSSH2_FX_PERMISSION_DENIED: - case LIBSSH2_FX_WRITE_PROTECT: - case LIBSSH2_FX_LOCK_CONFlICT: - return CURLE_REMOTE_ACCESS_DENIED; - - case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: - case LIBSSH2_FX_QUOTA_EXCEEDED: - return CURLE_REMOTE_DISK_FULL; - - case LIBSSH2_FX_FILE_ALREADY_EXISTS: - return CURLE_REMOTE_FILE_EXISTS; - - case LIBSSH2_FX_DIR_NOT_EMPTY: - return CURLE_QUOTE_ERROR; - - default: - break; - } - - return CURLE_SSH; -} - -static CURLcode libssh2_session_error_to_CURLE(int err) -{ - switch (err) { - /* Ordered by order of appearance in libssh2.h */ - case LIBSSH2_ERROR_NONE: - return CURLE_OK; - - case LIBSSH2_ERROR_SOCKET_NONE: - return CURLE_COULDNT_CONNECT; - - case LIBSSH2_ERROR_ALLOC: - return CURLE_OUT_OF_MEMORY; - - case LIBSSH2_ERROR_SOCKET_SEND: - return CURLE_SEND_ERROR; - - case LIBSSH2_ERROR_HOSTKEY_INIT: - case LIBSSH2_ERROR_HOSTKEY_SIGN: - case LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED: - case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED: - return CURLE_PEER_FAILED_VERIFICATION; - - case LIBSSH2_ERROR_PASSWORD_EXPIRED: - return CURLE_LOGIN_DENIED; - - case LIBSSH2_ERROR_SOCKET_TIMEOUT: - case LIBSSH2_ERROR_TIMEOUT: - return CURLE_OPERATION_TIMEDOUT; - - case LIBSSH2_ERROR_EAGAIN: - return CURLE_AGAIN; - } - - /* TODO: map some more of the libssh2 errors to the more appropriate CURLcode - error code, and possibly add a few new SSH-related one. We must however - not return or even depend on libssh2 errors in the public libcurl API */ - - return CURLE_SSH; -} - -static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc) -{ - (void)abstract; /* arg not used */ - return malloc(count); -} - -static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc) -{ - (void)abstract; /* arg not used */ - return realloc(ptr, count); -} - -static LIBSSH2_FREE_FUNC(my_libssh2_free) -{ - (void)abstract; /* arg not used */ - if(ptr) /* ssh2 agent sometimes call free with null ptr */ - free(ptr); -} - -/* - * SSH State machine related code - */ -/* This is the ONLY way to change SSH state! */ -static void state(struct connectdata *conn, sshstate nowstate) -{ -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ - static const char * const names[] = { - "SSH_STOP", - "SSH_INIT", - "SSH_S_STARTUP", - "SSH_HOSTKEY", - "SSH_AUTHLIST", - "SSH_AUTH_PKEY_INIT", - "SSH_AUTH_PKEY", - "SSH_AUTH_PASS_INIT", - "SSH_AUTH_PASS", - "SSH_AUTH_AGENT_INIT", - "SSH_AUTH_AGENT_LIST", - "SSH_AUTH_AGENT", - "SSH_AUTH_HOST_INIT", - "SSH_AUTH_HOST", - "SSH_AUTH_KEY_INIT", - "SSH_AUTH_KEY", - "SSH_AUTH_DONE", - "SSH_SFTP_INIT", - "SSH_SFTP_REALPATH", - "SSH_SFTP_QUOTE_INIT", - "SSH_SFTP_POSTQUOTE_INIT", - "SSH_SFTP_QUOTE", - "SSH_SFTP_NEXT_QUOTE", - "SSH_SFTP_QUOTE_STAT", - "SSH_SFTP_QUOTE_SETSTAT", - "SSH_SFTP_QUOTE_SYMLINK", - "SSH_SFTP_QUOTE_MKDIR", - "SSH_SFTP_QUOTE_RENAME", - "SSH_SFTP_QUOTE_RMDIR", - "SSH_SFTP_QUOTE_UNLINK", - "SSH_SFTP_TRANS_INIT", - "SSH_SFTP_UPLOAD_INIT", - "SSH_SFTP_CREATE_DIRS_INIT", - "SSH_SFTP_CREATE_DIRS", - "SSH_SFTP_CREATE_DIRS_MKDIR", - "SSH_SFTP_READDIR_INIT", - "SSH_SFTP_READDIR", - "SSH_SFTP_READDIR_LINK", - "SSH_SFTP_READDIR_BOTTOM", - "SSH_SFTP_READDIR_DONE", - "SSH_SFTP_DOWNLOAD_INIT", - "SSH_SFTP_DOWNLOAD_STAT", - "SSH_SFTP_CLOSE", - "SSH_SFTP_SHUTDOWN", - "SSH_SCP_TRANS_INIT", - "SSH_SCP_UPLOAD_INIT", - "SSH_SCP_DOWNLOAD_INIT", - "SSH_SCP_DONE", - "SSH_SCP_SEND_EOF", - "SSH_SCP_WAIT_EOF", - "SSH_SCP_WAIT_CLOSE", - "SSH_SCP_CHANNEL_FREE", - "SSH_SESSION_DISCONNECT", - "SSH_SESSION_FREE", - "QUIT" - }; -#endif - struct ssh_conn *sshc = &conn->proto.sshc; - -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(sshc->state != nowstate) { - infof(conn->data, "SFTP %p state change from %s to %s\n", - sshc, names[sshc->state], names[nowstate]); - } -#endif - - sshc->state = nowstate; -} - -/* figure out the path to work with in this particular request */ -static CURLcode ssh_getworkingpath(struct connectdata *conn, - char *homedir, /* when SFTP is used */ - char **path) /* returns the allocated - real path to work with */ -{ - struct SessionHandle *data = conn->data; - char *real_path = NULL; - char *working_path; - int working_path_len; - - working_path = curl_easy_unescape(data, data->state.path, 0, - &working_path_len); - if(!working_path) - return CURLE_OUT_OF_MEMORY; - - /* Check for /~/ , indicating relative to the user's home directory */ - if(conn->handler->protocol & CURLPROTO_SCP) { - real_path = malloc(working_path_len+1); - if(real_path == NULL) { - free(working_path); - return CURLE_OUT_OF_MEMORY; - } - if((working_path_len > 3) && (!memcmp(working_path, "/~/", 3))) - /* It is referenced to the home directory, so strip the leading '/~/' */ - memcpy(real_path, working_path+3, 4 + working_path_len-3); - else - memcpy(real_path, working_path, 1 + working_path_len); - } - else if(conn->handler->protocol & CURLPROTO_SFTP) { - if((working_path_len > 1) && (working_path[1] == '~')) { - size_t homelen = strlen(homedir); - real_path = malloc(homelen + working_path_len + 1); - if(real_path == NULL) { - free(working_path); - return CURLE_OUT_OF_MEMORY; - } - /* It is referenced to the home directory, so strip the - leading '/' */ - memcpy(real_path, homedir, homelen); - real_path[homelen] = '/'; - real_path[homelen+1] = '\0'; - if(working_path_len > 3) { - memcpy(real_path+homelen+1, working_path + 3, - 1 + working_path_len -3); - } - } - else { - real_path = malloc(working_path_len+1); - if(real_path == NULL) { - free(working_path); - return CURLE_OUT_OF_MEMORY; - } - memcpy(real_path, working_path, 1+working_path_len); - } - } - - free(working_path); - - /* store the pointer for the caller to receive */ - *path = real_path; - - return CURLE_OK; -} - -#ifdef HAVE_LIBSSH2_KNOWNHOST_API -static int sshkeycallback(CURL *easy, - const struct curl_khkey *knownkey, /* known */ - const struct curl_khkey *foundkey, /* found */ - enum curl_khmatch match, - void *clientp) -{ - (void)easy; - (void)knownkey; - (void)foundkey; - (void)clientp; - - /* we only allow perfect matches, and we reject everything else */ - return (match != CURLKHMATCH_OK)?CURLKHSTAT_REJECT:CURLKHSTAT_FINE; -} -#endif - -/* - * Earlier libssh2 versions didn't have the ability to seek to 64bit positions - * with 32bit size_t. - */ -#ifdef HAVE_LIBSSH2_SFTP_SEEK64 -#define SFTP_SEEK(x,y) libssh2_sftp_seek64(x, (libssh2_uint64_t)y) -#else -#define SFTP_SEEK(x,y) libssh2_sftp_seek(x, (size_t)y) -#endif - -/* - * Earlier libssh2 versions didn't do SCP properly beyond 32bit sizes on 32bit - * architectures so we check of the necessary function is present. - */ -#ifndef HAVE_LIBSSH2_SCP_SEND64 -#define SCP_SEND(a,b,c,d) libssh2_scp_send_ex(a, b, (int)(c), (size_t)d, 0, 0) -#else -#define SCP_SEND(a,b,c,d) libssh2_scp_send64(a, b, (int)(c), \ - (libssh2_uint64_t)d, 0, 0) -#endif - -/* - * libssh2 1.2.8 fixed the problem with 32bit ints used for sockets on win64. - */ -#ifdef HAVE_LIBSSH2_SESSION_HANDSHAKE -#define libssh2_session_startup(x,y) libssh2_session_handshake(x,y) -#endif - -static CURLcode ssh_knownhost(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - struct SessionHandle *data = conn->data; - - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - /* we're asked to verify the host against a file */ - struct ssh_conn *sshc = &conn->proto.sshc; - int rc; - int keytype; - size_t keylen; - const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, - &keylen, &keytype); - int keycheck = LIBSSH2_KNOWNHOST_CHECK_FAILURE; - int keybit = 0; - - if(remotekey) { - /* - * A subject to figure out is what host name we need to pass in here. - * What host name does OpenSSH store in its file if an IDN name is - * used? - */ - struct libssh2_knownhost *host; - enum curl_khmatch keymatch; - curl_sshkeycallback func = - data->set.ssh_keyfunc?data->set.ssh_keyfunc:sshkeycallback; - struct curl_khkey knownkey; - struct curl_khkey *knownkeyp = NULL; - struct curl_khkey foundkey; - - keybit = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? - LIBSSH2_KNOWNHOST_KEY_SSHRSA:LIBSSH2_KNOWNHOST_KEY_SSHDSS; - - keycheck = libssh2_knownhost_check(sshc->kh, - conn->host.name, - remotekey, keylen, - LIBSSH2_KNOWNHOST_TYPE_PLAIN| - LIBSSH2_KNOWNHOST_KEYENC_RAW| - keybit, - &host); - - infof(data, "SSH host check: %d, key: %s\n", keycheck, - (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)? - host->key:""); - - /* setup 'knownkey' */ - if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { - knownkey.key = host->key; - knownkey.len = 0; - knownkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? - CURLKHTYPE_RSA : CURLKHTYPE_DSS; - knownkeyp = &knownkey; - } - - /* setup 'foundkey' */ - foundkey.key = remotekey; - foundkey.len = keylen; - foundkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? - CURLKHTYPE_RSA : CURLKHTYPE_DSS; - - /* - * if any of the LIBSSH2_KNOWNHOST_CHECK_* defines and the - * curl_khmatch enum are ever modified, we need to introduce a - * translation table here! - */ - keymatch = (enum curl_khmatch)keycheck; - - /* Ask the callback how to behave */ - rc = func(data, knownkeyp, /* from the knownhosts file */ - &foundkey, /* from the remote host */ - keymatch, data->set.ssh_keyfunc_userp); - } - else - /* no remotekey means failure! */ - rc = CURLKHSTAT_REJECT; - - switch(rc) { - default: /* unknown return codes will equal reject */ - case CURLKHSTAT_REJECT: - state(conn, SSH_SESSION_FREE); - case CURLKHSTAT_DEFER: - /* DEFER means bail out but keep the SSH_HOSTKEY state */ - result = sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - break; - case CURLKHSTAT_FINE: - case CURLKHSTAT_FINE_ADD_TO_FILE: - /* proceed */ - if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { - /* the found host+key didn't match but has been told to be fine - anyway so we add it in memory */ - int addrc = libssh2_knownhost_add(sshc->kh, - conn->host.name, NULL, - remotekey, keylen, - LIBSSH2_KNOWNHOST_TYPE_PLAIN| - LIBSSH2_KNOWNHOST_KEYENC_RAW| - keybit, NULL); - if(addrc) - infof(data, "Warning adding the known host %s failed!\n", - conn->host.name); - else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE) { - /* now we write the entire in-memory list of known hosts to the - known_hosts file */ - int wrc = - libssh2_knownhost_writefile(sshc->kh, - data->set.str[STRING_SSH_KNOWNHOSTS], - LIBSSH2_KNOWNHOST_FILE_OPENSSH); - if(wrc) { - infof(data, "Warning, writing %s failed!\n", - data->set.str[STRING_SSH_KNOWNHOSTS]); - } - } - } - break; - } - } -#else /* HAVE_LIBSSH2_KNOWNHOST_API */ - (void)conn; -#endif - return result; -} - -static CURLcode ssh_check_fingerprint(struct connectdata *conn) -{ - struct ssh_conn *sshc = &conn->proto.sshc; - struct SessionHandle *data = conn->data; - const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; - char md5buffer[33]; - int i; - - const char *fingerprint = libssh2_hostkey_hash(sshc->ssh_session, - LIBSSH2_HOSTKEY_HASH_MD5); - - if(fingerprint) { - /* The fingerprint points to static storage (!), don't free() it. */ - for(i = 0; i < 16; i++) - snprintf(&md5buffer[i*2], 3, "%02x", (unsigned char) fingerprint[i]); - infof(data, "SSH MD5 fingerprint: %s\n", md5buffer); - } - - /* Before we authenticate we check the hostkey's MD5 fingerprint - * against a known fingerprint, if available. - */ - if(pubkey_md5 && strlen(pubkey_md5) == 32) { - if(!fingerprint || !strequal(md5buffer, pubkey_md5)) { - if(fingerprint) - failf(data, - "Denied establishing ssh session: mismatch md5 fingerprint. " - "Remote %s is not equal to %s", md5buffer, pubkey_md5); - else - failf(data, - "Denied establishing ssh session: md5 fingerprint not available"); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; - } - else { - infof(data, "MD5 checksum match!\n"); - /* as we already matched, we skip the check for known hosts */ - return CURLE_OK; - } - } - else - return ssh_knownhost(conn); -} - -/* - * ssh_statemach_act() runs the SSH state machine as far as it can without - * blocking and without reaching the end. The data the pointer 'block' points - * to will be set to TRUE if the libssh2 function returns LIBSSH2_ERROR_EAGAIN - * meaning it wants to be called again when the socket is ready - */ - -static CURLcode ssh_statemach_act(struct connectdata *conn, bool *block) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - struct SSHPROTO *sftp_scp = data->state.proto.ssh; - struct ssh_conn *sshc = &conn->proto.sshc; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - char *new_readdir_line; - int rc = LIBSSH2_ERROR_NONE; - int err; - int seekerr = CURL_SEEKFUNC_OK; - *block = 0; /* we're not blocking by default */ - - do { - - switch(sshc->state) { - case SSH_INIT: - sshc->secondCreateDirs = 0; - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_OK; - - /* Set libssh2 to non-blocking, since everything internally is - non-blocking */ - libssh2_session_set_blocking(sshc->ssh_session, 0); - - state(conn, SSH_S_STARTUP); - /* fall-through */ - - case SSH_S_STARTUP: - rc = libssh2_session_startup(sshc->ssh_session, (int)sock); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - failf(data, "Failure establishing ssh session"); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_FAILED_INIT; - break; - } - - state(conn, SSH_HOSTKEY); - - /* fall-through */ - case SSH_HOSTKEY: - /* - * Before we authenticate we should check the hostkey's fingerprint - * against our known hosts. How that is handled (reading from file, - * whatever) is up to us. - */ - result = ssh_check_fingerprint(conn); - if(result == CURLE_OK) - state(conn, SSH_AUTHLIST); - break; - - case SSH_AUTHLIST: - /* - * Figure out authentication methods - * NB: As soon as we have provided a username to an openssh server we - * must never change it later. Thus, always specify the correct username - * here, even though the libssh2 docs kind of indicate that it should be - * possible to get a 'generic' list (not user-specific) of authentication - * methods, presumably with a blank username. That won't work in my - * experience. - * So always specify it here. - */ - sshc->authlist = libssh2_userauth_list(sshc->ssh_session, - conn->user, - curlx_uztoui(strlen(conn->user))); - - if(!sshc->authlist) { - if((err = libssh2_session_last_errno(sshc->ssh_session)) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - state(conn, SSH_SESSION_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(err); - break; - } - } - infof(data, "SSH authentication methods available: %s\n", - sshc->authlist); - - state(conn, SSH_AUTH_PKEY_INIT); - break; - - case SSH_AUTH_PKEY_INIT: - /* - * Check the supported auth types in the order I feel is most secure - * with the requested type of authentication - */ - sshc->authed = FALSE; - - if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) && - (strstr(sshc->authlist, "publickey") != NULL)) { - char *home = NULL; - bool rsa_pub_empty_but_ok = FALSE; - - sshc->rsa_pub = sshc->rsa = NULL; - - /* To ponder about: should really the lib be messing about with the - HOME environment variable etc? */ - home = curl_getenv("HOME"); - - if(data->set.str[STRING_SSH_PUBLIC_KEY] && - !*data->set.str[STRING_SSH_PUBLIC_KEY]) - rsa_pub_empty_but_ok = true; - else if(data->set.str[STRING_SSH_PUBLIC_KEY]) - sshc->rsa_pub = aprintf("%s", data->set.str[STRING_SSH_PUBLIC_KEY]); - else if(home) - sshc->rsa_pub = aprintf("%s/.ssh/id_dsa.pub", home); - else - /* as a final resort, try current dir! */ - sshc->rsa_pub = strdup("id_dsa.pub"); - - if(!rsa_pub_empty_but_ok && (sshc->rsa_pub == NULL)) { - Curl_safefree(home); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - - if(data->set.str[STRING_SSH_PRIVATE_KEY]) - sshc->rsa = aprintf("%s", data->set.str[STRING_SSH_PRIVATE_KEY]); - else if(home) - sshc->rsa = aprintf("%s/.ssh/id_dsa", home); - else - /* as a final resort, try current dir! */ - sshc->rsa = strdup("id_dsa"); - - if(sshc->rsa == NULL) { - Curl_safefree(home); - Curl_safefree(sshc->rsa_pub); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - - sshc->passphrase = data->set.str[STRING_KEY_PASSWD]; - if(!sshc->passphrase) - sshc->passphrase = ""; - - Curl_safefree(home); - - infof(data, "Using ssh public key file %s\n", sshc->rsa_pub); - infof(data, "Using ssh private key file %s\n", sshc->rsa); - - state(conn, SSH_AUTH_PKEY); - } - else { - state(conn, SSH_AUTH_PASS_INIT); - } - break; - - case SSH_AUTH_PKEY: - /* The function below checks if the files exists, no need to stat() here. - */ - rc = libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session, - conn->user, - curlx_uztoui( - strlen(conn->user)), - sshc->rsa_pub, - sshc->rsa, sshc->passphrase); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - - Curl_safefree(sshc->rsa_pub); - Curl_safefree(sshc->rsa); - - if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized SSH public key authentication\n"); - state(conn, SSH_AUTH_DONE); - } - else { - char *err_msg; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "SSH public key authentication failed: %s\n", err_msg); - state(conn, SSH_AUTH_PASS_INIT); - } - break; - - case SSH_AUTH_PASS_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) && - (strstr(sshc->authlist, "password") != NULL)) { - state(conn, SSH_AUTH_PASS); - } - else { - state(conn, SSH_AUTH_HOST_INIT); - } - break; - - case SSH_AUTH_PASS: - rc = libssh2_userauth_password_ex(sshc->ssh_session, conn->user, - curlx_uztoui(strlen(conn->user)), - conn->passwd, - curlx_uztoui(strlen(conn->passwd)), - NULL); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized password authentication\n"); - state(conn, SSH_AUTH_DONE); - } - else { - state(conn, SSH_AUTH_HOST_INIT); - } - break; - - case SSH_AUTH_HOST_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) && - (strstr(sshc->authlist, "hostbased") != NULL)) { - state(conn, SSH_AUTH_HOST); - } - else { - state(conn, SSH_AUTH_AGENT_INIT); - } - break; - - case SSH_AUTH_HOST: - state(conn, SSH_AUTH_AGENT_INIT); - break; - - case SSH_AUTH_AGENT_INIT: -#ifdef HAVE_LIBSSH2_AGENT_API - if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT) - && (strstr(sshc->authlist, "publickey") != NULL)) { - - /* Connect to the ssh-agent */ - /* The agent could be shared by a curl thread i believe - but nothing obvious as keys can be added/removed at any time */ - if(!sshc->ssh_agent) { - sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session); - if(!sshc->ssh_agent) { - infof(data, "Could not create agent object\n"); - - state(conn, SSH_AUTH_KEY_INIT); - } - } - - rc = libssh2_agent_connect(sshc->ssh_agent); - if(rc == LIBSSH2_ERROR_EAGAIN) - break; - if(rc < 0) { - infof(data, "Failure connecting to agent\n"); - state(conn, SSH_AUTH_KEY_INIT); - } - else { - state(conn, SSH_AUTH_AGENT_LIST); - } - } - else -#endif /* HAVE_LIBSSH2_AGENT_API */ - state(conn, SSH_AUTH_KEY_INIT); - break; - - case SSH_AUTH_AGENT_LIST: -#ifdef HAVE_LIBSSH2_AGENT_API - rc = libssh2_agent_list_identities(sshc->ssh_agent); - - if(rc == LIBSSH2_ERROR_EAGAIN) - break; - if(rc < 0) { - infof(data, "Failure requesting identities to agent\n"); - state(conn, SSH_AUTH_KEY_INIT); - } - else { - state(conn, SSH_AUTH_AGENT); - sshc->sshagent_prev_identity = NULL; - } -#endif - break; - - case SSH_AUTH_AGENT: -#ifdef HAVE_LIBSSH2_AGENT_API - /* as prev_identity evolves only after an identity user auth finished we - can safely request it again as long as EAGAIN is returned here or by - libssh2_agent_userauth */ - rc = libssh2_agent_get_identity(sshc->ssh_agent, - &sshc->sshagent_identity, - sshc->sshagent_prev_identity); - if(rc == LIBSSH2_ERROR_EAGAIN) - break; - - if(rc == 0) { - rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user, - sshc->sshagent_identity); - - if(rc < 0) { - if(rc != LIBSSH2_ERROR_EAGAIN) { - /* tried and failed? go to next identity */ - sshc->sshagent_prev_identity = sshc->sshagent_identity; - } - break; - } - } - - if(rc < 0) - infof(data, "Failure requesting identities to agent\n"); - else if(rc == 1) - infof(data, "No identity would match\n"); - - if(rc == LIBSSH2_ERROR_NONE) { - sshc->authed = TRUE; - infof(data, "Agent based authentication successful\n"); - state(conn, SSH_AUTH_DONE); - } - else - state(conn, SSH_AUTH_KEY_INIT); -#endif - break; - - case SSH_AUTH_KEY_INIT: - if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) - && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) { - state(conn, SSH_AUTH_KEY); - } - else { - state(conn, SSH_AUTH_DONE); - } - break; - - case SSH_AUTH_KEY: - /* Authentication failed. Continue with keyboard-interactive now. */ - rc = libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session, - conn->user, - curlx_uztoui( - strlen(conn->user)), - &kbd_callback); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized keyboard interactive authentication\n"); - } - state(conn, SSH_AUTH_DONE); - break; - - case SSH_AUTH_DONE: - if(!sshc->authed) { - failf(data, "Authentication failure"); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_LOGIN_DENIED; - break; - } - - /* - * At this point we have an authenticated ssh session. - */ - infof(data, "Authentication complete\n"); - - Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSH is connected */ - - conn->sockfd = sock; - conn->writesockfd = CURL_SOCKET_BAD; - - if(conn->handler->protocol == CURLPROTO_SFTP) { - state(conn, SSH_SFTP_INIT); - break; - } - infof(data, "SSH CONNECT phase done\n"); - state(conn, SSH_STOP); - break; - - case SSH_SFTP_INIT: - /* - * Start the libssh2 sftp session - */ - sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session); - if(!sshc->sftp_session) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - char *err_msg; - - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - failf(data, "Failure initializing sftp session: %s", err_msg); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_FAILED_INIT; - break; - } - } - state(conn, SSH_SFTP_REALPATH); - break; - - case SSH_SFTP_REALPATH: - { - char tempHome[PATH_MAX]; - - /* - * Get the "home" directory - */ - rc = sftp_libssh2_realpath(sshc->sftp_session, ".", - tempHome, PATH_MAX-1); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc > 0) { - /* It seems that this string is not always NULL terminated */ - tempHome[rc] = '\0'; - sshc->homedir = strdup(tempHome); - if(!sshc->homedir) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - conn->data->state.most_recent_ftp_entrypath = sshc->homedir; - } - else { - /* Return the error type */ - err = sftp_libssh2_last_error(sshc->sftp_session); - result = sftp_libssh2_error_to_CURLE(err); - sshc->actualcode = result?result:CURLE_SSH; - DEBUGF(infof(data, "error = %d makes libcurl = %d\n", - err, (int)result)); - state(conn, SSH_STOP); - break; - } - } - /* This is the last step in the SFTP connect phase. Do note that while - we get the homedir here, we get the "workingpath" in the DO action - since the homedir will remain the same between request but the - working path will not. */ - DEBUGF(infof(data, "SSH CONNECT phase done\n")); - state(conn, SSH_STOP); - break; - - case SSH_SFTP_QUOTE_INIT: - - result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path); - if(result) { - sshc->actualcode = result; - state(conn, SSH_STOP); - break; - } - - if(data->set.quote) { - infof(data, "Sending quote commands\n"); - sshc->quote_item = data->set.quote; - state(conn, SSH_SFTP_QUOTE); - } - else { - state(conn, SSH_SFTP_TRANS_INIT); - } - break; - - case SSH_SFTP_POSTQUOTE_INIT: - if(data->set.postquote) { - infof(data, "Sending quote commands\n"); - sshc->quote_item = data->set.postquote; - state(conn, SSH_SFTP_QUOTE); - } - else { - state(conn, SSH_STOP); - } - break; - - case SSH_SFTP_QUOTE: - /* Send any quote commands */ - { - const char *cp; - - /* - * Support some of the "FTP" commands - */ - char *cmd = sshc->quote_item->data; - sshc->acceptfail = FALSE; - - /* if a command starts with an asterisk, which a legal SFTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ - - if(cmd[0] == '*') { - cmd++; - sshc->acceptfail = TRUE; - } - - if(curl_strequal("pwd", cmd)) { - /* output debug output if that is requested */ - char *tmp = aprintf("257 \"%s\" is current directory.\n", - sftp_scp->path); - if(!tmp) { - result = CURLE_OUT_OF_MEMORY; - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - break; - } - if(data->set.verbose) { - Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4, conn); - Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn); - } - /* this sends an FTP-like "header" to the header callback so that the - current directory can be read very similar to how it is read when - using ordinary FTP. */ - result = Curl_client_write(conn, CLIENTWRITE_HEADER, tmp, strlen(tmp)); - free(tmp); - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - } - else if(cmd) { - /* - * the arguments following the command must be separated from the - * command with a space so we can check for it unconditionally - */ - cp = strchr(cmd, ' '); - if(cp == NULL) { - failf(data, "Syntax error in SFTP command. Supply parameter(s)!"); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - - /* - * also, every command takes at least one argument so we get that - * first argument right now - */ - result = get_pathname(&cp, &sshc->quote_path1); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error: Bad first parameter"); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - - /* - * SFTP is a binary protocol, so we don't send text commands to - * the server. Instead, we scan for commands for commands used by - * OpenSSH's sftp program and call the appropriate libssh2 - * functions. - */ - if(curl_strnequal(cmd, "chgrp ", 6) || - curl_strnequal(cmd, "chmod ", 6) || - curl_strnequal(cmd, "chown ", 6) ) { - /* attribute change */ - - /* sshc->quote_path1 contains the mode to set */ - /* get the destination */ - result = get_pathname(&cp, &sshc->quote_path2); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in chgrp/chmod/chown: " - "Bad second parameter"); - Curl_safefree(sshc->quote_path1); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - memset(&sshc->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); - state(conn, SSH_SFTP_QUOTE_STAT); - break; - } - else if(curl_strnequal(cmd, "ln ", 3) || - curl_strnequal(cmd, "symlink ", 8)) { - /* symbolic linking */ - /* sshc->quote_path1 is the source */ - /* get the destination */ - result = get_pathname(&cp, &sshc->quote_path2); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, - "Syntax error in ln/symlink: Bad second parameter"); - Curl_safefree(sshc->quote_path1); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - state(conn, SSH_SFTP_QUOTE_SYMLINK); - break; - } - else if(curl_strnequal(cmd, "mkdir ", 6)) { - /* create dir */ - state(conn, SSH_SFTP_QUOTE_MKDIR); - break; - } - else if(curl_strnequal(cmd, "rename ", 7)) { - /* rename file */ - /* first param is the source path */ - /* second param is the dest. path */ - result = get_pathname(&cp, &sshc->quote_path2); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in rename: Bad second parameter"); - Curl_safefree(sshc->quote_path1); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - state(conn, SSH_SFTP_QUOTE_RENAME); - break; - } - else if(curl_strnequal(cmd, "rmdir ", 6)) { - /* delete dir */ - state(conn, SSH_SFTP_QUOTE_RMDIR); - break; - } - else if(curl_strnequal(cmd, "rm ", 3)) { - state(conn, SSH_SFTP_QUOTE_UNLINK); - break; - } - - failf(data, "Unknown SFTP command"); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - if(!sshc->quote_item) { - state(conn, SSH_SFTP_TRANS_INIT); - } - break; - - case SSH_SFTP_NEXT_QUOTE: - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - - sshc->quote_item = sshc->quote_item->next; - - if(sshc->quote_item) { - state(conn, SSH_SFTP_QUOTE); - } - else { - if(sshc->nextstate != SSH_NO_STATE) { - state(conn, sshc->nextstate); - sshc->nextstate = SSH_NO_STATE; - } - else { - state(conn, SSH_SFTP_TRANS_INIT); - } - } - break; - - case SSH_SFTP_QUOTE_STAT: - { - char *cmd = sshc->quote_item->data; - sshc->acceptfail = FALSE; - - /* if a command starts with an asterisk, which a legal SFTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ - - if(cmd[0] == '*') { - cmd++; - sshc->acceptfail = TRUE; - } - - if(!curl_strnequal(cmd, "chmod", 5)) { - /* Since chown and chgrp only set owner OR group but libssh2 wants to - * set them both at once, we need to obtain the current ownership - * first. This takes an extra protocol round trip. - */ - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_STAT, - &sshc->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { /* get those attributes */ - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Attempt to get SFTP stats failed: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - - /* Now set the new attributes... */ - if(curl_strnequal(cmd, "chgrp", 5)) { - sshc->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10); - sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; - if(sshc->quote_attrs.gid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chgrp gid not a number"); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(curl_strnequal(cmd, "chmod", 5)) { - sshc->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8); - sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; - /* permissions are octal */ - if(sshc->quote_attrs.permissions == 0 && - !ISDIGIT(sshc->quote_path1[0])) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chmod permissions not a number"); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(curl_strnequal(cmd, "chown", 5)) { - sshc->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10); - sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; - if(sshc->quote_attrs.uid == 0 && !ISDIGIT(sshc->quote_path1[0]) && - !sshc->acceptfail) { - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Syntax error: chown uid not a number"); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - - /* Now send the completed structure... */ - state(conn, SSH_SFTP_QUOTE_SETSTAT); - break; - } - - case SSH_SFTP_QUOTE_SETSTAT: - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_SETSTAT, - &sshc->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "Attempt to set SFTP stats failed: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_SYMLINK: - rc = libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_SYMLINK); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "symlink command failed: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_MKDIR: - rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - data->set.new_directory_perms); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_RENAME: - rc = libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1)), - sshc->quote_path2, - curlx_uztoui(strlen(sshc->quote_path2)), - LIBSSH2_SFTP_RENAME_OVERWRITE | - LIBSSH2_SFTP_RENAME_ATOMIC | - LIBSSH2_SFTP_RENAME_NATIVE); - - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "rename command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_RMDIR: - rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1))); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_QUOTE_UNLINK: - rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1, - curlx_uztoui(strlen(sshc->quote_path1))); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "rm command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - - case SSH_SFTP_TRANS_INIT: - if(data->set.upload) - state(conn, SSH_SFTP_UPLOAD_INIT); - else { - if(data->set.opt_no_body) - state(conn, SSH_STOP); - else if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') - state(conn, SSH_SFTP_READDIR_INIT); - else - state(conn, SSH_SFTP_DOWNLOAD_INIT); - } - break; - - case SSH_SFTP_UPLOAD_INIT: - { - unsigned long flags; - /* - * NOTE!!! libssh2 requires that the destination path is a full path - * that includes the destination file and name OR ends in a "/" - * If this is not done the destination file will be named the - * same name as the last directory in the path. - */ - - if(data->state.resume_from != 0) { - LIBSSH2_SFTP_ATTRIBUTES attrs; - if(data->state.resume_from < 0) { - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - data->state.resume_from = 0; - } - else { - curl_off_t size = attrs.filesize; - if(size < 0) { - failf(data, "Bad file size (%" FORMAT_OFF_T ")", size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - data->state.resume_from = attrs.filesize; - } - } - } - - if(data->set.ftp_append) - /* Try to open for append, but create if nonexisting */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_APPEND; - else if(data->state.resume_from > 0) - /* If we have restart position then open for append */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_APPEND; - else - /* Clear file before writing (normal behaviour) */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC; - - sshc->sftp_handle = - libssh2_sftp_open_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->path)), - flags, data->set.new_file_perms, - LIBSSH2_SFTP_OPENFILE); - - if(!sshc->sftp_handle) { - rc = libssh2_session_last_errno(sshc->ssh_session); - - if(LIBSSH2_ERROR_EAGAIN == rc) - break; - else { - if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc) - /* only when there was an SFTP protocol error can we extract - the sftp error! */ - err = sftp_libssh2_last_error(sshc->sftp_session); - else - err = -1; /* not an sftp error at all */ - - if(sshc->secondCreateDirs) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = err>= LIBSSH2_FX_OK? - sftp_libssh2_error_to_CURLE(err):CURLE_SSH; - failf(data, "Creating the dir/file failed: %s", - sftp_libssh2_strerror(err)); - break; - } - else if(((err == LIBSSH2_FX_NO_SUCH_FILE) || - (err == LIBSSH2_FX_FAILURE) || - (err == LIBSSH2_FX_NO_SUCH_PATH)) && - (data->set.ftp_create_missing_dirs && - (strlen(sftp_scp->path) > 1))) { - /* try to create the path remotely */ - sshc->secondCreateDirs = 1; - state(conn, SSH_SFTP_CREATE_DIRS_INIT); - break; - } - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = err>= LIBSSH2_FX_OK? - sftp_libssh2_error_to_CURLE(err):CURLE_SSH; - if(!sshc->actualcode) { - /* Sometimes, for some reason libssh2_sftp_last_error() returns - zero even though libssh2_sftp_open() failed previously! We need - to work around that! */ - sshc->actualcode = CURLE_SSH; - err=-1; - } - failf(data, "Upload failed: %s (%d/%d)", - err>= LIBSSH2_FX_OK?sftp_libssh2_strerror(err):"ssh error", - err, rc); - break; - } - } - - /* If we have restart point then we need to seek to the correct - position. */ - if(data->state.resume_from > 0) { - /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_FTP_COULDNT_USE_REST; - } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - else { - curl_off_t passed=0; - do { - size_t readthisamountnow = - (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? - BUFSIZE : curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - conn->fread_func(data->state.buffer, 1, readthisamountnow, - conn->fread_in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Failed to read data"); - return CURLE_FTP_COULDNT_USE_REST; - } - } while(passed < data->state.resume_from); - } - } - - /* now, decrease the size of the read */ - if(data->set.infilesize > 0) { - data->set.infilesize -= data->state.resume_from; - data->req.size = data->set.infilesize; - Curl_pgrsSetUploadSize(data, data->set.infilesize); - } - - SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); - } - if(data->set.infilesize > 0) { - data->req.size = data->set.infilesize; - Curl_pgrsSetUploadSize(data, data->set.infilesize); - } - /* upload data */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - - if(result) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = result; - } - else { - /* store this original bitmask setup to use later on if we can't - figure out a "real" bitmask */ - sshc->orig_waitfor = data->req.keepon; - - /* we want to use the _sending_ function even when the socket turns - out readable as the underlying libssh2 sftp send function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; - - /* since we don't really wait for anything at this point, we want the - state machine to move on as soon as possible so we set a very short - timeout here */ - Curl_expire(data, 1); - - state(conn, SSH_STOP); - } - break; - } - - case SSH_SFTP_CREATE_DIRS_INIT: - if(strlen(sftp_scp->path) > 1) { - sshc->slash_pos = sftp_scp->path + 1; /* ignore the leading '/' */ - state(conn, SSH_SFTP_CREATE_DIRS); - } - else { - state(conn, SSH_SFTP_UPLOAD_INIT); - } - break; - - case SSH_SFTP_CREATE_DIRS: - if((sshc->slash_pos = strchr(sshc->slash_pos, '/')) != NULL) { - *sshc->slash_pos = 0; - - infof(data, "Creating directory '%s'\n", sftp_scp->path); - state(conn, SSH_SFTP_CREATE_DIRS_MKDIR); - break; - } - else { - state(conn, SSH_SFTP_UPLOAD_INIT); - } - break; - - case SSH_SFTP_CREATE_DIRS_MKDIR: - /* 'mode' - parameter is preliminary - default to 0644 */ - rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->path)), - data->set.new_directory_perms); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - *sshc->slash_pos = '/'; - ++sshc->slash_pos; - if(rc == -1) { - /* - * Abort if failure wasn't that the dir already exists or the - * permission was denied (creation might succeed further down the - * path) - retry on unspecific FAILURE also - */ - err = sftp_libssh2_last_error(sshc->sftp_session); - if((err != LIBSSH2_FX_FILE_ALREADY_EXISTS) && - (err != LIBSSH2_FX_FAILURE) && - (err != LIBSSH2_FX_PERMISSION_DENIED)) { - result = sftp_libssh2_error_to_CURLE(err); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - } - state(conn, SSH_SFTP_CREATE_DIRS); - break; - - case SSH_SFTP_READDIR_INIT: - /* - * This is a directory that we are trying to get, so produce a directory - * listing - */ - sshc->sftp_handle = libssh2_sftp_open_ex(sshc->sftp_session, - sftp_scp->path, - curlx_uztoui( - strlen(sftp_scp->path)), - 0, 0, LIBSSH2_SFTP_OPENDIR); - if(!sshc->sftp_handle) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - err = sftp_libssh2_last_error(sshc->sftp_session); - failf(data, "Could not open directory for reading: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - result = sftp_libssh2_error_to_CURLE(err); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - } - if((sshc->readdir_filename = malloc(PATH_MAX+1)) == NULL) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - if((sshc->readdir_longentry = malloc(PATH_MAX+1)) == NULL) { - Curl_safefree(sshc->readdir_filename); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - state(conn, SSH_SFTP_READDIR); - break; - - case SSH_SFTP_READDIR: - sshc->readdir_len = libssh2_sftp_readdir_ex(sshc->sftp_handle, - sshc->readdir_filename, - PATH_MAX, - sshc->readdir_longentry, - PATH_MAX, - &sshc->readdir_attrs); - if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - if(sshc->readdir_len > 0) { - sshc->readdir_filename[sshc->readdir_len] = '\0'; - - if(data->set.ftp_list_only) { - char *tmpLine; - - tmpLine = aprintf("%s\n", sshc->readdir_filename); - if(tmpLine == NULL) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - result = Curl_client_write(conn, CLIENTWRITE_BODY, - tmpLine, sshc->readdir_len+1); - Curl_safefree(tmpLine); - - if(result) { - state(conn, SSH_STOP); - break; - } - /* since this counts what we send to the client, we include the - newline in this counter */ - data->req.bytecount += sshc->readdir_len+1; - - /* output debug output if that is requested */ - if(data->set.verbose) { - Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_filename, - sshc->readdir_len, conn); - } - } - else { - sshc->readdir_currLen = (int)strlen(sshc->readdir_longentry); - sshc->readdir_totalLen = 80 + sshc->readdir_currLen; - sshc->readdir_line = calloc(sshc->readdir_totalLen, 1); - if(!sshc->readdir_line) { - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - - memcpy(sshc->readdir_line, sshc->readdir_longentry, - sshc->readdir_currLen); - if((sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && - ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == - LIBSSH2_SFTP_S_IFLNK)) { - sshc->readdir_linkPath = malloc(PATH_MAX + 1); - if(sshc->readdir_linkPath == NULL) { - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - - snprintf(sshc->readdir_linkPath, PATH_MAX, "%s%s", sftp_scp->path, - sshc->readdir_filename); - state(conn, SSH_SFTP_READDIR_LINK); - break; - } - state(conn, SSH_SFTP_READDIR_BOTTOM); - break; - } - } - else if(sshc->readdir_len == 0) { - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_READDIR_DONE); - break; - } - else if(sshc->readdir_len <= 0) { - err = sftp_libssh2_last_error(sshc->sftp_session); - result = sftp_libssh2_error_to_CURLE(err); - sshc->actualcode = result?result:CURLE_SSH; - failf(data, "Could not open remote file for reading: %s :: %d", - sftp_libssh2_strerror(err), - libssh2_session_last_errno(sshc->ssh_session)); - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_CLOSE); - break; - } - break; - - case SSH_SFTP_READDIR_LINK: - sshc->readdir_len = - libssh2_sftp_symlink_ex(sshc->sftp_session, - sshc->readdir_linkPath, - curlx_uztoui(strlen(sshc->readdir_linkPath)), - sshc->readdir_filename, - PATH_MAX, LIBSSH2_SFTP_READLINK); - if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - Curl_safefree(sshc->readdir_linkPath); - - /* get room for the filename and extra output */ - sshc->readdir_totalLen += 4 + sshc->readdir_len; - new_readdir_line = realloc(sshc->readdir_line, sshc->readdir_totalLen); - if(!new_readdir_line) { - Curl_safefree(sshc->readdir_line); - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - sshc->readdir_line = new_readdir_line; - - sshc->readdir_currLen += snprintf(sshc->readdir_line + - sshc->readdir_currLen, - sshc->readdir_totalLen - - sshc->readdir_currLen, - " -> %s", - sshc->readdir_filename); - - state(conn, SSH_SFTP_READDIR_BOTTOM); - break; - - case SSH_SFTP_READDIR_BOTTOM: - sshc->readdir_currLen += snprintf(sshc->readdir_line + - sshc->readdir_currLen, - sshc->readdir_totalLen - - sshc->readdir_currLen, "\n"); - result = Curl_client_write(conn, CLIENTWRITE_BODY, - sshc->readdir_line, - sshc->readdir_currLen); - - if(result == CURLE_OK) { - - /* output debug output if that is requested */ - if(data->set.verbose) { - Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_line, - sshc->readdir_currLen, conn); - } - data->req.bytecount += sshc->readdir_currLen; - } - Curl_safefree(sshc->readdir_line); - if(result) { - state(conn, SSH_STOP); - } - else - state(conn, SSH_SFTP_READDIR); - break; - - case SSH_SFTP_READDIR_DONE: - if(libssh2_sftp_closedir(sshc->sftp_handle) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - sshc->sftp_handle = NULL; - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - state(conn, SSH_STOP); - break; - - case SSH_SFTP_DOWNLOAD_INIT: - /* - * Work on getting the specified file - */ - sshc->sftp_handle = - libssh2_sftp_open_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->path)), - LIBSSH2_FXF_READ, data->set.new_file_perms, - LIBSSH2_SFTP_OPENFILE); - if(!sshc->sftp_handle) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - err = sftp_libssh2_last_error(sshc->sftp_session); - failf(data, "Could not open remote file for reading: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - result = sftp_libssh2_error_to_CURLE(err); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - } - state(conn, SSH_SFTP_DOWNLOAD_STAT); - break; - - case SSH_SFTP_DOWNLOAD_STAT: - { - LIBSSH2_SFTP_ATTRIBUTES attrs; - - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - /* - * libssh2_sftp_open() didn't return an error, so maybe the server - * just doesn't support stat() - */ - data->req.size = -1; - data->req.maxdownload = -1; - } - else { - curl_off_t size = attrs.filesize; - - if(size < 0) { - failf(data, "Bad file size (%" FORMAT_OFF_T ")", size); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(conn->data->state.use_range) { - curl_off_t from, to; - char *ptr; - char *ptr2; - - from=curlx_strtoofft(conn->data->state.range, &ptr, 0); - while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) - ptr++; - to=curlx_strtoofft(ptr, &ptr2, 0); - if((ptr == ptr2) /* no "to" value given */ - || (to >= size)) { - to = size - 1; - } - if(from < 0) { - /* from is relative to end of file */ - from += size; - } - if(from >= size) { - failf(data, "Offset (%" - FORMAT_OFF_T ") was beyond file size (%" FORMAT_OFF_T ")", - from, attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(from > to) { - from = to; - size = 0; - } - else { - size = to - from + 1; - } - - SFTP_SEEK(conn->proto.sshc.sftp_handle, from); - } - data->req.size = size; - data->req.maxdownload = size; - Curl_pgrsSetDownloadSize(data, size); - } - - /* We can resume if we can seek to the resume position */ - if(data->state.resume_from) { - if(data->state.resume_from < 0) { - /* We're supposed to download the last abs(from) bytes */ - if((curl_off_t)attrs.filesize < -data->state.resume_from) { - failf(data, "Offset (%" - FORMAT_OFF_T ") was beyond file size (%" FORMAT_OFF_T ")", - data->state.resume_from, attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* download from where? */ - data->state.resume_from += attrs.filesize; - } - else { - if((curl_off_t)attrs.filesize < data->state.resume_from) { - failf(data, "Offset (%" FORMAT_OFF_T - ") was beyond file size (%" FORMAT_OFF_T ")", - data->state.resume_from, attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - } - /* Does a completed file need to be seeked and started or closed ? */ - /* Now store the number of bytes we are expected to download */ - data->req.size = attrs.filesize - data->state.resume_from; - data->req.maxdownload = attrs.filesize - data->state.resume_from; - Curl_pgrsSetDownloadSize(data, - attrs.filesize - data->state.resume_from); - SFTP_SEEK(sshc->sftp_handle, data->state.resume_from); - } - } - /* Setup the actual download */ - if(data->req.size == 0) { - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - infof(data, "File already completely downloaded\n"); - state(conn, SSH_STOP); - break; - } - else { - Curl_setup_transfer(conn, FIRSTSOCKET, data->req.size, - FALSE, NULL, -1, NULL); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->writesockfd = conn->sockfd; - - /* we want to use the _receiving_ function even when the socket turns - out writableable as the underlying libssh2 recv function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; - } - if(result) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = result; - } - else { - state(conn, SSH_STOP); - } - break; - - case SSH_SFTP_CLOSE: - if(sshc->sftp_handle) { - rc = libssh2_sftp_close(sshc->sftp_handle); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to close libssh2 file\n"); - } - sshc->sftp_handle = NULL; - } - if(sftp_scp) - Curl_safefree(sftp_scp->path); - - DEBUGF(infof(data, "SFTP DONE done\n")); - - /* Check if nextstate is set and move .nextstate could be POSTQUOTE_INIT - After nextstate is executed,the control should come back to - SSH_SFTP_CLOSE to pass the correct result back */ - if(sshc->nextstate != SSH_NO_STATE) { - state(conn, sshc->nextstate); - sshc->nextstate = SSH_SFTP_CLOSE; - } - else { - state(conn, SSH_STOP); - result = sshc->actualcode; - } - break; - - case SSH_SFTP_SHUTDOWN: - /* during times we get here due to a broken transfer and then the - sftp_handle might not have been taken down so make sure that is done - before we proceed */ - - if(sshc->sftp_handle) { - rc = libssh2_sftp_close(sshc->sftp_handle); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to close libssh2 file\n"); - } - sshc->sftp_handle = NULL; - } - if(sshc->sftp_session) { - rc = libssh2_sftp_shutdown(sshc->sftp_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to stop libssh2 sftp subsystem\n"); - } - sshc->sftp_session = NULL; - } - - Curl_safefree(sshc->homedir); - conn->data->state.most_recent_ftp_entrypath = NULL; - - state(conn, SSH_SESSION_DISCONNECT); - break; - - case SSH_SCP_TRANS_INIT: - result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path); - if(result) { - sshc->actualcode = result; - state(conn, SSH_STOP); - break; - } - - if(data->set.upload) { - if(data->set.infilesize < 0) { - failf(data, "SCP requires a known file size for upload"); - sshc->actualcode = CURLE_UPLOAD_FAILED; - state(conn, SSH_SCP_CHANNEL_FREE); - break; - } - state(conn, SSH_SCP_UPLOAD_INIT); - } - else { - state(conn, SSH_SCP_DOWNLOAD_INIT); - } - break; - - case SSH_SCP_UPLOAD_INIT: - /* - * libssh2 requires that the destination path is a full path that - * includes the destination file and name OR ends in a "/" . If this is - * not done the destination file will be named the same name as the last - * directory in the path. - */ - sshc->ssh_channel = - SCP_SEND(sshc->ssh_session, sftp_scp->path, data->set.new_file_perms, - data->set.infilesize); - if(!sshc->ssh_channel) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - int ssh_err; - char *err_msg; - - ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0)); - failf(conn->data, "%s", err_msg); - state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); - break; - } - } - - /* upload data */ - Curl_setup_transfer(conn, -1, data->req.size, FALSE, NULL, - FIRSTSOCKET, NULL); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - - if(result) { - state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = result; - } - else { - /* we want to use the _sending_ function even when the socket turns - out readable as the underlying libssh2 scp send function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_OUT; - - state(conn, SSH_STOP); - } - break; - - case SSH_SCP_DOWNLOAD_INIT: - { - /* - * We must check the remote file; if it is a directory no values will - * be set in sb - */ - struct stat sb; - curl_off_t bytecount; - - /* clear the struct scp recv will fill in */ - memset(&sb, 0, sizeof(struct stat)); - - /* get a fresh new channel from the ssh layer */ - sshc->ssh_channel = libssh2_scp_recv(sshc->ssh_session, - sftp_scp->path, &sb); - if(!sshc->ssh_channel) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - int ssh_err; - char *err_msg; - - ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0)); - failf(conn->data, "%s", err_msg); - state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); - break; - } - } - - /* download data */ - bytecount = (curl_off_t)sb.st_size; - data->req.maxdownload = (curl_off_t)sb.st_size; - Curl_setup_transfer(conn, FIRSTSOCKET, bytecount, FALSE, NULL, -1, NULL); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->writesockfd = conn->sockfd; - - /* we want to use the _receiving_ function even when the socket turns - out writableable as the underlying libssh2 recv function will deal - with both accordingly */ - conn->cselect_bits = CURL_CSELECT_IN; - - if(result) { - state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = result; - } - else - state(conn, SSH_STOP); - } - break; - - case SSH_SCP_DONE: - if(data->set.upload) - state(conn, SSH_SCP_SEND_EOF); - else - state(conn, SSH_SCP_CHANNEL_FREE); - break; - - case SSH_SCP_SEND_EOF: - if(sshc->ssh_channel) { - rc = libssh2_channel_send_eof(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - infof(data, "Failed to send libssh2 channel EOF\n"); - } - } - state(conn, SSH_SCP_WAIT_EOF); - break; - - case SSH_SCP_WAIT_EOF: - if(sshc->ssh_channel) { - rc = libssh2_channel_wait_eof(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - infof(data, "Failed to get channel EOF: %d\n", rc); - } - } - state(conn, SSH_SCP_WAIT_CLOSE); - break; - - case SSH_SCP_WAIT_CLOSE: - if(sshc->ssh_channel) { - rc = libssh2_channel_wait_closed(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - infof(data, "Channel failed to close: %d\n", rc); - } - } - state(conn, SSH_SCP_CHANNEL_FREE); - break; - - case SSH_SCP_CHANNEL_FREE: - if(sshc->ssh_channel) { - rc = libssh2_channel_free(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to free libssh2 scp subsystem\n"); - } - sshc->ssh_channel = NULL; - } - DEBUGF(infof(data, "SCP DONE phase complete\n")); -#if 0 /* PREV */ - state(conn, SSH_SESSION_DISCONNECT); -#endif - state(conn, SSH_STOP); - result = sshc->actualcode; - break; - - case SSH_SESSION_DISCONNECT: - /* during weird times when we've been prematurely aborted, the channel - is still alive when we reach this state and we MUST kill the channel - properly first */ - if(sshc->ssh_channel) { - rc = libssh2_channel_free(sshc->ssh_channel); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to free libssh2 scp subsystem\n"); - } - sshc->ssh_channel = NULL; - } - - if(sshc->ssh_session) { - rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to disconnect libssh2 session\n"); - } - } - - Curl_safefree(sshc->homedir); - conn->data->state.most_recent_ftp_entrypath = NULL; - - state(conn, SSH_SESSION_FREE); - break; - - case SSH_SESSION_FREE: -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - if(sshc->kh) { - libssh2_knownhost_free(sshc->kh); - sshc->kh = NULL; - } -#endif - -#ifdef HAVE_LIBSSH2_AGENT_API - if(sshc->ssh_agent) { - rc = libssh2_agent_disconnect(sshc->ssh_agent); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to disconnect from libssh2 agent\n"); - } - libssh2_agent_free (sshc->ssh_agent); - sshc->ssh_agent = NULL; - - /* NB: there is no need to free identities, they are part of internal - agent stuff */ - sshc->sshagent_identity = NULL; - sshc->sshagent_prev_identity = NULL; - } -#endif - - if(sshc->ssh_session) { - rc = libssh2_session_free(sshc->ssh_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to free libssh2 session\n"); - } - sshc->ssh_session = NULL; - } - - /* worst-case scenario cleanup */ - - DEBUGASSERT(sshc->ssh_session == NULL); - DEBUGASSERT(sshc->ssh_channel == NULL); - DEBUGASSERT(sshc->sftp_session == NULL); - DEBUGASSERT(sshc->sftp_handle == NULL); -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - DEBUGASSERT(sshc->kh == NULL); -#endif -#ifdef HAVE_LIBSSH2_AGENT_API - DEBUGASSERT(sshc->ssh_agent == NULL); -#endif - - Curl_safefree(sshc->rsa_pub); - Curl_safefree(sshc->rsa); - - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - - Curl_safefree(sshc->homedir); - - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - Curl_safefree(sshc->readdir_line); - Curl_safefree(sshc->readdir_linkPath); - - /* the code we are about to return */ - result = sshc->actualcode; - - memset(sshc, 0, sizeof(struct ssh_conn)); - - conn->bits.close = TRUE; - sshc->state = SSH_SESSION_FREE; /* current */ - sshc->nextstate = SSH_NO_STATE; - state(conn, SSH_STOP); - break; - - case SSH_QUIT: - /* fallthrough, just stop! */ - default: - /* internal error */ - sshc->nextstate = SSH_NO_STATE; - state(conn, SSH_STOP); - break; - } - - } while(!rc && (sshc->state != SSH_STOP)); - - if(rc == LIBSSH2_ERROR_EAGAIN) { - /* we would block, we need to wait for the socket to be ready (in the - right direction too)! */ - *block = TRUE; - } - - return result; -} - -/* called by the multi interface to figure out what socket(s) to wait for and - for what actions in the DO_DONE, PERFORM and WAITPERFORM states */ -static int ssh_perform_getsock(const struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks - number of sockets */ - int numsocks) -{ -#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION - int bitmap = GETSOCK_BLANK; - (void)numsocks; - - sock[0] = conn->sock[FIRSTSOCKET]; - - if(conn->waitfor & KEEP_RECV) - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); - - if(conn->waitfor & KEEP_SEND) - bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); - - return bitmap; -#else - /* if we don't know the direction we can use the generic *_getsock() - function even for the protocol_connect and doing states */ - return Curl_single_getsock(conn, sock, numsocks); -#endif -} - -/* Generic function called by the multi interface to figure out what socket(s) - to wait for and for what actions during the DOING and PROTOCONNECT states*/ -static int ssh_getsock(struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks number - of sockets */ - int numsocks) -{ -#ifndef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION - (void)conn; - (void)sock; - (void)numsocks; - /* if we don't know any direction we can just play along as we used to and - not provide any sensible info */ - return GETSOCK_BLANK; -#else - /* if we know the direction we can use the generic *_getsock() function even - for the protocol_connect and doing states */ - return ssh_perform_getsock(conn, sock, numsocks); -#endif -} - -#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION -/* - * When one of the libssh2 functions has returned LIBSSH2_ERROR_EAGAIN this - * function is used to figure out in what direction and stores this info so - * that the multi interface can take advantage of it. Make sure to call this - * function in all cases so that when it _doesn't_ return EAGAIN we can - * restore the default wait bits. - */ -static void ssh_block2waitfor(struct connectdata *conn, bool block) -{ - struct ssh_conn *sshc = &conn->proto.sshc; - int dir; - if(!block) - conn->waitfor = 0; - else if((dir = libssh2_session_block_directions(sshc->ssh_session))) { - /* translate the libssh2 define bits into our own bit defines */ - conn->waitfor = ((dir&LIBSSH2_SESSION_BLOCK_INBOUND)?KEEP_RECV:0) | - ((dir&LIBSSH2_SESSION_BLOCK_OUTBOUND)?KEEP_SEND:0); - } - else - /* It didn't block or libssh2 didn't reveal in which direction, put back - the original set */ - conn->waitfor = sshc->orig_waitfor; -} -#else - /* no libssh2 directional support so we simply don't know */ -#define ssh_block2waitfor(x,y) Curl_nop_stmt -#endif - -/* called repeatedly until done from curl_multi.c */ -static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done) -{ - struct ssh_conn *sshc = &conn->proto.sshc; - CURLcode result = CURLE_OK; - bool block; /* we store the status and use that to provide a ssh_getsock() - implementation */ - - result = ssh_statemach_act(conn, &block); - *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; - ssh_block2waitfor(conn, block); - - return result; -} - -static CURLcode ssh_easy_statemach(struct connectdata *conn, - bool duringconnect) -{ - struct ssh_conn *sshc = &conn->proto.sshc; - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - while((sshc->state != SSH_STOP) && !result) { - bool block; - long left; - - result = ssh_statemach_act(conn, &block); - if(result) - break; - - if(Curl_pgrsUpdate(conn)) - return CURLE_ABORTED_BY_CALLBACK; - else { - struct timeval now = Curl_tvnow(); - result = Curl_speedcheck(data, now); - if(result) - break; - } - - left = Curl_timeleft(data, NULL, duringconnect); - if(left < 0) { - failf(data, "Operation timed out"); - return CURLE_OPERATION_TIMEDOUT; - } - -#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION - if((CURLE_OK == result) && block) { - int dir = libssh2_session_block_directions(sshc->ssh_session); - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - curl_socket_t fd_read = CURL_SOCKET_BAD; - curl_socket_t fd_write = CURL_SOCKET_BAD; - if(LIBSSH2_SESSION_BLOCK_INBOUND & dir) - fd_read = sock; - if(LIBSSH2_SESSION_BLOCK_OUTBOUND & dir) - fd_write = sock; - /* wait for the socket to become ready */ - Curl_socket_ready(fd_read, fd_write, - left>1000?1000:left); /* ignore result */ - } -#endif - - } - - return result; -} - -/* - * SSH setup and connection - */ -static CURLcode ssh_init(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - struct SSHPROTO *ssh; - struct ssh_conn *sshc = &conn->proto.sshc; - - sshc->actualcode = CURLE_OK; /* reset error code */ - sshc->secondCreateDirs =0; /* reset the create dir attempt state - variable */ - - if(data->state.proto.ssh) - return CURLE_OK; - - ssh = calloc(1, sizeof(struct SSHPROTO)); - if(!ssh) - return CURLE_OUT_OF_MEMORY; - - data->state.proto.ssh = ssh; - - return CURLE_OK; -} - -static Curl_recv scp_recv, sftp_recv; -static Curl_send scp_send, sftp_send; - -/* - * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to - * do protocol-specific actions at connect-time. - */ -static CURLcode ssh_connect(struct connectdata *conn, bool *done) -{ -#ifdef CURL_LIBSSH2_DEBUG - curl_socket_t sock; -#endif - struct ssh_conn *ssh; - CURLcode result; - struct SessionHandle *data = conn->data; - - /* We default to persistent connections. We set this already in this connect - function to make the re-use checks properly be able to check this bit. */ - conn->bits.close = FALSE; - - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - result = ssh_init(conn); - if(result) - return result; - - if(conn->handler->protocol & CURLPROTO_SCP) { - conn->recv[FIRSTSOCKET] = scp_recv; - conn->send[FIRSTSOCKET] = scp_send; - } - else { - conn->recv[FIRSTSOCKET] = sftp_recv; - conn->send[FIRSTSOCKET] = sftp_send; - } - ssh = &conn->proto.sshc; - -#ifdef CURL_LIBSSH2_DEBUG - if(conn->user) { - infof(data, "User: %s\n", conn->user); - } - if(conn->passwd) { - infof(data, "Password: %s\n", conn->passwd); - } - sock = conn->sock[FIRSTSOCKET]; -#endif /* CURL_LIBSSH2_DEBUG */ - - ssh->ssh_session = libssh2_session_init_ex(my_libssh2_malloc, - my_libssh2_free, - my_libssh2_realloc, conn); - if(ssh->ssh_session == NULL) { - failf(data, "Failure initialising ssh session"); - return CURLE_FAILED_INIT; - } - -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - int rc; - ssh->kh = libssh2_knownhost_init(ssh->ssh_session); - if(!ssh->kh) { - /* eeek. TODO: free the ssh_session! */ - return CURLE_FAILED_INIT; - } - - /* read all known hosts from there */ - rc = libssh2_knownhost_readfile(ssh->kh, - data->set.str[STRING_SSH_KNOWNHOSTS], - LIBSSH2_KNOWNHOST_FILE_OPENSSH); - if(rc < 0) - infof(data, "Failed to read known hosts from %s\n", - data->set.str[STRING_SSH_KNOWNHOSTS]); - } -#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ - -#ifdef CURL_LIBSSH2_DEBUG - libssh2_trace(ssh->ssh_session, ~0); - infof(data, "SSH socket: %d\n", (int)sock); -#endif /* CURL_LIBSSH2_DEBUG */ - - state(conn, SSH_INIT); - - if(data->state.used_interface == Curl_if_multi) - result = ssh_multi_statemach(conn, done); - else { - result = ssh_easy_statemach(conn, TRUE); - if(!result) - *done = TRUE; - } - - return result; -} - -/* - *********************************************************************** - * - * scp_perform() - * - * This is the actual DO function for SCP. Get a file according to - * the options previously setup. - */ - -static -CURLcode scp_perform(struct connectdata *conn, - bool *connected, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - - DEBUGF(infof(conn->data, "DO phase starts\n")); - - *dophase_done = FALSE; /* not done yet */ - - /* start the first command in the DO phase */ - state(conn, SSH_SCP_TRANS_INIT); - - /* run the state-machine */ - if(conn->data->state.used_interface == Curl_if_multi) { - result = ssh_multi_statemach(conn, dophase_done); - } - else { - result = ssh_easy_statemach(conn, FALSE); - *dophase_done = TRUE; /* with the easy interface we are done here */ - } - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - - return result; -} - -/* called from curl_multi.c while DOing */ -static CURLcode scp_doing(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result; - result = ssh_multi_statemach(conn, dophase_done); - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - return result; -} - -/* - * The DO function is generic for both protocols. There was previously two - * separate ones but this way means less duplicated code. - */ - -static CURLcode ssh_do(struct connectdata *conn, bool *done) -{ - CURLcode res; - bool connected = 0; - struct SessionHandle *data = conn->data; - - *done = FALSE; /* default to false */ - - /* - Since connections can be re-used between SessionHandles, this might be a - connection already existing but on a fresh SessionHandle struct so we must - make sure we have a good 'struct SSHPROTO' to play with. For new - connections, the struct SSHPROTO is allocated and setup in the - ssh_connect() function. - */ - Curl_reset_reqproto(conn); - res = ssh_init(conn); - if(res) - return res; - - data->req.size = -1; /* make sure this is unknown at this point */ - - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - Curl_pgrsSetUploadSize(data, 0); - Curl_pgrsSetDownloadSize(data, 0); - - if(conn->handler->protocol & CURLPROTO_SCP) - res = scp_perform(conn, &connected, done); - else - res = sftp_perform(conn, &connected, done); - - return res; -} - -/* BLOCKING, but the function is using the state machine so the only reason - this is still blocking is that the multi interface code has no support for - disconnecting operations that takes a while */ -static CURLcode scp_disconnect(struct connectdata *conn, bool dead_connection) -{ - CURLcode result = CURLE_OK; - struct ssh_conn *ssh = &conn->proto.sshc; - (void) dead_connection; - - Curl_safefree(conn->data->state.proto.ssh); - - if(ssh->ssh_session) { - /* only if there's a session still around to use! */ - - state(conn, SSH_SESSION_DISCONNECT); - - result = ssh_easy_statemach(conn, FALSE); - } - - return result; -} - -/* generic done function for both SCP and SFTP called from their specific - done functions */ -static CURLcode ssh_done(struct connectdata *conn, CURLcode status) -{ - CURLcode result = CURLE_OK; - struct SSHPROTO *sftp_scp = conn->data->state.proto.ssh; - - if(status == CURLE_OK) { - /* run the state-machine - - TODO: when the multi interface is used, this _really_ should be using - the ssh_multi_statemach function but we have no general support for - non-blocking DONE operations, not in the multi state machine and with - Curl_done() invokes on several places in the code! - */ - result = ssh_easy_statemach(conn, FALSE); - } - else - result = status; - - if(sftp_scp) - Curl_safefree(sftp_scp->path); - if(Curl_pgrsDone(conn)) - return CURLE_ABORTED_BY_CALLBACK; - - conn->data->req.keepon = 0; /* clear all bits */ - return result; -} - - -static CURLcode scp_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - (void)premature; /* not used */ - - if(status == CURLE_OK) - state(conn, SSH_SCP_DONE); - - return ssh_done(conn, status); - -} - -/* return number of received (decrypted) bytes */ -static ssize_t scp_send(struct connectdata *conn, int sockindex, - const void *mem, size_t len, CURLcode *err) -{ - ssize_t nwrite; - (void)sockindex; /* we only support SCP on the fixed known primary socket */ - - /* libssh2_channel_write() returns int! */ - nwrite = (ssize_t) - libssh2_channel_write(conn->proto.sshc.ssh_channel, mem, len); - - ssh_block2waitfor(conn, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); - - if(nwrite == LIBSSH2_ERROR_EAGAIN) { - *err = CURLE_AGAIN; - nwrite = 0; - } - else if(nwrite < LIBSSH2_ERROR_NONE) { - *err = libssh2_session_error_to_CURLE((int)nwrite); - nwrite = -1; - } - - return nwrite; -} - -/* - * If the read would block (EWOULDBLOCK) we return -1. Otherwise we return - * a regular CURLcode value. - */ -static ssize_t scp_recv(struct connectdata *conn, int sockindex, - char *mem, size_t len, CURLcode *err) -{ - ssize_t nread; - (void)sockindex; /* we only support SCP on the fixed known primary socket */ - - /* libssh2_channel_read() returns int */ - nread = (ssize_t) - libssh2_channel_read(conn->proto.sshc.ssh_channel, mem, len); - - ssh_block2waitfor(conn, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); - if(nread == LIBSSH2_ERROR_EAGAIN) { - *err = CURLE_AGAIN; - nread = -1; - } - - return nread; -} - -/* - * =============== SFTP =============== - */ - -/* - *********************************************************************** - * - * sftp_perform() - * - * This is the actual DO function for SFTP. Get a file/directory according to - * the options previously setup. - */ - -static -CURLcode sftp_perform(struct connectdata *conn, - bool *connected, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - - DEBUGF(infof(conn->data, "DO phase starts\n")); - - *dophase_done = FALSE; /* not done yet */ - - /* start the first command in the DO phase */ - state(conn, SSH_SFTP_QUOTE_INIT); - - /* run the state-machine */ - if(conn->data->state.used_interface == Curl_if_multi) { - result = ssh_multi_statemach(conn, dophase_done); - } - else { - result = ssh_easy_statemach(conn, FALSE); - *dophase_done = TRUE; /* with the easy interface we are done here */ - } - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - - return result; -} - -/* called from curl_multi.c while DOing */ -static CURLcode sftp_doing(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result; - result = ssh_multi_statemach(conn, dophase_done); - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - return result; -} - -/* BLOCKING, but the function is using the state machine so the only reason - this is still blocking is that the multi interface code has no support for - disconnecting operations that takes a while */ -static CURLcode sftp_disconnect(struct connectdata *conn, bool dead_connection) -{ - CURLcode result = CURLE_OK; - (void) dead_connection; - - DEBUGF(infof(conn->data, "SSH DISCONNECT starts now\n")); - - Curl_safefree(conn->data->state.proto.ssh); - - if(conn->proto.sshc.ssh_session) { - /* only if there's a session still around to use! */ - state(conn, SSH_SFTP_SHUTDOWN); - result = ssh_easy_statemach(conn, FALSE); - } - - DEBUGF(infof(conn->data, "SSH DISCONNECT is done\n")); - - return result; - -} - -static CURLcode sftp_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - struct ssh_conn *sshc = &conn->proto.sshc; - - if(status == CURLE_OK) { - /* Post quote commands are executed after the SFTP_CLOSE state to avoid - errors that could happen due to open file handles during POSTQUOTE - operation */ - if(!status && !premature && conn->data->set.postquote) { - sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; - state(conn, SSH_SFTP_CLOSE); - } - else - state(conn, SSH_SFTP_CLOSE); - } - return ssh_done(conn, status); -} - -/* return number of sent bytes */ -static ssize_t sftp_send(struct connectdata *conn, int sockindex, - const void *mem, size_t len, CURLcode *err) -{ - ssize_t nwrite; /* libssh2_sftp_write() used to return size_t in 0.14 - but is changed to ssize_t in 0.15. These days we don't - support libssh2 0.15*/ - (void)sockindex; - - nwrite = libssh2_sftp_write(conn->proto.sshc.sftp_handle, mem, len); - - ssh_block2waitfor(conn, (nwrite == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); - - if(nwrite == LIBSSH2_ERROR_EAGAIN) { - *err = CURLE_AGAIN; - nwrite = 0; - } - else if(nwrite < LIBSSH2_ERROR_NONE) { - *err = libssh2_session_error_to_CURLE((int)nwrite); - nwrite = -1; - } - - return nwrite; -} - -/* - * Return number of received (decrypted) bytes - */ -static ssize_t sftp_recv(struct connectdata *conn, int sockindex, - char *mem, size_t len, CURLcode *err) -{ - ssize_t nread; - (void)sockindex; - - nread = libssh2_sftp_read(conn->proto.sshc.sftp_handle, mem, len); - - ssh_block2waitfor(conn, (nread == LIBSSH2_ERROR_EAGAIN)?TRUE:FALSE); - - if(nread == LIBSSH2_ERROR_EAGAIN) { - *err = CURLE_AGAIN; - nread = -1; - } - return nread; -} - -/* The get_pathname() function is being borrowed from OpenSSH-sftp.c - version 4.6p1. */ -/* - * Copyright (c) 2001-2004 Damien Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ -static CURLcode -get_pathname(const char **cpp, char **path) -{ - const char *cp = *cpp, *end; - char quot; - unsigned int i, j; - static const char WHITESPACE[] = " \t\r\n"; - - cp += strspn(cp, WHITESPACE); - if(!*cp) { - *cpp = cp; - *path = NULL; - return CURLE_QUOTE_ERROR; - } - - *path = malloc(strlen(cp) + 1); - if(*path == NULL) - return CURLE_OUT_OF_MEMORY; - - /* Check for quoted filenames */ - if(*cp == '\"' || *cp == '\'') { - quot = *cp++; - - /* Search for terminating quote, unescape some chars */ - for(i = j = 0; i <= strlen(cp); i++) { - if(cp[i] == quot) { /* Found quote */ - i++; - (*path)[j] = '\0'; - break; - } - if(cp[i] == '\0') { /* End of string */ - /*error("Unterminated quote");*/ - goto fail; - } - if(cp[i] == '\\') { /* Escaped characters */ - i++; - if(cp[i] != '\'' && cp[i] != '\"' && - cp[i] != '\\') { - /*error("Bad escaped character '\\%c'", - cp[i]);*/ - goto fail; - } - } - (*path)[j++] = cp[i]; - } - - if(j == 0) { - /*error("Empty quotes");*/ - goto fail; - } - *cpp = cp + i + strspn(cp + i, WHITESPACE); - } - else { - /* Read to end of filename */ - end = strpbrk(cp, WHITESPACE); - if(end == NULL) - end = strchr(cp, '\0'); - *cpp = end + strspn(end, WHITESPACE); - - memcpy(*path, cp, end - cp); - (*path)[end - cp] = '\0'; - } - return CURLE_OK; - - fail: - Curl_safefree(*path); - return CURLE_QUOTE_ERROR; -} - - -static const char *sftp_libssh2_strerror(int err) -{ - switch (err) { - case LIBSSH2_FX_NO_SUCH_FILE: - return "No such file or directory"; - - case LIBSSH2_FX_PERMISSION_DENIED: - return "Permission denied"; - - case LIBSSH2_FX_FAILURE: - return "Operation failed"; - - case LIBSSH2_FX_BAD_MESSAGE: - return "Bad message from SFTP server"; - - case LIBSSH2_FX_NO_CONNECTION: - return "Not connected to SFTP server"; - - case LIBSSH2_FX_CONNECTION_LOST: - return "Connection to SFTP server lost"; - - case LIBSSH2_FX_OP_UNSUPPORTED: - return "Operation not supported by SFTP server"; - - case LIBSSH2_FX_INVALID_HANDLE: - return "Invalid handle"; - - case LIBSSH2_FX_NO_SUCH_PATH: - return "No such file or directory"; - - case LIBSSH2_FX_FILE_ALREADY_EXISTS: - return "File already exists"; - - case LIBSSH2_FX_WRITE_PROTECT: - return "File is write protected"; - - case LIBSSH2_FX_NO_MEDIA: - return "No media"; - - case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM: - return "Disk full"; - - case LIBSSH2_FX_QUOTA_EXCEEDED: - return "User quota exceeded"; - - case LIBSSH2_FX_UNKNOWN_PRINCIPLE: - return "Unknown principle"; - - case LIBSSH2_FX_LOCK_CONFlICT: - return "File lock conflict"; - - case LIBSSH2_FX_DIR_NOT_EMPTY: - return "Directory not empty"; - - case LIBSSH2_FX_NOT_A_DIRECTORY: - return "Not a directory"; - - case LIBSSH2_FX_INVALID_FILENAME: - return "Invalid filename"; - - case LIBSSH2_FX_LINK_LOOP: - return "Link points to itself"; - } - return "Unknown error in libssh2"; -} - -#endif /* USE_LIBSSH2 */ diff --git a/lib/sslgen.c b/lib/sslgen.c deleted file mode 100644 index d85ba8ae6..000000000 --- a/lib/sslgen.c +++ /dev/null @@ -1,541 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -/* This file is for implementing all "generic" SSL functions that all libcurl - internals should use. It is then responsible for calling the proper - "backend" function. - - SSL-functions in libcurl should call functions in this source file, and not - to any specific SSL-layer. - - Curl_ssl_ - prefix for generic ones - Curl_ossl_ - prefix for OpenSSL ones - Curl_gtls_ - prefix for GnuTLS ones - Curl_nss_ - prefix for NSS ones - Curl_polarssl_ - prefix for PolarSSL ones - Curl_cyassl_ - prefix for CyaSSL ones - Curl_schannel_ - prefix for Schannel SSPI ones - Curl_darwinssl_ - prefix for SecureTransport (Darwin) ones - - Note that this source code uses curlssl_* functions, and they are all - defines/macros #defined by the lib-specific header files. - - "SSL/TLS Strong Encryption: An Introduction" - http://httpd.apache.org/docs-2.0/ssl/ssl_intro.html -*/ - -#include "curl_setup.h" - -#include "curl_urldata.h" -#define SSLGEN_C -#include "curl_sslgen.h" /* generic SSL protos etc */ -#include "curl_ssluse.h" /* OpenSSL versions */ -#include "curl_gtls.h" /* GnuTLS versions */ -#include "curl_nssg.h" /* NSS versions */ -#include "curl_qssl.h" /* QSOSSL versions */ -#include "curl_polarssl.h" /* PolarSSL versions */ -#include "curl_axtls.h" /* axTLS versions */ -#include "curl_cyassl.h" /* CyaSSL versions */ -#include "curl_schannel.h" /* Schannel SSPI version */ -#include "curl_darwinssl.h" /* SecureTransport (Darwin) version */ -#include "curl_sendf.h" -#include "curl_rawstr.h" -#include "curl_url.h" -#include "curl_memory.h" -#include "curl_progress.h" -#include "curl_share.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* convenience macro to check if this handle is using a shared SSL session */ -#define SSLSESSION_SHARED(data) (data->share && \ - (data->share->specifier & \ - (1<version == needle->version) && - (data->verifypeer == needle->verifypeer) && - (data->verifyhost == needle->verifyhost) && - safe_strequal(data->CApath, needle->CApath) && - safe_strequal(data->CAfile, needle->CAfile) && - safe_strequal(data->random_file, needle->random_file) && - safe_strequal(data->egdsocket, needle->egdsocket) && - safe_strequal(data->cipher_list, needle->cipher_list)) - return TRUE; - - return FALSE; -} - -bool -Curl_clone_ssl_config(struct ssl_config_data *source, - struct ssl_config_data *dest) -{ - dest->sessionid = source->sessionid; - dest->verifyhost = source->verifyhost; - dest->verifypeer = source->verifypeer; - dest->version = source->version; - - if(source->CAfile) { - dest->CAfile = strdup(source->CAfile); - if(!dest->CAfile) - return FALSE; - } - else - dest->CAfile = NULL; - - if(source->CApath) { - dest->CApath = strdup(source->CApath); - if(!dest->CApath) - return FALSE; - } - else - dest->CApath = NULL; - - if(source->cipher_list) { - dest->cipher_list = strdup(source->cipher_list); - if(!dest->cipher_list) - return FALSE; - } - else - dest->cipher_list = NULL; - - if(source->egdsocket) { - dest->egdsocket = strdup(source->egdsocket); - if(!dest->egdsocket) - return FALSE; - } - else - dest->egdsocket = NULL; - - if(source->random_file) { - dest->random_file = strdup(source->random_file); - if(!dest->random_file) - return FALSE; - } - else - dest->random_file = NULL; - - return TRUE; -} - -void Curl_free_ssl_config(struct ssl_config_data* sslc) -{ - Curl_safefree(sslc->CAfile); - Curl_safefree(sslc->CApath); - Curl_safefree(sslc->cipher_list); - Curl_safefree(sslc->egdsocket); - Curl_safefree(sslc->random_file); -} - -#ifdef USE_SSL - -/* "global" init done? */ -static bool init_ssl=FALSE; - -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -int Curl_ssl_init(void) -{ - /* make sure this is only done once */ - if(init_ssl) - return 1; - init_ssl = TRUE; /* never again */ - - return curlssl_init(); -} - - -/* Global cleanup */ -void Curl_ssl_cleanup(void) -{ - if(init_ssl) { - /* only cleanup if we did a previous init */ - curlssl_cleanup(); - init_ssl = FALSE; - } -} - -CURLcode -Curl_ssl_connect(struct connectdata *conn, int sockindex) -{ - CURLcode res; - /* mark this is being ssl-enabled from here on. */ - conn->ssl[sockindex].use = TRUE; - conn->ssl[sockindex].state = ssl_connection_negotiating; - - res = curlssl_connect(conn, sockindex); - - if(!res) - Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSL is connected */ - - return res; -} - -CURLcode -Curl_ssl_connect_nonblocking(struct connectdata *conn, int sockindex, - bool *done) -{ - CURLcode res; - /* mark this is being ssl requested from here on. */ - conn->ssl[sockindex].use = TRUE; -#ifdef curlssl_connect_nonblocking - res = curlssl_connect_nonblocking(conn, sockindex, done); -#else - *done = TRUE; /* fallback to BLOCKING */ - res = curlssl_connect(conn, sockindex); -#endif /* non-blocking connect support */ - if(!res && *done) - Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSL is connected */ - return res; -} - -/* - * Check if there's a session ID for the given connection in the cache, and if - * there's one suitable, it is provided. Returns TRUE when no entry matched. - */ -int Curl_ssl_getsessionid(struct connectdata *conn, - void **ssl_sessionid, - size_t *idsize) /* set 0 if unknown */ -{ - struct curl_ssl_session *check; - struct SessionHandle *data = conn->data; - size_t i; - long *general_age; - bool no_match = TRUE; - - *ssl_sessionid = NULL; - - if(!conn->ssl_config.sessionid) - /* session ID re-use is disabled */ - return TRUE; - - /* Lock if shared */ - if(SSLSESSION_SHARED(data)) { - Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); - general_age = &data->share->sessionage; - } - else - general_age = &data->state.sessionage; - - for(i = 0; i < data->set.ssl.max_ssl_sessions; i++) { - check = &data->state.session[i]; - if(!check->sessionid) - /* not session ID means blank entry */ - continue; - if(Curl_raw_equal(conn->host.name, check->name) && - (conn->remote_port == check->remote_port) && - Curl_ssl_config_matches(&conn->ssl_config, &check->ssl_config)) { - /* yes, we have a session ID! */ - (*general_age)++; /* increase general age */ - check->age = *general_age; /* set this as used in this age */ - *ssl_sessionid = check->sessionid; - if(idsize) - *idsize = check->idsize; - no_match = FALSE; - break; - } - } - - /* Unlock */ - if(SSLSESSION_SHARED(data)) - Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); - - return no_match; -} - -/* - * Kill a single session ID entry in the cache. - */ -void Curl_ssl_kill_session(struct curl_ssl_session *session) -{ - if(session->sessionid) { - /* defensive check */ - - /* free the ID the SSL-layer specific way */ - curlssl_session_free(session->sessionid); - - session->sessionid = NULL; - session->age = 0; /* fresh */ - - Curl_free_ssl_config(&session->ssl_config); - - Curl_safefree(session->name); - } -} - -/* - * Delete the given session ID from the cache. - */ -void Curl_ssl_delsessionid(struct connectdata *conn, void *ssl_sessionid) -{ - size_t i; - struct SessionHandle *data=conn->data; - - if(SSLSESSION_SHARED(data)) - Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); - - for(i = 0; i < data->set.ssl.max_ssl_sessions; i++) { - struct curl_ssl_session *check = &data->state.session[i]; - - if(check->sessionid == ssl_sessionid) { - Curl_ssl_kill_session(check); - break; - } - } - - if(SSLSESSION_SHARED(data)) - Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); -} - -/* - * Store session id in the session cache. The ID passed on to this function - * must already have been extracted and allocated the proper way for the SSL - * layer. Curl_XXXX_session_free() will be called to free/kill the session ID - * later on. - */ -CURLcode Curl_ssl_addsessionid(struct connectdata *conn, - void *ssl_sessionid, - size_t idsize) -{ - size_t i; - struct SessionHandle *data=conn->data; /* the mother of all structs */ - struct curl_ssl_session *store = &data->state.session[0]; - long oldest_age=data->state.session[0].age; /* zero if unused */ - char *clone_host; - long *general_age; - - /* Even though session ID re-use might be disabled, that only disables USING - IT. We still store it here in case the re-using is again enabled for an - upcoming transfer */ - - clone_host = strdup(conn->host.name); - if(!clone_host) - return CURLE_OUT_OF_MEMORY; /* bail out */ - - /* Now we should add the session ID and the host name to the cache, (remove - the oldest if necessary) */ - - /* If using shared SSL session, lock! */ - if(SSLSESSION_SHARED(data)) { - Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); - general_age = &data->share->sessionage; - } - else { - general_age = &data->state.sessionage; - } - - /* find an empty slot for us, or find the oldest */ - for(i = 1; (i < data->set.ssl.max_ssl_sessions) && - data->state.session[i].sessionid; i++) { - if(data->state.session[i].age < oldest_age) { - oldest_age = data->state.session[i].age; - store = &data->state.session[i]; - } - } - if(i == data->set.ssl.max_ssl_sessions) - /* cache is full, we must "kill" the oldest entry! */ - Curl_ssl_kill_session(store); - else - store = &data->state.session[i]; /* use this slot */ - - /* now init the session struct wisely */ - store->sessionid = ssl_sessionid; - store->idsize = idsize; - store->age = *general_age; /* set current age */ - if(store->name) - /* free it if there's one already present */ - free(store->name); - store->name = clone_host; /* clone host name */ - store->remote_port = conn->remote_port; /* port number */ - - - /* Unlock */ - if(SSLSESSION_SHARED(data)) - Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); - - if(!Curl_clone_ssl_config(&conn->ssl_config, &store->ssl_config)) { - store->sessionid = NULL; /* let caller free sessionid */ - free(clone_host); - return CURLE_OUT_OF_MEMORY; - } - - return CURLE_OK; -} - - -void Curl_ssl_close_all(struct SessionHandle *data) -{ - size_t i; - /* kill the session ID cache if not shared */ - if(data->state.session && !SSLSESSION_SHARED(data)) { - for(i = 0; i < data->set.ssl.max_ssl_sessions; i++) - /* the single-killer function handles empty table slots */ - Curl_ssl_kill_session(&data->state.session[i]); - - /* free the cache data */ - Curl_safefree(data->state.session); - } - - curlssl_close_all(data); -} - -void Curl_ssl_close(struct connectdata *conn, int sockindex) -{ - DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); - curlssl_close(conn, sockindex); -} - -CURLcode Curl_ssl_shutdown(struct connectdata *conn, int sockindex) -{ - if(curlssl_shutdown(conn, sockindex)) - return CURLE_SSL_SHUTDOWN_FAILED; - - conn->ssl[sockindex].use = FALSE; /* get back to ordinary socket usage */ - conn->ssl[sockindex].state = ssl_connection_none; - - conn->recv[sockindex] = Curl_recv_plain; - conn->send[sockindex] = Curl_send_plain; - - return CURLE_OK; -} - -/* Selects an SSL crypto engine - */ -CURLcode Curl_ssl_set_engine(struct SessionHandle *data, const char *engine) -{ - return curlssl_set_engine(data, engine); -} - -/* Selects the default SSL crypto engine - */ -CURLcode Curl_ssl_set_engine_default(struct SessionHandle *data) -{ - return curlssl_set_engine_default(data); -} - -/* Return list of OpenSSL crypto engine names. */ -struct curl_slist *Curl_ssl_engines_list(struct SessionHandle *data) -{ - return curlssl_engines_list(data); -} - -/* - * This sets up a session ID cache to the specified size. Make sure this code - * is agnostic to what underlying SSL technology we use. - */ -CURLcode Curl_ssl_initsessions(struct SessionHandle *data, size_t amount) -{ - struct curl_ssl_session *session; - - if(data->state.session) - /* this is just a precaution to prevent multiple inits */ - return CURLE_OK; - - session = calloc(amount, sizeof(struct curl_ssl_session)); - if(!session) - return CURLE_OUT_OF_MEMORY; - - /* store the info in the SSL section */ - data->set.ssl.max_ssl_sessions = amount; - data->state.session = session; - data->state.sessionage = 1; /* this is brand new */ - return CURLE_OK; -} - -size_t Curl_ssl_version(char *buffer, size_t size) -{ - return curlssl_version(buffer, size); -} - -/* - * This function tries to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -int Curl_ssl_check_cxn(struct connectdata *conn) -{ - return curlssl_check_cxn(conn); -} - -bool Curl_ssl_data_pending(const struct connectdata *conn, - int connindex) -{ - return curlssl_data_pending(conn, connindex); -} - -void Curl_ssl_free_certinfo(struct SessionHandle *data) -{ - int i; - struct curl_certinfo *ci = &data->info.certs; - if(ci->num_of_certs) { - /* free all individual lists used */ - for(i=0; inum_of_certs; i++) { - curl_slist_free_all(ci->certinfo[i]); - ci->certinfo[i] = NULL; - } - free(ci->certinfo); /* free the actual array too */ - ci->certinfo = NULL; - ci->num_of_certs = 0; - } -} - -#if defined(USE_SSLEAY) || defined(USE_GNUTLS) || defined(USE_NSS) || \ - defined(USE_DARWINSSL) -/* these functions are only used by some SSL backends */ - -void Curl_ssl_random(struct SessionHandle *data, - unsigned char *entropy, - size_t length) -{ - curlssl_random(data, entropy, length); -} - -void Curl_ssl_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len) -{ - curlssl_md5sum(tmp, tmplen, md5sum, md5len); -} -#endif /* USE_SSLEAY || USE_GNUTLS || USE_NSS || USE_DARWINSSL */ - -#endif /* USE_SSL */ diff --git a/lib/ssluse.c b/lib/ssluse.c deleted file mode 100644 index 0809d4614..000000000 --- a/lib/ssluse.c +++ /dev/null @@ -1,2736 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -/* - * Source file for all OpenSSL-specific code for the TLS/SSL layer. No code - * but curl_sslgen.c should ever call or use these functions. - */ - -/* - * The original SSLeay-using code for curl was written by Linas Vepstas and - * Sampo Kellomaki 1998. - */ - -#include "curl_setup.h" - -#ifdef HAVE_LIMITS_H -#include -#endif - -#include "curl_urldata.h" -#include "curl_sendf.h" -#include "curl_formdata.h" /* for the boundary function */ -#include "curl_url.h" /* for the ssl config check function */ -#include "curl_inet_pton.h" -#include "curl_ssluse.h" -#include "curl_connect.h" -#include "curl_strequal.h" -#include "curl_select.h" -#include "curl_sslgen.h" -#include "curl_rawstr.h" -#include "curl_hostcheck.h" - -#define _MPRINTF_REPLACE /* use the internal *printf() functions */ -#include - -#ifdef USE_SSLEAY - -#ifdef USE_OPENSSL -#include -#include -#include -#include -#include -#include -#else -#include -#include -#include -#endif - -#include "curl_warnless.h" -#include "curl_memory.h" -#include "curl_non_ascii.h" /* for Curl_convert_from_utf8 prototype */ - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#ifndef OPENSSL_VERSION_NUMBER -#error "OPENSSL_VERSION_NUMBER not defined" -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x0090581fL -#define HAVE_SSL_GET1_SESSION 1 -#else -#undef HAVE_SSL_GET1_SESSION -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x00904100L -#define HAVE_USERDATA_IN_PWD_CALLBACK 1 -#else -#undef HAVE_USERDATA_IN_PWD_CALLBACK -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x00907001L -/* ENGINE_load_private_key() takes four arguments */ -#define HAVE_ENGINE_LOAD_FOUR_ARGS -#include -#else -/* ENGINE_load_private_key() takes three arguments */ -#undef HAVE_ENGINE_LOAD_FOUR_ARGS -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x00903001L) && defined(HAVE_OPENSSL_PKCS12_H) -/* OpenSSL has PKCS 12 support */ -#define HAVE_PKCS12_SUPPORT -#else -/* OpenSSL/SSLEay does not have PKCS12 support */ -#undef HAVE_PKCS12_SUPPORT -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x00906001L -#define HAVE_ERR_ERROR_STRING_N 1 -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x00909000L -#define SSL_METHOD_QUAL const -#else -#define SSL_METHOD_QUAL -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x00907000L -/* 0.9.6 didn't have X509_STORE_set_flags() */ -#define HAVE_X509_STORE_SET_FLAGS 1 -#else -#define X509_STORE_set_flags(x,y) Curl_nop_stmt -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x10000000L -#define HAVE_ERR_REMOVE_THREAD_STATE 1 -#endif - -#ifndef HAVE_SSLV2_CLIENT_METHOD -#undef OPENSSL_NO_SSL2 /* undef first to avoid compiler warnings */ -#define OPENSSL_NO_SSL2 -#endif - -/* - * Number of bytes to read from the random number seed file. This must be - * a finite value (because some entropy "files" like /dev/urandom have - * an infinite length), but must be large enough to provide enough - * entopy to properly seed OpenSSL's PRNG. - */ -#define RAND_LOAD_LENGTH 1024 - -#ifndef HAVE_USERDATA_IN_PWD_CALLBACK -static char global_passwd[64]; -#endif - -static int passwd_callback(char *buf, int num, int encrypting -#ifdef HAVE_USERDATA_IN_PWD_CALLBACK - /* This was introduced in 0.9.4, we can set this - using SSL_CTX_set_default_passwd_cb_userdata() - */ - , void *global_passwd -#endif - ) -{ - DEBUGASSERT(0 == encrypting); - - if(!encrypting) { - int klen = curlx_uztosi(strlen((char *)global_passwd)); - if(num > klen) { - memcpy(buf, global_passwd, klen+1); - return klen; - } - } - return 0; -} - -/* - * rand_enough() is a function that returns TRUE if we have seeded the random - * engine properly. We use some preprocessor magic to provide a seed_enough() - * macro to use, just to prevent a compiler warning on this function if we - * pass in an argument that is never used. - */ - -#ifdef HAVE_RAND_STATUS -#define seed_enough(x) rand_enough() -static bool rand_enough(void) -{ - return (0 != RAND_status()) ? TRUE : FALSE; -} -#else -#define seed_enough(x) rand_enough(x) -static bool rand_enough(int nread) -{ - /* this is a very silly decision to make */ - return (nread > 500) ? TRUE : FALSE; -} -#endif - -static int ossl_seed(struct SessionHandle *data) -{ - char *buf = data->state.buffer; /* point to the big buffer */ - int nread=0; - - /* Q: should we add support for a random file name as a libcurl option? - A: Yes, it is here */ - -#ifndef RANDOM_FILE - /* if RANDOM_FILE isn't defined, we only perform this if an option tells - us to! */ - if(data->set.ssl.random_file) -#define RANDOM_FILE "" /* doesn't matter won't be used */ -#endif - { - /* let the option override the define */ - nread += RAND_load_file((data->set.str[STRING_SSL_RANDOM_FILE]? - data->set.str[STRING_SSL_RANDOM_FILE]: - RANDOM_FILE), - RAND_LOAD_LENGTH); - if(seed_enough(nread)) - return nread; - } - -#if defined(HAVE_RAND_EGD) - /* only available in OpenSSL 0.9.5 and later */ - /* EGD_SOCKET is set at configure time or not at all */ -#ifndef EGD_SOCKET - /* If we don't have the define set, we only do this if the egd-option - is set */ - if(data->set.str[STRING_SSL_EGDSOCKET]) -#define EGD_SOCKET "" /* doesn't matter won't be used */ -#endif - { - /* If there's an option and a define, the option overrides the - define */ - int ret = RAND_egd(data->set.str[STRING_SSL_EGDSOCKET]? - data->set.str[STRING_SSL_EGDSOCKET]:EGD_SOCKET); - if(-1 != ret) { - nread += ret; - if(seed_enough(nread)) - return nread; - } - } -#endif - - /* If we get here, it means we need to seed the PRNG using a "silly" - approach! */ - { - int len; - char *area; - - /* Changed call to RAND_seed to use the underlying RAND_add implementation - * directly. Do this in a loop, with the amount of additional entropy - * being dependent upon the algorithm used by Curl_FormBoundary(): N bytes - * of a 7-bit ascii set. -- Richard Gorton, March 11 2003. - */ - - do { - area = Curl_FormBoundary(); - if(!area) - return 3; /* out of memory */ - - len = curlx_uztosi(strlen(area)); - RAND_add(area, len, (len >> 1)); - - free(area); /* now remove the random junk */ - } while(!RAND_status()); - } - - /* generates a default path for the random seed file */ - buf[0]=0; /* blank it first */ - RAND_file_name(buf, BUFSIZE); - if(buf[0]) { - /* we got a file name to try */ - nread += RAND_load_file(buf, RAND_LOAD_LENGTH); - if(seed_enough(nread)) - return nread; - } - - infof(data, "libcurl is now using a weak random seed!\n"); - return nread; -} - -int Curl_ossl_seed(struct SessionHandle *data) -{ - /* we have the "SSL is seeded" boolean static to prevent multiple - time-consuming seedings in vain */ - static bool ssl_seeded = FALSE; - - if(!ssl_seeded || data->set.str[STRING_SSL_RANDOM_FILE] || - data->set.str[STRING_SSL_EGDSOCKET]) { - ossl_seed(data); - ssl_seeded = TRUE; - } - return 0; -} - - -#ifndef SSL_FILETYPE_ENGINE -#define SSL_FILETYPE_ENGINE 42 -#endif -#ifndef SSL_FILETYPE_PKCS12 -#define SSL_FILETYPE_PKCS12 43 -#endif -static int do_file_type(const char *type) -{ - if(!type || !type[0]) - return SSL_FILETYPE_PEM; - if(Curl_raw_equal(type, "PEM")) - return SSL_FILETYPE_PEM; - if(Curl_raw_equal(type, "DER")) - return SSL_FILETYPE_ASN1; - if(Curl_raw_equal(type, "ENG")) - return SSL_FILETYPE_ENGINE; - if(Curl_raw_equal(type, "P12")) - return SSL_FILETYPE_PKCS12; - return -1; -} - -static -int cert_stuff(struct connectdata *conn, - SSL_CTX* ctx, - char *cert_file, - const char *cert_type, - char *key_file, - const char *key_type) -{ - struct SessionHandle *data = conn->data; - - int file_type = do_file_type(cert_type); - - if(cert_file != NULL || file_type == SSL_FILETYPE_ENGINE) { - SSL *ssl; - X509 *x509; - int cert_done = 0; - - if(data->set.str[STRING_KEY_PASSWD]) { -#ifndef HAVE_USERDATA_IN_PWD_CALLBACK - /* - * If password has been given, we store that in the global - * area (*shudder*) for a while: - */ - size_t len = strlen(data->set.str[STRING_KEY_PASSWD]); - if(len < sizeof(global_passwd)) - memcpy(global_passwd, data->set.str[STRING_KEY_PASSWD], len+1); - else - global_passwd[0] = '\0'; -#else - /* - * We set the password in the callback userdata - */ - SSL_CTX_set_default_passwd_cb_userdata(ctx, - data->set.str[STRING_KEY_PASSWD]); -#endif - /* Set passwd callback: */ - SSL_CTX_set_default_passwd_cb(ctx, passwd_callback); - } - - -#define SSL_CLIENT_CERT_ERR \ - "unable to use client certificate (no key found or wrong pass phrase?)" - - switch(file_type) { - case SSL_FILETYPE_PEM: - /* SSL_CTX_use_certificate_chain_file() only works on PEM files */ - if(SSL_CTX_use_certificate_chain_file(ctx, - cert_file) != 1) { - failf(data, SSL_CLIENT_CERT_ERR); - return 0; - } - break; - - case SSL_FILETYPE_ASN1: - /* SSL_CTX_use_certificate_file() works with either PEM or ASN1, but - we use the case above for PEM so this can only be performed with - ASN1 files. */ - if(SSL_CTX_use_certificate_file(ctx, - cert_file, - file_type) != 1) { - failf(data, SSL_CLIENT_CERT_ERR); - return 0; - } - break; - case SSL_FILETYPE_ENGINE: -#if defined(HAVE_OPENSSL_ENGINE_H) && defined(ENGINE_CTRL_GET_CMD_FROM_NAME) - { - if(data->state.engine) { - const char *cmd_name = "LOAD_CERT_CTRL"; - struct { - const char *cert_id; - X509 *cert; - } params; - - params.cert_id = cert_file; - params.cert = NULL; - - /* Does the engine supports LOAD_CERT_CTRL ? */ - if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME, - 0, (void *)cmd_name, NULL)) { - failf(data, "ssl engine does not support loading certificates"); - return 0; - } - - /* Load the certificate from the engine */ - if(!ENGINE_ctrl_cmd(data->state.engine, cmd_name, - 0, ¶ms, NULL, 1)) { - failf(data, "ssl engine cannot load client cert with id" - " '%s' [%s]", cert_file, - ERR_error_string(ERR_get_error(), NULL)); - return 0; - } - - if(!params.cert) { - failf(data, "ssl engine didn't initialized the certificate " - "properly."); - return 0; - } - - if(SSL_CTX_use_certificate(ctx, params.cert) != 1) { - failf(data, "unable to set client certificate"); - X509_free(params.cert); - return 0; - } - X509_free(params.cert); /* we don't need the handle any more... */ - } - else { - failf(data, "crypto engine not set, can't load certificate"); - return 0; - } - } - break; -#else - failf(data, "file type ENG for certificate not implemented"); - return 0; -#endif - - case SSL_FILETYPE_PKCS12: - { -#ifdef HAVE_PKCS12_SUPPORT - FILE *f; - PKCS12 *p12; - EVP_PKEY *pri; - STACK_OF(X509) *ca = NULL; - int i; - - f = fopen(cert_file,"rb"); - if(!f) { - failf(data, "could not open PKCS12 file '%s'", cert_file); - return 0; - } - p12 = d2i_PKCS12_fp(f, NULL); - fclose(f); - - if(!p12) { - failf(data, "error reading PKCS12 file '%s'", cert_file ); - return 0; - } - - PKCS12_PBE_add(); - - if(!PKCS12_parse(p12, data->set.str[STRING_KEY_PASSWD], &pri, &x509, - &ca)) { - failf(data, - "could not parse PKCS12 file, check password, OpenSSL error %s", - ERR_error_string(ERR_get_error(), NULL) ); - PKCS12_free(p12); - return 0; - } - - PKCS12_free(p12); - - if(SSL_CTX_use_certificate(ctx, x509) != 1) { - failf(data, SSL_CLIENT_CERT_ERR); - EVP_PKEY_free(pri); - X509_free(x509); - sk_X509_pop_free(ca, X509_free); - return 0; - } - - if(SSL_CTX_use_PrivateKey(ctx, pri) != 1) { - failf(data, "unable to use private key from PKCS12 file '%s'", - cert_file); - EVP_PKEY_free(pri); - X509_free(x509); - sk_X509_pop_free(ca, X509_free); - return 0; - } - - if(!SSL_CTX_check_private_key (ctx)) { - failf(data, "private key from PKCS12 file '%s' " - "does not match certificate in same file", cert_file); - EVP_PKEY_free(pri); - X509_free(x509); - sk_X509_pop_free(ca, X509_free); - return 0; - } - /* Set Certificate Verification chain */ - if(ca && sk_X509_num(ca)) { - for(i = 0; i < sk_X509_num(ca); i++) { - if(!SSL_CTX_add_extra_chain_cert(ctx,sk_X509_value(ca, i))) { - failf(data, "cannot add certificate to certificate chain"); - EVP_PKEY_free(pri); - X509_free(x509); - sk_X509_pop_free(ca, X509_free); - return 0; - } - if(!SSL_CTX_add_client_CA(ctx, sk_X509_value(ca, i))) { - failf(data, "cannot add certificate to client CA list"); - EVP_PKEY_free(pri); - X509_free(x509); - sk_X509_pop_free(ca, X509_free); - return 0; - } - } - } - - EVP_PKEY_free(pri); - X509_free(x509); - sk_X509_pop_free(ca, X509_free); - cert_done = 1; - break; -#else - failf(data, "file type P12 for certificate not supported"); - return 0; -#endif - } - default: - failf(data, "not supported file type '%s' for certificate", cert_type); - return 0; - } - - file_type = do_file_type(key_type); - - switch(file_type) { - case SSL_FILETYPE_PEM: - if(cert_done) - break; - if(key_file == NULL) - /* cert & key can only be in PEM case in the same file */ - key_file=cert_file; - case SSL_FILETYPE_ASN1: - if(SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type) != 1) { - failf(data, "unable to set private key file: '%s' type %s", - key_file, key_type?key_type:"PEM"); - return 0; - } - break; - case SSL_FILETYPE_ENGINE: -#ifdef HAVE_OPENSSL_ENGINE_H - { /* XXXX still needs some work */ - EVP_PKEY *priv_key = NULL; - if(data->state.engine) { -#ifdef HAVE_ENGINE_LOAD_FOUR_ARGS - UI_METHOD *ui_method = UI_OpenSSL(); -#endif - /* the typecast below was added to please mingw32 */ - priv_key = (EVP_PKEY *) - ENGINE_load_private_key(data->state.engine,key_file, -#ifdef HAVE_ENGINE_LOAD_FOUR_ARGS - ui_method, -#endif - data->set.str[STRING_KEY_PASSWD]); - if(!priv_key) { - failf(data, "failed to load private key from crypto engine"); - return 0; - } - if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) { - failf(data, "unable to set private key"); - EVP_PKEY_free(priv_key); - return 0; - } - EVP_PKEY_free(priv_key); /* we don't need the handle any more... */ - } - else { - failf(data, "crypto engine not set, can't load private key"); - return 0; - } - } - break; -#else - failf(data, "file type ENG for private key not supported"); - return 0; -#endif - case SSL_FILETYPE_PKCS12: - if(!cert_done) { - failf(data, "file type P12 for private key not supported"); - return 0; - } - break; - default: - failf(data, "not supported file type for private key"); - return 0; - } - - ssl=SSL_new(ctx); - if(NULL == ssl) { - failf(data,"unable to create an SSL structure"); - return 0; - } - - x509=SSL_get_certificate(ssl); - - /* This version was provided by Evan Jordan and is supposed to not - leak memory as the previous version: */ - if(x509 != NULL) { - EVP_PKEY *pktmp = X509_get_pubkey(x509); - EVP_PKEY_copy_parameters(pktmp,SSL_get_privatekey(ssl)); - EVP_PKEY_free(pktmp); - } - - SSL_free(ssl); - - /* If we are using DSA, we can copy the parameters from - * the private key */ - - - /* Now we know that a key and cert have been set against - * the SSL context */ - if(!SSL_CTX_check_private_key(ctx)) { - failf(data, "Private key does not match the certificate public key"); - return 0; - } -#ifndef HAVE_USERDATA_IN_PWD_CALLBACK - /* erase it now */ - memset(global_passwd, 0, sizeof(global_passwd)); -#endif - } - return 1; -} - -/* returns non-zero on failure */ -static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) -{ -#if 0 - return X509_NAME_oneline(a, buf, size); -#else - BIO *bio_out = BIO_new(BIO_s_mem()); - BUF_MEM *biomem; - int rc; - - if(!bio_out) - return 1; /* alloc failed! */ - - rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); - BIO_get_mem_ptr(bio_out, &biomem); - - if((size_t)biomem->length < size) - size = biomem->length; - else - size--; /* don't overwrite the buffer end */ - - memcpy(buf, biomem->data, size); - buf[size]=0; - - BIO_free(bio_out); - - return !rc; -#endif -} - -static -int cert_verify_callback(int ok, X509_STORE_CTX *ctx) -{ - X509 *err_cert; - char buf[256]; - - err_cert=X509_STORE_CTX_get_current_cert(ctx); - (void)x509_name_oneline(X509_get_subject_name(err_cert), buf, sizeof(buf)); - return ok; -} - -/* Return error string for last OpenSSL error - */ -static char *SSL_strerror(unsigned long error, char *buf, size_t size) -{ -#ifdef HAVE_ERR_ERROR_STRING_N - /* OpenSSL 0.9.6 and later has a function named - ERRO_error_string_n() that takes the size of the buffer as a - third argument */ - ERR_error_string_n(error, buf, size); -#else - (void) size; - ERR_error_string(error, buf); -#endif - return buf; -} - -#endif /* USE_SSLEAY */ - -#ifdef USE_SSLEAY -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -int Curl_ossl_init(void) -{ -#ifdef HAVE_ENGINE_LOAD_BUILTIN_ENGINES - ENGINE_load_builtin_engines(); -#endif - - /* Lets get nice error messages */ - SSL_load_error_strings(); - - /* Init the global ciphers and digests */ - if(!SSLeay_add_ssl_algorithms()) - return 0; - - OpenSSL_add_all_algorithms(); - - return 1; -} - -#endif /* USE_SSLEAY */ - -#ifdef USE_SSLEAY - -/* Global cleanup */ -void Curl_ossl_cleanup(void) -{ - /* Free ciphers and digests lists */ - EVP_cleanup(); - -#ifdef HAVE_ENGINE_CLEANUP - /* Free engine list */ - ENGINE_cleanup(); -#endif - -#ifdef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA - /* Free OpenSSL ex_data table */ - CRYPTO_cleanup_all_ex_data(); -#endif - - /* Free OpenSSL error strings */ - ERR_free_strings(); - - /* Free thread local error state, destroying hash upon zero refcount */ -#ifdef HAVE_ERR_REMOVE_THREAD_STATE - ERR_remove_thread_state(NULL); -#else - ERR_remove_state(0); -#endif -} - -/* - * This function uses SSL_peek to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -int Curl_ossl_check_cxn(struct connectdata *conn) -{ - int rc; - char buf; - - rc = SSL_peek(conn->ssl[FIRSTSOCKET].handle, (void*)&buf, 1); - if(rc > 0) - return 1; /* connection still in place */ - - if(rc == 0) - return 0; /* connection has been closed */ - - return -1; /* connection status unknown */ -} - -/* Selects an OpenSSL crypto engine - */ -CURLcode Curl_ossl_set_engine(struct SessionHandle *data, const char *engine) -{ -#if defined(USE_SSLEAY) && defined(HAVE_OPENSSL_ENGINE_H) - ENGINE *e; - -#if OPENSSL_VERSION_NUMBER >= 0x00909000L - e = ENGINE_by_id(engine); -#else - /* avoid memory leak */ - for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { - const char *e_id = ENGINE_get_id(e); - if(!strcmp(engine, e_id)) - break; - } -#endif - - if(!e) { - failf(data, "SSL Engine '%s' not found", engine); - return CURLE_SSL_ENGINE_NOTFOUND; - } - - if(data->state.engine) { - ENGINE_finish(data->state.engine); - ENGINE_free(data->state.engine); - data->state.engine = NULL; - } - if(!ENGINE_init(e)) { - char buf[256]; - - ENGINE_free(e); - failf(data, "Failed to initialise SSL Engine '%s':\n%s", - engine, SSL_strerror(ERR_get_error(), buf, sizeof(buf))); - return CURLE_SSL_ENGINE_INITFAILED; - } - data->state.engine = e; - return CURLE_OK; -#else - (void)engine; - failf(data, "SSL Engine not supported"); - return CURLE_SSL_ENGINE_NOTFOUND; -#endif -} - -/* Sets engine as default for all SSL operations - */ -CURLcode Curl_ossl_set_engine_default(struct SessionHandle *data) -{ -#ifdef HAVE_OPENSSL_ENGINE_H - if(data->state.engine) { - if(ENGINE_set_default(data->state.engine, ENGINE_METHOD_ALL) > 0) { - infof(data,"set default crypto engine '%s'\n", - ENGINE_get_id(data->state.engine)); - } - else { - failf(data, "set default crypto engine '%s' failed", - ENGINE_get_id(data->state.engine)); - return CURLE_SSL_ENGINE_SETFAILED; - } - } -#else - (void) data; -#endif - return CURLE_OK; -} - -/* Return list of OpenSSL crypto engine names. - */ -struct curl_slist *Curl_ossl_engines_list(struct SessionHandle *data) -{ - struct curl_slist *list = NULL; -#if defined(USE_SSLEAY) && defined(HAVE_OPENSSL_ENGINE_H) - struct curl_slist *beg; - ENGINE *e; - - for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { - beg = curl_slist_append(list, ENGINE_get_id(e)); - if(!beg) { - curl_slist_free_all(list); - return NULL; - } - list = beg; - } -#endif - (void) data; - return list; -} - - -/* - * This function is called when an SSL connection is closed. - */ -void Curl_ossl_close(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - if(connssl->handle) { - (void)SSL_shutdown(connssl->handle); - SSL_set_connect_state(connssl->handle); - - SSL_free (connssl->handle); - connssl->handle = NULL; - } - if(connssl->ctx) { - SSL_CTX_free (connssl->ctx); - connssl->ctx = NULL; - } -} - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -int Curl_ossl_shutdown(struct connectdata *conn, int sockindex) -{ - int retval = 0; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct SessionHandle *data = conn->data; - char buf[120]; /* We will use this for the OpenSSL error buffer, so it has - to be at least 120 bytes long. */ - unsigned long sslerror; - ssize_t nread; - int buffsize; - int err; - int done = 0; - - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - (void)SSL_shutdown(connssl->handle); - - if(connssl->handle) { - buffsize = (int)sizeof(buf); - while(!done) { - int what = Curl_socket_ready(conn->sock[sockindex], - CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - ERR_clear_error(); - - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - nread = (ssize_t)SSL_read(conn->ssl[sockindex].handle, buf, - buffsize); - err = SSL_get_error(conn->ssl[sockindex].handle, (int)nread); - - switch(err) { - case SSL_ERROR_NONE: /* this is not an error */ - case SSL_ERROR_ZERO_RETURN: /* no more data */ - /* This is the expected response. There was no data but only - the close notify alert */ - done = 1; - break; - case SSL_ERROR_WANT_READ: - /* there's data pending, re-invoke SSL_read() */ - infof(data, "SSL_ERROR_WANT_READ\n"); - break; - case SSL_ERROR_WANT_WRITE: - /* SSL wants a write. Really odd. Let's bail out. */ - infof(data, "SSL_ERROR_WANT_WRITE\n"); - done = 1; - break; - default: - /* openssl/ssl.h says "look at error stack/return value/errno" */ - sslerror = ERR_get_error(); - failf(conn->data, "SSL read: %s, errno %d", - ERR_error_string(sslerror, buf), - SOCKERRNO); - done = 1; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = 1; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = 1; - } - } /* while()-loop for the select() */ - - if(data->set.verbose) { -#ifdef HAVE_SSL_GET_SHUTDOWN - switch(SSL_get_shutdown(connssl->handle)) { - case SSL_SENT_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN\n"); - break; - case SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN\n"); - break; - case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|" - "SSL_RECEIVED__SHUTDOWN\n"); - break; - } -#endif - } - - SSL_free (connssl->handle); - connssl->handle = NULL; - } - return retval; -} - -void Curl_ossl_session_free(void *ptr) -{ - /* free the ID */ - SSL_SESSION_free(ptr); -} - -/* - * This function is called when the 'data' struct is going away. Close - * down everything and free all resources! - */ -int Curl_ossl_close_all(struct SessionHandle *data) -{ -#ifdef HAVE_OPENSSL_ENGINE_H - if(data->state.engine) { - ENGINE_finish(data->state.engine); - ENGINE_free(data->state.engine); - data->state.engine = NULL; - } -#else - (void)data; -#endif - return 0; -} - -static int asn1_output(const ASN1_UTCTIME *tm, - char *buf, - size_t sizeofbuf) -{ - const char *asn1_string; - int gmt=FALSE; - int i; - int year=0,month=0,day=0,hour=0,minute=0,second=0; - - i=tm->length; - asn1_string=(const char *)tm->data; - - if(i < 10) - return 1; - if(asn1_string[i-1] == 'Z') - gmt=TRUE; - for(i=0; i<10; i++) - if((asn1_string[i] > '9') || (asn1_string[i] < '0')) - return 2; - - year= (asn1_string[0]-'0')*10+(asn1_string[1]-'0'); - if(year < 50) - year+=100; - - month= (asn1_string[2]-'0')*10+(asn1_string[3]-'0'); - if((month > 12) || (month < 1)) - return 3; - - day= (asn1_string[4]-'0')*10+(asn1_string[5]-'0'); - hour= (asn1_string[6]-'0')*10+(asn1_string[7]-'0'); - minute= (asn1_string[8]-'0')*10+(asn1_string[9]-'0'); - - if((asn1_string[10] >= '0') && (asn1_string[10] <= '9') && - (asn1_string[11] >= '0') && (asn1_string[11] <= '9')) - second= (asn1_string[10]-'0')*10+(asn1_string[11]-'0'); - - snprintf(buf, sizeofbuf, - "%04d-%02d-%02d %02d:%02d:%02d %s", - year+1900, month, day, hour, minute, second, (gmt?"GMT":"")); - - return 0; -} - -/* ====================================================== */ - - -/* Quote from RFC2818 section 3.1 "Server Identity" - - If a subjectAltName extension of type dNSName is present, that MUST - be used as the identity. Otherwise, the (most specific) Common Name - field in the Subject field of the certificate MUST be used. Although - the use of the Common Name is existing practice, it is deprecated and - Certification Authorities are encouraged to use the dNSName instead. - - Matching is performed using the matching rules specified by - [RFC2459]. If more than one identity of a given type is present in - the certificate (e.g., more than one dNSName name, a match in any one - of the set is considered acceptable.) Names may contain the wildcard - character * which is considered to match any single domain name - component or component fragment. E.g., *.a.com matches foo.a.com but - not bar.foo.a.com. f*.com matches foo.com but not bar.com. - - In some cases, the URI is specified as an IP address rather than a - hostname. In this case, the iPAddress subjectAltName must be present - in the certificate and must exactly match the IP in the URI. - -*/ -static CURLcode verifyhost(struct connectdata *conn, - X509 *server_cert) -{ - int matched = -1; /* -1 is no alternative match yet, 1 means match and 0 - means mismatch */ - int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */ - size_t addrlen = 0; - struct SessionHandle *data = conn->data; - STACK_OF(GENERAL_NAME) *altnames; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - CURLcode res = CURLE_OK; - -#ifdef ENABLE_IPV6 - if(conn->bits.ipv6_ip && - Curl_inet_pton(AF_INET6, conn->host.name, &addr)) { - target = GEN_IPADD; - addrlen = sizeof(struct in6_addr); - } - else -#endif - if(Curl_inet_pton(AF_INET, conn->host.name, &addr)) { - target = GEN_IPADD; - addrlen = sizeof(struct in_addr); - } - - /* get a "list" of alternative names */ - altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL); - - if(altnames) { - int numalts; - int i; - - /* get amount of alternatives, RFC2459 claims there MUST be at least - one, but we don't depend on it... */ - numalts = sk_GENERAL_NAME_num(altnames); - - /* loop through all alternatives while none has matched */ - for(i=0; (itype == target) { - /* get data and length */ - const char *altptr = (char *)ASN1_STRING_data(check->d.ia5); - size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5); - - switch(target) { - case GEN_DNS: /* name/pattern comparison */ - /* The OpenSSL man page explicitly says: "In general it cannot be - assumed that the data returned by ASN1_STRING_data() is null - terminated or does not contain embedded nulls." But also that - "The actual format of the data will depend on the actual string - type itself: for example for and IA5String the data will be ASCII" - - Gisle researched the OpenSSL sources: - "I checked the 0.9.6 and 0.9.8 sources before my patch and - it always 0-terminates an IA5String." - */ - if((altlen == strlen(altptr)) && - /* if this isn't true, there was an embedded zero in the name - string and we cannot match it. */ - Curl_cert_hostcheck(altptr, conn->host.name)) - matched = 1; - else - matched = 0; - break; - - case GEN_IPADD: /* IP address comparison */ - /* compare alternative IP address if the data chunk is the same size - our server IP address is */ - if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) - matched = 1; - else - matched = 0; - break; - } - } - } - GENERAL_NAMES_free(altnames); - } - - if(matched == 1) - /* an alternative name matched the server hostname */ - infof(data, "\t subjectAltName: %s matched\n", conn->host.dispname); - else if(matched == 0) { - /* an alternative name field existed, but didn't match and then - we MUST fail */ - infof(data, "\t subjectAltName does not match %s\n", conn->host.dispname); - res = CURLE_PEER_FAILED_VERIFICATION; - } - else { - /* we have to look to the last occurrence of a commonName in the - distinguished one to get the most significant one. */ - int j,i=-1 ; - -/* The following is done because of a bug in 0.9.6b */ - - unsigned char *nulstr = (unsigned char *)""; - unsigned char *peer_CN = nulstr; - - X509_NAME *name = X509_get_subject_name(server_cert) ; - if(name) - while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i))>=0) - i=j; - - /* we have the name entry and we will now convert this to a string - that we can use for comparison. Doing this we support BMPstring, - UTF8 etc. */ - - if(i>=0) { - ASN1_STRING *tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name,i)); - - /* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input - is already UTF-8 encoded. We check for this case and copy the raw - string manually to avoid the problem. This code can be made - conditional in the future when OpenSSL has been fixed. Work-around - brought by Alexis S. L. Carvalho. */ - if(tmp) { - if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { - j = ASN1_STRING_length(tmp); - if(j >= 0) { - peer_CN = OPENSSL_malloc(j+1); - if(peer_CN) { - memcpy(peer_CN, ASN1_STRING_data(tmp), j); - peer_CN[j] = '\0'; - } - } - } - else /* not a UTF8 name */ - j = ASN1_STRING_to_UTF8(&peer_CN, tmp); - - if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != j)) { - /* there was a terminating zero before the end of string, this - cannot match and we return failure! */ - failf(data, "SSL: illegal cert name field"); - res = CURLE_PEER_FAILED_VERIFICATION; - } - } - } - - if(peer_CN == nulstr) - peer_CN = NULL; - else { - /* convert peer_CN from UTF8 */ - CURLcode rc = Curl_convert_from_utf8(data, peer_CN, strlen(peer_CN)); - /* Curl_convert_from_utf8 calls failf if unsuccessful */ - if(rc) { - OPENSSL_free(peer_CN); - return rc; - } - } - - if(res) - /* error already detected, pass through */ - ; - else if(!peer_CN) { - failf(data, - "SSL: unable to obtain common name from peer certificate"); - res = CURLE_PEER_FAILED_VERIFICATION; - } - else if(!Curl_cert_hostcheck((const char *)peer_CN, conn->host.name)) { - failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", peer_CN, conn->host.dispname); - res = CURLE_PEER_FAILED_VERIFICATION; - } - else { - infof(data, "\t common name: %s (matched)\n", peer_CN); - } - if(peer_CN) - OPENSSL_free(peer_CN); - } - return res; -} -#endif /* USE_SSLEAY */ - -/* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions - and thus this cannot be done there. */ -#ifdef SSL_CTRL_SET_MSG_CALLBACK - -static const char *ssl_msg_type(int ssl_ver, int msg) -{ - if(ssl_ver == SSL2_VERSION_MAJOR) { - switch (msg) { - case SSL2_MT_ERROR: - return "Error"; - case SSL2_MT_CLIENT_HELLO: - return "Client hello"; - case SSL2_MT_CLIENT_MASTER_KEY: - return "Client key"; - case SSL2_MT_CLIENT_FINISHED: - return "Client finished"; - case SSL2_MT_SERVER_HELLO: - return "Server hello"; - case SSL2_MT_SERVER_VERIFY: - return "Server verify"; - case SSL2_MT_SERVER_FINISHED: - return "Server finished"; - case SSL2_MT_REQUEST_CERTIFICATE: - return "Request CERT"; - case SSL2_MT_CLIENT_CERTIFICATE: - return "Client CERT"; - } - } - else if(ssl_ver == SSL3_VERSION_MAJOR) { - switch (msg) { - case SSL3_MT_HELLO_REQUEST: - return "Hello request"; - case SSL3_MT_CLIENT_HELLO: - return "Client hello"; - case SSL3_MT_SERVER_HELLO: - return "Server hello"; - case SSL3_MT_CERTIFICATE: - return "CERT"; - case SSL3_MT_SERVER_KEY_EXCHANGE: - return "Server key exchange"; - case SSL3_MT_CLIENT_KEY_EXCHANGE: - return "Client key exchange"; - case SSL3_MT_CERTIFICATE_REQUEST: - return "Request CERT"; - case SSL3_MT_SERVER_DONE: - return "Server finished"; - case SSL3_MT_CERTIFICATE_VERIFY: - return "CERT verify"; - case SSL3_MT_FINISHED: - return "Finished"; - } - } - return "Unknown"; -} - -static const char *tls_rt_type(int type) -{ - return ( - type == SSL3_RT_CHANGE_CIPHER_SPEC ? "TLS change cipher, " : - type == SSL3_RT_ALERT ? "TLS alert, " : - type == SSL3_RT_HANDSHAKE ? "TLS handshake, " : - type == SSL3_RT_APPLICATION_DATA ? "TLS app data, " : - "TLS Unknown, "); -} - - -/* - * Our callback from the SSL/TLS layers. - */ -static void ssl_tls_trace(int direction, int ssl_ver, int content_type, - const void *buf, size_t len, const SSL *ssl, - struct connectdata *conn) -{ - struct SessionHandle *data; - const char *msg_name, *tls_rt_name; - char ssl_buf[1024]; - int ver, msg_type, txt_len; - - if(!conn || !conn->data || !conn->data->set.fdebug || - (direction != 0 && direction != 1)) - return; - - data = conn->data; - ssl_ver >>= 8; - ver = (ssl_ver == SSL2_VERSION_MAJOR ? '2' : - ssl_ver == SSL3_VERSION_MAJOR ? '3' : '?'); - - /* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL - * always pass-up content-type as 0. But the interesting message-type - * is at 'buf[0]'. - */ - if(ssl_ver == SSL3_VERSION_MAJOR && content_type != 0) - tls_rt_name = tls_rt_type(content_type); - else - tls_rt_name = ""; - - msg_type = *(char*)buf; - msg_name = ssl_msg_type(ssl_ver, msg_type); - - txt_len = snprintf(ssl_buf, sizeof(ssl_buf), "SSLv%c, %s%s (%d):\n", - ver, tls_rt_name, msg_name, msg_type); - Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len, NULL); - - Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : - CURLINFO_SSL_DATA_IN, (char *)buf, len, NULL); - (void) ssl; -} -#endif - -#ifdef USE_SSLEAY -/* ====================================================== */ - -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME -# define use_sni(x) sni = (x) -#else -# define use_sni(x) Curl_nop_stmt -#endif - -static CURLcode -ossl_connect_step1(struct connectdata *conn, - int sockindex) -{ - CURLcode retcode = CURLE_OK; - - struct SessionHandle *data = conn->data; - SSL_METHOD_QUAL SSL_METHOD *req_method=NULL; - void *ssl_sessionid=NULL; - X509_LOOKUP *lookup=NULL; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - long ctx_options; -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - bool sni; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif -#endif - - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); - - /* Make funny stuff to get random input */ - Curl_ossl_seed(data); - - /* check to see if we've been told to use an explicit SSL/TLS version */ - - switch(data->set.ssl.version) { - default: - case CURL_SSLVERSION_DEFAULT: -#ifdef USE_TLS_SRP - if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { - infof(data, "Set version TLSv1 for SRP authorisation\n"); - req_method = TLSv1_client_method() ; - } - else -#endif - /* we try to figure out version */ - req_method = SSLv23_client_method(); - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1: - req_method = TLSv1_client_method(); - use_sni(TRUE); - break; - case CURL_SSLVERSION_SSLv2: -#ifdef OPENSSL_NO_SSL2 - failf(data, "OpenSSL was built without SSLv2 support"); - return CURLE_NOT_BUILT_IN; -#else -#ifdef USE_TLS_SRP - if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) - return CURLE_SSL_CONNECT_ERROR; -#endif - req_method = SSLv2_client_method(); - use_sni(FALSE); - break; -#endif - case CURL_SSLVERSION_SSLv3: -#ifdef USE_TLS_SRP - if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) - return CURLE_SSL_CONNECT_ERROR; -#endif - req_method = SSLv3_client_method(); - use_sni(FALSE); - break; - } - - if(connssl->ctx) - SSL_CTX_free(connssl->ctx); - connssl->ctx = SSL_CTX_new(req_method); - - if(!connssl->ctx) { - failf(data, "SSL: couldn't create a context: %s", - ERR_error_string(ERR_peek_error(), NULL)); - return CURLE_OUT_OF_MEMORY; - } - -#ifdef SSL_MODE_RELEASE_BUFFERS - SSL_CTX_set_mode(connssl->ctx, SSL_MODE_RELEASE_BUFFERS); -#endif - -#ifdef SSL_CTRL_SET_MSG_CALLBACK - if(data->set.fdebug && data->set.verbose) { - /* the SSL trace callback is only used for verbose logging so we only - inform about failures of setting it */ - if(!SSL_CTX_callback_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK, - (void (*)(void))ssl_tls_trace)) { - infof(data, "SSL: couldn't set callback!\n"); - } - else if(!SSL_CTX_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, - conn)) { - infof(data, "SSL: couldn't set callback argument!\n"); - } - } -#endif - - /* OpenSSL contains code to work-around lots of bugs and flaws in various - SSL-implementations. SSL_CTX_set_options() is used to enabled those - work-arounds. The man page for this option states that SSL_OP_ALL enables - all the work-arounds and that "It is usually safe to use SSL_OP_ALL to - enable the bug workaround options if compatibility with somewhat broken - implementations is desired." - - The "-no_ticket" option was introduced in Openssl0.9.8j. It's a flag to - disable "rfc4507bis session ticket support". rfc4507bis was later turned - into the proper RFC5077 it seems: http://tools.ietf.org/html/rfc5077 - - The enabled extension concerns the session management. I wonder how often - libcurl stops a connection and then resumes a TLS session. also, sending - the session data is some overhead. .I suggest that you just use your - proposed patch (which explicitly disables TICKET). - - If someone writes an application with libcurl and openssl who wants to - enable the feature, one can do this in the SSL callback. - - SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper - interoperability with web server Netscape Enterprise Server 2.0.1 which - was released back in 1996. - - Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has - become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate - CVE-2010-4180 when using previous OpenSSL versions we no longer enable - this option regardless of OpenSSL version and SSL_OP_ALL definition. - - OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability - (http://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to - SSL_OP_ALL that _disables_ that work-around despite the fact that - SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to - keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit - must not be set. - */ - - ctx_options = SSL_OP_ALL; - -#ifdef SSL_OP_NO_TICKET - ctx_options |= SSL_OP_NO_TICKET; -#endif - -#ifdef SSL_OP_NO_COMPRESSION - ctx_options |= SSL_OP_NO_COMPRESSION; -#endif - -#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG - /* mitigate CVE-2010-4180 */ - ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; -#endif - -#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS - /* unless the user explicitly ask to allow the protocol vulnerability we - use the work-around */ - if(!conn->data->set.ssl_enable_beast) - ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; -#endif - - /* disable SSLv2 in the default case (i.e. allow SSLv3 and TLSv1) */ - if(data->set.ssl.version == CURL_SSLVERSION_DEFAULT) - ctx_options |= SSL_OP_NO_SSLv2; - - SSL_CTX_set_options(connssl->ctx, ctx_options); - -#if 0 - /* - * Not sure it's needed to tell SSL_connect() that socket is - * non-blocking. It doesn't seem to care, but just return with - * SSL_ERROR_WANT_x. - */ - if(data->state.used_interface == Curl_if_multi) - SSL_CTX_ctrl(connssl->ctx, BIO_C_SET_NBIO, 1, NULL); -#endif - - if(data->set.str[STRING_CERT] || data->set.str[STRING_CERT_TYPE]) { - if(!cert_stuff(conn, - connssl->ctx, - data->set.str[STRING_CERT], - data->set.str[STRING_CERT_TYPE], - data->set.str[STRING_KEY], - data->set.str[STRING_KEY_TYPE])) { - /* failf() is already done in cert_stuff() */ - return CURLE_SSL_CERTPROBLEM; - } - } - - if(data->set.str[STRING_SSL_CIPHER_LIST]) { - if(!SSL_CTX_set_cipher_list(connssl->ctx, - data->set.str[STRING_SSL_CIPHER_LIST])) { - failf(data, "failed setting cipher list"); - return CURLE_SSL_CIPHER; - } - } - -#ifdef USE_TLS_SRP - if(data->set.ssl.authtype == CURL_TLSAUTH_SRP) { - infof(data, "Using TLS-SRP username: %s\n", data->set.ssl.username); - - if(!SSL_CTX_set_srp_username(connssl->ctx, data->set.ssl.username)) { - failf(data, "Unable to set SRP user name"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - if(!SSL_CTX_set_srp_password(connssl->ctx,data->set.ssl.password)) { - failf(data, "failed setting SRP password"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - if(!data->set.str[STRING_SSL_CIPHER_LIST]) { - infof(data, "Setting cipher list SRP\n"); - - if(!SSL_CTX_set_cipher_list(connssl->ctx, "SRP")) { - failf(data, "failed setting SRP cipher list"); - return CURLE_SSL_CIPHER; - } - } - } -#endif - if(data->set.str[STRING_SSL_CAFILE] || data->set.str[STRING_SSL_CAPATH]) { - /* tell SSL where to find CA certificates that are used to verify - the servers certificate. */ - if(!SSL_CTX_load_verify_locations(connssl->ctx, - data->set.str[STRING_SSL_CAFILE], - data->set.str[STRING_SSL_CAPATH])) { - if(data->set.ssl.verifypeer) { - /* Fail if we insist on successfully verifying the server. */ - failf(data,"error setting certificate verify locations:\n" - " CAfile: %s\n CApath: %s", - data->set.str[STRING_SSL_CAFILE]? - data->set.str[STRING_SSL_CAFILE]: "none", - data->set.str[STRING_SSL_CAPATH]? - data->set.str[STRING_SSL_CAPATH] : "none"); - return CURLE_SSL_CACERT_BADFILE; - } - else { - /* Just continue with a warning if no strict certificate verification - is required. */ - infof(data, "error setting certificate verify locations," - " continuing anyway:\n"); - } - } - else { - /* Everything is fine. */ - infof(data, "successfully set certificate verify locations:\n"); - } - infof(data, - " CAfile: %s\n" - " CApath: %s\n", - data->set.str[STRING_SSL_CAFILE] ? data->set.str[STRING_SSL_CAFILE]: - "none", - data->set.str[STRING_SSL_CAPATH] ? data->set.str[STRING_SSL_CAPATH]: - "none"); - } - - if(data->set.str[STRING_SSL_CRLFILE]) { - /* tell SSL where to find CRL file that is used to check certificate - * revocation */ - lookup=X509_STORE_add_lookup(SSL_CTX_get_cert_store(connssl->ctx), - X509_LOOKUP_file()); - if(!lookup || - (!X509_load_crl_file(lookup,data->set.str[STRING_SSL_CRLFILE], - X509_FILETYPE_PEM)) ) { - failf(data,"error loading CRL file: %s", - data->set.str[STRING_SSL_CRLFILE]); - return CURLE_SSL_CRL_BADFILE; - } - else { - /* Everything is fine. */ - infof(data, "successfully load CRL file:\n"); - X509_STORE_set_flags(SSL_CTX_get_cert_store(connssl->ctx), - X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); - } - infof(data, - " CRLfile: %s\n", data->set.str[STRING_SSL_CRLFILE] ? - data->set.str[STRING_SSL_CRLFILE]: "none"); - } - - /* SSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(connssl->ctx, - data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE, - cert_verify_callback); - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - retcode = (*data->set.ssl.fsslctx)(data, connssl->ctx, - data->set.ssl.fsslctxp); - if(retcode) { - failf(data,"error signaled by ssl ctx callback"); - return retcode; - } - } - - /* Lets make an SSL structure */ - if(connssl->handle) - SSL_free(connssl->handle); - connssl->handle = SSL_new(connssl->ctx); - if(!connssl->handle) { - failf(data, "SSL: couldn't create a context (handle)!"); - return CURLE_OUT_OF_MEMORY; - } - SSL_set_connect_state(connssl->handle); - - connssl->server_cert = 0x0; - -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) && -#endif - sni && - !SSL_set_tlsext_host_name(connssl->handle, conn->host.name)) - infof(data, "WARNING: failed to configure server name indication (SNI) " - "TLS extension\n"); -#endif - - /* Check if there's a cached ID we can/should use here! */ - if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) { - /* we got a session id, use it! */ - if(!SSL_set_session(connssl->handle, ssl_sessionid)) { - failf(data, "SSL: SSL_set_session failed: %s", - ERR_error_string(ERR_get_error(),NULL)); - return CURLE_SSL_CONNECT_ERROR; - } - /* Informational message */ - infof (data, "SSL re-using session ID\n"); - } - - /* pass the raw socket into the SSL layers */ - if(!SSL_set_fd(connssl->handle, (int)sockfd)) { - failf(data, "SSL: SSL_set_fd failed: %s", - ERR_error_string(ERR_get_error(),NULL)); - return CURLE_SSL_CONNECT_ERROR; - } - - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; -} - -static CURLcode -ossl_connect_step2(struct connectdata *conn, int sockindex) -{ - struct SessionHandle *data = conn->data; - int err; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); - - ERR_clear_error(); - - err = SSL_connect(connssl->handle); - - /* 1 is fine - 0 is "not successful but was shut down controlled" - <0 is "handshake was not successful, because a fatal error occurred" */ - if(1 != err) { - int detail = SSL_get_error(connssl->handle, err); - - if(SSL_ERROR_WANT_READ == detail) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - else if(SSL_ERROR_WANT_WRITE == detail) { - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; - } - else { - /* untreated error */ - unsigned long errdetail; - char error_buffer[256]; /* OpenSSL documents that this must be at least - 256 bytes long. */ - CURLcode rc; - const char *cert_problem = NULL; - long lerr; - - connssl->connecting_state = ssl_connect_2; /* the connection failed, - we're not waiting for - anything else. */ - - errdetail = ERR_get_error(); /* Gets the earliest error code from the - thread's error queue and removes the - entry. */ - - switch(errdetail) { - case 0x1407E086: - /* 1407E086: - SSL routines: - SSL2_SET_CERTIFICATE: - certificate verify failed */ - /* fall-through */ - case 0x14090086: - /* 14090086: - SSL routines: - SSL3_GET_SERVER_CERTIFICATE: - certificate verify failed */ - rc = CURLE_SSL_CACERT; - - lerr = SSL_get_verify_result(connssl->handle); - if(lerr != X509_V_OK) { - snprintf(error_buffer, sizeof(error_buffer), - "SSL certificate problem: %s", - X509_verify_cert_error_string(lerr)); - } - else - cert_problem = "SSL certificate problem, verify that the CA cert is" - " OK."; - - break; - default: - rc = CURLE_SSL_CONNECT_ERROR; - SSL_strerror(errdetail, error_buffer, sizeof(error_buffer)); - break; - } - - /* detail is already set to the SSL error above */ - - /* If we e.g. use SSLv2 request-method and the server doesn't like us - * (RST connection etc.), OpenSSL gives no explanation whatsoever and - * the SO_ERROR is also lost. - */ - if(CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) { - failf(data, "Unknown SSL protocol error in connection to %s:%ld ", - conn->host.name, conn->port); - return rc; - } - /* Could be a CERT problem */ - - failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer); - return rc; - } - } - else { - /* we have been connected fine, we're not waiting for anything else. */ - connssl->connecting_state = ssl_connect_3; - - /* Informational message */ - infof (data, "SSL connection using %s\n", - SSL_get_cipher(connssl->handle)); - - return CURLE_OK; - } -} - -static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len) -{ - int i, ilen; - - if((ilen = (int)len) < 0) - return 1; /* buffer too big */ - - i = i2t_ASN1_OBJECT(buf, ilen, a); - - if(i >= ilen) - return 1; /* buffer too small */ - - return 0; -} - -static CURLcode push_certinfo_len(struct SessionHandle *data, - int certnum, - const char *label, - const char *value, - size_t valuelen) -{ - struct curl_certinfo *ci = &data->info.certs; - char *output; - struct curl_slist *nl; - CURLcode res = CURLE_OK; - size_t labellen = strlen(label); - size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */ - - output = malloc(outlen); - if(!output) - return CURLE_OUT_OF_MEMORY; - - /* sprintf the label and colon */ - snprintf(output, outlen, "%s:", label); - - /* memcpy the value (it might not be zero terminated) */ - memcpy(&output[labellen+1], value, valuelen); - - /* zero terminate the output */ - output[labellen + 1 + valuelen] = 0; - - /* TODO: we should rather introduce an internal API that can do the - equivalent of curl_slist_append but doesn't strdup() the given data as - like in this place the extra malloc/free is totally pointless */ - nl = curl_slist_append(ci->certinfo[certnum], output); - free(output); - if(!nl) { - curl_slist_free_all(ci->certinfo[certnum]); - ci->certinfo[certnum] = NULL; - res = CURLE_OUT_OF_MEMORY; - } - else - ci->certinfo[certnum] = nl; - - return res; -} - -/* this is a convenience function for push_certinfo_len that takes a zero - terminated value */ -static CURLcode push_certinfo(struct SessionHandle *data, - int certnum, - const char *label, - const char *value) -{ - size_t valuelen = strlen(value); - - return push_certinfo_len(data, certnum, label, value, valuelen); -} - -static void pubkey_show(struct SessionHandle *data, - int num, - const char *type, - const char *name, - unsigned char *raw, - int len) -{ - size_t left; - int i; - char namebuf[32]; - char *buffer; - - left = len*3 + 1; - buffer = malloc(left); - if(buffer) { - char *ptr=buffer; - snprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name); - for(i=0; i< len; i++) { - snprintf(ptr, left, "%02x:", raw[i]); - ptr += 3; - left -= 3; - } - infof(data, " %s: %s\n", namebuf, buffer); - push_certinfo(data, num, namebuf, buffer); - free(buffer); - } -} - -#define print_pubkey_BN(_type, _name, _num) \ -do { \ - if(pubkey->pkey._type->_name != NULL) { \ - int len = BN_num_bytes(pubkey->pkey._type->_name); \ - if(len < CERTBUFFERSIZE) { \ - BN_bn2bin(pubkey->pkey._type->_name, (unsigned char*)bufp); \ - bufp[len] = 0; \ - pubkey_show(data, _num, #_type, #_name, (unsigned char*)bufp, len); \ - } \ - } \ -} WHILE_FALSE - -static int X509V3_ext(struct SessionHandle *data, - int certnum, - STACK_OF(X509_EXTENSION) *exts) -{ - int i; - size_t j; - - if(sk_X509_EXTENSION_num(exts) <= 0) - /* no extensions, bail out */ - return 1; - - for(i=0; ivalue); - - BIO_get_mem_ptr(bio_out, &biomem); - - /* biomem->length bytes at biomem->data, this little loop here is only - done for the infof() call, we send the "raw" data to the certinfo - function */ - for(j=0; j<(size_t)biomem->length; j++) { - const char *sep=""; - if(biomem->data[j] == '\n') { - sep=", "; - j++; /* skip the newline */ - }; - while((biomem->data[j] == ' ') && (j<(size_t)biomem->length)) - j++; - if(j<(size_t)biomem->length) - ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%s%c", sep, - biomem->data[j]); - } - infof(data, " %s\n", buf); - - push_certinfo(data, certnum, namebuf, buf); - - BIO_free(bio_out); - - } - return 0; /* all is fine */ -} - - -static void X509_signature(struct SessionHandle *data, - int numcert, - ASN1_STRING *sig) -{ - char buf[1024]; - char *ptr = buf; - int i; - for(i=0; ilength; i++) - ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%02x:", sig->data[i]); - - infof(data, " Signature: %s\n", buf); - push_certinfo(data, numcert, "Signature", buf); -} - -static void dumpcert(struct SessionHandle *data, X509 *x, int numcert) -{ - BIO *bio_out = BIO_new(BIO_s_mem()); - BUF_MEM *biomem; - - /* this outputs the cert in this 64 column wide style with newlines and - -----BEGIN CERTIFICATE----- texts and more */ - PEM_write_bio_X509(bio_out, x); - - BIO_get_mem_ptr(bio_out, &biomem); - - infof(data, "%s\n", biomem->data); - - push_certinfo_len(data, numcert, "Cert", biomem->data, biomem->length); - - BIO_free(bio_out); - -} - - -static int init_certinfo(struct SessionHandle *data, - int num) -{ - struct curl_certinfo *ci = &data->info.certs; - struct curl_slist **table; - - Curl_ssl_free_certinfo(data); - - ci->num_of_certs = num; - table = calloc((size_t)num, sizeof(struct curl_slist *)); - if(!table) - return 1; - - ci->certinfo = table; - return 0; -} - -/* - * This size was previously 512 which has been reported "too small" without - * any specifics, so it was enlarged to allow more data to get shown uncut. - * The "perfect" size is yet to figure out. - */ -#define CERTBUFFERSIZE 8192 - -static CURLcode get_cert_chain(struct connectdata *conn, - struct ssl_connect_data *connssl) - -{ - STACK_OF(X509) *sk; - int i; - char *bufp; - struct SessionHandle *data = conn->data; - int numcerts; - - bufp = malloc(CERTBUFFERSIZE); - if(!bufp) - return CURLE_OUT_OF_MEMORY; - - sk = SSL_get_peer_cert_chain(connssl->handle); - if(!sk) { - free(bufp); - return CURLE_OUT_OF_MEMORY; - } - - numcerts = sk_X509_num(sk); - if(init_certinfo(data, numcerts)) { - free(bufp); - return CURLE_OUT_OF_MEMORY; - } - - infof(data, "--- Certificate chain\n"); - for(i=0; ilength <= 4) { - value = ASN1_INTEGER_get(num); - infof(data," Serial Number: %ld (0x%lx)\n", value, value); - snprintf(bufp, CERTBUFFERSIZE, "%lx", value); - } - else { - int left = CERTBUFFERSIZE; - - ptr = bufp; - *ptr++ = 0; - if(num->type == V_ASN1_NEG_INTEGER) - *ptr++='-'; - - for(j=0; (jlength) && (left>=4); j++) { - /* TODO: length restrictions */ - snprintf(ptr, 3, "%02x%c",num->data[j], - ((j+1 == num->length)?'\n':':')); - ptr += 3; - left-=4; - } - if(num->length) - infof(data," Serial Number: %s\n", bufp); - else - bufp[0]=0; - } - if(bufp[0]) - push_certinfo(data, i, "Serial Number", bufp); /* hex */ - - cinf = x->cert_info; - - j = asn1_object_dump(cinf->signature->algorithm, bufp, CERTBUFFERSIZE); - if(!j) { - infof(data, " Signature Algorithm: %s\n", bufp); - push_certinfo(data, i, "Signature Algorithm", bufp); - } - - certdate = X509_get_notBefore(x); - asn1_output(certdate, bufp, CERTBUFFERSIZE); - infof(data, " Start date: %s\n", bufp); - push_certinfo(data, i, "Start date", bufp); - - certdate = X509_get_notAfter(x); - asn1_output(certdate, bufp, CERTBUFFERSIZE); - infof(data, " Expire date: %s\n", bufp); - push_certinfo(data, i, "Expire date", bufp); - - j = asn1_object_dump(cinf->key->algor->algorithm, bufp, CERTBUFFERSIZE); - if(!j) { - infof(data, " Public Key Algorithm: %s\n", bufp); - push_certinfo(data, i, "Public Key Algorithm", bufp); - } - - pubkey = X509_get_pubkey(x); - if(!pubkey) - infof(data, " Unable to load public key\n"); - else { - switch(pubkey->type) { - case EVP_PKEY_RSA: - infof(data, " RSA Public Key (%d bits)\n", - BN_num_bits(pubkey->pkey.rsa->n)); - snprintf(bufp, CERTBUFFERSIZE, "%d", BN_num_bits(pubkey->pkey.rsa->n)); - push_certinfo(data, i, "RSA Public Key", bufp); - - print_pubkey_BN(rsa, n, i); - print_pubkey_BN(rsa, e, i); - print_pubkey_BN(rsa, d, i); - print_pubkey_BN(rsa, p, i); - print_pubkey_BN(rsa, q, i); - print_pubkey_BN(rsa, dmp1, i); - print_pubkey_BN(rsa, dmq1, i); - print_pubkey_BN(rsa, iqmp, i); - break; - case EVP_PKEY_DSA: - print_pubkey_BN(dsa, p, i); - print_pubkey_BN(dsa, q, i); - print_pubkey_BN(dsa, g, i); - print_pubkey_BN(dsa, priv_key, i); - print_pubkey_BN(dsa, pub_key, i); - break; - case EVP_PKEY_DH: - print_pubkey_BN(dh, p, i); - print_pubkey_BN(dh, g, i); - print_pubkey_BN(dh, priv_key, i); - print_pubkey_BN(dh, pub_key, i); - break; -#if 0 - case EVP_PKEY_EC: /* symbol not present in OpenSSL 0.9.6 */ - /* left TODO */ - break; -#endif - } - EVP_PKEY_free(pubkey); - } - - X509V3_ext(data, i, cinf->extensions); - - X509_signature(data, i, x->signature); - - dumpcert(data, x, i); - } - - free(bufp); - - return CURLE_OK; -} - -/* - * Get the server cert, verify it and show it etc, only call failf() if the - * 'strict' argument is TRUE as otherwise all this is for informational - * purposes only! - * - * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack. - */ -static CURLcode servercert(struct connectdata *conn, - struct ssl_connect_data *connssl, - bool strict) -{ - CURLcode retcode = CURLE_OK; - int rc; - long lerr; - ASN1_TIME *certdate; - struct SessionHandle *data = conn->data; - X509 *issuer; - FILE *fp; - char *buffer = data->state.buffer; - - if(data->set.ssl.certinfo) - /* we've been asked to gather certificate info! */ - (void)get_cert_chain(conn, connssl); - - data->set.ssl.certverifyresult = !X509_V_OK; - - connssl->server_cert = SSL_get_peer_certificate(connssl->handle); - if(!connssl->server_cert) { - if(strict) - failf(data, "SSL: couldn't get peer certificate!"); - return CURLE_PEER_FAILED_VERIFICATION; - } - infof (data, "Server certificate:\n"); - - rc = x509_name_oneline(X509_get_subject_name(connssl->server_cert), - buffer, BUFSIZE); - if(rc) { - if(strict) - failf(data, "SSL: couldn't get X509-subject!"); - X509_free(connssl->server_cert); - connssl->server_cert = NULL; - return CURLE_SSL_CONNECT_ERROR; - } - infof(data, "\t subject: %s\n", buffer); - - certdate = X509_get_notBefore(connssl->server_cert); - asn1_output(certdate, buffer, BUFSIZE); - infof(data, "\t start date: %s\n", buffer); - - certdate = X509_get_notAfter(connssl->server_cert); - asn1_output(certdate, buffer, BUFSIZE); - infof(data, "\t expire date: %s\n", buffer); - - if(data->set.ssl.verifyhost) { - retcode = verifyhost(conn, connssl->server_cert); - if(retcode) { - X509_free(connssl->server_cert); - connssl->server_cert = NULL; - return retcode; - } - } - - rc = x509_name_oneline(X509_get_issuer_name(connssl->server_cert), - buffer, BUFSIZE); - if(rc) { - if(strict) - failf(data, "SSL: couldn't get X509-issuer name!"); - retcode = CURLE_SSL_CONNECT_ERROR; - } - else { - infof(data, "\t issuer: %s\n", buffer); - - /* We could do all sorts of certificate verification stuff here before - deallocating the certificate. */ - - /* e.g. match issuer name with provided issuer certificate */ - if(data->set.str[STRING_SSL_ISSUERCERT]) { - fp=fopen(data->set.str[STRING_SSL_ISSUERCERT],"r"); - if(!fp) { - if(strict) - failf(data, "SSL: Unable to open issuer cert (%s)", - data->set.str[STRING_SSL_ISSUERCERT]); - X509_free(connssl->server_cert); - connssl->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; - } - issuer = PEM_read_X509(fp,NULL,ZERO_NULL,NULL); - if(!issuer) { - if(strict) - failf(data, "SSL: Unable to read issuer cert (%s)", - data->set.str[STRING_SSL_ISSUERCERT]); - X509_free(connssl->server_cert); - X509_free(issuer); - fclose(fp); - return CURLE_SSL_ISSUER_ERROR; - } - fclose(fp); - if(X509_check_issued(issuer,connssl->server_cert) != X509_V_OK) { - if(strict) - failf(data, "SSL: Certificate issuer check failed (%s)", - data->set.str[STRING_SSL_ISSUERCERT]); - X509_free(connssl->server_cert); - X509_free(issuer); - connssl->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; - } - infof(data, "\t SSL certificate issuer check ok (%s)\n", - data->set.str[STRING_SSL_ISSUERCERT]); - X509_free(issuer); - } - - lerr = data->set.ssl.certverifyresult= - SSL_get_verify_result(connssl->handle); - if(data->set.ssl.certverifyresult != X509_V_OK) { - if(data->set.ssl.verifypeer) { - /* We probably never reach this, because SSL_connect() will fail - and we return earlier if verifypeer is set? */ - if(strict) - failf(data, "SSL certificate verify result: %s (%ld)", - X509_verify_cert_error_string(lerr), lerr); - retcode = CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, "\t SSL certificate verify result: %s (%ld)," - " continuing anyway.\n", - X509_verify_cert_error_string(lerr), lerr); - } - else - infof(data, "\t SSL certificate verify ok.\n"); - } - - X509_free(connssl->server_cert); - connssl->server_cert = NULL; - connssl->connecting_state = ssl_connect_done; - - return retcode; -} - - -static CURLcode -ossl_connect_step3(struct connectdata *conn, - int sockindex) -{ - CURLcode retcode = CURLE_OK; - void *old_ssl_sessionid=NULL; - struct SessionHandle *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - int incache; - SSL_SESSION *our_ssl_sessionid; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - -#ifdef HAVE_SSL_GET1_SESSION - our_ssl_sessionid = SSL_get1_session(connssl->handle); - - /* SSL_get1_session() will increment the reference - count and the session will stay in memory until explicitly freed with - SSL_SESSION_free(3), regardless of its state. - This function was introduced in openssl 0.9.5a. */ -#else - our_ssl_sessionid = SSL_get_session(connssl->handle); - - /* if SSL_get1_session() is unavailable, use SSL_get_session(). - This is an inferior option because the session can be flushed - at any time by openssl. It is included only so curl compiles - under versions of openssl < 0.9.5a. - - WARNING: How curl behaves if it's session is flushed is - untested. - */ -#endif - - incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL)); - if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing\n"); - Curl_ssl_delsessionid(conn, old_ssl_sessionid); - incache = FALSE; - } - } - if(!incache) { - retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid, - 0 /* unknown size */); - if(retcode) { - failf(data, "failed to store ssl session"); - return retcode; - } - } -#ifdef HAVE_SSL_GET1_SESSION - else { - /* Session was incache, so refcount already incremented earlier. - * Avoid further increments with each SSL_get1_session() call. - * This does not free the session as refcount remains > 0 - */ - SSL_SESSION_free(our_ssl_sessionid); - } -#endif - - /* - * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to - * verify the peer ignore faults and failures from the server cert - * operations. - */ - - if(!data->set.ssl.verifypeer) - (void)servercert(conn, connssl, FALSE); - else - retcode = servercert(conn, connssl, TRUE); - - if(CURLE_OK == retcode) - connssl->connecting_state = ssl_connect_done; - return retcode; -} - -static Curl_recv ossl_recv; -static Curl_send ossl_send; - -static CURLcode -ossl_connect_common(struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode retcode; - struct SessionHandle *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - long timeout_ms; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1==connssl->connecting_state) { - /* Find out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - retcode = ossl_connect_step1(conn, sockindex); - if(retcode) - return retcode; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing== - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading== - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * or epoll() will always have a valid fdset to wait on. - */ - retcode = ossl_connect_step2(conn, sockindex); - if(retcode || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return retcode; - - } /* repeat step2 until all transactions are done. */ - - - if(ssl_connect_3==connssl->connecting_state) { - retcode = ossl_connect_step3(conn, sockindex); - if(retcode) - return retcode; - } - - if(ssl_connect_done==connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = ossl_recv; - conn->send[sockindex] = ossl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -CURLcode -Curl_ossl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) -{ - return ossl_connect_common(conn, sockindex, TRUE, done); -} - -CURLcode -Curl_ossl_connect(struct connectdata *conn, - int sockindex) -{ - CURLcode retcode; - bool done = FALSE; - - retcode = ossl_connect_common(conn, sockindex, FALSE, &done); - if(retcode) - return retcode; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -bool Curl_ossl_data_pending(const struct connectdata *conn, - int connindex) -{ - if(conn->ssl[connindex].handle) - /* SSL is in use */ - return (0 != SSL_pending(conn->ssl[connindex].handle)) ? TRUE : FALSE; - else - return FALSE; -} - -static ssize_t ossl_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - /* SSL_write() is said to return 'int' while write() and send() returns - 'size_t' */ - int err; - char error_buffer[120]; /* OpenSSL documents that this must be at least 120 - bytes long. */ - unsigned long sslerror; - int memlen; - int rc; - - ERR_clear_error(); - - memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - rc = SSL_write(conn->ssl[sockindex].handle, mem, memlen); - - if(rc < 0) { - err = SSL_get_error(conn->ssl[sockindex].handle, rc); - - switch(err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* The operation did not complete; the same TLS/SSL I/O function - should be called again later. This is basically an EWOULDBLOCK - equivalent. */ - *curlcode = CURLE_AGAIN; - return -1; - case SSL_ERROR_SYSCALL: - failf(conn->data, "SSL_write() returned SYSCALL, errno = %d", - SOCKERRNO); - *curlcode = CURLE_SEND_ERROR; - return -1; - case SSL_ERROR_SSL: - /* A failure in the SSL library occurred, usually a protocol error. - The OpenSSL error queue contains more information on the error. */ - sslerror = ERR_get_error(); - failf(conn->data, "SSL_write() error: %s", - ERR_error_string(sslerror, error_buffer)); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - /* a true error */ - failf(conn->data, "SSL_write() return error %d", err); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - return (ssize_t)rc; /* number of bytes */ -} - -static ssize_t ossl_recv(struct connectdata *conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *curlcode) -{ - char error_buffer[120]; /* OpenSSL documents that this must be at - least 120 bytes long. */ - unsigned long sslerror; - ssize_t nread; - int buffsize; - - ERR_clear_error(); - - buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - nread = (ssize_t)SSL_read(conn->ssl[num].handle, buf, buffsize); - if(nread < 0) { - /* failed SSL_read */ - int err = SSL_get_error(conn->ssl[num].handle, (int)nread); - - switch(err) { - case SSL_ERROR_NONE: /* this is not an error */ - case SSL_ERROR_ZERO_RETURN: /* no more data */ - break; - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ - *curlcode = CURLE_AGAIN; - return -1; - default: - /* openssl/ssl.h says "look at error stack/return value/errno" */ - sslerror = ERR_get_error(); - failf(conn->data, "SSL read: %s, errno %d", - ERR_error_string(sslerror, error_buffer), - SOCKERRNO); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - } - return nread; -} - -size_t Curl_ossl_version(char *buffer, size_t size) -{ -#ifdef YASSL_VERSION - /* yassl provides an OpenSSL API compatibility layer so it looks identical - to OpenSSL in all other aspects */ - return snprintf(buffer, size, "yassl/%s", YASSL_VERSION); -#else /* YASSL_VERSION */ - -#if(SSLEAY_VERSION_NUMBER >= 0x905000) - { - char sub[2]; - unsigned long ssleay_value; - sub[1]='\0'; - ssleay_value=SSLeay(); - if(ssleay_value < 0x906000) { - ssleay_value=SSLEAY_VERSION_NUMBER; - sub[0]='\0'; - } - else { - if(ssleay_value&0xff0) { - sub[0]=(char)(((ssleay_value>>4)&0xff) + 'a' -1); - } - else - sub[0]='\0'; - } - - return snprintf(buffer, size, "OpenSSL/%lx.%lx.%lx%s", - (ssleay_value>>28)&0xf, - (ssleay_value>>20)&0xff, - (ssleay_value>>12)&0xff, - sub); - } - -#else /* SSLEAY_VERSION_NUMBER is less than 0.9.5 */ - -#if(SSLEAY_VERSION_NUMBER >= 0x900000) - return snprintf(buffer, size, "OpenSSL/%lx.%lx.%lx", - (SSLEAY_VERSION_NUMBER>>28)&0xff, - (SSLEAY_VERSION_NUMBER>>20)&0xff, - (SSLEAY_VERSION_NUMBER>>12)&0xf); - -#else /* (SSLEAY_VERSION_NUMBER >= 0x900000) */ - { - char sub[2]; - sub[1]='\0'; - if(SSLEAY_VERSION_NUMBER&0x0f) { - sub[0]=(SSLEAY_VERSION_NUMBER&0x0f) + 'a' -1; - } - else - sub[0]='\0'; - - return snprintf(buffer, size, "SSL/%x.%x.%x%s", - (SSLEAY_VERSION_NUMBER>>12)&0xff, - (SSLEAY_VERSION_NUMBER>>8)&0xf, - (SSLEAY_VERSION_NUMBER>>4)&0xf, sub); - } -#endif /* (SSLEAY_VERSION_NUMBER >= 0x900000) */ -#endif /* SSLEAY_VERSION_NUMBER is less than 0.9.5 */ - -#endif /* YASSL_VERSION */ -} - -void Curl_ossl_random(struct SessionHandle *data, unsigned char *entropy, - size_t length) -{ - Curl_ossl_seed(data); /* Initiate the seed if not already done */ - RAND_bytes(entropy, curlx_uztosi(length)); -} - -void Curl_ossl_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum /* output */, - size_t unused) -{ - MD5_CTX MD5pw; - (void)unused; - MD5_Init(&MD5pw); - MD5_Update(&MD5pw, tmp, tmplen); - MD5_Final(md5sum, &MD5pw); -} -#endif /* USE_SSLEAY */ diff --git a/lib/strdup.c b/lib/strdup.c deleted file mode 100644 index 8dcaa67f0..000000000 --- a/lib/strdup.c +++ /dev/null @@ -1,52 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ -/* - * This file is 'mem-include-scan' clean. See test 1132. - */ -#include "curl_setup.h" - -#include "curl_strdup.h" - -#ifndef HAVE_STRDUP -char *curlx_strdup(const char *str) -{ - size_t len; - char *newstr; - - if(!str) - return (char *)NULL; - - len = strlen(str); - - if(len >= ((size_t)-1) / sizeof(char)) - return (char *)NULL; - - newstr = malloc((len+1)*sizeof(char)); - if(!newstr) - return (char *)NULL; - - memcpy(newstr,str,(len+1)*sizeof(char)); - - return newstr; - -} -#endif diff --git a/lib/strequal.c b/lib/strequal.c deleted file mode 100644 index 5d370c854..000000000 --- a/lib/strequal.c +++ /dev/null @@ -1,124 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_STRINGS_H -#include -#endif - -#include "curl_strequal.h" - -/* - * @unittest: 1301 - */ -int curl_strequal(const char *first, const char *second) -{ -#if defined(HAVE_STRCASECMP) - return !(strcasecmp)(first, second); -#elif defined(HAVE_STRCMPI) - return !(strcmpi)(first, second); -#elif defined(HAVE_STRICMP) - return !(stricmp)(first, second); -#else - while(*first && *second) { - if(toupper(*first) != toupper(*second)) { - break; - } - first++; - second++; - } - return toupper(*first) == toupper(*second); -#endif -} - -/* - * @unittest: 1301 - */ -int curl_strnequal(const char *first, const char *second, size_t max) -{ -#if defined(HAVE_STRNCASECMP) - return !strncasecmp(first, second, max); -#elif defined(HAVE_STRNCMPI) - return !strncmpi(first, second, max); -#elif defined(HAVE_STRNICMP) - return !strnicmp(first, second, max); -#else - while(*first && *second && max) { - if(toupper(*first) != toupper(*second)) { - break; - } - max--; - first++; - second++; - } - if(0 == max) - return 1; /* they are equal this far */ - - return toupper(*first) == toupper(*second); -#endif -} - -#ifndef HAVE_STRLCAT -/* - * The strlcat() function appends the NUL-terminated string src to the end - * of dst. It will append at most size - strlen(dst) - 1 bytes, NUL-termi- - * nating the result. - * - * The strlcpy() and strlcat() functions return the total length of the - * string they tried to create. For strlcpy() that means the length of src. - * For strlcat() that means the initial length of dst plus the length of - * src. While this may seem somewhat confusing it was done to make trunca- - * tion detection simple. - * - * - */ -size_t Curl_strlcat(char *dst, const char *src, size_t siz) -{ - char *d = dst; - const char *s = src; - size_t n = siz; - union { - ssize_t sig; - size_t uns; - } dlen; - - /* Find the end of dst and adjust bytes left but don't go past end */ - while(n-- != 0 && *d != '\0') - d++; - dlen.sig = d - dst; - n = siz - dlen.uns; - - if(n == 0) - return(dlen.uns + strlen(s)); - while(*s != '\0') { - if(n != 1) { - *d++ = *s; - n--; - } - s++; - } - *d = '\0'; - - return(dlen.uns + (s - src)); /* count does not include NUL */ -} -#endif diff --git a/lib/strerror.c b/lib/strerror.c deleted file mode 100644 index 27567a1ac..000000000 --- a/lib/strerror.c +++ /dev/null @@ -1,1119 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2004 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_STRERROR_R -# if (!defined(HAVE_POSIX_STRERROR_R) && \ - !defined(HAVE_GLIBC_STRERROR_R) && \ - !defined(HAVE_VXWORKS_STRERROR_R)) || \ - (defined(HAVE_POSIX_STRERROR_R) && defined(HAVE_VXWORKS_STRERROR_R)) || \ - (defined(HAVE_GLIBC_STRERROR_R) && defined(HAVE_VXWORKS_STRERROR_R)) || \ - (defined(HAVE_POSIX_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R)) -# error "strerror_r MUST be either POSIX, glibc or vxworks-style" -# endif -#endif - -#include - -#ifdef USE_LIBIDN -#include -#endif - -#include "curl_strerror.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -const char * -curl_easy_strerror(CURLcode error) -{ -#ifndef CURL_DISABLE_VERBOSE_STRINGS - switch (error) { - case CURLE_OK: - return "No error"; - - case CURLE_UNSUPPORTED_PROTOCOL: - return "Unsupported protocol"; - - case CURLE_FAILED_INIT: - return "Failed initialization"; - - case CURLE_URL_MALFORMAT: - return "URL using bad/illegal format or missing URL"; - - case CURLE_NOT_BUILT_IN: - return "A requested feature, protocol or option was not found built-in in" - " this libcurl due to a build-time decision."; - - case CURLE_COULDNT_RESOLVE_PROXY: - return "Couldn't resolve proxy name"; - - case CURLE_COULDNT_RESOLVE_HOST: - return "Couldn't resolve host name"; - - case CURLE_COULDNT_CONNECT: - return "Couldn't connect to server"; - - case CURLE_FTP_WEIRD_SERVER_REPLY: - return "FTP: weird server reply"; - - case CURLE_REMOTE_ACCESS_DENIED: - return "Access denied to remote resource"; - - case CURLE_FTP_ACCEPT_FAILED: - return "FTP: The server failed to connect to data port"; - - case CURLE_FTP_ACCEPT_TIMEOUT: - return "FTP: Accepting server connect has timed out"; - - case CURLE_FTP_PRET_FAILED: - return "FTP: The server did not accept the PRET command."; - - case CURLE_FTP_WEIRD_PASS_REPLY: - return "FTP: unknown PASS reply"; - - case CURLE_FTP_WEIRD_PASV_REPLY: - return "FTP: unknown PASV reply"; - - case CURLE_FTP_WEIRD_227_FORMAT: - return "FTP: unknown 227 response format"; - - case CURLE_FTP_CANT_GET_HOST: - return "FTP: can't figure out the host in the PASV response"; - - case CURLE_FTP_COULDNT_SET_TYPE: - return "FTP: couldn't set file type"; - - case CURLE_PARTIAL_FILE: - return "Transferred a partial file"; - - case CURLE_FTP_COULDNT_RETR_FILE: - return "FTP: couldn't retrieve (RETR failed) the specified file"; - - case CURLE_QUOTE_ERROR: - return "Quote command returned error"; - - case CURLE_HTTP_RETURNED_ERROR: - return "HTTP response code said error"; - - case CURLE_WRITE_ERROR: - return "Failed writing received data to disk/application"; - - case CURLE_UPLOAD_FAILED: - return "Upload failed (at start/before it took off)"; - - case CURLE_READ_ERROR: - return "Failed to open/read local data from file/application"; - - case CURLE_OUT_OF_MEMORY: - return "Out of memory"; - - case CURLE_OPERATION_TIMEDOUT: - return "Timeout was reached"; - - case CURLE_FTP_PORT_FAILED: - return "FTP: command PORT failed"; - - case CURLE_FTP_COULDNT_USE_REST: - return "FTP: command REST failed"; - - case CURLE_RANGE_ERROR: - return "Requested range was not delivered by the server"; - - case CURLE_HTTP_POST_ERROR: - return "Internal problem setting up the POST"; - - case CURLE_SSL_CONNECT_ERROR: - return "SSL connect error"; - - case CURLE_BAD_DOWNLOAD_RESUME: - return "Couldn't resume download"; - - case CURLE_FILE_COULDNT_READ_FILE: - return "Couldn't read a file:// file"; - - case CURLE_LDAP_CANNOT_BIND: - return "LDAP: cannot bind"; - - case CURLE_LDAP_SEARCH_FAILED: - return "LDAP: search failed"; - - case CURLE_FUNCTION_NOT_FOUND: - return "A required function in the library was not found"; - - case CURLE_ABORTED_BY_CALLBACK: - return "Operation was aborted by an application callback"; - - case CURLE_BAD_FUNCTION_ARGUMENT: - return "A libcurl function was given a bad argument"; - - case CURLE_INTERFACE_FAILED: - return "Failed binding local connection end"; - - case CURLE_TOO_MANY_REDIRECTS : - return "Number of redirects hit maximum amount"; - - case CURLE_UNKNOWN_OPTION: - return "An unknown option was passed in to libcurl"; - - case CURLE_TELNET_OPTION_SYNTAX : - return "Malformed telnet option"; - - case CURLE_PEER_FAILED_VERIFICATION: - return "SSL peer certificate or SSH remote key was not OK"; - - case CURLE_GOT_NOTHING: - return "Server returned nothing (no headers, no data)"; - - case CURLE_SSL_ENGINE_NOTFOUND: - return "SSL crypto engine not found"; - - case CURLE_SSL_ENGINE_SETFAILED: - return "Can not set SSL crypto engine as default"; - - case CURLE_SSL_ENGINE_INITFAILED: - return "Failed to initialise SSL crypto engine"; - - case CURLE_SEND_ERROR: - return "Failed sending data to the peer"; - - case CURLE_RECV_ERROR: - return "Failure when receiving data from the peer"; - - case CURLE_SSL_CERTPROBLEM: - return "Problem with the local SSL certificate"; - - case CURLE_SSL_CIPHER: - return "Couldn't use specified SSL cipher"; - - case CURLE_SSL_CACERT: - return "Peer certificate cannot be authenticated with given CA " - "certificates"; - - case CURLE_SSL_CACERT_BADFILE: - return "Problem with the SSL CA cert (path? access rights?)"; - - case CURLE_BAD_CONTENT_ENCODING: - return "Unrecognized or bad HTTP Content or Transfer-Encoding"; - - case CURLE_LDAP_INVALID_URL: - return "Invalid LDAP URL"; - - case CURLE_FILESIZE_EXCEEDED: - return "Maximum file size exceeded"; - - case CURLE_USE_SSL_FAILED: - return "Requested SSL level failed"; - - case CURLE_SSL_SHUTDOWN_FAILED: - return "Failed to shut down the SSL connection"; - - case CURLE_SSL_CRL_BADFILE: - return "Failed to load CRL file (path? access rights?, format?)"; - - case CURLE_SSL_ISSUER_ERROR: - return "Issuer check against peer certificate failed"; - - case CURLE_SEND_FAIL_REWIND: - return "Send failed since rewinding of the data stream failed"; - - case CURLE_LOGIN_DENIED: - return "Login denied"; - - case CURLE_TFTP_NOTFOUND: - return "TFTP: File Not Found"; - - case CURLE_TFTP_PERM: - return "TFTP: Access Violation"; - - case CURLE_REMOTE_DISK_FULL: - return "Disk full or allocation exceeded"; - - case CURLE_TFTP_ILLEGAL: - return "TFTP: Illegal operation"; - - case CURLE_TFTP_UNKNOWNID: - return "TFTP: Unknown transfer ID"; - - case CURLE_REMOTE_FILE_EXISTS: - return "Remote file already exists"; - - case CURLE_TFTP_NOSUCHUSER: - return "TFTP: No such user"; - - case CURLE_CONV_FAILED: - return "Conversion failed"; - - case CURLE_CONV_REQD: - return "Caller must register CURLOPT_CONV_ callback options"; - - case CURLE_REMOTE_FILE_NOT_FOUND: - return "Remote file not found"; - - case CURLE_SSH: - return "Error in the SSH layer"; - - case CURLE_AGAIN: - return "Socket not ready for send/recv"; - - case CURLE_RTSP_CSEQ_ERROR: - return "RTSP CSeq mismatch or invalid CSeq"; - - case CURLE_RTSP_SESSION_ERROR: - return "RTSP session error"; - - case CURLE_FTP_BAD_FILE_LIST: - return "Unable to parse FTP file list"; - - case CURLE_CHUNK_FAILED: - return "Chunk callback failed"; - - /* error codes not used by current libcurl */ - case CURLE_OBSOLETE16: - case CURLE_OBSOLETE20: - case CURLE_OBSOLETE24: - case CURLE_OBSOLETE29: - case CURLE_OBSOLETE32: - case CURLE_OBSOLETE40: - case CURLE_OBSOLETE44: - case CURLE_OBSOLETE46: - case CURLE_OBSOLETE50: - case CURLE_OBSOLETE57: - case CURL_LAST: - break; - } - /* - * By using a switch, gcc -Wall will complain about enum values - * which do not appear, helping keep this function up-to-date. - * By using gcc -Wall -Werror, you can't forget. - * - * A table would not have the same benefit. Most compilers will - * generate code very similar to a table in any case, so there - * is little performance gain from a table. And something is broken - * for the user's application, anyways, so does it matter how fast - * it _doesn't_ work? - * - * The line number for the error will be near this comment, which - * is why it is here, and not at the start of the switch. - */ - return "Unknown error"; -#else - if(error == CURLE_OK) - return "No error"; - else - return "Error"; -#endif -} - -const char * -curl_multi_strerror(CURLMcode error) -{ -#ifndef CURL_DISABLE_VERBOSE_STRINGS - switch (error) { - case CURLM_CALL_MULTI_PERFORM: - return "Please call curl_multi_perform() soon"; - - case CURLM_OK: - return "No error"; - - case CURLM_BAD_HANDLE: - return "Invalid multi handle"; - - case CURLM_BAD_EASY_HANDLE: - return "Invalid easy handle"; - - case CURLM_OUT_OF_MEMORY: - return "Out of memory"; - - case CURLM_INTERNAL_ERROR: - return "Internal error"; - - case CURLM_BAD_SOCKET: - return "Invalid socket argument"; - - case CURLM_UNKNOWN_OPTION: - return "Unknown option"; - - case CURLM_LAST: - break; - } - - return "Unknown error"; -#else - if(error == CURLM_OK) - return "No error"; - else - return "Error"; -#endif -} - -const char * -curl_share_strerror(CURLSHcode error) -{ -#ifndef CURL_DISABLE_VERBOSE_STRINGS - switch (error) { - case CURLSHE_OK: - return "No error"; - - case CURLSHE_BAD_OPTION: - return "Unknown share option"; - - case CURLSHE_IN_USE: - return "Share currently in use"; - - case CURLSHE_INVALID: - return "Invalid share handle"; - - case CURLSHE_NOMEM: - return "Out of memory"; - - case CURLSHE_NOT_BUILT_IN: - return "Feature not enabled in this library"; - - case CURLSHE_LAST: - break; - } - - return "CURLSHcode unknown"; -#else - if(error == CURLSHE_OK) - return "No error"; - else - return "Error"; -#endif -} - -#ifdef USE_WINSOCK - -/* This function handles most / all (?) Winsock errors cURL is able to produce. - */ -static const char * -get_winsock_error (int err, char *buf, size_t len) -{ - const char *p; - -#ifndef CURL_DISABLE_VERBOSE_STRINGS - switch (err) { - case WSAEINTR: - p = "Call interrupted"; - break; - case WSAEBADF: - p = "Bad file"; - break; - case WSAEACCES: - p = "Bad access"; - break; - case WSAEFAULT: - p = "Bad argument"; - break; - case WSAEINVAL: - p = "Invalid arguments"; - break; - case WSAEMFILE: - p = "Out of file descriptors"; - break; - case WSAEWOULDBLOCK: - p = "Call would block"; - break; - case WSAEINPROGRESS: - case WSAEALREADY: - p = "Blocking call in progress"; - break; - case WSAENOTSOCK: - p = "Descriptor is not a socket"; - break; - case WSAEDESTADDRREQ: - p = "Need destination address"; - break; - case WSAEMSGSIZE: - p = "Bad message size"; - break; - case WSAEPROTOTYPE: - p = "Bad protocol"; - break; - case WSAENOPROTOOPT: - p = "Protocol option is unsupported"; - break; - case WSAEPROTONOSUPPORT: - p = "Protocol is unsupported"; - break; - case WSAESOCKTNOSUPPORT: - p = "Socket is unsupported"; - break; - case WSAEOPNOTSUPP: - p = "Operation not supported"; - break; - case WSAEAFNOSUPPORT: - p = "Address family not supported"; - break; - case WSAEPFNOSUPPORT: - p = "Protocol family not supported"; - break; - case WSAEADDRINUSE: - p = "Address already in use"; - break; - case WSAEADDRNOTAVAIL: - p = "Address not available"; - break; - case WSAENETDOWN: - p = "Network down"; - break; - case WSAENETUNREACH: - p = "Network unreachable"; - break; - case WSAENETRESET: - p = "Network has been reset"; - break; - case WSAECONNABORTED: - p = "Connection was aborted"; - break; - case WSAECONNRESET: - p = "Connection was reset"; - break; - case WSAENOBUFS: - p = "No buffer space"; - break; - case WSAEISCONN: - p = "Socket is already connected"; - break; - case WSAENOTCONN: - p = "Socket is not connected"; - break; - case WSAESHUTDOWN: - p = "Socket has been shut down"; - break; - case WSAETOOMANYREFS: - p = "Too many references"; - break; - case WSAETIMEDOUT: - p = "Timed out"; - break; - case WSAECONNREFUSED: - p = "Connection refused"; - break; - case WSAELOOP: - p = "Loop??"; - break; - case WSAENAMETOOLONG: - p = "Name too long"; - break; - case WSAEHOSTDOWN: - p = "Host down"; - break; - case WSAEHOSTUNREACH: - p = "Host unreachable"; - break; - case WSAENOTEMPTY: - p = "Not empty"; - break; - case WSAEPROCLIM: - p = "Process limit reached"; - break; - case WSAEUSERS: - p = "Too many users"; - break; - case WSAEDQUOT: - p = "Bad quota"; - break; - case WSAESTALE: - p = "Something is stale"; - break; - case WSAEREMOTE: - p = "Remote error"; - break; -#ifdef WSAEDISCON /* missing in SalfordC! */ - case WSAEDISCON: - p = "Disconnected"; - break; -#endif - /* Extended Winsock errors */ - case WSASYSNOTREADY: - p = "Winsock library is not ready"; - break; - case WSANOTINITIALISED: - p = "Winsock library not initialised"; - break; - case WSAVERNOTSUPPORTED: - p = "Winsock version not supported"; - break; - - /* getXbyY() errors (already handled in herrmsg): - * Authoritative Answer: Host not found */ - case WSAHOST_NOT_FOUND: - p = "Host not found"; - break; - - /* Non-Authoritative: Host not found, or SERVERFAIL */ - case WSATRY_AGAIN: - p = "Host not found, try again"; - break; - - /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */ - case WSANO_RECOVERY: - p = "Unrecoverable error in call to nameserver"; - break; - - /* Valid name, no data record of requested type */ - case WSANO_DATA: - p = "No data record of requested type"; - break; - - default: - return NULL; - } -#else - if(err == CURLE_OK) - return NULL; - else - p = "error"; -#endif - strncpy (buf, p, len); - buf [len-1] = '\0'; - return buf; -} -#endif /* USE_WINSOCK */ - -/* - * Our thread-safe and smart strerror() replacement. - * - * The 'err' argument passed in to this function MUST be a true errno number - * as reported on this system. We do no range checking on the number before - * we pass it to the "number-to-message" conversion function and there might - * be systems that don't do proper range checking in there themselves. - * - * We don't do range checking (on systems other than Windows) since there is - * no good reliable and portable way to do it. - */ -const char *Curl_strerror(struct connectdata *conn, int err) -{ - char *buf, *p; - size_t max; - int old_errno = ERRNO; - - DEBUGASSERT(conn); - DEBUGASSERT(err >= 0); - - buf = conn->syserr_buf; - max = sizeof(conn->syserr_buf)-1; - *buf = '\0'; - -#ifdef USE_WINSOCK - -#ifdef _WIN32_WCE - { - wchar_t wbuf[256]; - wbuf[0] = L'\0'; - - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, - LANG_NEUTRAL, wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL); - wcstombs(buf,wbuf,max); - } -#else - /* 'sys_nerr' is the maximum errno number, it is not widely portable */ - if(err >= 0 && err < sys_nerr) - strncpy(buf, strerror(err), max); - else { - if(!get_winsock_error(err, buf, max) && - !FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, - LANG_NEUTRAL, buf, (DWORD)max, NULL)) - snprintf(buf, max, "Unknown error %d (%#x)", err, err); - } -#endif - -#else /* not USE_WINSOCK coming up */ - -#if defined(HAVE_STRERROR_R) && defined(HAVE_POSIX_STRERROR_R) - /* - * The POSIX-style strerror_r() may set errno to ERANGE if insufficient - * storage is supplied via 'strerrbuf' and 'buflen' to hold the generated - * message string, or EINVAL if 'errnum' is not a valid error number. - */ - if(0 != strerror_r(err, buf, max)) { - if('\0' == buf[0]) - snprintf(buf, max, "Unknown error %d", err); - } -#elif defined(HAVE_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R) - /* - * The glibc-style strerror_r() only *might* use the buffer we pass to - * the function, but it always returns the error message as a pointer, - * so we must copy that string unconditionally (if non-NULL). - */ - { - char buffer[256]; - char *msg = strerror_r(err, buffer, sizeof(buffer)); - if(msg) - strncpy(buf, msg, max); - else - snprintf(buf, max, "Unknown error %d", err); - } -#elif defined(HAVE_STRERROR_R) && defined(HAVE_VXWORKS_STRERROR_R) - /* - * The vxworks-style strerror_r() does use the buffer we pass to the function. - * The buffer size should be at least MAXERRSTR_SIZE (150) defined in rtsold.h - */ - { - char buffer[256]; - if(OK == strerror_r(err, buffer)) - strncpy(buf, buffer, max); - else - snprintf(buf, max, "Unknown error %d", err); - } -#else - { - char *msg = strerror(err); - if(msg) - strncpy(buf, msg, max); - else - snprintf(buf, max, "Unknown error %d", err); - } -#endif - -#endif /* end of ! USE_WINSOCK */ - - buf[max] = '\0'; /* make sure the string is zero terminated */ - - /* strip trailing '\r\n' or '\n'. */ - if((p = strrchr(buf,'\n')) != NULL && (p - buf) >= 2) - *p = '\0'; - if((p = strrchr(buf,'\r')) != NULL && (p - buf) >= 1) - *p = '\0'; - - if(old_errno != ERRNO) - SET_ERRNO(old_errno); - - return buf; -} - -#ifdef USE_LIBIDN -/* - * Return error-string for libidn status as returned from idna_to_ascii_lz(). - */ -const char *Curl_idn_strerror (struct connectdata *conn, int err) -{ -#ifdef HAVE_IDNA_STRERROR - (void)conn; - return idna_strerror((Idna_rc) err); -#else - const char *str; - char *buf; - size_t max; - - DEBUGASSERT(conn); - - buf = conn->syserr_buf; - max = sizeof(conn->syserr_buf)-1; - *buf = '\0'; - -#ifndef CURL_DISABLE_VERBOSE_STRINGS - switch ((Idna_rc)err) { - case IDNA_SUCCESS: - str = "No error"; - break; - case IDNA_STRINGPREP_ERROR: - str = "Error in string preparation"; - break; - case IDNA_PUNYCODE_ERROR: - str = "Error in Punycode operation"; - break; - case IDNA_CONTAINS_NON_LDH: - str = "Illegal ASCII characters"; - break; - case IDNA_CONTAINS_MINUS: - str = "Contains minus"; - break; - case IDNA_INVALID_LENGTH: - str = "Invalid output length"; - break; - case IDNA_NO_ACE_PREFIX: - str = "No ACE prefix (\"xn--\")"; - break; - case IDNA_ROUNDTRIP_VERIFY_ERROR: - str = "Round trip verify error"; - break; - case IDNA_CONTAINS_ACE_PREFIX: - str = "Already have ACE prefix (\"xn--\")"; - break; - case IDNA_ICONV_ERROR: - str = "Locale conversion failed"; - break; - case IDNA_MALLOC_ERROR: - str = "Allocation failed"; - break; - case IDNA_DLOPEN_ERROR: - str = "dlopen() error"; - break; - default: - snprintf(buf, max, "error %d", err); - str = NULL; - break; - } -#else - if((Idna_rc)err == IDNA_SUCCESS) - str = "No error"; - else - str = "Error"; -#endif - if(str) - strncpy(buf, str, max); - buf[max] = '\0'; - return (buf); -#endif -} -#endif /* USE_LIBIDN */ - -#ifdef USE_WINDOWS_SSPI -const char *Curl_sspi_strerror (struct connectdata *conn, int err) -{ -#ifndef CURL_DISABLE_VERBOSE_STRINGS - char txtbuf[80]; - char msgbuf[sizeof(conn->syserr_buf)]; - char *p, *str, *msg = NULL; - bool msg_formatted = FALSE; - int old_errno; -#endif - const char *txt; - char *outbuf; - size_t outmax; - - DEBUGASSERT(conn); - - outbuf = conn->syserr_buf; - outmax = sizeof(conn->syserr_buf)-1; - *outbuf = '\0'; - -#ifndef CURL_DISABLE_VERBOSE_STRINGS - - old_errno = ERRNO; - - switch (err) { - case SEC_E_OK: - txt = "No error"; - break; - case SEC_E_ALGORITHM_MISMATCH: - txt = "SEC_E_ALGORITHM_MISMATCH"; - break; - case SEC_E_BAD_BINDINGS: - txt = "SEC_E_BAD_BINDINGS"; - break; - case SEC_E_BAD_PKGID: - txt = "SEC_E_BAD_PKGID"; - break; - case SEC_E_BUFFER_TOO_SMALL: - txt = "SEC_E_BUFFER_TOO_SMALL"; - break; - case SEC_E_CANNOT_INSTALL: - txt = "SEC_E_CANNOT_INSTALL"; - break; - case SEC_E_CANNOT_PACK: - txt = "SEC_E_CANNOT_PACK"; - break; - case SEC_E_CERT_EXPIRED: - txt = "SEC_E_CERT_EXPIRED"; - break; - case SEC_E_CERT_UNKNOWN: - txt = "SEC_E_CERT_UNKNOWN"; - break; - case SEC_E_CERT_WRONG_USAGE: - txt = "SEC_E_CERT_WRONG_USAGE"; - break; - case SEC_E_CONTEXT_EXPIRED: - txt = "SEC_E_CONTEXT_EXPIRED"; - break; - case SEC_E_CROSSREALM_DELEGATION_FAILURE: - txt = "SEC_E_CROSSREALM_DELEGATION_FAILURE"; - break; - case SEC_E_CRYPTO_SYSTEM_INVALID: - txt = "SEC_E_CRYPTO_SYSTEM_INVALID"; - break; - case SEC_E_DECRYPT_FAILURE: - txt = "SEC_E_DECRYPT_FAILURE"; - break; - case SEC_E_DELEGATION_POLICY: - txt = "SEC_E_DELEGATION_POLICY"; - break; - case SEC_E_DELEGATION_REQUIRED: - txt = "SEC_E_DELEGATION_REQUIRED"; - break; - case SEC_E_DOWNGRADE_DETECTED: - txt = "SEC_E_DOWNGRADE_DETECTED"; - break; - case SEC_E_ENCRYPT_FAILURE: - txt = "SEC_E_ENCRYPT_FAILURE"; - break; - case SEC_E_ILLEGAL_MESSAGE: - txt = "SEC_E_ILLEGAL_MESSAGE"; - break; - case SEC_E_INCOMPLETE_CREDENTIALS: - txt = "SEC_E_INCOMPLETE_CREDENTIALS"; - break; - case SEC_E_INCOMPLETE_MESSAGE: - txt = "SEC_E_INCOMPLETE_MESSAGE"; - 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_PARAMETER: - txt = "SEC_E_INVALID_PARAMETER"; - break; - case SEC_E_INVALID_TOKEN: - txt = "SEC_E_INVALID_TOKEN"; - break; - case SEC_E_ISSUING_CA_UNTRUSTED: - txt = "SEC_E_ISSUING_CA_UNTRUSTED"; - break; - case SEC_E_ISSUING_CA_UNTRUSTED_KDC: - txt = "SEC_E_ISSUING_CA_UNTRUSTED_KDC"; - break; - case SEC_E_KDC_CERT_EXPIRED: - txt = "SEC_E_KDC_CERT_EXPIRED"; - break; - case SEC_E_KDC_CERT_REVOKED: - txt = "SEC_E_KDC_CERT_REVOKED"; - break; - case SEC_E_KDC_INVALID_REQUEST: - txt = "SEC_E_KDC_INVALID_REQUEST"; - break; - case SEC_E_KDC_UNABLE_TO_REFER: - txt = "SEC_E_KDC_UNABLE_TO_REFER"; - break; - case SEC_E_KDC_UNKNOWN_ETYPE: - txt = "SEC_E_KDC_UNKNOWN_ETYPE"; - break; - case SEC_E_LOGON_DENIED: - txt = "SEC_E_LOGON_DENIED"; - break; - case SEC_E_MAX_REFERRALS_EXCEEDED: - txt = "SEC_E_MAX_REFERRALS_EXCEEDED"; - break; - case SEC_E_MESSAGE_ALTERED: - txt = "SEC_E_MESSAGE_ALTERED"; - break; - case SEC_E_MULTIPLE_ACCOUNTS: - txt = "SEC_E_MULTIPLE_ACCOUNTS"; - break; - case SEC_E_MUST_BE_KDC: - txt = "SEC_E_MUST_BE_KDC"; - break; - case SEC_E_NOT_OWNER: - txt = "SEC_E_NOT_OWNER"; - 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_NO_IMPERSONATION: - txt = "SEC_E_NO_IMPERSONATION"; - break; - case SEC_E_NO_IP_ADDRESSES: - txt = "SEC_E_NO_IP_ADDRESSES"; - break; - case SEC_E_NO_KERB_KEY: - txt = "SEC_E_NO_KERB_KEY"; - break; - case SEC_E_NO_PA_DATA: - txt = "SEC_E_NO_PA_DATA"; - break; - case SEC_E_NO_S4U_PROT_SUPPORT: - txt = "SEC_E_NO_S4U_PROT_SUPPORT"; - break; - case SEC_E_NO_TGT_REPLY: - txt = "SEC_E_NO_TGT_REPLY"; - break; - case SEC_E_OUT_OF_SEQUENCE: - txt = "SEC_E_OUT_OF_SEQUENCE"; - break; - case SEC_E_PKINIT_CLIENT_FAILURE: - txt = "SEC_E_PKINIT_CLIENT_FAILURE"; - break; - case SEC_E_PKINIT_NAME_MISMATCH: - txt = "SEC_E_PKINIT_NAME_MISMATCH"; - break; - case SEC_E_POLICY_NLTM_ONLY: - txt = "SEC_E_POLICY_NLTM_ONLY"; - break; - case SEC_E_QOP_NOT_SUPPORTED: - txt = "SEC_E_QOP_NOT_SUPPORTED"; - break; - case SEC_E_REVOCATION_OFFLINE_C: - txt = "SEC_E_REVOCATION_OFFLINE_C"; - break; - case SEC_E_REVOCATION_OFFLINE_KDC: - txt = "SEC_E_REVOCATION_OFFLINE_KDC"; - break; - case SEC_E_SECPKG_NOT_FOUND: - txt = "SEC_E_SECPKG_NOT_FOUND"; - break; - case SEC_E_SECURITY_QOS_FAILED: - txt = "SEC_E_SECURITY_QOS_FAILED"; - break; - case SEC_E_SHUTDOWN_IN_PROGRESS: - txt = "SEC_E_SHUTDOWN_IN_PROGRESS"; - break; - case SEC_E_SMARTCARD_CERT_EXPIRED: - txt = "SEC_E_SMARTCARD_CERT_EXPIRED"; - break; - case SEC_E_SMARTCARD_CERT_REVOKED: - txt = "SEC_E_SMARTCARD_CERT_REVOKED"; - break; - case SEC_E_SMARTCARD_LOGON_REQUIRED: - txt = "SEC_E_SMARTCARD_LOGON_REQUIRED"; - break; - case SEC_E_STRONG_CRYPTO_NOT_SUPPORTED: - txt = "SEC_E_STRONG_CRYPTO_NOT_SUPPORTED"; - break; - case SEC_E_TARGET_UNKNOWN: - txt = "SEC_E_TARGET_UNKNOWN"; - break; - case SEC_E_TIME_SKEW: - txt = "SEC_E_TIME_SKEW"; - break; - case SEC_E_TOO_MANY_PRINCIPALS: - txt = "SEC_E_TOO_MANY_PRINCIPALS"; - break; - case SEC_E_UNFINISHED_CONTEXT_DELETED: - txt = "SEC_E_UNFINISHED_CONTEXT_DELETED"; - break; - case SEC_E_UNKNOWN_CREDENTIALS: - txt = "SEC_E_UNKNOWN_CREDENTIALS"; - break; - case SEC_E_UNSUPPORTED_FUNCTION: - txt = "SEC_E_UNSUPPORTED_FUNCTION"; - break; - case SEC_E_UNSUPPORTED_PREAUTH: - txt = "SEC_E_UNSUPPORTED_PREAUTH"; - break; - case SEC_E_UNTRUSTED_ROOT: - txt = "SEC_E_UNTRUSTED_ROOT"; - break; - case SEC_E_WRONG_CREDENTIAL_HANDLE: - txt = "SEC_E_WRONG_CREDENTIAL_HANDLE"; - break; - case SEC_E_WRONG_PRINCIPAL: - txt = "SEC_E_WRONG_PRINCIPAL"; - break; - 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_CONTEXT_EXPIRED: - txt = "SEC_I_CONTEXT_EXPIRED"; - 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_I_LOCAL_LOGON: - txt = "SEC_I_LOCAL_LOGON"; - break; - case SEC_I_NO_LSA_CONTEXT: - txt = "SEC_I_NO_LSA_CONTEXT"; - break; - case SEC_I_RENEGOTIATE: - txt = "SEC_I_RENEGOTIATE"; - break; - case SEC_I_SIGNATURE_NEEDED: - txt = "SEC_I_SIGNATURE_NEEDED"; - break; - default: - txt = "Unknown error"; - } - - if(err == SEC_E_OK) - strncpy(outbuf, txt, outmax); - else { - str = txtbuf; - snprintf(txtbuf, sizeof(txtbuf), "%s (0x%04X%04X)", - txt, (err >> 16) & 0xffff, err & 0xffff); - txtbuf[sizeof(txtbuf)-1] = '\0'; - -#ifdef _WIN32_WCE - { - wchar_t wbuf[256]; - wbuf[0] = L'\0'; - - if(FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, LANG_NEUTRAL, - wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL)) { - wcstombs(msgbuf,wbuf,sizeof(msgbuf)-1); - msg_formatted = TRUE; - } - } -#else - if(FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, LANG_NEUTRAL, - msgbuf, sizeof(msgbuf)-1, NULL)) { - msg_formatted = TRUE; - } -#endif - if(msg_formatted) { - msgbuf[sizeof(msgbuf)-1] = '\0'; - /* strip trailing '\r\n' or '\n' */ - if((p = strrchr(msgbuf,'\n')) != NULL && (p - msgbuf) >= 2) - *p = '\0'; - if((p = strrchr(msgbuf,'\r')) != NULL && (p - msgbuf) >= 1) - *p = '\0'; - msg = msgbuf; - } - if(msg) - snprintf(outbuf, outmax, "%s - %s", str, msg); - else - strncpy(outbuf, str, outmax); - } - - if(old_errno != ERRNO) - SET_ERRNO(old_errno); - -#else - - if(err == SEC_E_OK) - txt = "No error"; - else - txt = "Error"; - - strncpy(outbuf, txt, outmax); - -#endif - - outbuf[outmax] = '\0'; - - return outbuf; -} -#endif /* USE_WINDOWS_SSPI */ diff --git a/lib/strtok.c b/lib/strtok.c deleted file mode 100644 index 33bdd96af..000000000 --- a/lib/strtok.c +++ /dev/null @@ -1,66 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2007, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef HAVE_STRTOK_R -#include - -#include "curl_strtok.h" - -char * -Curl_strtok_r(char *ptr, const char *sep, char **end) -{ - if(!ptr) - /* we got NULL input so then we get our last position instead */ - ptr = *end; - - /* pass all letters that are including in the separator string */ - while(*ptr && strchr(sep, *ptr)) - ++ptr; - - if(*ptr) { - /* so this is where the next piece of string starts */ - char *start = ptr; - - /* set the end pointer to the first byte after the start */ - *end = start + 1; - - /* scan through the string to find where it ends, it ends on a - null byte or a character that exists in the separator string */ - while(**end && !strchr(sep, **end)) - ++*end; - - if(**end) { - /* the end is not a null byte */ - **end = '\0'; /* zero terminate it! */ - ++*end; /* advance the last pointer to beyond the null byte */ - } - - return start; /* return the position where the string starts */ - } - - /* we ended up on a null byte, there are no more strings to find! */ - return NULL; -} - -#endif /* this was only compiled if strtok_r wasn't present */ diff --git a/lib/strtoofft.c b/lib/strtoofft.c deleted file mode 100644 index d203d9cc7..000000000 --- a/lib/strtoofft.c +++ /dev/null @@ -1,188 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_strtoofft.h" - -/* - * NOTE: - * - * In the ISO C standard (IEEE Std 1003.1), there is a strtoimax() function we - * could use in case strtoll() doesn't exist... See - * http://www.opengroup.org/onlinepubs/009695399/functions/strtoimax.html - */ - -#ifdef NEED_CURL_STRTOLL - -/* Range tests can be used for alphanum decoding if characters are consecutive, - like in ASCII. Else an array is scanned. Determine this condition now. */ - -#if('9' - '0') != 9 || ('Z' - 'A') != 25 || ('z' - 'a') != 25 - -#define NO_RANGE_TEST - -static const char valchars[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; -#endif - -static int get_char(char c, int base); - -/** - * Emulated version of the strtoll function. This extracts a long long - * value from the given input string and returns it. - */ -curl_off_t -curlx_strtoll(const char *nptr, char **endptr, int base) -{ - char *end; - int is_negative = 0; - int overflow; - int i; - curl_off_t value = 0; - curl_off_t newval; - - /* Skip leading whitespace. */ - end = (char *)nptr; - while(ISSPACE(end[0])) { - end++; - } - - /* Handle the sign, if any. */ - if(end[0] == '-') { - is_negative = 1; - end++; - } - else if(end[0] == '+') { - end++; - } - else if(end[0] == '\0') { - /* We had nothing but perhaps some whitespace -- there was no number. */ - if(endptr) { - *endptr = end; - } - return 0; - } - - /* Handle special beginnings, if present and allowed. */ - if(end[0] == '0' && end[1] == 'x') { - if(base == 16 || base == 0) { - end += 2; - base = 16; - } - } - else if(end[0] == '0') { - if(base == 8 || base == 0) { - end++; - base = 8; - } - } - - /* Matching strtol, if the base is 0 and it doesn't look like - * the number is octal or hex, we assume it's base 10. - */ - if(base == 0) { - base = 10; - } - - /* Loop handling digits. */ - value = 0; - overflow = 0; - for(i = get_char(end[0], base); - i != -1; - end++, i = get_char(end[0], base)) { - newval = base * value + i; - if(newval < value) { - /* We've overflowed. */ - overflow = 1; - break; - } - else - value = newval; - } - - if(!overflow) { - if(is_negative) { - /* Fix the sign. */ - value *= -1; - } - } - else { - if(is_negative) - value = CURL_OFF_T_MIN; - else - value = CURL_OFF_T_MAX; - - SET_ERRNO(ERANGE); - } - - if(endptr) - *endptr = end; - - return value; -} - -/** - * Returns the value of c in the given base, or -1 if c cannot - * be interpreted properly in that base (i.e., is out of range, - * is a null, etc.). - * - * @param c the character to interpret according to base - * @param base the base in which to interpret c - * - * @return the value of c in base, or -1 if c isn't in range - */ -static int get_char(char c, int base) -{ -#ifndef NO_RANGE_TEST - int value = -1; - if(c <= '9' && c >= '0') { - value = c - '0'; - } - else if(c <= 'Z' && c >= 'A') { - value = c - 'A' + 10; - } - else if(c <= 'z' && c >= 'a') { - value = c - 'a' + 10; - } -#else - const char * cp; - int value; - - cp = memchr(valchars, c, 10 + 26 + 26); - - if(!cp) - return -1; - - value = cp - valchars; - - if(value >= 10 + 26) - value -= 26; /* Lowercase. */ -#endif - - if(value >= base) { - value = -1; - } - - return value; -} -#endif /* Only present if we need strtoll, but don't have it. */ diff --git a/lib/telnet.c b/lib/telnet.c deleted file mode 100644 index 54eab1c92..000000000 --- a/lib/telnet.c +++ /dev/null @@ -1,1678 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_TELNET - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#include "curl_urldata.h" -#include -#include "curl_transfer.h" -#include "curl_sendf.h" -#include "curl_telnet.h" -#include "curl_connect.h" -#include "curl_progress.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#define TELOPTS -#define TELCMDS - -#include "curl_arpa_telnet.h" -#include "curl_memory.h" -#include "curl_select.h" -#include "curl_strequal.h" -#include "curl_rawstr.h" -#include "curl_warnless.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#define SUBBUFSIZE 512 - -#define CURL_SB_CLEAR(x) x->subpointer = x->subbuffer -#define CURL_SB_TERM(x) \ - do { \ - x->subend = x->subpointer; \ - CURL_SB_CLEAR(x); \ - } WHILE_FALSE -#define CURL_SB_ACCUM(x,c) \ - do { \ - if(x->subpointer < (x->subbuffer+sizeof x->subbuffer)) \ - *x->subpointer++ = (c); \ - } WHILE_FALSE - -#define CURL_SB_GET(x) ((*x->subpointer++)&0xff) -#define CURL_SB_PEEK(x) ((*x->subpointer)&0xff) -#define CURL_SB_EOF(x) (x->subpointer >= x->subend) -#define CURL_SB_LEN(x) (x->subend - x->subpointer) - -#ifdef CURL_DISABLE_VERBOSE_STRINGS -#define printoption(a,b,c,d) Curl_nop_stmt -#endif - -#ifdef USE_WINSOCK -typedef FARPROC WSOCK2_FUNC; -static CURLcode check_wsock2 ( struct SessionHandle *data ); -#endif - -static -CURLcode telrcv(struct connectdata *, - const unsigned char *inbuf, /* Data received from socket */ - ssize_t count); /* Number of bytes received */ - -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void printoption(struct SessionHandle *data, - const char *direction, - int cmd, int option); -#endif - -static void negotiate(struct connectdata *); -static void send_negotiation(struct connectdata *, int cmd, int option); -static void set_local_option(struct connectdata *, int cmd, int option); -static void set_remote_option(struct connectdata *, int cmd, int option); - -static void printsub(struct SessionHandle *data, - int direction, unsigned char *pointer, - size_t length); -static void suboption(struct connectdata *); -static void sendsuboption(struct connectdata *conn, int option); - -static CURLcode telnet_do(struct connectdata *conn, bool *done); -static CURLcode telnet_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode send_telnet_data(struct connectdata *conn, - char *buffer, ssize_t nread); - -/* For negotiation compliant to RFC 1143 */ -#define CURL_NO 0 -#define CURL_YES 1 -#define CURL_WANTYES 2 -#define CURL_WANTNO 3 - -#define CURL_EMPTY 0 -#define CURL_OPPOSITE 1 - -/* - * Telnet receiver states for fsm - */ -typedef enum -{ - CURL_TS_DATA = 0, - CURL_TS_IAC, - CURL_TS_WILL, - CURL_TS_WONT, - CURL_TS_DO, - CURL_TS_DONT, - CURL_TS_CR, - CURL_TS_SB, /* sub-option collection */ - CURL_TS_SE /* looking for sub-option end */ -} TelnetReceive; - -struct TELNET { - int please_negotiate; - int already_negotiated; - int us[256]; - int usq[256]; - int us_preferred[256]; - int him[256]; - int himq[256]; - int him_preferred[256]; - int subnegotiation[256]; - char subopt_ttype[32]; /* Set with suboption TTYPE */ - char subopt_xdisploc[128]; /* Set with suboption XDISPLOC */ - unsigned short subopt_wsx; /* Set with suboption NAWS */ - unsigned short subopt_wsy; /* Set with suboption NAWS */ - struct curl_slist *telnet_vars; /* Environment variables */ - - /* suboptions */ - unsigned char subbuffer[SUBBUFSIZE]; - unsigned char *subpointer, *subend; /* buffer for sub-options */ - - TelnetReceive telrcv_state; -}; - - -/* - * TELNET protocol handler. - */ - -const struct Curl_handler Curl_handler_telnet = { - "TELNET", /* scheme */ - ZERO_NULL, /* setup_connection */ - telnet_do, /* do_it */ - telnet_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_TELNET, /* defport */ - CURLPROTO_TELNET, /* protocol */ - PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ -}; - - -#ifdef USE_WINSOCK -static CURLcode -check_wsock2 ( struct SessionHandle *data ) -{ - int err; - WORD wVersionRequested; - WSADATA wsaData; - - DEBUGASSERT(data); - - /* telnet requires at least WinSock 2.0 so ask for it. */ - wVersionRequested = MAKEWORD(2, 0); - - err = WSAStartup(wVersionRequested, &wsaData); - - /* We must've called this once already, so this call */ - /* should always succeed. But, just in case... */ - if(err != 0) { - failf(data,"WSAStartup failed (%d)",err); - return CURLE_FAILED_INIT; - } - - /* We have to have a WSACleanup call for every successful */ - /* WSAStartup call. */ - WSACleanup(); - - /* Check that our version is supported */ - if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || - HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested)) { - /* Our version isn't supported */ - failf(data,"insufficient winsock version to support " - "telnet"); - return CURLE_FAILED_INIT; - } - - /* Our version is supported */ - return CURLE_OK; -} -#endif - -static -CURLcode init_telnet(struct connectdata *conn) -{ - struct TELNET *tn; - - tn = calloc(1, sizeof(struct TELNET)); - if(!tn) - return CURLE_OUT_OF_MEMORY; - - conn->data->state.proto.telnet = (void *)tn; /* make us known */ - - tn->telrcv_state = CURL_TS_DATA; - - /* Init suboptions */ - CURL_SB_CLEAR(tn); - - /* Set the options we want by default */ - tn->us_preferred[CURL_TELOPT_SGA] = CURL_YES; - tn->him_preferred[CURL_TELOPT_SGA] = CURL_YES; - - /* To be compliant with previous releases of libcurl - we enable this option by default. This behaviour - can be changed thanks to the "BINARY" option in - CURLOPT_TELNETOPTIONS - */ - tn->us_preferred[CURL_TELOPT_BINARY] = CURL_YES; - tn->him_preferred[CURL_TELOPT_BINARY] = CURL_YES; - - /* We must allow the server to echo what we sent - but it is not necessary to request the server - to do so (it might forces the server to close - the connection). Hence, we ignore ECHO in the - negotiate function - */ - tn->him_preferred[CURL_TELOPT_ECHO] = CURL_YES; - - /* Set the subnegotiation fields to send information - just after negotiation passed (do/will) - - Default values are (0,0) initialized by calloc. - According to the RFC1013 it is valid: - A value equal to zero is acceptable for the width (or height), - and means that no character width (or height) is being sent. - In this case, the width (or height) that will be assumed by the - Telnet server is operating system specific (it will probably be - based upon the terminal type information that may have been sent - using the TERMINAL TYPE Telnet option). */ - tn->subnegotiation[CURL_TELOPT_NAWS] = CURL_YES; - return CURLE_OK; -} - -static void negotiate(struct connectdata *conn) -{ - int i; - struct TELNET *tn = (struct TELNET *) conn->data->state.proto.telnet; - - for(i = 0;i < CURL_NTELOPTS;i++) { - if(i==CURL_TELOPT_ECHO) - continue; - - if(tn->us_preferred[i] == CURL_YES) - set_local_option(conn, i, CURL_YES); - - if(tn->him_preferred[i] == CURL_YES) - set_remote_option(conn, i, CURL_YES); - } -} - -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void printoption(struct SessionHandle *data, - const char *direction, int cmd, int option) -{ - const char *fmt; - const char *opt; - - if(data->set.verbose) { - if(cmd == CURL_IAC) { - if(CURL_TELCMD_OK(option)) - infof(data, "%s IAC %s\n", direction, CURL_TELCMD(option)); - else - infof(data, "%s IAC %d\n", direction, option); - } - else { - fmt = (cmd == CURL_WILL) ? "WILL" : (cmd == CURL_WONT) ? "WONT" : - (cmd == CURL_DO) ? "DO" : (cmd == CURL_DONT) ? "DONT" : 0; - if(fmt) { - if(CURL_TELOPT_OK(option)) - opt = CURL_TELOPT(option); - else if(option == CURL_TELOPT_EXOPL) - opt = "EXOPL"; - else - opt = NULL; - - if(opt) - infof(data, "%s %s %s\n", direction, fmt, opt); - else - infof(data, "%s %s %d\n", direction, fmt, option); - } - else - infof(data, "%s %d %d\n", direction, cmd, option); - } - } -} -#endif - -static void send_negotiation(struct connectdata *conn, int cmd, int option) -{ - unsigned char buf[3]; - ssize_t bytes_written; - int err; - struct SessionHandle *data = conn->data; - - buf[0] = CURL_IAC; - buf[1] = (unsigned char)cmd; - buf[2] = (unsigned char)option; - - bytes_written = swrite(conn->sock[FIRSTSOCKET], buf, 3); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data,"Sending data failed (%d)",err); - } - - printoption(conn->data, "SENT", cmd, option); -} - -static -void set_remote_option(struct connectdata *conn, int option, int newstate) -{ - struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; - if(newstate == CURL_YES) { - switch(tn->him[option]) { - case CURL_NO: - tn->him[option] = CURL_WANTYES; - send_negotiation(conn, CURL_DO, option); - break; - - case CURL_YES: - /* Already enabled */ - break; - - case CURL_WANTNO: - switch(tn->himq[option]) { - case CURL_EMPTY: - /* Already negotiating for CURL_YES, queue the request */ - tn->himq[option] = CURL_OPPOSITE; - break; - case CURL_OPPOSITE: - /* Error: already queued an enable request */ - break; - } - break; - - case CURL_WANTYES: - switch(tn->himq[option]) { - case CURL_EMPTY: - /* Error: already negotiating for enable */ - break; - case CURL_OPPOSITE: - tn->himq[option] = CURL_EMPTY; - break; - } - break; - } - } - else { /* NO */ - switch(tn->him[option]) { - case CURL_NO: - /* Already disabled */ - break; - - case CURL_YES: - tn->him[option] = CURL_WANTNO; - send_negotiation(conn, CURL_DONT, option); - break; - - case CURL_WANTNO: - switch(tn->himq[option]) { - case CURL_EMPTY: - /* Already negotiating for NO */ - break; - case CURL_OPPOSITE: - tn->himq[option] = CURL_EMPTY; - break; - } - break; - - case CURL_WANTYES: - switch(tn->himq[option]) { - case CURL_EMPTY: - tn->himq[option] = CURL_OPPOSITE; - break; - case CURL_OPPOSITE: - break; - } - break; - } - } -} - -static -void rec_will(struct connectdata *conn, int option) -{ - struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; - switch(tn->him[option]) { - case CURL_NO: - if(tn->him_preferred[option] == CURL_YES) { - tn->him[option] = CURL_YES; - send_negotiation(conn, CURL_DO, option); - } - else - send_negotiation(conn, CURL_DONT, option); - - break; - - case CURL_YES: - /* Already enabled */ - break; - - case CURL_WANTNO: - switch(tn->himq[option]) { - case CURL_EMPTY: - /* Error: DONT answered by WILL */ - tn->him[option] = CURL_NO; - break; - case CURL_OPPOSITE: - /* Error: DONT answered by WILL */ - tn->him[option] = CURL_YES; - tn->himq[option] = CURL_EMPTY; - break; - } - break; - - case CURL_WANTYES: - switch(tn->himq[option]) { - case CURL_EMPTY: - tn->him[option] = CURL_YES; - break; - case CURL_OPPOSITE: - tn->him[option] = CURL_WANTNO; - tn->himq[option] = CURL_EMPTY; - send_negotiation(conn, CURL_DONT, option); - break; - } - break; - } -} - -static -void rec_wont(struct connectdata *conn, int option) -{ - struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; - switch(tn->him[option]) { - case CURL_NO: - /* Already disabled */ - break; - - case CURL_YES: - tn->him[option] = CURL_NO; - send_negotiation(conn, CURL_DONT, option); - break; - - case CURL_WANTNO: - switch(tn->himq[option]) { - case CURL_EMPTY: - tn->him[option] = CURL_NO; - break; - - case CURL_OPPOSITE: - tn->him[option] = CURL_WANTYES; - tn->himq[option] = CURL_EMPTY; - send_negotiation(conn, CURL_DO, option); - break; - } - break; - - case CURL_WANTYES: - switch(tn->himq[option]) { - case CURL_EMPTY: - tn->him[option] = CURL_NO; - break; - case CURL_OPPOSITE: - tn->him[option] = CURL_NO; - tn->himq[option] = CURL_EMPTY; - break; - } - break; - } -} - -static void -set_local_option(struct connectdata *conn, int option, int newstate) -{ - struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; - if(newstate == CURL_YES) { - switch(tn->us[option]) { - case CURL_NO: - tn->us[option] = CURL_WANTYES; - send_negotiation(conn, CURL_WILL, option); - break; - - case CURL_YES: - /* Already enabled */ - break; - - case CURL_WANTNO: - switch(tn->usq[option]) { - case CURL_EMPTY: - /* Already negotiating for CURL_YES, queue the request */ - tn->usq[option] = CURL_OPPOSITE; - break; - case CURL_OPPOSITE: - /* Error: already queued an enable request */ - break; - } - break; - - case CURL_WANTYES: - switch(tn->usq[option]) { - case CURL_EMPTY: - /* Error: already negotiating for enable */ - break; - case CURL_OPPOSITE: - tn->usq[option] = CURL_EMPTY; - break; - } - break; - } - } - else { /* NO */ - switch(tn->us[option]) { - case CURL_NO: - /* Already disabled */ - break; - - case CURL_YES: - tn->us[option] = CURL_WANTNO; - send_negotiation(conn, CURL_WONT, option); - break; - - case CURL_WANTNO: - switch(tn->usq[option]) { - case CURL_EMPTY: - /* Already negotiating for NO */ - break; - case CURL_OPPOSITE: - tn->usq[option] = CURL_EMPTY; - break; - } - break; - - case CURL_WANTYES: - switch(tn->usq[option]) { - case CURL_EMPTY: - tn->usq[option] = CURL_OPPOSITE; - break; - case CURL_OPPOSITE: - break; - } - break; - } - } -} - -static -void rec_do(struct connectdata *conn, int option) -{ - struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; - switch(tn->us[option]) { - case CURL_NO: - if(tn->us_preferred[option] == CURL_YES) { - tn->us[option] = CURL_YES; - send_negotiation(conn, CURL_WILL, option); - if(tn->subnegotiation[option] == CURL_YES) - /* transmission of data option */ - sendsuboption(conn, option); - } - else if(tn->subnegotiation[option] == CURL_YES) { - /* send information to achieve this option*/ - tn->us[option] = CURL_YES; - send_negotiation(conn, CURL_WILL, option); - sendsuboption(conn, option); - } - else - send_negotiation(conn, CURL_WONT, option); - break; - - case CURL_YES: - /* Already enabled */ - break; - - case CURL_WANTNO: - switch(tn->usq[option]) { - case CURL_EMPTY: - /* Error: DONT answered by WILL */ - tn->us[option] = CURL_NO; - break; - case CURL_OPPOSITE: - /* Error: DONT answered by WILL */ - tn->us[option] = CURL_YES; - tn->usq[option] = CURL_EMPTY; - break; - } - break; - - case CURL_WANTYES: - switch(tn->usq[option]) { - case CURL_EMPTY: - tn->us[option] = CURL_YES; - if(tn->subnegotiation[option] == CURL_YES) { - /* transmission of data option */ - sendsuboption(conn, option); - } - break; - case CURL_OPPOSITE: - tn->us[option] = CURL_WANTNO; - tn->himq[option] = CURL_EMPTY; - send_negotiation(conn, CURL_WONT, option); - break; - } - break; - } -} - -static -void rec_dont(struct connectdata *conn, int option) -{ - struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; - switch(tn->us[option]) { - case CURL_NO: - /* Already disabled */ - break; - - case CURL_YES: - tn->us[option] = CURL_NO; - send_negotiation(conn, CURL_WONT, option); - break; - - case CURL_WANTNO: - switch(tn->usq[option]) { - case CURL_EMPTY: - tn->us[option] = CURL_NO; - break; - - case CURL_OPPOSITE: - tn->us[option] = CURL_WANTYES; - tn->usq[option] = CURL_EMPTY; - send_negotiation(conn, CURL_WILL, option); - break; - } - break; - - case CURL_WANTYES: - switch(tn->usq[option]) { - case CURL_EMPTY: - tn->us[option] = CURL_NO; - break; - case CURL_OPPOSITE: - tn->us[option] = CURL_NO; - tn->usq[option] = CURL_EMPTY; - break; - } - break; - } -} - - -static void printsub(struct SessionHandle *data, - int direction, /* '<' or '>' */ - unsigned char *pointer, /* where suboption data is */ - size_t length) /* length of suboption data */ -{ - unsigned int i = 0; - unsigned short *pval; - - if(data->set.verbose) { - if(direction) { - infof(data, "%s IAC SB ", (direction == '<')? "RCVD":"SENT"); - if(length >= 3) { - int j; - - i = pointer[length-2]; - j = pointer[length-1]; - - if(i != CURL_IAC || j != CURL_SE) { - infof(data, "(terminated by "); - if(CURL_TELOPT_OK(i)) - infof(data, "%s ", CURL_TELOPT(i)); - else if(CURL_TELCMD_OK(i)) - infof(data, "%s ", CURL_TELCMD(i)); - else - infof(data, "%u ", i); - if(CURL_TELOPT_OK(j)) - infof(data, "%s", CURL_TELOPT(j)); - else if(CURL_TELCMD_OK(j)) - infof(data, "%s", CURL_TELCMD(j)); - else - infof(data, "%d", j); - infof(data, ", not IAC SE!) "); - } - } - length -= 2; - } - if(length < 1) { - infof(data, "(Empty suboption?)"); - return; - } - - if(CURL_TELOPT_OK(pointer[0])) { - switch(pointer[0]) { - case CURL_TELOPT_TTYPE: - case CURL_TELOPT_XDISPLOC: - case CURL_TELOPT_NEW_ENVIRON: - case CURL_TELOPT_NAWS: - infof(data, "%s", CURL_TELOPT(pointer[0])); - break; - default: - infof(data, "%s (unsupported)", CURL_TELOPT(pointer[0])); - break; - } - } - else - infof(data, "%d (unknown)", pointer[i]); - - switch(pointer[0]) { - case CURL_TELOPT_NAWS: - pval = (unsigned short*)(pointer+1); - infof(data, "Width: %hu ; Height: %hu", - ntohs(pval[0]), ntohs(pval[1])); - break; - default: - switch(pointer[1]) { - case CURL_TELQUAL_IS: - infof(data, " IS"); - break; - case CURL_TELQUAL_SEND: - infof(data, " SEND"); - break; - case CURL_TELQUAL_INFO: - infof(data, " INFO/REPLY"); - break; - case CURL_TELQUAL_NAME: - infof(data, " NAME"); - break; - } - - switch(pointer[0]) { - case CURL_TELOPT_TTYPE: - case CURL_TELOPT_XDISPLOC: - pointer[length] = 0; - infof(data, " \"%s\"", &pointer[2]); - break; - case CURL_TELOPT_NEW_ENVIRON: - if(pointer[1] == CURL_TELQUAL_IS) { - infof(data, " "); - for(i = 3;i < length;i++) { - switch(pointer[i]) { - case CURL_NEW_ENV_VAR: - infof(data, ", "); - break; - case CURL_NEW_ENV_VALUE: - infof(data, " = "); - break; - default: - infof(data, "%c", pointer[i]); - break; - } - } - } - break; - default: - for(i = 2; i < length; i++) - infof(data, " %.2x", pointer[i]); - break; - } - } - if(direction) - infof(data, "\n"); - } -} - -static CURLcode check_telnet_options(struct connectdata *conn) -{ - struct curl_slist *head; - struct curl_slist *beg; - char option_keyword[128]; - char option_arg[256]; - struct SessionHandle *data = conn->data; - struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; - CURLcode result = CURLE_OK; - int binary_option; - - /* Add the user name as an environment variable if it - was given on the command line */ - if(conn->bits.user_passwd) { - snprintf(option_arg, sizeof(option_arg), "USER,%s", conn->user); - beg = curl_slist_append(tn->telnet_vars, option_arg); - if(!beg) { - curl_slist_free_all(tn->telnet_vars); - tn->telnet_vars = NULL; - return CURLE_OUT_OF_MEMORY; - } - tn->telnet_vars = beg; - tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; - } - - for(head = data->set.telnet_options; head; head=head->next) { - if(sscanf(head->data, "%127[^= ]%*[ =]%255s", - option_keyword, option_arg) == 2) { - - /* Terminal type */ - if(Curl_raw_equal(option_keyword, "TTYPE")) { - strncpy(tn->subopt_ttype, option_arg, 31); - tn->subopt_ttype[31] = 0; /* String termination */ - tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; - continue; - } - - /* Display variable */ - if(Curl_raw_equal(option_keyword, "XDISPLOC")) { - strncpy(tn->subopt_xdisploc, option_arg, 127); - tn->subopt_xdisploc[127] = 0; /* String termination */ - tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; - continue; - } - - /* Environment variable */ - if(Curl_raw_equal(option_keyword, "NEW_ENV")) { - beg = curl_slist_append(tn->telnet_vars, option_arg); - if(!beg) { - result = CURLE_OUT_OF_MEMORY; - break; - } - tn->telnet_vars = beg; - tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; - continue; - } - - /* Window Size */ - if(Curl_raw_equal(option_keyword, "WS")) { - if(sscanf(option_arg, "%hu%*[xX]%hu", - &tn->subopt_wsx, &tn->subopt_wsy) == 2) - tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES; - else { - failf(data, "Syntax error in telnet option: %s", head->data); - result = CURLE_TELNET_OPTION_SYNTAX; - break; - } - continue; - } - - /* To take care or not of the 8th bit in data exchange */ - if(Curl_raw_equal(option_keyword, "BINARY")) { - binary_option=atoi(option_arg); - if(binary_option!=1) { - tn->us_preferred[CURL_TELOPT_BINARY] = CURL_NO; - tn->him_preferred[CURL_TELOPT_BINARY] = CURL_NO; - } - continue; - } - - failf(data, "Unknown telnet option %s", head->data); - result = CURLE_UNKNOWN_TELNET_OPTION; - break; - } - else { - failf(data, "Syntax error in telnet option: %s", head->data); - result = CURLE_TELNET_OPTION_SYNTAX; - break; - } - } - - if(result) { - curl_slist_free_all(tn->telnet_vars); - tn->telnet_vars = NULL; - } - - return result; -} - -/* - * suboption() - * - * Look at the sub-option buffer, and try to be helpful to the other - * side. - */ - -static void suboption(struct connectdata *conn) -{ - struct curl_slist *v; - unsigned char temp[2048]; - ssize_t bytes_written; - size_t len; - size_t tmplen; - int err; - char varname[128]; - char varval[128]; - struct SessionHandle *data = conn->data; - struct TELNET *tn = (struct TELNET *)data->state.proto.telnet; - - printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn)+2); - switch (CURL_SB_GET(tn)) { - case CURL_TELOPT_TTYPE: - len = strlen(tn->subopt_ttype) + 4 + 2; - snprintf((char *)temp, sizeof(temp), - "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_TTYPE, - CURL_TELQUAL_IS, tn->subopt_ttype, CURL_IAC, CURL_SE); - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data,"Sending data failed (%d)",err); - } - printsub(data, '>', &temp[2], len-2); - break; - case CURL_TELOPT_XDISPLOC: - len = strlen(tn->subopt_xdisploc) + 4 + 2; - snprintf((char *)temp, sizeof(temp), - "%c%c%c%c%s%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_XDISPLOC, - CURL_TELQUAL_IS, tn->subopt_xdisploc, CURL_IAC, CURL_SE); - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data,"Sending data failed (%d)",err); - } - printsub(data, '>', &temp[2], len-2); - break; - case CURL_TELOPT_NEW_ENVIRON: - snprintf((char *)temp, sizeof(temp), - "%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON, - CURL_TELQUAL_IS); - len = 4; - - for(v = tn->telnet_vars;v;v = v->next) { - tmplen = (strlen(v->data) + 1); - /* Add the variable only if it fits */ - if(len + tmplen < (int)sizeof(temp)-6) { - sscanf(v->data, "%127[^,],%127s", varname, varval); - snprintf((char *)&temp[len], sizeof(temp) - len, - "%c%s%c%s", CURL_NEW_ENV_VAR, varname, - CURL_NEW_ENV_VALUE, varval); - len += tmplen; - } - } - snprintf((char *)&temp[len], sizeof(temp) - len, - "%c%c", CURL_IAC, CURL_SE); - len += 2; - bytes_written = swrite(conn->sock[FIRSTSOCKET], temp, len); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data,"Sending data failed (%d)",err); - } - printsub(data, '>', &temp[2], len-2); - break; - } - return; -} - - -/* - * sendsuboption() - * - * Send suboption information to the server side. - */ - -static void sendsuboption(struct connectdata *conn, int option) -{ - ssize_t bytes_written; - int err; - unsigned short x, y; - unsigned char*uc1, *uc2; - - struct SessionHandle *data = conn->data; - struct TELNET *tn = (struct TELNET *)data->state.proto.telnet; - - switch (option) { - case CURL_TELOPT_NAWS: - /* We prepare data to be sent */ - CURL_SB_CLEAR(tn); - CURL_SB_ACCUM(tn, CURL_IAC); - CURL_SB_ACCUM(tn, CURL_SB); - CURL_SB_ACCUM(tn, CURL_TELOPT_NAWS); - /* We must deal either with litte or big endien processors */ - /* Window size must be sent according to the 'network order' */ - x=htons(tn->subopt_wsx); - y=htons(tn->subopt_wsy); - uc1 = (unsigned char*)&x; - uc2 = (unsigned char*)&y; - CURL_SB_ACCUM(tn, uc1[0]); - CURL_SB_ACCUM(tn, uc1[1]); - CURL_SB_ACCUM(tn, uc2[0]); - CURL_SB_ACCUM(tn, uc2[1]); - - CURL_SB_ACCUM(tn, CURL_IAC); - CURL_SB_ACCUM(tn, CURL_SE); - CURL_SB_TERM(tn); - /* data suboption is now ready */ - - printsub(data, '>', (unsigned char *)tn->subbuffer+2, - CURL_SB_LEN(tn)-2); - - /* we send the header of the suboption... */ - bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer, 3); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data, "Sending data failed (%d)", err); - } - /* ... then the window size with the send_telnet_data() function - to deal with 0xFF cases ... */ - send_telnet_data(conn, (char *)tn->subbuffer+3, 4); - /* ... and the footer */ - bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer+7, 2); - if(bytes_written < 0) { - err = SOCKERRNO; - failf(data, "Sending data failed (%d)", err); - } - break; - } -} - - -static -CURLcode telrcv(struct connectdata *conn, - const unsigned char *inbuf, /* Data received from socket */ - ssize_t count) /* Number of bytes received */ -{ - unsigned char c; - CURLcode result; - int in = 0; - int startwrite=-1; - struct SessionHandle *data = conn->data; - struct TELNET *tn = (struct TELNET *)data->state.proto.telnet; - -#define startskipping() \ - if(startwrite >= 0) { \ - result = Curl_client_write(conn, \ - CLIENTWRITE_BODY, \ - (char *)&inbuf[startwrite], \ - in-startwrite); \ - if(result != CURLE_OK) \ - return result; \ - } \ - startwrite = -1 - -#define writebyte() \ - if(startwrite < 0) \ - startwrite = in - -#define bufferflush() startskipping() - - while(count--) { - c = inbuf[in]; - - switch (tn->telrcv_state) { - case CURL_TS_CR: - tn->telrcv_state = CURL_TS_DATA; - if(c == '\0') { - startskipping(); - break; /* Ignore \0 after CR */ - } - writebyte(); - break; - - case CURL_TS_DATA: - if(c == CURL_IAC) { - tn->telrcv_state = CURL_TS_IAC; - startskipping(); - break; - } - else if(c == '\r') - tn->telrcv_state = CURL_TS_CR; - writebyte(); - break; - - case CURL_TS_IAC: - process_iac: - DEBUGASSERT(startwrite < 0); - switch (c) { - case CURL_WILL: - tn->telrcv_state = CURL_TS_WILL; - break; - case CURL_WONT: - tn->telrcv_state = CURL_TS_WONT; - break; - case CURL_DO: - tn->telrcv_state = CURL_TS_DO; - break; - case CURL_DONT: - tn->telrcv_state = CURL_TS_DONT; - break; - case CURL_SB: - CURL_SB_CLEAR(tn); - tn->telrcv_state = CURL_TS_SB; - break; - case CURL_IAC: - tn->telrcv_state = CURL_TS_DATA; - writebyte(); - break; - case CURL_DM: - case CURL_NOP: - case CURL_GA: - default: - tn->telrcv_state = CURL_TS_DATA; - printoption(data, "RCVD", CURL_IAC, c); - break; - } - break; - - case CURL_TS_WILL: - printoption(data, "RCVD", CURL_WILL, c); - tn->please_negotiate = 1; - rec_will(conn, c); - tn->telrcv_state = CURL_TS_DATA; - break; - - case CURL_TS_WONT: - printoption(data, "RCVD", CURL_WONT, c); - tn->please_negotiate = 1; - rec_wont(conn, c); - tn->telrcv_state = CURL_TS_DATA; - break; - - case CURL_TS_DO: - printoption(data, "RCVD", CURL_DO, c); - tn->please_negotiate = 1; - rec_do(conn, c); - tn->telrcv_state = CURL_TS_DATA; - break; - - case CURL_TS_DONT: - printoption(data, "RCVD", CURL_DONT, c); - tn->please_negotiate = 1; - rec_dont(conn, c); - tn->telrcv_state = CURL_TS_DATA; - break; - - case CURL_TS_SB: - if(c == CURL_IAC) - tn->telrcv_state = CURL_TS_SE; - else - CURL_SB_ACCUM(tn,c); - break; - - case CURL_TS_SE: - if(c != CURL_SE) { - if(c != CURL_IAC) { - /* - * This is an error. We only expect to get "IAC IAC" or "IAC SE". - * Several things may have happened. An IAC was not doubled, the - * IAC SE was left off, or another option got inserted into the - * suboption are all possibilities. If we assume that the IAC was - * not doubled, and really the IAC SE was left off, we could get - * into an infinate loop here. So, instead, we terminate the - * suboption, and process the partial suboption if we can. - */ - CURL_SB_ACCUM(tn, CURL_IAC); - CURL_SB_ACCUM(tn, c); - tn->subpointer -= 2; - CURL_SB_TERM(tn); - - printoption(data, "In SUBOPTION processing, RCVD", CURL_IAC, c); - suboption(conn); /* handle sub-option */ - tn->telrcv_state = CURL_TS_IAC; - goto process_iac; - } - CURL_SB_ACCUM(tn,c); - tn->telrcv_state = CURL_TS_SB; - } - else - { - CURL_SB_ACCUM(tn, CURL_IAC); - CURL_SB_ACCUM(tn, CURL_SE); - tn->subpointer -= 2; - CURL_SB_TERM(tn); - suboption(conn); /* handle sub-option */ - tn->telrcv_state = CURL_TS_DATA; - } - break; - } - ++in; - } - bufferflush(); - return CURLE_OK; -} - -/* Escape and send a telnet data block */ -/* TODO: write large chunks of data instead of one byte at a time */ -static CURLcode send_telnet_data(struct connectdata *conn, - char *buffer, ssize_t nread) -{ - unsigned char outbuf[2]; - ssize_t bytes_written, total_written; - int out_count; - CURLcode rc = CURLE_OK; - - while(rc == CURLE_OK && nread--) { - outbuf[0] = *buffer++; - out_count = 1; - if(outbuf[0] == CURL_IAC) - outbuf[out_count++] = CURL_IAC; - - total_written = 0; - do { - /* Make sure socket is writable to avoid EWOULDBLOCK condition */ - struct pollfd pfd[1]; - pfd[0].fd = conn->sock[FIRSTSOCKET]; - pfd[0].events = POLLOUT; - switch (Curl_poll(pfd, 1, -1)) { - case -1: /* error, abort writing */ - case 0: /* timeout (will never happen) */ - rc = CURLE_SEND_ERROR; - break; - default: /* write! */ - bytes_written = 0; - rc = Curl_write(conn, conn->sock[FIRSTSOCKET], outbuf+total_written, - out_count-total_written, &bytes_written); - total_written += bytes_written; - break; - } - /* handle partial write */ - } while(rc == CURLE_OK && total_written < out_count); - } - return rc; -} - -static CURLcode telnet_done(struct connectdata *conn, - CURLcode status, bool premature) -{ - struct TELNET *tn = (struct TELNET *)conn->data->state.proto.telnet; - (void)status; /* unused */ - (void)premature; /* not used */ - - if(!tn) - return CURLE_OK; - - curl_slist_free_all(tn->telnet_vars); - tn->telnet_vars = NULL; - - Curl_safefree(conn->data->state.proto.telnet); - - return CURLE_OK; -} - -static CURLcode telnet_do(struct connectdata *conn, bool *done) -{ - CURLcode code; - struct SessionHandle *data = conn->data; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; -#ifdef USE_WINSOCK - HMODULE wsock2; - WSOCK2_FUNC close_event_func; - WSOCK2_FUNC create_event_func; - WSOCK2_FUNC event_select_func; - WSOCK2_FUNC enum_netevents_func; - WSAEVENT event_handle; - WSANETWORKEVENTS events; - HANDLE stdin_handle; - HANDLE objs[2]; - DWORD obj_count; - DWORD wait_timeout; - DWORD waitret; - DWORD readfile_read; - int err; -#else - int interval_ms; - struct pollfd pfd[2]; - int poll_cnt; - curl_off_t total_dl = 0; - curl_off_t total_ul = 0; -#endif - ssize_t nread; - struct timeval now; - bool keepon = TRUE; - char *buf = data->state.buffer; - struct TELNET *tn; - - *done = TRUE; /* unconditionally */ - - code = init_telnet(conn); - if(code) - return code; - - tn = (struct TELNET *)data->state.proto.telnet; - - code = check_telnet_options(conn); - if(code) - return code; - -#ifdef USE_WINSOCK - /* - ** This functionality only works with WinSock >= 2.0. So, - ** make sure have it. - */ - code = check_wsock2(data); - if(code) - return code; - - /* OK, so we have WinSock 2.0. We need to dynamically */ - /* load ws2_32.dll and get the function pointers we need. */ - wsock2 = LoadLibrary(TEXT("WS2_32.DLL")); - if(wsock2 == NULL) { - failf(data,"failed to load WS2_32.DLL (%d)", ERRNO); - return CURLE_FAILED_INIT; - } - - /* Grab a pointer to WSACreateEvent */ - create_event_func = GetProcAddress(wsock2,"WSACreateEvent"); - if(create_event_func == NULL) { - failf(data,"failed to find WSACreateEvent function (%d)", - ERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - - /* And WSACloseEvent */ - close_event_func = GetProcAddress(wsock2,"WSACloseEvent"); - if(close_event_func == NULL) { - failf(data,"failed to find WSACloseEvent function (%d)", - ERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - - /* And WSAEventSelect */ - event_select_func = GetProcAddress(wsock2,"WSAEventSelect"); - if(event_select_func == NULL) { - failf(data,"failed to find WSAEventSelect function (%d)", - ERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - - /* And WSAEnumNetworkEvents */ - enum_netevents_func = GetProcAddress(wsock2,"WSAEnumNetworkEvents"); - if(enum_netevents_func == NULL) { - failf(data,"failed to find WSAEnumNetworkEvents function (%d)", - ERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - - /* We want to wait for both stdin and the socket. Since - ** the select() function in winsock only works on sockets - ** we have to use the WaitForMultipleObjects() call. - */ - - /* First, create a sockets event object */ - event_handle = (WSAEVENT)create_event_func(); - if(event_handle == WSA_INVALID_EVENT) { - failf(data,"WSACreateEvent failed (%d)", SOCKERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - - /* Tell winsock what events we want to listen to */ - if(event_select_func(sockfd, event_handle, FD_READ|FD_CLOSE) == - SOCKET_ERROR) { - close_event_func(event_handle); - FreeLibrary(wsock2); - return CURLE_OK; - } - - /* The get the Windows file handle for stdin */ - stdin_handle = GetStdHandle(STD_INPUT_HANDLE); - - /* Create the list of objects to wait for */ - objs[0] = event_handle; - objs[1] = stdin_handle; - - /* If stdin_handle is a pipe, use PeekNamedPipe() method to check it, - else use the old WaitForMultipleObjects() way */ - if(GetFileType(stdin_handle) == FILE_TYPE_PIPE || - data->set.is_fread_set) { - /* Don't wait for stdin_handle, just wait for event_handle */ - obj_count = 1; - /* Check stdin_handle per 100 milliseconds */ - wait_timeout = 100; - } - else { - obj_count = 2; - wait_timeout = 1000; - } - - /* Keep on listening and act on events */ - while(keepon) { - waitret = WaitForMultipleObjects(obj_count, objs, FALSE, wait_timeout); - switch(waitret) { - case WAIT_TIMEOUT: - { - for(;;) { - if(obj_count == 1) { - /* read from user-supplied method */ - code = (int)conn->fread_func(buf, 1, BUFSIZE - 1, conn->fread_in); - if(code == CURL_READFUNC_ABORT) { - keepon = FALSE; - code = CURLE_READ_ERROR; - break; - } - - if(code == CURL_READFUNC_PAUSE) - break; - - if(code == 0) /* no bytes */ - break; - - readfile_read = code; /* fall thru with number of bytes read */ - } - else { - /* read from stdin */ - if(!PeekNamedPipe(stdin_handle, NULL, 0, NULL, - &readfile_read, NULL)) { - keepon = FALSE; - code = CURLE_READ_ERROR; - break; - } - - if(!readfile_read) - break; - - if(!ReadFile(stdin_handle, buf, sizeof(data->state.buffer), - &readfile_read, NULL)) { - keepon = FALSE; - code = CURLE_READ_ERROR; - break; - } - } - - code = send_telnet_data(conn, buf, readfile_read); - if(code) { - keepon = FALSE; - break; - } - } - } - break; - - case WAIT_OBJECT_0 + 1: - { - if(!ReadFile(stdin_handle, buf, sizeof(data->state.buffer), - &readfile_read, NULL)) { - keepon = FALSE; - code = CURLE_READ_ERROR; - break; - } - - code = send_telnet_data(conn, buf, readfile_read); - if(code) { - keepon = FALSE; - break; - } - } - break; - - case WAIT_OBJECT_0: - - if(SOCKET_ERROR == enum_netevents_func(sockfd, event_handle, &events)) { - if((err = SOCKERRNO) != EINPROGRESS) { - infof(data,"WSAEnumNetworkEvents failed (%d)", err); - keepon = FALSE; - code = CURLE_READ_ERROR; - } - break; - } - if(events.lNetworkEvents & FD_READ) { - /* read data from network */ - code = Curl_read(conn, sockfd, buf, BUFSIZE - 1, &nread); - /* read would've blocked. Loop again */ - if(code == CURLE_AGAIN) - break; - /* returned not-zero, this an error */ - else if(code) { - keepon = FALSE; - break; - } - /* returned zero but actually received 0 or less here, - the server closed the connection and we bail out */ - else if(nread <= 0) { - keepon = FALSE; - break; - } - - code = telrcv(conn, (unsigned char *)buf, nread); - if(code) { - keepon = FALSE; - break; - } - - /* Negotiate if the peer has started negotiating, - otherwise don't. We don't want to speak telnet with - non-telnet servers, like POP or SMTP. */ - if(tn->please_negotiate && !tn->already_negotiated) { - negotiate(conn); - tn->already_negotiated = 1; - } - } - if(events.lNetworkEvents & FD_CLOSE) { - keepon = FALSE; - } - break; - - } - - if(data->set.timeout) { - now = Curl_tvnow(); - if(Curl_tvdiff(now, conn->created) >= data->set.timeout) { - failf(data, "Time-out"); - code = CURLE_OPERATION_TIMEDOUT; - keepon = FALSE; - } - } - } - - /* We called WSACreateEvent, so call WSACloseEvent */ - if(!close_event_func(event_handle)) { - infof(data,"WSACloseEvent failed (%d)", SOCKERRNO); - } - - /* "Forget" pointers into the library we're about to free */ - create_event_func = NULL; - close_event_func = NULL; - event_select_func = NULL; - enum_netevents_func = NULL; - - /* We called LoadLibrary, so call FreeLibrary */ - if(!FreeLibrary(wsock2)) - infof(data,"FreeLibrary(wsock2) failed (%d)", ERRNO); -#else - pfd[0].fd = sockfd; - pfd[0].events = POLLIN; - - if(conn->fread_func != (curl_read_callback)fread) { - poll_cnt = 1; - interval_ms = 100; /* poll user-supplied read function */ - } - else { - /* really using fread, so infile is a FILE* */ - pfd[1].fd = fileno((FILE *)conn->fread_in); - pfd[1].events = POLLIN; - poll_cnt = 2; - interval_ms = 1 * 1000; - } - - while(keepon) { - switch (Curl_poll(pfd, poll_cnt, interval_ms)) { - case -1: /* error, stop reading */ - keepon = FALSE; - continue; - case 0: /* timeout */ - pfd[0].revents = 0; - pfd[1].revents = 0; - /* fall through */ - default: /* read! */ - if(pfd[0].revents & POLLIN) { - /* read data from network */ - code = Curl_read(conn, sockfd, buf, BUFSIZE - 1, &nread); - /* read would've blocked. Loop again */ - if(code == CURLE_AGAIN) - break; - /* returned not-zero, this an error */ - else if(code) { - keepon = FALSE; - break; - } - /* returned zero but actually received 0 or less here, - the server closed the connection and we bail out */ - else if(nread <= 0) { - keepon = FALSE; - break; - } - - total_dl += nread; - Curl_pgrsSetDownloadCounter(data, total_dl); - code = telrcv(conn, (unsigned char *)buf, nread); - if(code) { - keepon = FALSE; - break; - } - - /* Negotiate if the peer has started negotiating, - otherwise don't. We don't want to speak telnet with - non-telnet servers, like POP or SMTP. */ - if(tn->please_negotiate && !tn->already_negotiated) { - negotiate(conn); - tn->already_negotiated = 1; - } - } - - nread = 0; - if(poll_cnt == 2) { - if(pfd[1].revents & POLLIN) { /* read from in file */ - nread = read(pfd[1].fd, buf, BUFSIZE - 1); - } - } - else { - /* read from user-supplied method */ - nread = (int)conn->fread_func(buf, 1, BUFSIZE - 1, conn->fread_in); - if(nread == CURL_READFUNC_ABORT) { - keepon = FALSE; - break; - } - if(nread == CURL_READFUNC_PAUSE) - break; - } - - if(nread > 0) { - code = send_telnet_data(conn, buf, nread); - if(code) { - keepon = FALSE; - break; - } - total_ul += nread; - Curl_pgrsSetUploadCounter(data, total_ul); - } - else if(nread < 0) - keepon = FALSE; - - break; - } /* poll switch statement */ - - if(data->set.timeout) { - now = Curl_tvnow(); - if(Curl_tvdiff(now, conn->created) >= data->set.timeout) { - failf(data, "Time-out"); - code = CURLE_OPERATION_TIMEDOUT; - keepon = FALSE; - } - } - - if(Curl_pgrsUpdate(conn)) { - code = CURLE_ABORTED_BY_CALLBACK; - break; - } - } -#endif - /* mark this as "no further transfer wanted" */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - - return code; -} -#endif diff --git a/lib/tftp.c b/lib/tftp.c deleted file mode 100644 index 1af246ec0..000000000 --- a/lib/tftp.c +++ /dev/null @@ -1,1500 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifndef CURL_DISABLE_TFTP - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#include "curl_urldata.h" -#include -#include "curl_transfer.h" -#include "curl_sendf.h" -#include "curl_tftp.h" -#include "curl_progress.h" -#include "curl_connect.h" -#include "curl_strerror.h" -#include "curl_sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "curl_multiif.h" -#include "curl_url.h" -#include "curl_rawstr.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -#include "curl_select.h" - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* RFC2348 allows the block size to be negotiated */ -#define TFTP_BLKSIZE_DEFAULT 512 -#define TFTP_BLKSIZE_MIN 8 -#define TFTP_BLKSIZE_MAX 65464 -#define TFTP_OPTION_BLKSIZE "blksize" - -/* from RFC2349: */ -#define TFTP_OPTION_TSIZE "tsize" -#define TFTP_OPTION_INTERVAL "timeout" - -typedef enum { - TFTP_MODE_NETASCII=0, - TFTP_MODE_OCTET -} tftp_mode_t; - -typedef enum { - TFTP_STATE_START=0, - TFTP_STATE_RX, - TFTP_STATE_TX, - TFTP_STATE_FIN -} tftp_state_t; - -typedef enum { - TFTP_EVENT_NONE = -1, - TFTP_EVENT_INIT = 0, - TFTP_EVENT_RRQ = 1, - TFTP_EVENT_WRQ = 2, - TFTP_EVENT_DATA = 3, - TFTP_EVENT_ACK = 4, - TFTP_EVENT_ERROR = 5, - TFTP_EVENT_OACK = 6, - TFTP_EVENT_TIMEOUT -} tftp_event_t; - -typedef enum { - TFTP_ERR_UNDEF=0, - TFTP_ERR_NOTFOUND, - TFTP_ERR_PERM, - TFTP_ERR_DISKFULL, - TFTP_ERR_ILLEGAL, - TFTP_ERR_UNKNOWNID, - TFTP_ERR_EXISTS, - TFTP_ERR_NOSUCHUSER, /* This will never be triggered by this code */ - - /* The remaining error codes are internal to curl */ - TFTP_ERR_NONE = -100, - TFTP_ERR_TIMEOUT, - TFTP_ERR_NORESPONSE -} tftp_error_t; - -typedef struct tftp_packet { - unsigned char *data; -} tftp_packet_t; - -typedef struct tftp_state_data { - tftp_state_t state; - tftp_mode_t mode; - tftp_error_t error; - tftp_event_t event; - struct connectdata *conn; - curl_socket_t sockfd; - int retries; - int retry_time; - int retry_max; - time_t start_time; - time_t max_time; - time_t rx_time; - unsigned short block; - struct Curl_sockaddr_storage local_addr; - struct Curl_sockaddr_storage remote_addr; - curl_socklen_t remote_addrlen; - int rbytes; - int sbytes; - int blksize; - int requested_blksize; - tftp_packet_t rpacket; - tftp_packet_t spacket; -} tftp_state_data_t; - - -/* Forward declarations */ -static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) ; -static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) ; -static CURLcode tftp_connect(struct connectdata *conn, bool *done); -static CURLcode tftp_disconnect(struct connectdata *conn, - bool dead_connection); -static CURLcode tftp_do(struct connectdata *conn, bool *done); -static CURLcode tftp_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode tftp_setup_connection(struct connectdata * conn); -static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done); -static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done); -static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static CURLcode tftp_translate_code(tftp_error_t error); - - -/* - * TFTP protocol handler. - */ - -const struct Curl_handler Curl_handler_tftp = { - "TFTP", /* scheme */ - tftp_setup_connection, /* setup_connection */ - tftp_do, /* do_it */ - tftp_done, /* done */ - ZERO_NULL, /* do_more */ - tftp_connect, /* connect_it */ - tftp_multi_statemach, /* connecting */ - tftp_doing, /* doing */ - tftp_getsock, /* proto_getsock */ - tftp_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - tftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_TFTP, /* defport */ - CURLPROTO_TFTP, /* protocol */ - PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ -}; - -/********************************************************** - * - * tftp_set_timeouts - - * - * Set timeouts based on state machine state. - * Use user provided connect timeouts until DATA or ACK - * packet is received, then use user-provided transfer timeouts - * - * - **********************************************************/ -static CURLcode tftp_set_timeouts(tftp_state_data_t *state) -{ - time_t maxtime, timeout; - long timeout_ms; - bool start = (state->state == TFTP_STATE_START) ? TRUE : FALSE; - - time(&state->start_time); - - /* Compute drop-dead time */ - timeout_ms = Curl_timeleft(state->conn->data, NULL, start); - - if(timeout_ms < 0) { - /* time-out, bail out, go home */ - failf(state->conn->data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - if(start) { - - maxtime = (time_t)(timeout_ms + 500) / 1000; - state->max_time = state->start_time+maxtime; - - /* Set per-block timeout to total */ - timeout = maxtime ; - - /* Average restart after 5 seconds */ - state->retry_max = (int)timeout/5; - - if(state->retry_max < 1) - /* avoid division by zero below */ - state->retry_max = 1; - - /* Compute the re-start interval to suit the timeout */ - state->retry_time = (int)timeout/state->retry_max; - if(state->retry_time<1) - state->retry_time=1; - - } - else { - if(timeout_ms > 0) - maxtime = (time_t)(timeout_ms + 500) / 1000; - else - maxtime = 3600; - - state->max_time = state->start_time+maxtime; - - /* Set per-block timeout to total */ - timeout = maxtime; - - /* Average reposting an ACK after 5 seconds */ - state->retry_max = (int)timeout/5; - } - /* But bound the total number */ - if(state->retry_max<3) - state->retry_max=3; - - if(state->retry_max>50) - state->retry_max=50; - - /* Compute the re-ACK interval to suit the timeout */ - state->retry_time = (int)(timeout/state->retry_max); - if(state->retry_time<1) - state->retry_time=1; - - infof(state->conn->data, - "set timeouts for state %d; Total %ld, retry %d maxtry %d\n", - (int)state->state, (long)(state->max_time-state->start_time), - state->retry_time, state->retry_max); - - /* init RX time */ - time(&state->rx_time); - - return CURLE_OK; -} - -/********************************************************** - * - * tftp_set_send_first - * - * Event handler for the START state - * - **********************************************************/ - -static void setpacketevent(tftp_packet_t *packet, unsigned short num) -{ - packet->data[0] = (unsigned char)(num >> 8); - packet->data[1] = (unsigned char)(num & 0xff); -} - - -static void setpacketblock(tftp_packet_t *packet, unsigned short num) -{ - packet->data[2] = (unsigned char)(num >> 8); - packet->data[3] = (unsigned char)(num & 0xff); -} - -static unsigned short getrpacketevent(const tftp_packet_t *packet) -{ - return (unsigned short)((packet->data[0] << 8) | packet->data[1]); -} - -static unsigned short getrpacketblock(const tftp_packet_t *packet) -{ - return (unsigned short)((packet->data[2] << 8) | packet->data[3]); -} - -static size_t Curl_strnlen(const char *string, size_t maxlen) -{ - const char *end = memchr (string, '\0', maxlen); - return end ? (size_t) (end - string) : maxlen; -} - -static const char *tftp_option_get(const char *buf, size_t len, - const char **option, const char **value) -{ - size_t loc; - - loc = Curl_strnlen( buf, len ); - loc++; /* NULL term */ - - if(loc >= len) - return NULL; - *option = buf; - - loc += Curl_strnlen( buf+loc, len-loc ); - loc++; /* NULL term */ - - if(loc > len) - return NULL; - *value = &buf[strlen(*option) + 1]; - - return &buf[loc]; -} - -static CURLcode tftp_parse_option_ack(tftp_state_data_t *state, - const char *ptr, int len) -{ - const char *tmp = ptr; - struct SessionHandle *data = state->conn->data; - - /* if OACK doesn't contain blksize option, the default (512) must be used */ - state->blksize = TFTP_BLKSIZE_DEFAULT; - - while(tmp < ptr + len) { - const char *option, *value; - - tmp = tftp_option_get(tmp, ptr + len - tmp, &option, &value); - if(tmp == NULL) { - failf(data, "Malformed ACK packet, rejecting"); - return CURLE_TFTP_ILLEGAL; - } - - infof(data, "got option=(%s) value=(%s)\n", option, value); - - if(checkprefix(option, TFTP_OPTION_BLKSIZE)) { - long blksize; - - blksize = strtol( value, NULL, 10 ); - - if(!blksize) { - failf(data, "invalid blocksize value in OACK packet"); - return CURLE_TFTP_ILLEGAL; - } - else if(blksize > TFTP_BLKSIZE_MAX) { - failf(data, "%s (%d)", "blksize is larger than max supported", - TFTP_BLKSIZE_MAX); - return CURLE_TFTP_ILLEGAL; - } - else if(blksize < TFTP_BLKSIZE_MIN) { - failf(data, "%s (%d)", "blksize is smaller than min supported", - TFTP_BLKSIZE_MIN); - return CURLE_TFTP_ILLEGAL; - } - else if(blksize > state->requested_blksize) { - /* could realloc pkt buffers here, but the spec doesn't call out - * support for the server requesting a bigger blksize than the client - * requests */ - failf(data, "%s (%ld)", - "server requested blksize larger than allocated", blksize); - return CURLE_TFTP_ILLEGAL; - } - - state->blksize = (int)blksize; - infof(data, "%s (%d) %s (%d)\n", "blksize parsed from OACK", - state->blksize, "requested", state->requested_blksize); - } - else if(checkprefix(option, TFTP_OPTION_TSIZE)) { - long tsize = 0; - - tsize = strtol( value, NULL, 10 ); - infof(data, "%s (%ld)\n", "tsize parsed from OACK", tsize); - - /* tsize should be ignored on upload: Who cares about the size of the - remote file? */ - if(!data->set.upload) { - if(!tsize) { - failf(data, "invalid tsize -:%s:- value in OACK packet", value); - return CURLE_TFTP_ILLEGAL; - } - Curl_pgrsSetDownloadSize(data, tsize); - } - } - } - - return CURLE_OK; -} - -static size_t tftp_option_add(tftp_state_data_t *state, size_t csize, - char *buf, const char *option) -{ - if(( strlen(option) + csize + 1 ) > (size_t)state->blksize) - return 0; - strcpy(buf, option); - return( strlen(option) + 1 ); -} - -static CURLcode tftp_connect_for_tx(tftp_state_data_t *state, - tftp_event_t event) -{ - CURLcode res; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - struct SessionHandle *data = state->conn->data; - - infof(data, "%s\n", "Connected for transmit"); -#endif - state->state = TFTP_STATE_TX; - res = tftp_set_timeouts(state); - if(res != CURLE_OK) - return(res); - return tftp_tx(state, event); -} - -static CURLcode tftp_connect_for_rx(tftp_state_data_t *state, - tftp_event_t event) -{ - CURLcode res; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - struct SessionHandle *data = state->conn->data; - - infof(data, "%s\n", "Connected for receive"); -#endif - state->state = TFTP_STATE_RX; - res = tftp_set_timeouts(state); - if(res != CURLE_OK) - return(res); - return tftp_rx(state, event); -} - -static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event) -{ - size_t sbytes; - ssize_t senddata; - const char *mode = "octet"; - char *filename; - char buf[64]; - struct SessionHandle *data = state->conn->data; - CURLcode res = CURLE_OK; - - /* Set ascii mode if -B flag was used */ - if(data->set.prefer_ascii) - mode = "netascii"; - - switch(event) { - - case TFTP_EVENT_INIT: /* Send the first packet out */ - case TFTP_EVENT_TIMEOUT: /* Resend the first packet out */ - /* Increment the retry counter, quit if over the limit */ - state->retries++; - if(state->retries>state->retry_max) { - state->error = TFTP_ERR_NORESPONSE; - state->state = TFTP_STATE_FIN; - return res; - } - - if(data->set.upload) { - /* If we are uploading, send an WRQ */ - setpacketevent(&state->spacket, TFTP_EVENT_WRQ); - state->conn->data->req.upload_fromhere = - (char *)state->spacket.data+4; - if(data->set.infilesize != -1) - Curl_pgrsSetUploadSize(data, data->set.infilesize); - } - else { - /* If we are downloading, send an RRQ */ - setpacketevent(&state->spacket, TFTP_EVENT_RRQ); - } - /* As RFC3617 describes the separator slash is not actually part of the - file name so we skip the always-present first letter of the path - string. */ - filename = curl_easy_unescape(data, &state->conn->data->state.path[1], 0, - NULL); - if(!filename) - return CURLE_OUT_OF_MEMORY; - - snprintf((char *)state->spacket.data+2, - state->blksize, - "%s%c%s%c", filename, '\0', mode, '\0'); - sbytes = 4 + strlen(filename) + strlen(mode); - - /* add tsize option */ - if(data->set.upload && (data->set.infilesize != -1)) - snprintf( buf, sizeof(buf), "%" FORMAT_OFF_T, data->set.infilesize ); - else - strcpy(buf, "0"); /* the destination is large enough */ - - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, - TFTP_OPTION_TSIZE); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, buf); - /* add blksize option */ - snprintf( buf, sizeof(buf), "%d", state->requested_blksize ); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, - TFTP_OPTION_BLKSIZE); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, buf ); - - /* add timeout option */ - snprintf( buf, sizeof(buf), "%d", state->retry_time); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, - TFTP_OPTION_INTERVAL); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, buf ); - - /* the typecase for the 3rd argument is mostly for systems that do - not have a size_t argument, like older unixes that want an 'int' */ - senddata = sendto(state->sockfd, (void *)state->spacket.data, - (SEND_TYPE_ARG3)sbytes, 0, - state->conn->ip_addr->ai_addr, - state->conn->ip_addr->ai_addrlen); - if(senddata != (ssize_t)sbytes) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); - } - Curl_safefree(filename); - break; - - case TFTP_EVENT_OACK: - if(data->set.upload) { - res = tftp_connect_for_tx(state, event); - } - else { - res = tftp_connect_for_rx(state, event); - } - break; - - case TFTP_EVENT_ACK: /* Connected for transmit */ - res = tftp_connect_for_tx(state, event); - break; - - case TFTP_EVENT_DATA: /* Connected for receive */ - res = tftp_connect_for_rx(state, event); - break; - - case TFTP_EVENT_ERROR: - state->state = TFTP_STATE_FIN; - break; - - default: - failf(state->conn->data, "tftp_send_first: internal error"); - break; - } - return res; -} - -/* the next blocknum is x + 1 but it needs to wrap at an unsigned 16bit - boundary */ -#define NEXT_BLOCKNUM(x) (((x)+1)&0xffff) - -/********************************************************** - * - * tftp_rx - * - * Event handler for the RX state - * - **********************************************************/ -static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) -{ - ssize_t sbytes; - int rblock; - struct SessionHandle *data = state->conn->data; - - switch(event) { - - case TFTP_EVENT_DATA: - /* Is this the block we expect? */ - rblock = getrpacketblock(&state->rpacket); - if(NEXT_BLOCKNUM(state->block) == rblock) { - /* This is the expected block. Reset counters and ACK it. */ - state->retries = 0; - } - else if(state->block == rblock) { - /* This is the last recently received block again. Log it and ACK it - again. */ - infof(data, "Received last DATA packet block %d again.\n", rblock); - } - else { - /* totally unexpected, just log it */ - infof(data, - "Received unexpected DATA packet block %d, expecting block %d\n", - rblock, NEXT_BLOCKNUM(state->block)); - break; - } - - /* ACK this block. */ - state->block = (unsigned short)rblock; - setpacketevent(&state->spacket, TFTP_EVENT_ACK); - setpacketblock(&state->spacket, state->block); - sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4, SEND_4TH_ARG, - (struct sockaddr *)&state->remote_addr, - state->remote_addrlen); - if(sbytes < 0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); - return CURLE_SEND_ERROR; - } - - /* Check if completed (That is, a less than full packet is received) */ - if(state->rbytes < (ssize_t)state->blksize+4) { - state->state = TFTP_STATE_FIN; - } - else { - state->state = TFTP_STATE_RX; - } - time(&state->rx_time); - break; - - case TFTP_EVENT_OACK: - /* ACK option acknowledgement so we can move on to data */ - state->block = 0; - state->retries = 0; - setpacketevent(&state->spacket, TFTP_EVENT_ACK); - setpacketblock(&state->spacket, state->block); - sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4, SEND_4TH_ARG, - (struct sockaddr *)&state->remote_addr, - state->remote_addrlen); - if(sbytes < 0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); - return CURLE_SEND_ERROR; - } - - /* we're ready to RX data */ - state->state = TFTP_STATE_RX; - time(&state->rx_time); - break; - - case TFTP_EVENT_TIMEOUT: - /* Increment the retry count and fail if over the limit */ - state->retries++; - infof(data, - "Timeout waiting for block %d ACK. Retries = %d\n", - NEXT_BLOCKNUM(state->block), state->retries); - if(state->retries > state->retry_max) { - state->error = TFTP_ERR_TIMEOUT; - state->state = TFTP_STATE_FIN; - } - else { - /* Resend the previous ACK */ - sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4, SEND_4TH_ARG, - (struct sockaddr *)&state->remote_addr, - state->remote_addrlen); - if(sbytes<0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); - return CURLE_SEND_ERROR; - } - } - break; - - case TFTP_EVENT_ERROR: - setpacketevent(&state->spacket, TFTP_EVENT_ERROR); - setpacketblock(&state->spacket, state->block); - (void)sendto(state->sockfd, (void *)state->spacket.data, - 4, SEND_4TH_ARG, - (struct sockaddr *)&state->remote_addr, - state->remote_addrlen); - /* don't bother with the return code, but if the socket is still up we - * should be a good TFTP client and let the server know we're done */ - state->state = TFTP_STATE_FIN; - break; - - default: - failf(data, "%s", "tftp_rx: internal error"); - return CURLE_TFTP_ILLEGAL; /* not really the perfect return code for - this */ - } - return CURLE_OK; -} - -/********************************************************** - * - * tftp_tx - * - * Event handler for the TX state - * - **********************************************************/ -static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) -{ - struct SessionHandle *data = state->conn->data; - ssize_t sbytes; - int rblock; - CURLcode res = CURLE_OK; - struct SingleRequest *k = &data->req; - - switch(event) { - - case TFTP_EVENT_ACK: - case TFTP_EVENT_OACK: - if(event == TFTP_EVENT_ACK) { - /* Ack the packet */ - rblock = getrpacketblock(&state->rpacket); - - if(rblock != state->block && - /* There's a bug in tftpd-hpa that causes it to send us an ack for - * 65535 when the block number wraps to 0. So when we're expecting - * 0, also accept 65535. See - * http://syslinux.zytor.com/archives/2010-September/015253.html - * */ - !(state->block == 0 && rblock == 65535)) { - /* This isn't the expected block. Log it and up the retry counter */ - infof(data, "Received ACK for block %d, expecting %d\n", - rblock, state->block); - state->retries++; - /* Bail out if over the maximum */ - if(state->retries>state->retry_max) { - failf(data, "tftp_tx: giving up waiting for block %d ack", - state->block); - res = CURLE_SEND_ERROR; - } - else { - /* Re-send the data packet */ - sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4+state->sbytes, SEND_4TH_ARG, - (struct sockaddr *)&state->remote_addr, - state->remote_addrlen); - /* Check all sbytes were sent */ - if(sbytes<0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); - res = CURLE_SEND_ERROR; - } - } - return res; - } - /* This is the expected packet. Reset the counters and send the next - block */ - time(&state->rx_time); - state->block++; - } - else - state->block = 1; /* first data block is 1 when using OACK */ - - state->retries = 0; - setpacketevent(&state->spacket, TFTP_EVENT_DATA); - setpacketblock(&state->spacket, state->block); - if(state->block > 1 && state->sbytes < (int)state->blksize) { - state->state = TFTP_STATE_FIN; - return CURLE_OK; - } - res = Curl_fillreadbuffer(state->conn, state->blksize, &state->sbytes); - if(res) - return res; - sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4+state->sbytes, SEND_4TH_ARG, - (struct sockaddr *)&state->remote_addr, - state->remote_addrlen); - /* Check all sbytes were sent */ - if(sbytes<0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); - return CURLE_SEND_ERROR; - } - /* Update the progress meter */ - k->writebytecount += state->sbytes; - Curl_pgrsSetUploadCounter(data, k->writebytecount); - break; - - case TFTP_EVENT_TIMEOUT: - /* Increment the retry counter and log the timeout */ - state->retries++; - infof(data, "Timeout waiting for block %d ACK. " - " Retries = %d\n", NEXT_BLOCKNUM(state->block), state->retries); - /* Decide if we've had enough */ - if(state->retries > state->retry_max) { - state->error = TFTP_ERR_TIMEOUT; - state->state = TFTP_STATE_FIN; - } - else { - /* Re-send the data packet */ - sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4+state->sbytes, SEND_4TH_ARG, - (struct sockaddr *)&state->remote_addr, - state->remote_addrlen); - /* Check all sbytes were sent */ - if(sbytes<0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); - return CURLE_SEND_ERROR; - } - /* since this was a re-send, we remain at the still byte position */ - Curl_pgrsSetUploadCounter(data, k->writebytecount); - } - break; - - case TFTP_EVENT_ERROR: - state->state = TFTP_STATE_FIN; - setpacketevent(&state->spacket, TFTP_EVENT_ERROR); - setpacketblock(&state->spacket, state->block); - (void)sendto(state->sockfd, (void *)state->spacket.data, 4, SEND_4TH_ARG, - (struct sockaddr *)&state->remote_addr, - state->remote_addrlen); - /* don't bother with the return code, but if the socket is still up we - * should be a good TFTP client and let the server know we're done */ - state->state = TFTP_STATE_FIN; - break; - - default: - failf(data, "tftp_tx: internal error, event: %i", (int)(event)); - break; - } - - return res; -} - -/********************************************************** - * - * tftp_translate_code - * - * Translate internal error codes to CURL error codes - * - **********************************************************/ -static CURLcode tftp_translate_code(tftp_error_t error) -{ - CURLcode code = CURLE_OK; - - if(error != TFTP_ERR_NONE) { - switch(error) { - case TFTP_ERR_NOTFOUND: - code = CURLE_TFTP_NOTFOUND; - break; - case TFTP_ERR_PERM: - code = CURLE_TFTP_PERM; - break; - case TFTP_ERR_DISKFULL: - code = CURLE_REMOTE_DISK_FULL; - break; - case TFTP_ERR_UNDEF: - case TFTP_ERR_ILLEGAL: - code = CURLE_TFTP_ILLEGAL; - break; - case TFTP_ERR_UNKNOWNID: - code = CURLE_TFTP_UNKNOWNID; - break; - case TFTP_ERR_EXISTS: - code = CURLE_REMOTE_FILE_EXISTS; - break; - case TFTP_ERR_NOSUCHUSER: - code = CURLE_TFTP_NOSUCHUSER; - break; - case TFTP_ERR_TIMEOUT: - code = CURLE_OPERATION_TIMEDOUT; - break; - case TFTP_ERR_NORESPONSE: - code = CURLE_COULDNT_CONNECT; - break; - default: - code= CURLE_ABORTED_BY_CALLBACK; - break; - } - } - else { - code = CURLE_OK; - } - - return(code); -} - -/********************************************************** - * - * tftp_state_machine - * - * The tftp state machine event dispatcher - * - **********************************************************/ -static CURLcode tftp_state_machine(tftp_state_data_t *state, - tftp_event_t event) -{ - CURLcode res = CURLE_OK; - struct SessionHandle *data = state->conn->data; - switch(state->state) { - case TFTP_STATE_START: - DEBUGF(infof(data, "TFTP_STATE_START\n")); - res = tftp_send_first(state, event); - break; - case TFTP_STATE_RX: - DEBUGF(infof(data, "TFTP_STATE_RX\n")); - res = tftp_rx(state, event); - break; - case TFTP_STATE_TX: - DEBUGF(infof(data, "TFTP_STATE_TX\n")); - res = tftp_tx(state, event); - break; - case TFTP_STATE_FIN: - infof(data, "%s\n", "TFTP finished"); - break; - default: - DEBUGF(infof(data, "STATE: %d\n", state->state)); - failf(data, "%s", "Internal state machine error"); - res = CURLE_TFTP_ILLEGAL; - break; - } - return res; -} - -/********************************************************** - * - * tftp_disconnect - * - * The disconnect callback - * - **********************************************************/ -static CURLcode tftp_disconnect(struct connectdata *conn, bool dead_connection) -{ - tftp_state_data_t *state = conn->proto.tftpc; - (void) dead_connection; - - /* done, free dynamically allocated pkt buffers */ - if(state) { - Curl_safefree(state->rpacket.data); - Curl_safefree(state->spacket.data); - free(state); - } - - return CURLE_OK; -} - -/********************************************************** - * - * tftp_connect - * - * The connect callback - * - **********************************************************/ -static CURLcode tftp_connect(struct connectdata *conn, bool *done) -{ - CURLcode code; - tftp_state_data_t *state; - int blksize, rc; - - blksize = TFTP_BLKSIZE_DEFAULT; - - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - state = conn->proto.tftpc = calloc(1, sizeof(tftp_state_data_t)); - if(!state) - return CURLE_OUT_OF_MEMORY; - - /* alloc pkt buffers based on specified blksize */ - if(conn->data->set.tftp_blksize) { - blksize = (int)conn->data->set.tftp_blksize; - if(blksize > TFTP_BLKSIZE_MAX || blksize < TFTP_BLKSIZE_MIN ) - return CURLE_TFTP_ILLEGAL; - } - - if(!state->rpacket.data) { - state->rpacket.data = calloc(1, blksize + 2 + 2); - - if(!state->rpacket.data) - return CURLE_OUT_OF_MEMORY; - } - - if(!state->spacket.data) { - state->spacket.data = calloc(1, blksize + 2 + 2); - - if(!state->spacket.data) - return CURLE_OUT_OF_MEMORY; - } - - conn->bits.close = TRUE; /* we don't keep TFTP connections up bascially - because there's none or very little gain for UDP - */ - - state->conn = conn; - state->sockfd = state->conn->sock[FIRSTSOCKET]; - state->state = TFTP_STATE_START; - state->error = TFTP_ERR_NONE; - state->blksize = TFTP_BLKSIZE_DEFAULT; - state->requested_blksize = blksize; - - ((struct sockaddr *)&state->local_addr)->sa_family = - (unsigned short)(conn->ip_addr->ai_family); - - tftp_set_timeouts(state); - - if(!conn->bits.bound) { - /* If not already bound, bind to any interface, random UDP port. If it is - * reused or a custom local port was desired, this has already been done! - * - * We once used the size of the local_addr struct as the third argument - * for bind() to better work with IPv6 or whatever size the struct could - * have, but we learned that at least Tru64, AIX and IRIX *requires* the - * size of that argument to match the exact size of a 'sockaddr_in' struct - * when running IPv4-only. - * - * Therefore we use the size from the address we connected to, which we - * assume uses the same IP version and thus hopefully this works for both - * IPv4 and IPv6... - */ - rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr, - conn->ip_addr->ai_addrlen); - if(rc) { - failf(conn->data, "bind() failed; %s", - Curl_strerror(conn, SOCKERRNO)); - return CURLE_COULDNT_CONNECT; - } - conn->bits.bound = TRUE; - } - - Curl_pgrsStartNow(conn->data); - - *done = TRUE; - code = CURLE_OK; - return(code); -} - -/********************************************************** - * - * tftp_done - * - * The done callback - * - **********************************************************/ -static CURLcode tftp_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - CURLcode code = CURLE_OK; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; - - (void)status; /* unused */ - (void)premature; /* not used */ - - if(Curl_pgrsDone(conn)) - return CURLE_ABORTED_BY_CALLBACK; - - /* If we have encountered an error */ - code = tftp_translate_code(state->error); - - return code; -} - -/********************************************************** - * - * tftp_getsock - * - * The getsock callback - * - **********************************************************/ -static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) -{ - if(!numsocks) - return GETSOCK_BLANK; - - socks[0] = conn->sock[FIRSTSOCKET]; - - return GETSOCK_READSOCK(0); -} - -/********************************************************** - * - * tftp_receive_packet - * - * Called once select fires and data is ready on the socket - * - **********************************************************/ -static CURLcode tftp_receive_packet(struct connectdata *conn) -{ - struct Curl_sockaddr_storage fromaddr; - curl_socklen_t fromlen; - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; - struct SingleRequest *k = &data->req; - - /* Receive the packet */ - fromlen = sizeof(fromaddr); - state->rbytes = (int)recvfrom(state->sockfd, - (void *)state->rpacket.data, - state->blksize+4, - 0, - (struct sockaddr *)&fromaddr, - &fromlen); - if(state->remote_addrlen==0) { - memcpy(&state->remote_addr, &fromaddr, fromlen); - state->remote_addrlen = fromlen; - } - - /* Sanity check packet length */ - if(state->rbytes < 4) { - failf(data, "Received too short packet"); - /* Not a timeout, but how best to handle it? */ - state->event = TFTP_EVENT_TIMEOUT; - } - else { - /* The event is given by the TFTP packet time */ - state->event = (tftp_event_t)getrpacketevent(&state->rpacket); - - switch(state->event) { - case TFTP_EVENT_DATA: - /* Don't pass to the client empty or retransmitted packets */ - if(state->rbytes > 4 && - (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, - (char *)state->rpacket.data+4, - state->rbytes-4); - if(result) { - tftp_state_machine(state, TFTP_EVENT_ERROR); - return result; - } - k->bytecount += state->rbytes-4; - Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount); - } - break; - case TFTP_EVENT_ERROR: - state->error = (tftp_error_t)getrpacketblock(&state->rpacket); - infof(data, "%s\n", (const char *)state->rpacket.data+4); - break; - case TFTP_EVENT_ACK: - break; - case TFTP_EVENT_OACK: - result = tftp_parse_option_ack(state, - (const char *)state->rpacket.data+2, - state->rbytes-2); - if(result) - return result; - break; - case TFTP_EVENT_RRQ: - case TFTP_EVENT_WRQ: - default: - failf(data, "%s", "Internal error: Unexpected packet"); - break; - } - - /* Update the progress meter */ - if(Curl_pgrsUpdate(conn)) { - tftp_state_machine(state, TFTP_EVENT_ERROR); - return CURLE_ABORTED_BY_CALLBACK; - } - } - return result; -} - -/********************************************************** - * - * tftp_state_timeout - * - * Check if timeouts have been reached - * - **********************************************************/ -static long tftp_state_timeout(struct connectdata *conn, tftp_event_t *event) -{ - time_t current; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; - - if(event) - *event = TFTP_EVENT_NONE; - - time(¤t); - if(current > state->max_time) { - DEBUGF(infof(conn->data, "timeout: %ld > %ld\n", - (long)current, (long)state->max_time)); - state->error = TFTP_ERR_TIMEOUT; - state->state = TFTP_STATE_FIN; - return 0; - } - else if(current > state->rx_time+state->retry_time) { - if(event) - *event = TFTP_EVENT_TIMEOUT; - time(&state->rx_time); /* update even though we received nothing */ - } - - /* there's a typecast below here since 'time_t' may in fact be larger than - 'long', but we estimate that a 'long' will still be able to hold number - of seconds even if "only" 32 bit */ - return (long)(state->max_time - current); -} - - -/********************************************************** - * - * tftp_easy_statemach - * - * Handle easy request until completion - * - **********************************************************/ -static CURLcode tftp_easy_statemach(struct connectdata *conn) -{ - int rc; - int check_time = 0; - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; - curl_socket_t fd_read; - long timeout_ms; - struct SingleRequest *k = &data->req; - struct timeval transaction_start = Curl_tvnow(); - - k->start = transaction_start; - k->now = transaction_start; - - /* Run the TFTP State Machine */ - for(; (state->state != TFTP_STATE_FIN) && (result == CURLE_OK); ) { - - timeout_ms = state->retry_time * 1000; - - if(data->set.upload) { - if(data->set.max_send_speed && - (data->progress.ulspeed > data->set.max_send_speed)) { - fd_read = CURL_SOCKET_BAD; - timeout_ms = Curl_sleep_time(data->set.max_send_speed, - data->progress.ulspeed, state->blksize); - } - else { - fd_read = state->sockfd; - } - } - else { - if(data->set.max_recv_speed && - (data->progress.dlspeed > data->set.max_recv_speed)) { - fd_read = CURL_SOCKET_BAD; - timeout_ms = Curl_sleep_time(data->set.max_recv_speed, - data->progress.dlspeed, state->blksize); - } - else - fd_read = state->sockfd; - } - - if(data->set.timeout) { - timeout_ms = data->set.timeout - Curl_tvdiff(k->now, k->start); - if(timeout_ms > state->retry_time * 1000) - timeout_ms = state->retry_time * 1000; - else if(timeout_ms < 0) - timeout_ms = 0; - } - - - /* Wait until ready to read or timeout occurs */ - rc = Curl_socket_ready(fd_read, CURL_SOCKET_BAD, timeout_ms); - - k->now = Curl_tvnow(); - - /* Force a progress callback if it's been too long */ - if(Curl_tvdiff(k->now, k->start) >= data->set.timeout) { - if(Curl_pgrsUpdate(conn)) { - tftp_state_machine(state, TFTP_EVENT_ERROR); - return CURLE_ABORTED_BY_CALLBACK; - } - k->start = k->now; - } - - if(rc == -1) { - /* bail out */ - int error = SOCKERRNO; - failf(data, "%s", Curl_strerror(conn, error)); - state->event = TFTP_EVENT_ERROR; - } - else { - - if(rc==0) { - /* A timeout occurred, but our timeout is variable, so maybe - just continue? */ - long rtms = state->retry_time * 1000; - if(Curl_tvdiff(k->now, transaction_start) > rtms) { - state->event = TFTP_EVENT_TIMEOUT; - /* Force a look at transfer timeouts */ - check_time = 1; - } - else { - continue; /* skip state machine */ - } - } - else { - result = tftp_receive_packet(conn); - if(result == CURLE_OK) - transaction_start = Curl_tvnow(); - - if(k->bytecountp) - *k->bytecountp = k->bytecount; /* read count */ - if(k->writebytecountp) - *k->writebytecountp = k->writebytecount; /* write count */ - } - } - - if(check_time) { - tftp_state_timeout(conn, NULL); - check_time = 0; - } - - if(result) - return(result); - - result = tftp_state_machine(state, state->event); - } - - /* Tell curl we're done */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - - return(result); -} - -/********************************************************** - * - * tftp_multi_statemach - * - * Handle single RX socket event and return - * - **********************************************************/ -static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done) -{ - int rc; - tftp_event_t event; - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; - long timeout_ms = tftp_state_timeout(conn, &event); - - *done = FALSE; - - if(timeout_ms <= 0) { - failf(data, "TFTP response timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - else if(event != TFTP_EVENT_NONE) { - result = tftp_state_machine(state, event); - if(result != CURLE_OK) - return(result); - *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; - if(*done) - /* Tell curl we're done */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - } - else { - /* no timeouts to handle, check our socket */ - rc = Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD, 0); - - if(rc == -1) { - /* bail out */ - int error = SOCKERRNO; - failf(data, "%s", Curl_strerror(conn, error)); - state->event = TFTP_EVENT_ERROR; - } - else if(rc != 0) { - result = tftp_receive_packet(conn); - if(result != CURLE_OK) - return(result); - result = tftp_state_machine(state, state->event); - if(result != CURLE_OK) - return(result); - *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; - if(*done) - /* Tell curl we're done */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - } - /* if rc == 0, then select() timed out */ - } - - return result; -} - -/********************************************************** - * - * tftp_doing - * - * Called from curl_multi.c while DOing - * - **********************************************************/ -static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done) -{ - CURLcode result; - result = tftp_multi_statemach(conn, dophase_done); - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - return result; -} - -/********************************************************** - * - * tftp_peform - * - * Entry point for transfer from tftp_do, sarts state mach - * - **********************************************************/ -static CURLcode tftp_perform(struct connectdata *conn, bool *dophase_done) -{ - CURLcode result = CURLE_OK; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; - - *dophase_done = FALSE; - - result = tftp_state_machine(state, TFTP_EVENT_INIT); - - if(state->state == TFTP_STATE_FIN || result != CURLE_OK) - return(result); - - if(conn->data->state.used_interface == Curl_if_multi) - tftp_multi_statemach(conn, dophase_done); - else { - result = tftp_easy_statemach(conn); - *dophase_done = TRUE; /* with the easy interface we are done here */ - } - - if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); - - return result; -} - - -/********************************************************** - * - * tftp_do - * - * The do callback - * - * This callback initiates the TFTP transfer - * - **********************************************************/ - -static CURLcode tftp_do(struct connectdata *conn, bool *done) -{ - tftp_state_data_t *state; - CURLcode code; - - *done = FALSE; - - /* - Since connections can be re-used between SessionHandles, this might be a - connection already existing but on a fresh SessionHandle struct so we must - make sure we have a good 'struct TFTP' to play with. For new connections, - the struct TFTP is allocated and setup in the tftp_connect() function. - */ - Curl_reset_reqproto(conn); - - if(!conn->proto.tftpc) { - code = tftp_connect(conn, done); - if(code) - return code; - } - state = (tftp_state_data_t *)conn->proto.tftpc; - - code = tftp_perform(conn, done); - - /* If tftp_perform() returned an error, use that for return code. If it - was OK, see if tftp_translate_code() has an error. */ - if(code == CURLE_OK) - /* If we have encountered an internal tftp error, translate it. */ - code = tftp_translate_code(state->error); - - return code; -} - -static CURLcode tftp_setup_connection(struct connectdata * conn) -{ - struct SessionHandle *data = conn->data; - char * type; - char command; - - conn->socktype = SOCK_DGRAM; /* UDP datagram based */ - - /* TFTP URLs support an extension like ";mode=" that - * we'll try to get now! */ - type = strstr(data->state.path, ";mode="); - - if(!type) - type = strstr(conn->host.rawalloc, ";mode="); - - if(type) { - *type = 0; /* it was in the middle of the hostname */ - command = Curl_raw_toupper(type[6]); - - switch (command) { - case 'A': /* ASCII mode */ - case 'N': /* NETASCII mode */ - data->set.prefer_ascii = TRUE; - break; - - case 'O': /* octet mode */ - case 'I': /* binary mode */ - default: - /* switch off ASCII */ - data->set.prefer_ascii = FALSE; - break; - } - } - - return CURLE_OK; -} -#endif diff --git a/lib/timeval.c b/lib/timeval.c deleted file mode 100644 index 8e4c7bd76..000000000 --- a/lib/timeval.c +++ /dev/null @@ -1,134 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2008, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_timeval.h" - -#if defined(WIN32) && !defined(MSDOS) - -struct timeval curlx_tvnow(void) -{ - /* - ** GetTickCount() is available on _all_ Windows versions from W95 up - ** to nowadays. Returns milliseconds elapsed since last system boot, - ** increases monotonically and wraps once 49.7 days have elapsed. - */ - struct timeval now; - DWORD milliseconds = GetTickCount(); - now.tv_sec = milliseconds / 1000; - now.tv_usec = (milliseconds % 1000) * 1000; - return now; -} - -#elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) - -struct timeval curlx_tvnow(void) -{ - /* - ** clock_gettime() is granted to be increased monotonically when the - ** monotonic clock is queried. Time starting point is unspecified, it - ** could be the system start-up time, the Epoch, or something else, - ** in any case the time starting point does not change once that the - ** system has started up. - */ - struct timeval now; - struct timespec tsnow; - if(0 == clock_gettime(CLOCK_MONOTONIC, &tsnow)) { - now.tv_sec = tsnow.tv_sec; - now.tv_usec = tsnow.tv_nsec / 1000; - } - /* - ** Even when the configure process has truly detected monotonic clock - ** availability, it might happen that it is not actually available at - ** run-time. When this occurs simply fallback to other time source. - */ -#ifdef HAVE_GETTIMEOFDAY - else - (void)gettimeofday(&now, NULL); -#else - else { - now.tv_sec = (long)time(NULL); - now.tv_usec = 0; - } -#endif - return now; -} - -#elif defined(HAVE_GETTIMEOFDAY) - -struct timeval curlx_tvnow(void) -{ - /* - ** gettimeofday() is not granted to be increased monotonically, due to - ** clock drifting and external source time synchronization it can jump - ** forward or backward in time. - */ - struct timeval now; - (void)gettimeofday(&now, NULL); - return now; -} - -#else - -struct timeval curlx_tvnow(void) -{ - /* - ** time() returns the value of time in seconds since the Epoch. - */ - struct timeval now; - now.tv_sec = (long)time(NULL); - now.tv_usec = 0; - return now; -} - -#endif - -/* - * Make sure that the first argument is the more recent time, as otherwise - * we'll get a weird negative time-diff back... - * - * Returns: the time difference in number of milliseconds. - */ -long curlx_tvdiff(struct timeval newer, struct timeval older) -{ - return (newer.tv_sec-older.tv_sec)*1000+ - (newer.tv_usec-older.tv_usec)/1000; -} - -/* - * Same as curlx_tvdiff but with full usec resolution. - * - * Returns: the time difference in seconds with subsecond resolution. - */ -double curlx_tvdiff_secs(struct timeval newer, struct timeval older) -{ - if(newer.tv_sec != older.tv_sec) - return (double)(newer.tv_sec-older.tv_sec)+ - (double)(newer.tv_usec-older.tv_usec)/1000000.0; - else - return (double)(newer.tv_usec-older.tv_usec)/1000000.0; -} - -/* return the number of seconds in the given input timeval struct */ -long Curl_tvlong(struct timeval t1) -{ - return t1.tv_sec; -} diff --git a/lib/transfer.c b/lib/transfer.c deleted file mode 100644 index a1dee1dd0..000000000 --- a/lib/transfer.c +++ /dev/null @@ -1,2338 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_strtoofft.h" -#include "curl_strequal.h" -#include "curl_rawstr.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif -#ifdef HAVE_SIGNAL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#ifndef HAVE_SOCKET -#error "We can't compile without socket() support!" -#endif - -#include "curl_urldata.h" -#include -#include "curl_netrc.h" - -#include "curl_content_encoding.h" -#include "curl_hostip.h" -#include "curl_transfer.h" -#include "curl_sendf.h" -#include "curl_speedcheck.h" -#include "curl_progress.h" -#include "curl_http.h" -#include "curl_url.h" -#include "curl_getinfo.h" -#include "curl_sslgen.h" -#include "curl_http_digest.h" -#include "curl_ntlm.h" -#include "curl_http_negotiate.h" -#include "curl_share.h" -#include "curl_memory.h" -#include "curl_select.h" -#include "curl_multiif.h" -#include "curl_connect.h" -#include "curl_non_ascii.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -/* The last #include file should be: */ -#include "curl_memdebug.h" - -#define CURL_TIMEOUT_EXPECT_100 1000 /* counting ms here */ - -/* - * This function will call the read callback to fill our buffer with data - * to upload. - */ -CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp) -{ - struct SessionHandle *data = conn->data; - size_t buffersize = (size_t)bytes; - int nread; -#ifdef CURL_DOES_CONVERSIONS - bool sending_http_headers = FALSE; - - if((conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) && - (data->state.proto.http->sending == HTTPSEND_REQUEST)) { - /* We're sending the HTTP request headers, not the data. - Remember that so we don't re-translate them into garbage. */ - sending_http_headers = TRUE; - } -#endif - - if(data->req.upload_chunky) { - /* if chunked Transfer-Encoding */ - buffersize -= (8 + 2 + 2); /* 32bit hex + CRLF + CRLF */ - data->req.upload_fromhere += (8 + 2); /* 32bit hex + CRLF */ - } - - /* this function returns a size_t, so we typecast to int to prevent warnings - with picky compilers */ - nread = (int)conn->fread_func(data->req.upload_fromhere, 1, - buffersize, conn->fread_in); - - if(nread == CURL_READFUNC_ABORT) { - failf(data, "operation aborted by callback"); - *nreadp = 0; - return CURLE_ABORTED_BY_CALLBACK; - } - else if(nread == CURL_READFUNC_PAUSE) { - struct SingleRequest *k = &data->req; - /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ - k->keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ - if(data->req.upload_chunky) { - /* Back out the preallocation done above */ - data->req.upload_fromhere -= (8 + 2); - } - *nreadp = 0; - return CURLE_OK; /* nothing was read */ - } - else if((size_t)nread > buffersize) { - /* the read function returned a too large value */ - *nreadp = 0; - failf(data, "read function returned funny value"); - return CURLE_READ_ERROR; - } - - if(!data->req.forbidchunk && data->req.upload_chunky) { - /* if chunked Transfer-Encoding - * build chunk: - * - * CRLF - * CRLF - */ - /* On non-ASCII platforms the may or may not be - translated based on set.prefer_ascii while the protocol - portion must always be translated to the network encoding. - To further complicate matters, line end conversion might be - done later on, so we need to prevent CRLFs from becoming - CRCRLFs if that's the case. To do this we use bare LFs - here, knowing they'll become CRLFs later on. - */ - - char hexbuffer[11]; - const char *endofline_native; - const char *endofline_network; - int hexlen; - - if( -#ifdef CURL_DO_LINEEND_CONV - (data->set.prefer_ascii) || -#endif - (data->set.crlf)) { - /* \n will become \r\n later on */ - endofline_native = "\n"; - endofline_network = "\x0a"; - } - else { - endofline_native = "\r\n"; - endofline_network = "\x0d\x0a"; - } - hexlen = snprintf(hexbuffer, sizeof(hexbuffer), - "%x%s", nread, endofline_native); - - /* move buffer pointer */ - data->req.upload_fromhere -= hexlen; - nread += hexlen; - - /* copy the prefix to the buffer, leaving out the NUL */ - memcpy(data->req.upload_fromhere, hexbuffer, hexlen); - - /* always append ASCII CRLF to the data */ - memcpy(data->req.upload_fromhere + nread, - endofline_network, - strlen(endofline_network)); - -#ifdef CURL_DOES_CONVERSIONS - CURLcode res; - int length; - if(data->set.prefer_ascii) { - /* translate the protocol and data */ - length = nread; - } - else { - /* just translate the protocol portion */ - length = strlen(hexbuffer); - } - res = Curl_convert_to_network(data, data->req.upload_fromhere, length); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(res) - return(res); -#endif /* CURL_DOES_CONVERSIONS */ - - if((nread - hexlen) == 0) - /* mark this as done once this chunk is transferred */ - data->req.upload_done = TRUE; - - nread+=(int)strlen(endofline_native); /* for the added end of line */ - } -#ifdef CURL_DOES_CONVERSIONS - else if((data->set.prefer_ascii) && (!sending_http_headers)) { - CURLcode res; - res = Curl_convert_to_network(data, data->req.upload_fromhere, nread); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(res != CURLE_OK) - return(res); - } -#endif /* CURL_DOES_CONVERSIONS */ - - *nreadp = nread; - - return CURLE_OK; -} - - -/* - * Curl_readrewind() rewinds the read stream. This is typically used for HTTP - * POST/PUT with multi-pass authentication when a sending was denied and a - * resend is necessary. - */ -CURLcode Curl_readrewind(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - - conn->bits.rewindaftersend = FALSE; /* we rewind now */ - - /* explicitly switch off sending data on this connection now since we are - about to restart a new transfer and thus we want to avoid inadvertently - sending more data on the existing connection until the next transfer - starts */ - data->req.keepon &= ~KEEP_SEND; - - /* We have sent away data. If not using CURLOPT_POSTFIELDS or - CURLOPT_HTTPPOST, call app to rewind - */ - if(data->set.postfields || - (data->set.httpreq == HTTPREQ_POST_FORM)) - ; /* do nothing */ - else { - if(data->set.seek_func) { - int err; - - err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); - if(err) { - failf(data, "seek callback returned error %d", (int)err); - return CURLE_SEND_FAIL_REWIND; - } - } - else if(data->set.ioctl_func) { - curlioerr err; - - err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, - data->set.ioctl_client); - infof(data, "the ioctl callback returned %d\n", (int)err); - - if(err) { - /* FIXME: convert to a human readable error message */ - failf(data, "ioctl callback returned error %d", (int)err); - return CURLE_SEND_FAIL_REWIND; - } - } - else { - /* If no CURLOPT_READFUNCTION is used, we know that we operate on a - given FILE * stream and we can actually attempt to rewind that - ourselves with fseek() */ - if(data->set.fread_func == (curl_read_callback)fread) { - if(-1 != fseek(data->set.in, 0, SEEK_SET)) - /* successful rewind */ - return CURLE_OK; - } - - /* no callback set or failure above, makes us fail at once */ - failf(data, "necessary data rewind wasn't possible"); - return CURLE_SEND_FAIL_REWIND; - } - } - return CURLE_OK; -} - -static int data_pending(const struct connectdata *conn) -{ - /* in the case of libssh2, we can never be really sure that we have emptied - its internal buffers so we MUST always try until we get EAGAIN back */ - return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) || - Curl_ssl_data_pending(conn, FIRSTSOCKET); -} - -static void read_rewind(struct connectdata *conn, - size_t thismuch) -{ - DEBUGASSERT(conn->read_pos >= thismuch); - - conn->read_pos -= thismuch; - conn->bits.stream_was_rewound = TRUE; - -#ifdef DEBUGBUILD - { - char buf[512 + 1]; - size_t show; - - show = CURLMIN(conn->buf_len - conn->read_pos, sizeof(buf)-1); - if(conn->master_buffer) { - memcpy(buf, conn->master_buffer + conn->read_pos, show); - buf[show] = '\0'; - } - else { - buf[0] = '\0'; - } - - DEBUGF(infof(conn->data, - "Buffer after stream rewind (read_pos = %zu): [%s]\n", - conn->read_pos, buf)); - } -#endif -} - -/* - * Check to see if CURLOPT_TIMECONDITION was met by comparing the time of the - * remote document with the time provided by CURLOPT_TIMEVAL - */ -bool Curl_meets_timecondition(struct SessionHandle *data, time_t timeofdoc) -{ - if((timeofdoc == 0) || (data->set.timevalue == 0)) - return TRUE; - - switch(data->set.timecondition) { - case CURL_TIMECOND_IFMODSINCE: - default: - if(timeofdoc <= data->set.timevalue) { - infof(data, - "The requested document is not new enough\n"); - data->info.timecond = TRUE; - return FALSE; - } - break; - case CURL_TIMECOND_IFUNMODSINCE: - if(timeofdoc >= data->set.timevalue) { - infof(data, - "The requested document is not old enough\n"); - data->info.timecond = TRUE; - return FALSE; - } - break; - } - - return TRUE; -} - -/* - * Go ahead and do a read if we have a readable socket or if - * the stream was rewound (in which case we have data in a - * buffer) - */ -static CURLcode readwrite_data(struct SessionHandle *data, - struct connectdata *conn, - struct SingleRequest *k, - int *didwhat, bool *done) -{ - CURLcode result = CURLE_OK; - ssize_t nread; /* number of bytes read */ - size_t excess = 0; /* excess bytes read */ - bool is_empty_data = FALSE; - bool readmore = FALSE; /* used by RTP to signal for more data */ - - *done = FALSE; - - /* This is where we loop until we have read everything there is to - read or we get a CURLE_AGAIN */ - do { - size_t buffersize = data->set.buffer_size? - data->set.buffer_size : BUFSIZE; - size_t bytestoread = buffersize; - - if(k->size != -1 && !k->header) { - /* make sure we don't read "too much" if we can help it since we - might be pipelining and then someone else might want to read what - follows! */ - curl_off_t totalleft = k->size - k->bytecount; - if(totalleft < (curl_off_t)bytestoread) - bytestoread = (size_t)totalleft; - } - - if(bytestoread) { - /* receive data from the network! */ - result = Curl_read(conn, conn->sockfd, k->buf, bytestoread, &nread); - - /* read would've blocked */ - if(CURLE_AGAIN == result) - break; /* get out of loop */ - - if(result>0) - return result; - } - else { - /* read nothing but since we wanted nothing we consider this an OK - situation to proceed from */ - nread = 0; - } - - if((k->bytecount == 0) && (k->writebytecount == 0)) { - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - if(k->exp100 > EXP100_SEND_DATA) - /* set time stamp to compare with when waiting for the 100 */ - k->start100 = Curl_tvnow(); - } - - *didwhat |= KEEP_RECV; - /* indicates data of zero size, i.e. empty file */ - is_empty_data = ((nread == 0) && (k->bodywrites == 0)) ? TRUE : FALSE; - - /* NUL terminate, allowing string ops to be used */ - if(0 < nread || is_empty_data) { - k->buf[nread] = 0; - } - else if(0 >= nread) { - /* if we receive 0 or less here, the server closed the connection - and we bail out from this! */ - DEBUGF(infof(data, "nread <= 0, server closed connection, bailing\n")); - k->keepon &= ~KEEP_RECV; - break; - } - - /* Default buffer to use when we write the buffer, it may be changed - in the flow below before the actual storing is done. */ - k->str = k->buf; - - if(conn->handler->readwrite) { - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - return result; - if(readmore) - break; - } - -#ifndef CURL_DISABLE_HTTP - /* Since this is a two-state thing, we check if we are parsing - headers at the moment or not. */ - if(k->header) { - /* we are in parse-the-header-mode */ - bool stop_reading = FALSE; - result = Curl_http_readwrite_headers(data, conn, &nread, &stop_reading); - if(result) - return result; - - if(conn->handler->readwrite && - (k->maxdownload <= 0 && nread > 0)) { - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - return result; - if(readmore) - break; - } - - if(stop_reading) { - /* We've stopped dealing with input, get out of the do-while loop */ - - if(nread > 0) { - if(conn->data->multi && Curl_multi_canPipeline(conn->data->multi)) { - infof(data, - "Rewinding stream by : %zd" - " bytes on url %s (zero-length body)\n", - nread, data->state.path); - read_rewind(conn, (size_t)nread); - } - else { - infof(data, - "Excess found in a non pipelined read:" - " excess = %zd" - " url = %s (zero-length body)\n", - nread, data->state.path); - } - } - - break; - } - } -#endif /* CURL_DISABLE_HTTP */ - - - /* This is not an 'else if' since it may be a rest from the header - parsing, where the beginning of the buffer is headers and the end - is non-headers. */ - if(k->str && !k->header && (nread > 0 || is_empty_data)) { - -#ifndef CURL_DISABLE_HTTP - if(0 == k->bodywrites && !is_empty_data) { - /* These checks are only made the first time we are about to - write a piece of the body */ - if(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) { - /* HTTP-only checks */ - - if(data->req.newurl) { - if(conn->bits.close) { - /* Abort after the headers if "follow Location" is set - and we're set to close anyway. */ - k->keepon &= ~KEEP_RECV; - *done = TRUE; - return CURLE_OK; - } - /* We have a new url to load, but since we want to be able - to re-use this connection properly, we read the full - response in "ignore more" */ - k->ignorebody = TRUE; - infof(data, "Ignoring the response-body\n"); - } - if(data->state.resume_from && !k->content_range && - (data->set.httpreq==HTTPREQ_GET) && - !k->ignorebody) { - /* we wanted to resume a download, although the server doesn't - * seem to support this and we did this with a GET (if it - * wasn't a GET we did a POST or PUT resume) */ - failf(data, "HTTP server doesn't seem to support " - "byte ranges. Cannot resume."); - return CURLE_RANGE_ERROR; - } - - if(data->set.timecondition && !data->state.range) { - /* A time condition has been set AND no ranges have been - requested. This seems to be what chapter 13.3.4 of - RFC 2616 defines to be the correct action for a - HTTP/1.1 client */ - - if(!Curl_meets_timecondition(data, k->timeofdoc)) { - *done = TRUE; - /* we abort the transfer before it is completed == we ruin the - re-use ability. Close the connection */ - conn->bits.close = TRUE; - return CURLE_OK; - } - } /* we have a time condition */ - - } /* this is HTTP or RTSP */ - } /* this is the first time we write a body part */ -#endif /* CURL_DISABLE_HTTP */ - - k->bodywrites++; - - /* pass data to the debug function before it gets "dechunked" */ - if(data->set.verbose) { - if(k->badheader) { - Curl_debug(data, CURLINFO_DATA_IN, data->state.headerbuff, - (size_t)k->hbuflen, conn); - if(k->badheader == HEADER_PARTHEADER) - Curl_debug(data, CURLINFO_DATA_IN, - k->str, (size_t)nread, conn); - } - else - Curl_debug(data, CURLINFO_DATA_IN, - k->str, (size_t)nread, conn); - } - -#ifndef CURL_DISABLE_HTTP - if(k->chunk) { - /* - * Here comes a chunked transfer flying and we need to decode this - * properly. While the name says read, this function both reads - * and writes away the data. The returned 'nread' holds the number - * of actual data it wrote to the client. - */ - - CHUNKcode res = - Curl_httpchunk_read(conn, k->str, nread, &nread); - - if(CHUNKE_OK < res) { - if(CHUNKE_WRITE_ERROR == res) { - failf(data, "Failed writing data"); - return CURLE_WRITE_ERROR; - } - failf(data, "Problem (%d) in the Chunked-Encoded data", (int)res); - return CURLE_RECV_ERROR; - } - else if(CHUNKE_STOP == res) { - size_t dataleft; - /* we're done reading chunks! */ - k->keepon &= ~KEEP_RECV; /* read no more */ - - /* There are now possibly N number of bytes at the end of the - str buffer that weren't written to the client. - - We DO care about this data if we are pipelining. - Push it back to be read on the next pass. */ - - dataleft = conn->chunk.dataleft; - if(dataleft != 0) { - infof(conn->data, "Leftovers after chunking: %zu bytes\n", - dataleft); - if(conn->data->multi && - Curl_multi_canPipeline(conn->data->multi)) { - /* only attempt the rewind if we truly are pipelining */ - infof(conn->data, "Rewinding %zu bytes\n",dataleft); - read_rewind(conn, dataleft); - } - } - } - /* If it returned OK, we just keep going */ - } -#endif /* CURL_DISABLE_HTTP */ - - /* Account for body content stored in the header buffer */ - if(k->badheader && !k->ignorebody) { - DEBUGF(infof(data, "Increasing bytecount by %zu from hbuflen\n", - k->hbuflen)); - k->bytecount += k->hbuflen; - } - - if((-1 != k->maxdownload) && - (k->bytecount + nread >= k->maxdownload)) { - - excess = (size_t)(k->bytecount + nread - k->maxdownload); - if(excess > 0 && !k->ignorebody) { - if(conn->data->multi && Curl_multi_canPipeline(conn->data->multi)) { - /* The 'excess' amount below can't be more than BUFSIZE which - always will fit in a size_t */ - infof(data, - "Rewinding stream by : %zu" - " bytes on url %s (size = %" FORMAT_OFF_T - ", maxdownload = %" FORMAT_OFF_T - ", bytecount = %" FORMAT_OFF_T ", nread = %zd)\n", - excess, data->state.path, - k->size, k->maxdownload, k->bytecount, nread); - read_rewind(conn, excess); - } - else { - infof(data, - "Excess found in a non pipelined read:" - " excess = %zu" - ", size = %" FORMAT_OFF_T - ", maxdownload = %" FORMAT_OFF_T - ", bytecount = %" FORMAT_OFF_T "\n", - excess, k->size, k->maxdownload, k->bytecount); - } - } - - nread = (ssize_t) (k->maxdownload - k->bytecount); - if(nread < 0 ) /* this should be unusual */ - nread = 0; - - k->keepon &= ~KEEP_RECV; /* we're done reading */ - } - - k->bytecount += nread; - - Curl_pgrsSetDownloadCounter(data, k->bytecount); - - if(!k->chunk && (nread || k->badheader || is_empty_data)) { - /* If this is chunky transfer, it was already written */ - - if(k->badheader && !k->ignorebody) { - /* we parsed a piece of data wrongly assuming it was a header - and now we output it as body instead */ - - /* Don't let excess data pollute body writes */ - if(k->maxdownload == -1 || (curl_off_t)k->hbuflen <= k->maxdownload) - result = Curl_client_write(conn, CLIENTWRITE_BODY, - data->state.headerbuff, - k->hbuflen); - else - result = Curl_client_write(conn, CLIENTWRITE_BODY, - data->state.headerbuff, - (size_t)k->maxdownload); - - if(result) - return result; - } - if(k->badheader < HEADER_ALLBAD) { - /* This switch handles various content encodings. If there's an - error here, be sure to check over the almost identical code - in curl_http_chunks.c. - Make sure that ALL_CONTENT_ENCODINGS contains all the - encodings handled here. */ -#ifdef HAVE_LIBZ - switch (conn->data->set.http_ce_skip ? - IDENTITY : k->auto_decoding) { - case IDENTITY: -#endif - /* This is the default when the server sends no - Content-Encoding header. See Curl_readwrite_init; the - memset() call initializes k->auto_decoding to zero. */ - if(!k->ignorebody) { - -#ifndef CURL_DISABLE_POP3 - if(conn->handler->protocol&CURLPROTO_POP3) - result = Curl_pop3_write(conn, k->str, nread); - else -#endif /* CURL_DISABLE_POP3 */ - - result = Curl_client_write(conn, CLIENTWRITE_BODY, k->str, - nread); - } -#ifdef HAVE_LIBZ - break; - - case DEFLATE: - /* Assume CLIENTWRITE_BODY; headers are not encoded. */ - if(!k->ignorebody) - result = Curl_unencode_deflate_write(conn, k, nread); - break; - - case GZIP: - /* Assume CLIENTWRITE_BODY; headers are not encoded. */ - if(!k->ignorebody) - result = Curl_unencode_gzip_write(conn, k, nread); - break; - - case COMPRESS: - default: - failf (data, "Unrecognized content encoding type. " - "libcurl understands `identity', `deflate' and `gzip' " - "content encodings."); - result = CURLE_BAD_CONTENT_ENCODING; - break; - } -#endif - } - k->badheader = HEADER_NORMAL; /* taken care of now */ - - if(result) - return result; - } - - } /* if(! header and data to read ) */ - - if(conn->handler->readwrite && - (excess > 0 && !conn->bits.stream_was_rewound)) { - /* Parse the excess data */ - k->str += nread; - nread = (ssize_t)excess; - - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - return result; - - if(readmore) - k->keepon |= KEEP_RECV; /* we're not done reading */ - break; - } - - if(is_empty_data) { - /* if we received nothing, the server closed the connection and we - are done */ - k->keepon &= ~KEEP_RECV; - } - - } while(data_pending(conn)); - - if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) && - conn->bits.close ) { - /* When we've read the entire thing and the close bit is set, the server - may now close the connection. If there's now any kind of sending going - on from our side, we need to stop that immediately. */ - infof(data, "we are done reading and this is set to close, stop send\n"); - k->keepon &= ~KEEP_SEND; /* no writing anymore either */ - } - - return CURLE_OK; -} - -/* - * Send data to upload to the server, when the socket is writable. - */ -static CURLcode readwrite_upload(struct SessionHandle *data, - struct connectdata *conn, - struct SingleRequest *k, - int *didwhat) -{ - ssize_t i, si; - ssize_t bytes_written; - CURLcode result; - ssize_t nread; /* number of bytes read */ - bool sending_http_headers = FALSE; - - if((k->bytecount == 0) && (k->writebytecount == 0)) - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - - *didwhat |= KEEP_SEND; - - /* - * We loop here to do the READ and SEND loop until we run out of - * data to send or until we get EWOULDBLOCK back - * - * FIXME: above comment is misleading. Currently no looping is - * actually done in do-while loop below. - */ - do { - - /* only read more data if there's no upload data already - present in the upload buffer */ - if(0 == data->req.upload_present) { - /* init the "upload from here" pointer */ - data->req.upload_fromhere = k->uploadbuf; - - if(!k->upload_done) { - /* HTTP pollution, this should be written nicer to become more - protocol agnostic. */ - int fillcount; - - if((k->exp100 == EXP100_SENDING_REQUEST) && - (data->state.proto.http->sending == HTTPSEND_BODY)) { - /* If this call is to send body data, we must take some action: - We have sent off the full HTTP 1.1 request, and we shall now - go into the Expect: 100 state and await such a header */ - k->exp100 = EXP100_AWAITING_CONTINUE; /* wait for the header */ - k->keepon &= ~KEEP_SEND; /* disable writing */ - k->start100 = Curl_tvnow(); /* timeout count starts now */ - *didwhat &= ~KEEP_SEND; /* we didn't write anything actually */ - - /* set a timeout for the multi interface */ - Curl_expire(data, CURL_TIMEOUT_EXPECT_100); - break; - } - - if(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP)) { - if(data->state.proto.http->sending == HTTPSEND_REQUEST) - /* We're sending the HTTP request headers, not the data. - Remember that so we don't change the line endings. */ - sending_http_headers = TRUE; - else - sending_http_headers = FALSE; - } - - result = Curl_fillreadbuffer(conn, BUFSIZE, &fillcount); - if(result) - return result; - - nread = (ssize_t)fillcount; - } - else - nread = 0; /* we're done uploading/reading */ - - if(!nread && (k->keepon & KEEP_SEND_PAUSE)) { - /* this is a paused transfer */ - break; - } - else if(nread<=0) { - /* done */ - k->keepon &= ~KEEP_SEND; /* we're done writing */ - - if(conn->bits.rewindaftersend) { - result = Curl_readrewind(conn); - if(result) - return result; - } - break; - } - - /* store number of bytes available for upload */ - data->req.upload_present = nread; - -#ifndef CURL_DISABLE_SMTP - if(conn->handler->protocol & CURLPROTO_SMTP) { - result = Curl_smtp_escape_eob(conn, nread); - if(result) - return result; - } - else -#endif /* CURL_DISABLE_SMTP */ - - /* convert LF to CRLF if so asked */ - if((!sending_http_headers) && ( -#ifdef CURL_DO_LINEEND_CONV - /* always convert if we're FTPing in ASCII mode */ - (data->set.prefer_ascii) || -#endif - (data->set.crlf))) { - if(data->state.scratch == NULL) - data->state.scratch = malloc(2*BUFSIZE); - if(data->state.scratch == NULL) { - failf (data, "Failed to alloc scratch buffer!"); - return CURLE_OUT_OF_MEMORY; - } - /* - * ASCII/EBCDIC Note: This is presumably a text (not binary) - * transfer so the data should already be in ASCII. - * That means the hex values for ASCII CR (0x0d) & LF (0x0a) - * must be used instead of the escape sequences \r & \n. - */ - for(i = 0, si = 0; i < nread; i++, si++) { - if(data->req.upload_fromhere[i] == 0x0a) { - data->state.scratch[si++] = 0x0d; - data->state.scratch[si] = 0x0a; - if(!data->set.crlf) { - /* we're here only because FTP is in ASCII mode... - bump infilesize for the LF we just added */ - data->set.infilesize++; - } - } - else - data->state.scratch[si] = data->req.upload_fromhere[i]; - } - if(si != nread) { - /* only perform the special operation if we really did replace - anything */ - nread = si; - - /* upload from the new (replaced) buffer instead */ - data->req.upload_fromhere = data->state.scratch; - - /* set the new amount too */ - data->req.upload_present = nread; - } - } - } /* if 0 == data->req.upload_present */ - else { - /* We have a partial buffer left from a previous "round". Use - that instead of reading more data */ - } - - /* write to socket (send away data) */ - result = Curl_write(conn, - conn->writesockfd, /* socket to send to */ - data->req.upload_fromhere, /* buffer pointer */ - data->req.upload_present, /* buffer size */ - &bytes_written); /* actually sent */ - - if(result) - return result; - - if(data->set.verbose) - /* show the data before we change the pointer upload_fromhere */ - Curl_debug(data, CURLINFO_DATA_OUT, data->req.upload_fromhere, - (size_t)bytes_written, conn); - - k->writebytecount += bytes_written; - - if(k->writebytecount == data->set.infilesize) { - /* we have sent all data we were supposed to */ - k->upload_done = TRUE; - infof(data, "We are completely uploaded and fine\n"); - } - - if(data->req.upload_present != bytes_written) { - /* we only wrote a part of the buffer (if anything), deal with it! */ - - /* store the amount of bytes left in the buffer to write */ - data->req.upload_present -= bytes_written; - - /* advance the pointer where to find the buffer when the next send - is to happen */ - data->req.upload_fromhere += bytes_written; - } - else { - /* we've uploaded that buffer now */ - data->req.upload_fromhere = k->uploadbuf; - data->req.upload_present = 0; /* no more bytes left */ - - if(k->upload_done) { - /* switch off writing, we're done! */ - k->keepon &= ~KEEP_SEND; /* we're done writing */ - } - } - - Curl_pgrsSetUploadCounter(data, k->writebytecount); - - } WHILE_FALSE; /* just to break out from! */ - - return CURLE_OK; -} - -/* - * Curl_readwrite() is the low-level function to be called when data is to - * be read and written to/from the connection. - */ -CURLcode Curl_readwrite(struct connectdata *conn, - bool *done) -{ - struct SessionHandle *data = conn->data; - struct SingleRequest *k = &data->req; - CURLcode result; - int didwhat=0; - - curl_socket_t fd_read; - curl_socket_t fd_write; - int select_res = conn->cselect_bits; - - conn->cselect_bits = 0; - - /* only use the proper socket if the *_HOLD bit is not set simultaneously as - then we are in rate limiting state in that transfer direction */ - - if((k->keepon & KEEP_RECVBITS) == KEEP_RECV) - fd_read = conn->sockfd; - else - fd_read = CURL_SOCKET_BAD; - - if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) - fd_write = conn->writesockfd; - else - fd_write = CURL_SOCKET_BAD; - - if(!select_res) /* Call for select()/poll() only, if read/write/error - status is not known. */ - select_res = Curl_socket_ready(fd_read, fd_write, 0); - - if(select_res == CURL_CSELECT_ERR) { - failf(data, "select/poll returned error"); - return CURLE_SEND_ERROR; - } - - /* We go ahead and do a read if we have a readable socket or if - the stream was rewound (in which case we have data in a - buffer) */ - if((k->keepon & KEEP_RECV) && - ((select_res & CURL_CSELECT_IN) || conn->bits.stream_was_rewound)) { - - result = readwrite_data(data, conn, k, &didwhat, done); - if(result || *done) - return result; - } - - /* If we still have writing to do, we check if we have a writable socket. */ - if((k->keepon & KEEP_SEND) && (select_res & CURL_CSELECT_OUT)) { - /* write */ - - result = readwrite_upload(data, conn, k, &didwhat); - if(result) - return result; - } - - k->now = Curl_tvnow(); - if(didwhat) { - /* Update read/write counters */ - if(k->bytecountp) - *k->bytecountp = k->bytecount; /* read count */ - if(k->writebytecountp) - *k->writebytecountp = k->writebytecount; /* write count */ - } - else { - /* no read no write, this is a timeout? */ - if(k->exp100 == EXP100_AWAITING_CONTINUE) { - /* This should allow some time for the header to arrive, but only a - very short time as otherwise it'll be too much wasted time too - often. */ - - /* Quoting RFC2616, section "8.2.3 Use of the 100 (Continue) Status": - - Therefore, when a client sends this header field to an origin server - (possibly via a proxy) from which it has never seen a 100 (Continue) - status, the client SHOULD NOT wait for an indefinite period before - sending the request body. - - */ - - long ms = Curl_tvdiff(k->now, k->start100); - if(ms > CURL_TIMEOUT_EXPECT_100) { - /* we've waited long enough, continue anyway */ - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - infof(data, "Done waiting for 100-continue\n"); - } - } - } - - if(Curl_pgrsUpdate(conn)) - result = CURLE_ABORTED_BY_CALLBACK; - else - result = Curl_speedcheck(data, k->now); - if(result) - return result; - - if(k->keepon) { - if(0 > Curl_timeleft(data, &k->now, FALSE)) { - if(k->size != -1) { - failf(data, "Operation timed out after %ld milliseconds with %" - FORMAT_OFF_T " out of %" FORMAT_OFF_T " bytes received", - Curl_tvdiff(k->now, data->progress.t_startsingle), k->bytecount, - k->size); - } - else { - failf(data, "Operation timed out after %ld milliseconds with %" - FORMAT_OFF_T " bytes received", - Curl_tvdiff(k->now, data->progress.t_startsingle), k->bytecount); - } - return CURLE_OPERATION_TIMEDOUT; - } - } - else { - /* - * The transfer has been performed. Just make some general checks before - * returning. - */ - - if(!(data->set.opt_no_body) && (k->size != -1) && - (k->bytecount != k->size) && -#ifdef CURL_DO_LINEEND_CONV - /* Most FTP servers don't adjust their file SIZE response for CRLFs, - so we'll check to see if the discrepancy can be explained - by the number of CRLFs we've changed to LFs. - */ - (k->bytecount != (k->size + data->state.crlf_conversions)) && -#endif /* CURL_DO_LINEEND_CONV */ - !data->req.newurl) { - failf(data, "transfer closed with %" FORMAT_OFF_T - " bytes remaining to read", - k->size - k->bytecount); - return CURLE_PARTIAL_FILE; - } - else if(!(data->set.opt_no_body) && - k->chunk && - (conn->chunk.state != CHUNK_STOP)) { - /* - * In chunked mode, return an error if the connection is closed prior to - * the empty (terminating) chunk is read. - * - * The condition above used to check for - * conn->proto.http->chunk.datasize != 0 which is true after reading - * *any* chunk, not just the empty chunk. - * - */ - failf(data, "transfer closed with outstanding read data remaining"); - return CURLE_PARTIAL_FILE; - } - if(Curl_pgrsUpdate(conn)) - return CURLE_ABORTED_BY_CALLBACK; - } - - /* Now update the "done" boolean we return */ - *done = (0 == (k->keepon&(KEEP_RECV|KEEP_SEND| - KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE; - - return CURLE_OK; -} - -/* - * Curl_single_getsock() gets called by the multi interface code when the app - * has requested to get the sockets for the current connection. This function - * will then be called once for every connection that the multi interface - * keeps track of. This function will only be called for connections that are - * in the proper state to have this information available. - */ -int Curl_single_getsock(const struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks number - of sockets */ - int numsocks) -{ - const struct SessionHandle *data = conn->data; - int bitmap = GETSOCK_BLANK; - unsigned sockindex = 0; - - if(conn->handler->perform_getsock) - return conn->handler->perform_getsock(conn, sock, numsocks); - - if(numsocks < 2) - /* simple check but we might need two slots */ - return GETSOCK_BLANK; - - /* don't include HOLD and PAUSE connections */ - if((data->req.keepon & KEEP_RECVBITS) == KEEP_RECV) { - - DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); - - bitmap |= GETSOCK_READSOCK(sockindex); - sock[sockindex] = conn->sockfd; - } - - /* don't include HOLD and PAUSE connections */ - if((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) { - - if((conn->sockfd != conn->writesockfd) || - !(data->req.keepon & KEEP_RECV)) { - /* only if they are not the same socket or we didn't have a readable - one, we increase index */ - if(data->req.keepon & KEEP_RECV) - sockindex++; /* increase index if we need two entries */ - - DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); - - sock[sockindex] = conn->writesockfd; - } - - bitmap |= GETSOCK_WRITESOCK(sockindex); - } - - return bitmap; -} - -/* - * Determine optimum sleep time based on configured rate, current rate, - * and packet size. - * Returns value in milliseconds. - * - * The basic idea is to adjust the desired rate up/down in this method - * based on whether we are running too slow or too fast. Then, calculate - * how many milliseconds to wait for the next packet to achieve this new - * rate. - */ -long Curl_sleep_time(curl_off_t rate_bps, curl_off_t cur_rate_bps, - int pkt_size) -{ - curl_off_t min_sleep = 0; - curl_off_t rv = 0; - - if(rate_bps == 0) - return 0; - - /* If running faster than about .1% of the desired speed, slow - * us down a bit. Use shift instead of division as the 0.1% - * cutoff is arbitrary anyway. - */ - if(cur_rate_bps > (rate_bps + (rate_bps >> 10))) { - /* running too fast, decrease target rate by 1/64th of rate */ - rate_bps -= rate_bps >> 6; - min_sleep = 1; - } - else if(cur_rate_bps < (rate_bps - (rate_bps >> 10))) { - /* running too slow, increase target rate by 1/64th of rate */ - rate_bps += rate_bps >> 6; - } - - /* Determine number of milliseconds to wait until we do - * the next packet at the adjusted rate. We should wait - * longer when using larger packets, for instance. - */ - rv = ((curl_off_t)((pkt_size * 8) * 1000) / rate_bps); - - /* Catch rounding errors and always slow down at least 1ms if - * we are running too fast. - */ - if(rv < min_sleep) - rv = min_sleep; - - /* Bound value to fit in 'long' on 32-bit platform. That's - * plenty long enough anyway! - */ - if(rv > 0x7fffffff) - rv = 0x7fffffff; - - return (long)rv; -} - - -/* - * Transfer() - * - * This function is what performs the actual transfer. It is capable of doing - * both ways simultaneously. The transfer must already have been setup by a - * call to Curl_setup_transfer(). - * - * Note that headers are created in a preallocated buffer of a default size. - * That buffer can be enlarged on demand, but it is never shrunken again. - * - */ - -static CURLcode -Transfer(struct connectdata *conn) -{ - CURLcode result; - struct SessionHandle *data = conn->data; - struct SingleRequest *k = &data->req; - bool done=FALSE; - bool first=TRUE; - long timeout_ms; - int buffersize; - long totmp; - - if((conn->sockfd == CURL_SOCKET_BAD) && - (conn->writesockfd == CURL_SOCKET_BAD)) - /* nothing to read, nothing to write, we're already OK! */ - return CURLE_OK; - - /* we want header and/or body, if neither then don't do this! */ - if(!k->getheader && data->set.opt_no_body) - return CURLE_OK; - - while(!done) { - curl_socket_t fd_read = conn->sockfd; - curl_socket_t fd_write = conn->writesockfd; - int keepon = k->keepon; - timeout_ms = 1000; - - if(conn->waitfor) { - /* if waitfor is set, get the RECV and SEND bits from that but keep the - other bits */ - keepon &= ~ (KEEP_RECV|KEEP_SEND); - keepon |= conn->waitfor & (KEEP_RECV|KEEP_SEND); - } - - /* limit-rate logic: if speed exceeds threshold, then do not include fd in - select set. The current speed is recalculated in each Curl_readwrite() - call */ - if((keepon & KEEP_SEND) && - (!data->set.max_send_speed || - (data->progress.ulspeed < data->set.max_send_speed) )) { - k->keepon &= ~KEEP_SEND_HOLD; - } - else { - if(data->set.upload && data->set.max_send_speed && - (data->progress.ulspeed > data->set.max_send_speed) ) { - /* calculate upload rate-limitation timeout. */ - buffersize = (int)(data->set.buffer_size ? - data->set.buffer_size : BUFSIZE); - totmp = Curl_sleep_time(data->set.max_send_speed, - data->progress.ulspeed, buffersize); - if(totmp < timeout_ms) - timeout_ms = totmp; - } - fd_write = CURL_SOCKET_BAD; - if(keepon & KEEP_SEND) - k->keepon |= KEEP_SEND_HOLD; /* hold it */ - } - - if((keepon & KEEP_RECV) && - (!data->set.max_recv_speed || - (data->progress.dlspeed < data->set.max_recv_speed)) ) { - k->keepon &= ~KEEP_RECV_HOLD; - } - else { - if((!data->set.upload) && data->set.max_recv_speed && - (data->progress.dlspeed > data->set.max_recv_speed)) { - /* Calculate download rate-limitation timeout. */ - buffersize = (int)(data->set.buffer_size ? - data->set.buffer_size : BUFSIZE); - totmp = Curl_sleep_time(data->set.max_recv_speed, - data->progress.dlspeed, buffersize); - if(totmp < timeout_ms) - timeout_ms = totmp; - } - fd_read = CURL_SOCKET_BAD; - if(keepon & KEEP_RECV) - k->keepon |= KEEP_RECV_HOLD; /* hold it */ - } - - /* pause logic. Don't check descriptors for paused connections */ - if(k->keepon & KEEP_RECV_PAUSE) - fd_read = CURL_SOCKET_BAD; - if(k->keepon & KEEP_SEND_PAUSE) - fd_write = CURL_SOCKET_BAD; - - /* The *_HOLD and *_PAUSE logic is necessary since even though there might - be no traffic during the select interval, we still call - Curl_readwrite() for the timeout case and if we limit transfer speed we - must make sure that this function doesn't transfer anything while in - HOLD status. - - The no timeout for the first round is for the protocols for which data - has already been slurped off the socket and thus waiting for action - won't work since it'll wait even though there is already data present - to work with. */ - if(first && - ((fd_read != CURL_SOCKET_BAD) || (fd_write != CURL_SOCKET_BAD))) - /* if this is the first lap and one of the file descriptors is fine - to work with, skip the timeout */ - timeout_ms = 0; - else { - totmp = Curl_timeleft(data, &k->now, FALSE); - if(totmp < 0) - return CURLE_OPERATION_TIMEDOUT; - else if(!totmp) - totmp = 1000; - - if(totmp < timeout_ms) - timeout_ms = totmp; - } - - switch (Curl_socket_ready(fd_read, fd_write, timeout_ms)) { - case -1: /* select() error, stop reading */ -#ifdef EINTR - /* The EINTR is not serious, and it seems you might get this more - often when using the lib in a multi-threaded environment! */ - if(SOCKERRNO == EINTR) - continue; -#endif - return CURLE_RECV_ERROR; /* indicate a network problem */ - case 0: /* timeout */ - default: /* readable descriptors */ - - result = Curl_readwrite(conn, &done); - /* "done" signals to us if the transfer(s) are ready */ - break; - } - if(result) - return result; - - first = FALSE; /* not the first lap anymore */ - } - - return CURLE_OK; -} - - -/* - * Curl_pretransfer() is called immediately before a transfer starts. - */ -CURLcode Curl_pretransfer(struct SessionHandle *data) -{ - CURLcode res; - if(!data->change.url) { - /* we can't do anything without URL */ - failf(data, "No URL set!"); - return CURLE_URL_MALFORMAT; - } - - /* Init the SSL session ID cache here. We do it here since we want to do it - after the *_setopt() calls (that could specify the size of the cache) but - before any transfer takes place. */ - res = Curl_ssl_initsessions(data, data->set.ssl.max_ssl_sessions); - if(res) - return res; - - data->set.followlocation=0; /* reset the location-follow counter */ - data->state.this_is_a_follow = FALSE; /* reset this */ - data->state.errorbuf = FALSE; /* no error has occurred */ - data->state.httpversion = 0; /* don't assume any particular server version */ - - data->state.ssl_connect_retry = FALSE; - - data->state.authproblem = FALSE; - data->state.authhost.want = data->set.httpauth; - data->state.authproxy.want = data->set.proxyauth; - Curl_safefree(data->info.wouldredirect); - data->info.wouldredirect = NULL; - - /* If there is a list of cookie files to read, do it now! */ - if(data->change.cookielist) - Curl_cookie_loadfiles(data); - - /* If there is a list of host pairs to deal with */ - if(data->change.resolve) - res = Curl_loadhostpairs(data); - - if(!res) { - /* Allow data->set.use_port to set which port to use. This needs to be - * disabled for example when we follow Location: headers to URLs using - * different ports! */ - data->state.allow_port = TRUE; - -#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) - /************************************************************* - * Tell signal handler to ignore SIGPIPE - *************************************************************/ - if(!data->set.no_signal) - data->state.prev_signal = signal(SIGPIPE, SIG_IGN); -#endif - - Curl_initinfo(data); /* reset session-specific information "variables" */ - Curl_pgrsStartNow(data); - - if(data->set.timeout) - Curl_expire(data, data->set.timeout); - - if(data->set.connecttimeout) - Curl_expire(data, data->set.connecttimeout); - - /* In case the handle is re-used and an authentication method was picked - in the session we need to make sure we only use the one(s) we now - consider to be fine */ - data->state.authhost.picked &= data->state.authhost.want; - data->state.authproxy.picked &= data->state.authproxy.want; - } - - return res; -} - -/* - * Curl_posttransfer() is called immediately after a transfer ends - */ -CURLcode Curl_posttransfer(struct SessionHandle *data) -{ -#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL) - /* restore the signal handler for SIGPIPE before we get back */ - if(!data->set.no_signal) - signal(SIGPIPE, data->state.prev_signal); -#else - (void)data; /* unused parameter */ -#endif - - return CURLE_OK; -} - -#ifndef CURL_DISABLE_HTTP -/* - * strlen_url() returns the length of the given URL if the spaces within the - * URL were properly URL encoded. - */ -static size_t strlen_url(const char *url) -{ - const char *ptr; - size_t newlen=0; - bool left=TRUE; /* left side of the ? */ - - for(ptr=url; *ptr; ptr++) { - switch(*ptr) { - case '?': - left=FALSE; - /* fall through */ - default: - newlen++; - break; - case ' ': - if(left) - newlen+=3; - else - newlen++; - break; - } - } - return newlen; -} - -/* strcpy_url() copies a url to a output buffer and URL-encodes the spaces in - * the source URL accordingly. - */ -static void strcpy_url(char *output, const char *url) -{ - /* we must add this with whitespace-replacing */ - bool left=TRUE; - const char *iptr; - char *optr = output; - for(iptr = url; /* read from here */ - *iptr; /* until zero byte */ - iptr++) { - switch(*iptr) { - case '?': - left=FALSE; - /* fall through */ - default: - *optr++=*iptr; - break; - case ' ': - if(left) { - *optr++='%'; /* add a '%' */ - *optr++='2'; /* add a '2' */ - *optr++='0'; /* add a '0' */ - } - else - *optr++='+'; /* add a '+' here */ - break; - } - } - *optr=0; /* zero terminate output buffer */ - -} - -/* - * Returns true if the given URL is absolute (as opposed to relative) - */ -static bool is_absolute_url(const char *url) -{ - char prot[16]; /* URL protocol string storage */ - char letter; /* used for a silly sscanf */ - - return (2 == sscanf(url, "%15[^?&/:]://%c", prot, &letter)) ? TRUE : FALSE; -} - -/* - * Concatenate a relative URL to a base URL making it absolute. - * URL-encodes any spaces. - * The returned pointer must be freed by the caller unless NULL - * (returns NULL on out of memory). - */ -static char *concat_url(const char *base, const char *relurl) -{ - /*** - TRY to append this new path to the old URL - to the right of the host part. Oh crap, this is doomed to cause - problems in the future... - */ - char *newest; - char *protsep; - char *pathsep; - size_t newlen; - - const char *useurl = relurl; - size_t urllen; - - /* we must make our own copy of the URL to play with, as it may - point to read-only data */ - char *url_clone=strdup(base); - - if(!url_clone) - return NULL; /* skip out of this NOW */ - - /* protsep points to the start of the host name */ - protsep=strstr(url_clone, "//"); - if(!protsep) - protsep=url_clone; - else - protsep+=2; /* pass the slashes */ - - if('/' != relurl[0]) { - int level=0; - - /* First we need to find out if there's a ?-letter in the URL, - and cut it and the right-side of that off */ - pathsep = strchr(protsep, '?'); - if(pathsep) - *pathsep=0; - - /* we have a relative path to append to the last slash if there's one - available, or if the new URL is just a query string (starts with a - '?') we append the new one at the end of the entire currently worked - out URL */ - if(useurl[0] != '?') { - pathsep = strrchr(protsep, '/'); - if(pathsep) - *pathsep=0; - } - - /* Check if there's any slash after the host name, and if so, remember - that position instead */ - pathsep = strchr(protsep, '/'); - if(pathsep) - protsep = pathsep+1; - else - protsep = NULL; - - /* now deal with one "./" or any amount of "../" in the newurl - and act accordingly */ - - if((useurl[0] == '.') && (useurl[1] == '/')) - useurl+=2; /* just skip the "./" */ - - while((useurl[0] == '.') && - (useurl[1] == '.') && - (useurl[2] == '/')) { - level++; - useurl+=3; /* pass the "../" */ - } - - if(protsep) { - while(level--) { - /* cut off one more level from the right of the original URL */ - pathsep = strrchr(protsep, '/'); - if(pathsep) - *pathsep=0; - else { - *protsep=0; - break; - } - } - } - } - else { - /* We got a new absolute path for this server */ - - if((relurl[0] == '/') && (relurl[1] == '/')) { - /* the new URL starts with //, just keep the protocol part from the - original one */ - *protsep=0; - useurl = &relurl[2]; /* we keep the slashes from the original, so we - skip the new ones */ - } - else { - /* cut off the original URL from the first slash, or deal with URLs - without slash */ - pathsep = strchr(protsep, '/'); - if(pathsep) { - /* When people use badly formatted URLs, such as - "http://www.url.com?dir=/home/daniel" we must not use the first - slash, if there's a ?-letter before it! */ - char *sep = strchr(protsep, '?'); - if(sep && (sep < pathsep)) - pathsep = sep; - *pathsep=0; - } - else { - /* There was no slash. Now, since we might be operating on a badly - formatted URL, such as "http://www.url.com?id=2380" which doesn't - use a slash separator as it is supposed to, we need to check for a - ?-letter as well! */ - pathsep = strchr(protsep, '?'); - if(pathsep) - *pathsep=0; - } - } - } - - /* If the new part contains a space, this is a mighty stupid redirect - but we still make an effort to do "right". To the left of a '?' - letter we replace each space with %20 while it is replaced with '+' - on the right side of the '?' letter. - */ - newlen = strlen_url(useurl); - - urllen = strlen(url_clone); - - newest = malloc(urllen + 1 + /* possible slash */ - newlen + 1 /* zero byte */); - - if(!newest) { - free(url_clone); /* don't leak this */ - return NULL; - } - - /* copy over the root url part */ - memcpy(newest, url_clone, urllen); - - /* check if we need to append a slash */ - if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0])) - ; - else - newest[urllen++]='/'; - - /* then append the new piece on the right side */ - strcpy_url(&newest[urllen], useurl); - - free(url_clone); - - return newest; -} -#endif /* CURL_DISABLE_HTTP */ - -/* - * Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string - * as given by the remote server and set up the new URL to request. - */ -CURLcode Curl_follow(struct SessionHandle *data, - char *newurl, /* this 'newurl' is the Location: string, - and it must be malloc()ed before passed - here */ - followtype type) /* see curl_transfer.h */ -{ -#ifdef CURL_DISABLE_HTTP - (void)data; - (void)newurl; - (void)type; - /* Location: following will not happen when HTTP is disabled */ - return CURLE_TOO_MANY_REDIRECTS; -#else - - /* Location: redirect */ - bool disallowport = FALSE; - - if(type == FOLLOW_REDIR) { - if((data->set.maxredirs != -1) && - (data->set.followlocation >= data->set.maxredirs)) { - failf(data,"Maximum (%ld) redirects followed", data->set.maxredirs); - return CURLE_TOO_MANY_REDIRECTS; - } - - /* mark the next request as a followed location: */ - data->state.this_is_a_follow = TRUE; - - data->set.followlocation++; /* count location-followers */ - - if(data->set.http_auto_referer) { - /* We are asked to automatically set the previous URL as the referer - when we get the next URL. We pick the ->url field, which may or may - not be 100% correct */ - - if(data->change.referer_alloc) { - Curl_safefree(data->change.referer); - data->change.referer_alloc = FALSE; - } - - data->change.referer = strdup(data->change.url); - if(!data->change.referer) - return CURLE_OUT_OF_MEMORY; - data->change.referer_alloc = TRUE; /* yes, free this later */ - } - } - - if(!is_absolute_url(newurl)) { - /*** - *DANG* this is an RFC 2068 violation. The URL is supposed - to be absolute and this doesn't seem to be that! - */ - char *absolute = concat_url(data->change.url, newurl); - if(!absolute) - return CURLE_OUT_OF_MEMORY; - free(newurl); - newurl = absolute; - } - else { - /* This is an absolute URL, don't allow the custom port number */ - disallowport = TRUE; - - if(strchr(newurl, ' ')) { - /* This new URL contains at least one space, this is a mighty stupid - redirect but we still make an effort to do "right". */ - char *newest; - size_t newlen = strlen_url(newurl); - - newest = malloc(newlen+1); /* get memory for this */ - if(!newest) - return CURLE_OUT_OF_MEMORY; - strcpy_url(newest, newurl); /* create a space-free URL */ - - free(newurl); /* that was no good */ - newurl = newest; /* use this instead now */ - } - - } - - if(type == FOLLOW_FAKE) { - /* we're only figuring out the new url if we would've followed locations - but now we're done so we can get out! */ - data->info.wouldredirect = newurl; - return CURLE_OK; - } - - if(disallowport) - data->state.allow_port = FALSE; - - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - - data->change.url = newurl; - data->change.url_alloc = TRUE; - newurl = NULL; /* don't free! */ - - infof(data, "Issue another request to this URL: '%s'\n", data->change.url); - - /* - * We get here when the HTTP code is 300-399 (and 401). We need to perform - * differently based on exactly what return code there was. - * - * News from 7.10.6: we can also get here on a 401 or 407, in case we act on - * a HTTP (proxy-) authentication scheme other than Basic. - */ - switch(data->info.httpcode) { - /* 401 - Act on a WWW-Authenticate, we keep on moving and do the - Authorization: XXXX header in the HTTP request code snippet */ - /* 407 - Act on a Proxy-Authenticate, we keep on moving and do the - Proxy-Authorization: XXXX header in the HTTP request code snippet */ - /* 300 - Multiple Choices */ - /* 306 - Not used */ - /* 307 - Temporary Redirect */ - default: /* for all above (and the unknown ones) */ - /* Some codes are explicitly mentioned since I've checked RFC2616 and they - * seem to be OK to POST to. - */ - break; - case 301: /* Moved Permanently */ - /* (quote from RFC2616, section 10.3.2): - * - * When automatically redirecting a POST request after receiving a 301 - * status code, some existing HTTP/1.0 user agents will erroneously change - * it into a GET request. - * - * ---- - * - * As most of the important user agents do this obvious RFC2616 violation, - * many webservers expect this. So these servers often answers to a POST - * request with an error page. To be sure that libcurl gets the page that - * most user agents would get, libcurl has to force GET. - * - * This behavior can be overridden with CURLOPT_POSTREDIR. - */ - if((data->set.httpreq == HTTPREQ_POST - || data->set.httpreq == HTTPREQ_POST_FORM) - && !(data->set.keep_post & CURL_REDIR_POST_301)) { - infof(data, - "Violate RFC 2616/10.3.2 and switch from POST to GET\n"); - data->set.httpreq = HTTPREQ_GET; - } - break; - case 302: /* Found */ - /* (From 10.3.3) - - Note: RFC 1945 and RFC 2068 specify that the client is not allowed - to change the method on the redirected request. However, most - existing user agent implementations treat 302 as if it were a 303 - response, performing a GET on the Location field-value regardless - of the original request method. The status codes 303 and 307 have - been added for servers that wish to make unambiguously clear which - kind of reaction is expected of the client. - - (From 10.3.4) - - Note: Many pre-HTTP/1.1 user agents do not understand the 303 - status. When interoperability with such clients is a concern, the - 302 status code may be used instead, since most user agents react - to a 302 response as described here for 303. - - This behavior can be overridden with CURLOPT_POSTREDIR - */ - if((data->set.httpreq == HTTPREQ_POST - || data->set.httpreq == HTTPREQ_POST_FORM) - && !(data->set.keep_post & CURL_REDIR_POST_302)) { - infof(data, - "Violate RFC 2616/10.3.3 and switch from POST to GET\n"); - data->set.httpreq = HTTPREQ_GET; - } - break; - - case 303: /* See Other */ - /* Disable both types of POSTs, unless the user explicitely - asks for POST after POST */ - if(data->set.httpreq != HTTPREQ_GET - && !(data->set.keep_post & CURL_REDIR_POST_303)) { - data->set.httpreq = HTTPREQ_GET; /* enforce GET request */ - infof(data, "Disables POST, goes with %s\n", - data->set.opt_no_body?"HEAD":"GET"); - } - break; - case 304: /* Not Modified */ - /* 304 means we did a conditional request and it was "Not modified". - * We shouldn't get any Location: header in this response! - */ - break; - case 305: /* Use Proxy */ - /* (quote from RFC2616, section 10.3.6): - * "The requested resource MUST be accessed through the proxy given - * by the Location field. The Location field gives the URI of the - * proxy. The recipient is expected to repeat this single request - * via the proxy. 305 responses MUST only be generated by origin - * servers." - */ - break; - } - Curl_pgrsTime(data, TIMER_REDIRECT); - Curl_pgrsResetTimesSizes(data); - - return CURLE_OK; -#endif /* CURL_DISABLE_HTTP */ -} - -static CURLcode -connect_host(struct SessionHandle *data, - struct connectdata **conn) -{ - CURLcode res = CURLE_OK; - - bool async; - bool protocol_done=TRUE; /* will be TRUE always since this is only used - within the easy interface */ - Curl_pgrsTime(data, TIMER_STARTSINGLE); - res = Curl_connect(data, conn, &async, &protocol_done); - - if((CURLE_OK == res) && async) { - /* Now, if async is TRUE here, we need to wait for the name - to resolve */ - res = Curl_resolver_wait_resolv(*conn, NULL); - if(CURLE_OK == res) { - /* Resolved, continue with the connection */ - res = Curl_async_resolved(*conn, &protocol_done); - if(res) - *conn = NULL; - } - else { - /* if we can't resolve, we kill this "connection" now */ - (void)Curl_disconnect(*conn, /* dead_connection */ FALSE); - *conn = NULL; - } - } - - return res; -} - -CURLcode -Curl_reconnect_request(struct connectdata **connp) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = *connp; - struct SessionHandle *data = conn->data; - - /* This was a re-use of a connection and we got a write error in the - * DO-phase. Then we DISCONNECT this connection and have another attempt to - * CONNECT and then DO again! The retry cannot possibly find another - * connection to re-use, since we only keep one possible connection for - * each. */ - - infof(data, "Re-used connection seems dead, get a new one\n"); - - conn->bits.close = TRUE; /* enforce close of this connection */ - result = Curl_done(&conn, result, FALSE); /* we are so done with this */ - - /* conn may no longer be a good pointer, clear it to avoid mistakes by - parent functions */ - *connp = NULL; - - /* - * According to bug report #1330310. We need to check for CURLE_SEND_ERROR - * here as well. I figure this could happen when the request failed on a FTP - * connection and thus Curl_done() itself tried to use the connection - * (again). Slight Lack of feedback in the report, but I don't think this - * extra check can do much harm. - */ - if((CURLE_OK == result) || (CURLE_SEND_ERROR == result)) { - bool async; - bool protocol_done = TRUE; - - /* Now, redo the connect and get a new connection */ - result = Curl_connect(data, connp, &async, &protocol_done); - if(CURLE_OK == result) { - /* We have connected or sent away a name resolve query fine */ - - conn = *connp; /* setup conn to again point to something nice */ - if(async) { - /* Now, if async is TRUE here, we need to wait for the name - to resolve */ - result = Curl_resolver_wait_resolv(conn, NULL); - if(result) - return result; - - /* Resolved, continue with the connection */ - result = Curl_async_resolved(conn, &protocol_done); - if(result) - return result; - } - } - } - - return result; -} - -/* Returns CURLE_OK *and* sets '*url' if a request retry is wanted. - - NOTE: that the *url is malloc()ed. */ -CURLcode Curl_retry_request(struct connectdata *conn, - char **url) -{ - struct SessionHandle *data = conn->data; - - *url = NULL; - - /* if we're talking upload, we can't do the checks below, unless the protocol - is HTTP as when uploading over HTTP we will still get a response */ - if(data->set.upload && - !(conn->handler->protocol&(CURLPROTO_HTTP|CURLPROTO_RTSP))) - return CURLE_OK; - - if(/* workaround for broken TLS servers */ data->state.ssl_connect_retry || - ((data->req.bytecount + - data->req.headerbytecount == 0) && - conn->bits.reuse && - !data->set.opt_no_body && - data->set.rtspreq != RTSPREQ_RECEIVE)) { - /* We got no data, we attempted to re-use a connection and yet we want a - "body". This might happen if the connection was left alive when we were - done using it before, but that was closed when we wanted to read from - it again. Bad luck. Retry the same request on a fresh connect! */ - infof(conn->data, "Connection died, retrying a fresh connect\n"); - *url = strdup(conn->data->change.url); - if(!*url) - return CURLE_OUT_OF_MEMORY; - - conn->bits.close = TRUE; /* close this connection */ - conn->bits.retry = TRUE; /* mark this as a connection we're about - to retry. Marking it this way should - prevent i.e HTTP transfers to return - error just because nothing has been - transferred! */ - - - if((conn->handler->protocol&CURLPROTO_HTTP) && - data->state.proto.http->writebytecount) - return Curl_readrewind(conn); - } - return CURLE_OK; -} - -static CURLcode Curl_do_perform(struct SessionHandle *data) -{ - CURLcode res; - CURLcode res2; - struct connectdata *conn=NULL; - char *newurl = NULL; /* possibly a new URL to follow to! */ - followtype follow = FOLLOW_NONE; - - data->state.used_interface = Curl_if_easy; - - res = Curl_pretransfer(data); - if(res) - return res; - - /* - * It is important that there is NO 'return' from this function at any other - * place than falling down to the end of the function! This is because we - * have cleanup stuff that must be done before we get back, and that is only - * performed after this do-while loop. - */ - - for(;;) { - res = connect_host(data, &conn); /* primary connection */ - - if(res == CURLE_OK) { - bool do_done; - if(data->set.connect_only) { - /* keep connection open for application to use the socket */ - conn->bits.close = FALSE; - res = Curl_done(&conn, CURLE_OK, FALSE); - break; - } - res = Curl_do(&conn, &do_done); - - if(res == CURLE_OK) { - if(conn->data->set.wildcardmatch) { - if(conn->data->wildcard.state == CURLWC_DONE || - conn->data->wildcard.state == CURLWC_SKIP) { - /* keep connection open for application to use the socket */ - conn->bits.close = FALSE; - res = Curl_done(&conn, CURLE_OK, FALSE); - break; - } - } - res = Transfer(conn); /* now fetch that URL please */ - if((res == CURLE_OK) || (res == CURLE_RECV_ERROR)) { - bool retry = FALSE; - CURLcode rc = Curl_retry_request(conn, &newurl); - if(rc) - res = rc; - else - retry = (newurl?TRUE:FALSE); - - if(retry) { - /* we know (newurl != NULL) at this point */ - res = CURLE_OK; - follow = FOLLOW_RETRY; - } - else if(res == CURLE_OK) { - /* - * We must duplicate the new URL here as the connection data may - * be free()ed in the Curl_done() function. We prefer the newurl - * one since that's used for redirects or just further requests - * for retries or multi-stage HTTP auth methods etc. - */ - if(data->req.newurl) { - follow = FOLLOW_REDIR; - newurl = strdup(data->req.newurl); - if(!newurl) - res = CURLE_OUT_OF_MEMORY; - } - else if(data->req.location) { - follow = FOLLOW_FAKE; - newurl = strdup(data->req.location); - if(!newurl) - res = CURLE_OUT_OF_MEMORY; - } - } - - /* in the above cases where 'newurl' gets assigned, we have a fresh - * allocated memory pointed to */ - } - if(res != CURLE_OK) { - /* The transfer phase returned error, we mark the connection to get - * closed to prevent being re-used. This is because we can't - * possibly know if the connection is in a good shape or not now. */ - conn->bits.close = TRUE; - - if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) { - /* if we failed anywhere, we must clean up the secondary socket if - it was used */ - Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); - conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; - } - } - - /* Always run Curl_done(), even if some of the previous calls - failed, but return the previous (original) error code */ - res2 = Curl_done(&conn, res, FALSE); - - if(CURLE_OK == res) - res = res2; - } - else if(conn) - /* Curl_do() failed, clean up left-overs in the done-call, but note - that at some cases the conn pointer is NULL when Curl_do() failed - and the connection cache is very small so only call Curl_done() if - conn is still "alive". */ - /* ignore return code since we already have an error to return */ - (void)Curl_done(&conn, res, FALSE); - - /* - * Important: 'conn' cannot be used here, since it may have been closed - * in 'Curl_done' or other functions. - */ - - if((res == CURLE_OK) && follow) { - res = Curl_follow(data, newurl, follow); - if(CURLE_OK == res) { - /* if things went fine, Curl_follow() freed or otherwise took - responsibility for the newurl pointer */ - newurl = NULL; - if(follow >= FOLLOW_RETRY) { - follow = FOLLOW_NONE; - continue; - } - /* else we break out of the loop below */ - } - } - } - break; /* it only reaches here when this shouldn't loop */ - - } /* loop if Location: */ - - if(newurl) - free(newurl); - - if(res && !data->state.errorbuf) { - /* - * As an extra precaution: if no error string has been set and there was - * an error, use the strerror() string or if things are so bad that not - * even that is good, set a bad string that mentions the error code. - */ - const char *str = curl_easy_strerror(res); - if(!str) - failf(data, "unspecified error %d", (int)res); - else - failf(data, "%s", str); - } - - /* run post-transfer unconditionally, but don't clobber the return code if - we already have an error code recorder */ - res2 = Curl_posttransfer(data); - if(!res && res2) - res = res2; - - return res; -} - -/* - * Curl_perform() is the internal high-level function that gets called by the - * external curl_easy_perform() function. It inits, performs and cleans up a - * single file transfer. - */ -CURLcode Curl_perform(struct SessionHandle *data) -{ - CURLcode res; - if(!data->set.wildcardmatch) - return Curl_do_perform(data); - - /* init main wildcard structures */ - res = Curl_wildcard_init(&data->wildcard); - if(res) - return res; - - res = Curl_do_perform(data); - if(res) { - Curl_wildcard_dtor(&data->wildcard); - return res; - } - - /* wildcard loop */ - while(!res && data->wildcard.state != CURLWC_DONE) - res = Curl_do_perform(data); - - Curl_wildcard_dtor(&data->wildcard); - - /* wildcard download finished or failed */ - data->wildcard.state = CURLWC_INIT; - return res; -} - -/* - * Curl_setup_transfer() is called to setup some basic properties for the - * upcoming transfer. - */ -void -Curl_setup_transfer( - struct connectdata *conn, /* connection data */ - int sockindex, /* socket index to read from or -1 */ - curl_off_t size, /* -1 if unknown at this point */ - bool getheader, /* TRUE if header parsing is wanted */ - curl_off_t *bytecountp, /* return number of bytes read or NULL */ - int writesockindex, /* socket index to write to, it may very well be - the same we read from. -1 disables */ - curl_off_t *writecountp /* return number of bytes written or NULL */ - ) -{ - struct SessionHandle *data; - struct SingleRequest *k; - - DEBUGASSERT(conn != NULL); - - data = conn->data; - k = &data->req; - - DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); - - /* now copy all input parameters */ - conn->sockfd = sockindex == -1 ? - CURL_SOCKET_BAD : conn->sock[sockindex]; - conn->writesockfd = writesockindex == -1 ? - CURL_SOCKET_BAD:conn->sock[writesockindex]; - k->getheader = getheader; - - k->size = size; - k->bytecountp = bytecountp; - k->writebytecountp = writecountp; - - /* The code sequence below is placed in this function just because all - necessary input is not always known in do_complete() as this function may - be called after that */ - - if(!k->getheader) { - k->header = FALSE; - if(size > 0) - Curl_pgrsSetDownloadSize(data, size); - } - /* we want header and/or body, if neither then don't do this! */ - if(k->getheader || !data->set.opt_no_body) { - - if(conn->sockfd != CURL_SOCKET_BAD) - k->keepon |= KEEP_RECV; - - if(conn->writesockfd != CURL_SOCKET_BAD) { - /* HTTP 1.1 magic: - - Even if we require a 100-return code before uploading data, we might - need to write data before that since the REQUEST may not have been - finished sent off just yet. - - Thus, we must check if the request has been sent before we set the - state info where we wait for the 100-return code - */ - if((data->state.expect100header) && - (data->state.proto.http->sending == HTTPSEND_BODY)) { - /* wait with write until we either got 100-continue or a timeout */ - k->exp100 = EXP100_AWAITING_CONTINUE; - k->start100 = Curl_tvnow(); - - /* set a timeout for the multi interface */ - Curl_expire(data, CURL_TIMEOUT_EXPECT_100); - } - else { - if(data->state.expect100header) - /* when we've sent off the rest of the headers, we must await a - 100-continue but first finish sending the request */ - k->exp100 = EXP100_SENDING_REQUEST; - - /* enable the write bit when we're not waiting for continue */ - k->keepon |= KEEP_SEND; - } - } /* if(conn->writesockfd != CURL_SOCKET_BAD) */ - } /* if(k->getheader || !data->set.opt_no_body) */ - -} diff --git a/lib/url.c b/lib/url.c deleted file mode 100644 index 52badc5d7..000000000 --- a/lib/url.c +++ /dev/null @@ -1,5423 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#ifdef __VMS -#include -#include -#endif - -#ifndef HAVE_SOCKET -#error "We can't compile without socket() support!" -#endif - -#ifdef HAVE_LIMITS_H -#include -#endif - -#ifdef USE_LIBIDN -#include -#include -#include -#ifdef HAVE_IDN_FREE_H -#include -#else -/* prototype from idn-free.h, not provided by libidn 0.4.5's make install! */ -void idn_free (void *ptr); -#endif -#ifndef HAVE_IDN_FREE -/* if idn_free() was not found in this version of libidn use free() instead */ -#define idn_free(x) (free)(x) -#endif -#elif defined(USE_WIN32_IDN) -/* prototype for curl_win32_idn_to_ascii() */ -int curl_win32_idn_to_ascii(const char *in, char **out); -#endif /* USE_LIBIDN */ - -#include "curl_urldata.h" -#include "curl_netrc.h" - -#include "curl_formdata.h" -#include "curl_sslgen.h" -#include "curl_hostip.h" -#include "curl_transfer.h" -#include "curl_sendf.h" -#include "curl_progress.h" -#include "curl_cookie.h" -#include "curl_strequal.h" -#include "curl_strerror.h" -#include "curl_escape.h" -#include "curl_strtok.h" -#include "curl_share.h" -#include "curl_content_encoding.h" -#include "curl_http_digest.h" -#include "curl_http_negotiate.h" -#include "curl_select.h" -#include "curl_multiif.h" -#include "curl_easyif.h" -#include "curl_speedcheck.h" -#include "curl_rawstr.h" -#include "curl_warnless.h" -#include "curl_non_ascii.h" -#include "curl_inet_pton.h" - -/* And now for the protocols */ -#include "curl_ftp.h" -#include "curl_dict.h" -#include "curl_telnet.h" -#include "curl_tftp.h" -#include "curl_http.h" -#include "curl_file.h" -#include "curl_ldap.h" -#include "curl_ssh.h" -#include "curl_imap.h" -#include "curl_url.h" -#include "curl_connect.h" -#include "curl_inet_ntop.h" -#include "curl_ntlm.h" -#include "curl_ntlm_wb.h" -#include "curl_socks.h" -#include "curl_rtmp.h" -#include "curl_gopher.h" -#include "curl_http_proxy.h" -#include "curl_bundles.h" -#include "curl_conncache.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -/* Local static prototypes */ -static bool ConnectionKillOne(struct SessionHandle *data); -static void conn_free(struct connectdata *conn); -static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke); -static CURLcode do_init(struct connectdata *conn); -static CURLcode parse_url_userpass(struct SessionHandle *data, - struct connectdata *conn, - char *user, char *passwd); -/* - * Protocol table. - */ - -static const struct Curl_handler * const protocols[] = { - -#ifndef CURL_DISABLE_HTTP - &Curl_handler_http, -#endif - -#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) - &Curl_handler_https, -#endif - -#ifndef CURL_DISABLE_FTP - &Curl_handler_ftp, -#endif - -#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) - &Curl_handler_ftps, -#endif - -#ifndef CURL_DISABLE_TELNET - &Curl_handler_telnet, -#endif - -#ifndef CURL_DISABLE_DICT - &Curl_handler_dict, -#endif - -#ifndef CURL_DISABLE_LDAP - &Curl_handler_ldap, -#if !defined(CURL_DISABLE_LDAPS) && \ - ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ - (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) - &Curl_handler_ldaps, -#endif -#endif - -#ifndef CURL_DISABLE_FILE - &Curl_handler_file, -#endif - -#ifndef CURL_DISABLE_TFTP - &Curl_handler_tftp, -#endif - -#ifdef USE_LIBSSH2 - &Curl_handler_scp, - &Curl_handler_sftp, -#endif - -#ifndef CURL_DISABLE_IMAP - &Curl_handler_imap, -#ifdef USE_SSL - &Curl_handler_imaps, -#endif -#endif - -#ifndef CURL_DISABLE_POP3 - &Curl_handler_pop3, -#ifdef USE_SSL - &Curl_handler_pop3s, -#endif -#endif - -#ifndef CURL_DISABLE_SMTP - &Curl_handler_smtp, -#ifdef USE_SSL - &Curl_handler_smtps, -#endif -#endif - -#ifndef CURL_DISABLE_RTSP - &Curl_handler_rtsp, -#endif - -#ifndef CURL_DISABLE_GOPHER - &Curl_handler_gopher, -#endif - -#ifdef USE_LIBRTMP - &Curl_handler_rtmp, - &Curl_handler_rtmpt, - &Curl_handler_rtmpe, - &Curl_handler_rtmpte, - &Curl_handler_rtmps, - &Curl_handler_rtmpts, -#endif - - (struct Curl_handler *) NULL -}; - -/* - * Dummy handler for undefined protocol schemes. - */ - -static const struct Curl_handler Curl_handler_dummy = { - "", /* scheme */ - ZERO_NULL, /* setup_connection */ - ZERO_NULL, /* do_it */ - ZERO_NULL, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - ZERO_NULL, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - 0, /* defport */ - 0, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -static void close_connections(struct SessionHandle *data) -{ - /* Loop through all open connections and kill them one by one */ - bool killed; - do { - killed = ConnectionKillOne(data); - } while(killed); -} - -void Curl_freeset(struct SessionHandle * data) -{ - /* Free all dynamic strings stored in the data->set substructure. */ - enum dupstring i; - for(i=(enum dupstring)0; i < STRING_LAST; i++) - Curl_safefree(data->set.str[i]); - - if(data->change.referer_alloc) { - Curl_safefree(data->change.referer); - data->change.referer_alloc = FALSE; - } - data->change.referer = NULL; -} - -static CURLcode setstropt(char **charp, char * s) -{ - /* Release the previous storage at `charp' and replace by a dynamic storage - copy of `s'. Return CURLE_OK or CURLE_OUT_OF_MEMORY. */ - - Curl_safefree(*charp); - - if(s) { - s = strdup(s); - - if(!s) - return CURLE_OUT_OF_MEMORY; - - *charp = s; - } - - return CURLE_OK; -} - -static CURLcode setstropt_userpwd(char *option, char **user_storage, - char **pwd_storage) -{ - char* separator; - CURLcode result = CURLE_OK; - - if(!option) { - /* we treat a NULL passed in as a hint to clear existing info */ - Curl_safefree(*user_storage); - *user_storage = (char *) NULL; - Curl_safefree(*pwd_storage); - *pwd_storage = (char *) NULL; - return CURLE_OK; - } - - separator = strchr(option, ':'); - if(separator != NULL) { - - /* store username part of option */ - char * p; - size_t username_len = (size_t)(separator-option); - p = malloc(username_len+1); - if(!p) - result = CURLE_OUT_OF_MEMORY; - else { - memcpy(p, option, username_len); - p[username_len] = '\0'; - Curl_safefree(*user_storage); - *user_storage = p; - } - - /* store password part of option */ - if(result == CURLE_OK) - result = setstropt(pwd_storage, separator+1); - } - else { - result = setstropt(user_storage, option); - } - return result; -} - -CURLcode Curl_dupset(struct SessionHandle * dst, struct SessionHandle * src) -{ - CURLcode r = CURLE_OK; - enum dupstring i; - - /* Copy src->set into dst->set first, then deal with the strings - afterwards */ - dst->set = src->set; - - /* clear all string pointers first */ - memset(dst->set.str, 0, STRING_LAST * sizeof(char *)); - - /* duplicate all strings */ - for(i=(enum dupstring)0; i< STRING_LAST; i++) { - r = setstropt(&dst->set.str[i], src->set.str[i]); - if(r != CURLE_OK) - break; - } - - /* If a failure occurred, freeing has to be performed externally. */ - return r; -} - -/* - * This is the internal function curl_easy_cleanup() calls. This should - * cleanup and free all resources associated with this sessionhandle. - * - * NOTE: if we ever add something that attempts to write to a socket or - * similar here, we must ignore SIGPIPE first. It is currently only done - * when curl_easy_perform() is invoked. - */ - -CURLcode Curl_close(struct SessionHandle *data) -{ - struct Curl_multi *m; - - if(!data) - return CURLE_OK; - - Curl_expire(data, 0); /* shut off timers */ - - m = data->multi; - - if(m) - /* This handle is still part of a multi handle, take care of this first - and detach this handle from there. */ - curl_multi_remove_handle(data->multi, data); - - /* Destroy the timeout list that is held in the easy handle. It is - /normally/ done by curl_multi_remove_handle() but this is "just in - case" */ - if(data->state.timeoutlist) { - Curl_llist_destroy(data->state.timeoutlist, NULL); - data->state.timeoutlist = NULL; - } - - data->magic = 0; /* force a clear AFTER the possibly enforced removal from - the multi handle, since that function uses the magic - field! */ - - if(data->state.conn_cache) { - if(data->state.conn_cache->type == CONNCACHE_PRIVATE) { - /* close all connections still alive that are in the private connection - cache, as we no longer have the pointer left to the shared one. */ - close_connections(data); - Curl_conncache_destroy(data->state.conn_cache); - data->state.conn_cache = NULL; - } - } - - if(data->dns.hostcachetype == HCACHE_PRIVATE) - Curl_hostcache_destroy(data); - - if(data->state.rangestringalloc) - free(data->state.range); - - /* Free the pathbuffer */ - Curl_safefree(data->state.pathbuffer); - data->state.path = NULL; - - Curl_safefree(data->state.proto.generic); - - /* Close down all open SSL info and sessions */ - Curl_ssl_close_all(data); - Curl_safefree(data->state.first_host); - Curl_safefree(data->state.scratch); - Curl_ssl_free_certinfo(data); - - if(data->change.referer_alloc) { - Curl_safefree(data->change.referer); - data->change.referer_alloc = FALSE; - } - data->change.referer = NULL; - - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - data->change.url = NULL; - - Curl_safefree(data->state.headerbuff); - - Curl_flush_cookies(data, 1); - - Curl_digest_cleanup(data); - - Curl_safefree(data->info.contenttype); - Curl_safefree(data->info.wouldredirect); - - /* this destroys the channel and we cannot use it anymore after this */ - Curl_resolver_cleanup(data->state.resolver); - - Curl_convert_close(data); - - /* No longer a dirty share, if it exists */ - if(data->share) { - Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); - data->share->dirty--; - Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); - } - - Curl_freeset(data); - free(data); - return CURLE_OK; -} - -/* - * Initialize the UserDefined fields within a SessionHandle. - * This may be safely called on a new or existing SessionHandle. - */ -CURLcode Curl_init_userdefined(struct UserDefined *set) -{ - CURLcode res = CURLE_OK; - - set->out = stdout; /* default output to stdout */ - set->in = stdin; /* default input from stdin */ - set->err = stderr; /* default stderr to stderr */ - - /* use fwrite as default function to store output */ - set->fwrite_func = (curl_write_callback)fwrite; - - /* use fread as default function to read input */ - set->fread_func = (curl_read_callback)fread; - set->is_fread_set = 0; - set->is_fwrite_set = 0; - - set->seek_func = ZERO_NULL; - set->seek_client = ZERO_NULL; - - /* conversion callbacks for non-ASCII hosts */ - set->convfromnetwork = ZERO_NULL; - set->convtonetwork = ZERO_NULL; - set->convfromutf8 = ZERO_NULL; - - set->infilesize = -1; /* we don't know any size */ - set->postfieldsize = -1; /* unknown size */ - set->maxredirs = -1; /* allow any amount by default */ - - set->httpreq = HTTPREQ_GET; /* Default HTTP request */ - set->rtspreq = RTSPREQ_OPTIONS; /* Default RTSP request */ - set->ftp_use_epsv = TRUE; /* FTP defaults to EPSV operations */ - set->ftp_use_eprt = TRUE; /* FTP defaults to EPRT operations */ - set->ftp_use_pret = FALSE; /* mainly useful for drftpd servers */ - set->ftp_filemethod = FTPFILE_MULTICWD; - - set->dns_cache_timeout = 60; /* Timeout every 60 seconds by default */ - - /* Set the default size of the SSL session ID cache */ - set->ssl.max_ssl_sessions = 5; - - set->proxyport = CURL_DEFAULT_PROXY_PORT; /* from curl_url.h */ - set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */ - set->httpauth = CURLAUTH_BASIC; /* defaults to basic */ - set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */ - - /* make libcurl quiet by default: */ - set->hide_progress = TRUE; /* CURLOPT_NOPROGRESS changes these */ - - /* - * libcurl 7.10 introduced SSL verification *by default*! This needs to be - * switched off unless wanted. - */ - set->ssl.verifypeer = TRUE; - set->ssl.verifyhost = TRUE; -#ifdef USE_TLS_SRP - set->ssl.authtype = CURL_TLSAUTH_NONE; -#endif - set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth - type */ - set->ssl.sessionid = TRUE; /* session ID caching enabled by default */ - - set->new_file_perms = 0644; /* Default permissions */ - set->new_directory_perms = 0755; /* Default permissions */ - - /* for the *protocols fields we don't use the CURLPROTO_ALL convenience - define since we internally only use the lower 16 bits for the passed - in bitmask to not conflict with the private bits */ - set->allowed_protocols = CURLPROTO_ALL; - set->redir_protocols = - CURLPROTO_ALL & ~(CURLPROTO_FILE|CURLPROTO_SCP); /* not FILE or SCP */ - -#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); -#elif defined(CURL_CA_PATH) - res = setstropt(&set->str[STRING_SSL_CAPATH], (char *) CURL_CA_PATH); -#endif - - set->wildcardmatch = FALSE; - set->chunk_bgn = ZERO_NULL; - set->chunk_end = ZERO_NULL; - - /* tcp keepalives are disabled by default, but provide reasonable values for - * the interval and idle times. - */ - set->tcp_keepalive = FALSE; - set->tcp_keepintvl = 60; - set->tcp_keepidle = 60; - - return res; -} - -/** - * Curl_open() - * - * @param curl is a pointer to a sessionhandle pointer that gets set by this - * function. - * @return CURLcode - */ - -CURLcode Curl_open(struct SessionHandle **curl) -{ - CURLcode res = CURLE_OK; - struct SessionHandle *data; - CURLcode status; - - /* Very simple start-up: alloc the struct, init it with zeroes and return */ - data = calloc(1, sizeof(struct SessionHandle)); - if(!data) { - /* this is a very serious error */ - DEBUGF(fprintf(stderr, "Error: calloc of SessionHandle failed\n")); - return CURLE_OUT_OF_MEMORY; - } - - data->magic = CURLEASY_MAGIC_NUMBER; - - status = Curl_resolver_init(&data->state.resolver); - if(status) { - DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); - free(data); - return status; - } - - /* We do some initial setup here, all those fields that can't be just 0 */ - - data->state.headerbuff = malloc(HEADERSIZE); - if(!data->state.headerbuff) { - DEBUGF(fprintf(stderr, "Error: malloc of headerbuff failed\n")); - res = CURLE_OUT_OF_MEMORY; - } - else { - Curl_easy_initHandleData(data); - res = Curl_init_userdefined(&data->set); - - data->state.headersize=HEADERSIZE; - - Curl_convert_init(data); - - /* most recent connection is not yet defined */ - data->state.lastconnect = NULL; - - data->progress.flags |= PGRS_HIDE; - data->state.current_speed = -1; /* init to negative == impossible */ - - data->wildcard.state = CURLWC_INIT; - data->wildcard.filelist = NULL; - data->set.fnmatch = ZERO_NULL; - /* This no longer creates a connection cache here. It is instead made on - the first call to curl_easy_perform() or when the handle is added to a - multi stack. */ - } - - - if(res) { - Curl_resolver_cleanup(data->state.resolver); - if(data->state.headerbuff) - free(data->state.headerbuff); - Curl_freeset(data); - free(data); - data = NULL; - } - else - *curl = data; - - return res; -} - -CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, - va_list param) -{ - char *argptr; - CURLcode result = CURLE_OK; - long arg; -#ifndef CURL_DISABLE_HTTP - curl_off_t bigsize; -#endif - - switch(option) { - case CURLOPT_DNS_CACHE_TIMEOUT: - data->set.dns_cache_timeout = va_arg(param, long); - break; - case CURLOPT_DNS_USE_GLOBAL_CACHE: - /* remember we want this enabled */ - arg = va_arg(param, long); - data->set.global_dns_cache = (0 != arg)?TRUE:FALSE; - break; - case CURLOPT_SSL_CIPHER_LIST: - /* set a list of cipher we want to use in the SSL connection */ - result = setstropt(&data->set.str[STRING_SSL_CIPHER_LIST], - va_arg(param, char *)); - break; - - case CURLOPT_RANDOM_FILE: - /* - * This is the path name to a file that contains random data to seed - * the random SSL stuff with. The file is only used for reading. - */ - result = setstropt(&data->set.str[STRING_SSL_RANDOM_FILE], - va_arg(param, char *)); - break; - case CURLOPT_EGDSOCKET: - /* - * The Entropy Gathering Daemon socket pathname - */ - result = setstropt(&data->set.str[STRING_SSL_EGDSOCKET], - va_arg(param, char *)); - break; - case CURLOPT_MAXCONNECTS: - /* - * Set the absolute number of maximum simultaneous alive connection that - * libcurl is allowed to have. - */ - data->set.maxconnects = va_arg(param, long); - break; - case CURLOPT_FORBID_REUSE: - /* - * When this transfer is done, it must not be left to be reused by a - * subsequent transfer but shall be closed immediately. - */ - data->set.reuse_forbid = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_FRESH_CONNECT: - /* - * This transfer shall not use a previously cached connection but - * should be made with a fresh new connect! - */ - data->set.reuse_fresh = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_VERBOSE: - /* - * Verbose means infof() calls that give a lot of information about - * the connection and transfer procedures as well as internal choices. - */ - data->set.verbose = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_HEADER: - /* - * Set to include the header in the general data output stream. - */ - data->set.include_header = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_NOPROGRESS: - /* - * Shut off the internal supported progress meter - */ - data->set.hide_progress = (0 != va_arg(param, long))?TRUE:FALSE; - if(data->set.hide_progress) - data->progress.flags |= PGRS_HIDE; - else - data->progress.flags &= ~PGRS_HIDE; - break; - case CURLOPT_NOBODY: - /* - * Do not include the body part in the output data stream. - */ - data->set.opt_no_body = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_FAILONERROR: - /* - * Don't output the >=300 error code HTML-page, but instead only - * return error. - */ - data->set.http_fail_on_error = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_UPLOAD: - case CURLOPT_PUT: - /* - * We want to sent data to the remote host. If this is HTTP, that equals - * using the PUT request. - */ - data->set.upload = (0 != va_arg(param, long))?TRUE:FALSE; - if(data->set.upload) { - /* If this is HTTP, PUT is what's needed to "upload" */ - data->set.httpreq = HTTPREQ_PUT; - data->set.opt_no_body = FALSE; /* this is implied */ - } - else - /* In HTTP, the opposite of upload is GET (unless NOBODY is true as - then this can be changed to HEAD later on) */ - data->set.httpreq = HTTPREQ_GET; - break; - case CURLOPT_FILETIME: - /* - * Try to get the file time of the remote document. The time will - * later (possibly) become available using curl_easy_getinfo(). - */ - data->set.get_filetime = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_FTP_CREATE_MISSING_DIRS: - /* - * An FTP option that modifies an upload to create missing directories on - * the server. - */ - switch(va_arg(param, long)) { - case 0: - data->set.ftp_create_missing_dirs = 0; - break; - case 1: - data->set.ftp_create_missing_dirs = 1; - break; - case 2: - data->set.ftp_create_missing_dirs = 2; - break; - default: - /* reserve other values for future use */ - result = CURLE_UNKNOWN_OPTION; - break; - } - break; - case CURLOPT_SERVER_RESPONSE_TIMEOUT: - /* - * Option that specifies how quickly an server response must be obtained - * before it is considered failure. For pingpong protocols. - */ - data->set.server_response_timeout = va_arg( param , long ) * 1000; - break; - case CURLOPT_TFTP_BLKSIZE: - /* - * TFTP option that specifies the block size to use for data transmission - */ - data->set.tftp_blksize = va_arg(param, long); - break; - case CURLOPT_DIRLISTONLY: - /* - * An option that changes the command to one that asks for a list - * only, no file info details. - */ - data->set.ftp_list_only = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_APPEND: - /* - * We want to upload and append to an existing file. - */ - data->set.ftp_append = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_FTP_FILEMETHOD: - /* - * How do access files over FTP. - */ - data->set.ftp_filemethod = (curl_ftpfile)va_arg(param, long); - break; - case CURLOPT_NETRC: - /* - * Parse the $HOME/.netrc file - */ - data->set.use_netrc = (enum CURL_NETRC_OPTION)va_arg(param, long); - break; - case CURLOPT_NETRC_FILE: - /* - * Use this file instead of the $HOME/.netrc file - */ - result = setstropt(&data->set.str[STRING_NETRC_FILE], - va_arg(param, char *)); - break; - case CURLOPT_TRANSFERTEXT: - /* - * This option was previously named 'FTPASCII'. Renamed to work with - * more protocols than merely FTP. - * - * Transfer using ASCII (instead of BINARY). - */ - data->set.prefer_ascii = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_TIMECONDITION: - /* - * Set HTTP time condition. This must be one of the defines in the - * curl/curl.h header file. - */ - data->set.timecondition = (curl_TimeCond)va_arg(param, long); - break; - case CURLOPT_TIMEVALUE: - /* - * This is the value to compare with the remote document with the - * method set with CURLOPT_TIMECONDITION - */ - data->set.timevalue = (time_t)va_arg(param, long); - break; - case CURLOPT_SSLVERSION: - /* - * Set explicit SSL version to try to connect with, as some SSL - * implementations are lame. - */ - data->set.ssl.version = va_arg(param, long); - break; - -#ifndef CURL_DISABLE_HTTP - case CURLOPT_AUTOREFERER: - /* - * Switch on automatic referer that gets set if curl follows locations. - */ - data->set.http_auto_referer = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_ACCEPT_ENCODING: - /* - * String to use at the value of Accept-Encoding header. - * - * If the encoding is set to "" we use an Accept-Encoding header that - * encompasses all the encodings we support. - * If the encoding is set to NULL we don't send an Accept-Encoding header - * and ignore an received Content-Encoding header. - * - */ - argptr = va_arg(param, char *); - result = setstropt(&data->set.str[STRING_ENCODING], - (argptr && !*argptr)? - (char *) ALL_CONTENT_ENCODINGS: argptr); - break; - - case CURLOPT_TRANSFER_ENCODING: - data->set.http_transfer_encoding = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_FOLLOWLOCATION: - /* - * Follow Location: header hints on a HTTP-server. - */ - data->set.http_follow_location = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_UNRESTRICTED_AUTH: - /* - * Send authentication (user+password) when following locations, even when - * hostname changed. - */ - data->set.http_disable_hostname_check_before_authentication = - (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_MAXREDIRS: - /* - * The maximum amount of hops you allow curl to follow Location: - * headers. This should mostly be used to detect never-ending loops. - */ - data->set.maxredirs = va_arg(param, long); - break; - - case CURLOPT_POSTREDIR: - { - /* - * Set the behaviour of POST when redirecting - * CURL_REDIR_GET_ALL - POST is changed to GET after 301 and 302 - * CURL_REDIR_POST_301 - POST is kept as POST after 301 - * CURL_REDIR_POST_302 - POST is kept as POST after 302 - * CURL_REDIR_POST_303 - POST is kept as POST after 303 - * CURL_REDIR_POST_ALL - POST is kept as POST after 301, 302 and 303 - * other - POST is kept as POST after 301 and 302 - */ - int postRedir = curlx_sltosi(va_arg(param, long)); - data->set.keep_post = postRedir & CURL_REDIR_POST_ALL; - } - break; - - case CURLOPT_POST: - /* Does this option serve a purpose anymore? Yes it does, when - CURLOPT_POSTFIELDS isn't used and the POST data is read off the - callback! */ - if(va_arg(param, long)) { - data->set.httpreq = HTTPREQ_POST; - data->set.opt_no_body = FALSE; /* this is implied */ - } - else - data->set.httpreq = HTTPREQ_GET; - break; - - case CURLOPT_COPYPOSTFIELDS: - /* - * A string with POST data. Makes curl HTTP POST. Even if it is NULL. - * If needed, CURLOPT_POSTFIELDSIZE must have been set prior to - * CURLOPT_COPYPOSTFIELDS and not altered later. - */ - argptr = va_arg(param, char *); - - if(!argptr || data->set.postfieldsize == -1) - result = setstropt(&data->set.str[STRING_COPYPOSTFIELDS], argptr); - else { - /* - * Check that requested length does not overflow the size_t type. - */ - - if((data->set.postfieldsize < 0) || - ((sizeof(curl_off_t) != sizeof(size_t)) && - (data->set.postfieldsize > (curl_off_t)((size_t)-1)))) - result = CURLE_OUT_OF_MEMORY; - else { - char * p; - - (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - - /* Allocate even when size == 0. This satisfies the need of possible - later address compare to detect the COPYPOSTFIELDS mode, and - to mark that postfields is used rather than read function or - form data. - */ - p = malloc((size_t)(data->set.postfieldsize? - data->set.postfieldsize:1)); - - if(!p) - result = CURLE_OUT_OF_MEMORY; - else { - if(data->set.postfieldsize) - memcpy(p, argptr, (size_t)data->set.postfieldsize); - - data->set.str[STRING_COPYPOSTFIELDS] = p; - } - } - } - - data->set.postfields = data->set.str[STRING_COPYPOSTFIELDS]; - data->set.httpreq = HTTPREQ_POST; - break; - - case CURLOPT_POSTFIELDS: - /* - * Like above, but use static data instead of copying it. - */ - data->set.postfields = va_arg(param, void *); - /* Release old copied data. */ - (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.httpreq = HTTPREQ_POST; - break; - - case CURLOPT_POSTFIELDSIZE: - /* - * The size of the POSTFIELD data to prevent libcurl to do strlen() to - * figure it out. Enables binary posts. - */ - bigsize = va_arg(param, long); - - if(data->set.postfieldsize < bigsize && - data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { - /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.postfields = NULL; - } - - data->set.postfieldsize = bigsize; - break; - - case CURLOPT_POSTFIELDSIZE_LARGE: - /* - * The size of the POSTFIELD data to prevent libcurl to do strlen() to - * figure it out. Enables binary posts. - */ - bigsize = va_arg(param, curl_off_t); - - if(data->set.postfieldsize < bigsize && - data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { - /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.postfields = NULL; - } - - data->set.postfieldsize = bigsize; - break; - - case CURLOPT_HTTPPOST: - /* - * Set to make us do HTTP POST - */ - data->set.httppost = va_arg(param, struct curl_httppost *); - data->set.httpreq = HTTPREQ_POST_FORM; - data->set.opt_no_body = FALSE; /* this is implied */ - break; - - case CURLOPT_REFERER: - /* - * String to set in the HTTP Referer: field. - */ - if(data->change.referer_alloc) { - Curl_safefree(data->change.referer); - data->change.referer_alloc = FALSE; - } - result = setstropt(&data->set.str[STRING_SET_REFERER], - va_arg(param, char *)); - data->change.referer = data->set.str[STRING_SET_REFERER]; - break; - - case CURLOPT_USERAGENT: - /* - * String to use in the HTTP User-Agent field - */ - result = setstropt(&data->set.str[STRING_USERAGENT], - va_arg(param, char *)); - break; - - case CURLOPT_HTTPHEADER: - /* - * Set a list with HTTP headers to use (or replace internals with) - */ - data->set.headers = va_arg(param, struct curl_slist *); - break; - - case CURLOPT_HTTP200ALIASES: - /* - * Set a list of aliases for HTTP 200 in response header - */ - data->set.http200aliases = va_arg(param, struct curl_slist *); - break; - -#if !defined(CURL_DISABLE_COOKIES) - case CURLOPT_COOKIE: - /* - * Cookie string to send to the remote server in the request. - */ - result = setstropt(&data->set.str[STRING_COOKIE], - va_arg(param, char *)); - break; - - case CURLOPT_COOKIEFILE: - /* - * Set cookie file to read and parse. Can be used multiple times. - */ - argptr = (char *)va_arg(param, void *); - if(argptr) { - struct curl_slist *cl; - /* append the cookie file name to the list of file names, and deal with - them later */ - cl = curl_slist_append(data->change.cookielist, argptr); - if(!cl) { - curl_slist_free_all(data->change.cookielist); - data->change.cookielist = NULL; - return CURLE_OUT_OF_MEMORY; - } - data->change.cookielist = cl; /* store the list for later use */ - } - break; - - case CURLOPT_COOKIEJAR: - /* - * Set cookie file name to dump all cookies to when we're done. - */ - result = setstropt(&data->set.str[STRING_COOKIEJAR], - va_arg(param, char *)); - - /* - * Activate the cookie parser. This may or may not already - * have been made. - */ - data->cookies = Curl_cookie_init(data, NULL, data->cookies, - data->set.cookiesession); - break; - - case CURLOPT_COOKIESESSION: - /* - * Set this option to TRUE to start a new "cookie session". It will - * prevent the forthcoming read-cookies-from-file actions to accept - * cookies that are marked as being session cookies, as they belong to a - * previous session. - * - * In the original Netscape cookie spec, "session cookies" are cookies - * with no expire date set. RFC2109 describes the same action if no - * 'Max-Age' is set and RFC2965 includes the RFC2109 description and adds - * a 'Discard' action that can enforce the discard even for cookies that - * have a Max-Age. - * - * We run mostly with the original cookie spec, as hardly anyone implements - * anything else. - */ - data->set.cookiesession = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_COOKIELIST: - argptr = va_arg(param, char *); - - if(argptr == NULL) - break; - - if(Curl_raw_equal(argptr, "ALL")) { - /* clear all cookies */ - Curl_cookie_clearall(data->cookies); - break; - } - else if(Curl_raw_equal(argptr, "SESS")) { - /* clear session cookies */ - Curl_cookie_clearsess(data->cookies); - break; - } - else if(Curl_raw_equal(argptr, "FLUSH")) { - /* flush cookies to file */ - Curl_flush_cookies(data, 0); - break; - } - - if(!data->cookies) - /* if cookie engine was not running, activate it */ - data->cookies = Curl_cookie_init(data, NULL, NULL, TRUE); - - argptr = strdup(argptr); - if(!argptr) { - result = CURLE_OUT_OF_MEMORY; - break; - } - - if(checkprefix("Set-Cookie:", argptr)) - /* HTTP Header format line */ - Curl_cookie_add(data, data->cookies, TRUE, argptr + 11, NULL, NULL); - - else - /* Netscape format line */ - Curl_cookie_add(data, data->cookies, FALSE, argptr, NULL, NULL); - - free(argptr); - break; -#endif /* CURL_DISABLE_COOKIES */ - - case CURLOPT_HTTPGET: - /* - * Set to force us do HTTP GET - */ - if(va_arg(param, long)) { - data->set.httpreq = HTTPREQ_GET; - data->set.upload = FALSE; /* switch off upload */ - data->set.opt_no_body = FALSE; /* this is implied */ - } - break; - - case CURLOPT_HTTP_VERSION: - /* - * This sets a requested HTTP version to be used. The value is one of - * the listed enums in curl/curl.h. - */ - data->set.httpversion = va_arg(param, long); - break; - - case CURLOPT_HTTPAUTH: - /* - * Set HTTP Authentication type BITMASK. - */ - { - int bitcheck; - bool authbits; - unsigned long auth = va_arg(param, unsigned long); - - if(auth == CURLAUTH_NONE) { - data->set.httpauth = auth; - break; - } - - /* the DIGEST_IE bit is only used to set a special marker, for all the - rest we need to handle it as normal DIGEST */ - data->state.authhost.iestyle = (auth & CURLAUTH_DIGEST_IE)?TRUE:FALSE; - - if(auth & CURLAUTH_DIGEST_IE) { - auth |= CURLAUTH_DIGEST; /* set standard digest bit */ - auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ - } - - /* switch off bits we can't support */ -#ifndef USE_NTLM - auth &= ~CURLAUTH_NTLM; /* no NTLM support */ - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#elif !defined(NTLM_WB_ENABLED) - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#endif -#ifndef USE_HTTP_NEGOTIATE - auth &= ~CURLAUTH_GSSNEGOTIATE; /* no GSS-Negotiate without GSSAPI or - WINDOWS_SSPI */ -#endif - - /* check if any auth bit lower than CURLAUTH_ONLY is still set */ - bitcheck = 0; - authbits = FALSE; - while(bitcheck < 31) { - if(auth & (1UL << bitcheck++)) { - authbits = TRUE; - break; - } - } - if(!authbits) - return CURLE_NOT_BUILT_IN; /* no supported types left! */ - - data->set.httpauth = auth; - } - break; - -#endif /* CURL_DISABLE_HTTP */ - - case CURLOPT_CUSTOMREQUEST: - /* - * Set a custom string to use as request - */ - result = setstropt(&data->set.str[STRING_CUSTOMREQUEST], - va_arg(param, char *)); - - /* we don't set - data->set.httpreq = HTTPREQ_CUSTOM; - here, we continue as if we were using the already set type - and this just changes the actual request keyword */ - break; - -#ifndef CURL_DISABLE_PROXY - case CURLOPT_HTTPPROXYTUNNEL: - /* - * Tunnel operations through the proxy instead of normal proxy use - */ - data->set.tunnel_thru_httpproxy = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_PROXYPORT: - /* - * Explicitly set HTTP proxy port number. - */ - data->set.proxyport = va_arg(param, long); - break; - - case CURLOPT_PROXYAUTH: - /* - * Set HTTP Authentication type BITMASK. - */ - { - int bitcheck; - bool authbits; - unsigned long auth = va_arg(param, unsigned long); - - if(auth == CURLAUTH_NONE) { - data->set.proxyauth = auth; - break; - } - - /* the DIGEST_IE bit is only used to set a special marker, for all the - rest we need to handle it as normal DIGEST */ - data->state.authproxy.iestyle = (auth & CURLAUTH_DIGEST_IE)?TRUE:FALSE; - - if(auth & CURLAUTH_DIGEST_IE) { - auth |= CURLAUTH_DIGEST; /* set standard digest bit */ - auth &= ~CURLAUTH_DIGEST_IE; /* unset ie digest bit */ - } - /* switch off bits we can't support */ -#ifndef USE_NTLM - auth &= ~CURLAUTH_NTLM; /* no NTLM support */ - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#elif !defined(NTLM_WB_ENABLED) - auth &= ~CURLAUTH_NTLM_WB; /* no NTLM_WB support */ -#endif -#ifndef USE_HTTP_NEGOTIATE - auth &= ~CURLAUTH_GSSNEGOTIATE; /* no GSS-Negotiate without GSSAPI or - WINDOWS_SSPI */ -#endif - - /* check if any auth bit lower than CURLAUTH_ONLY is still set */ - bitcheck = 0; - authbits = FALSE; - while(bitcheck < 31) { - if(auth & (1UL << bitcheck++)) { - authbits = TRUE; - break; - } - } - if(!authbits) - return CURLE_NOT_BUILT_IN; /* no supported types left! */ - - data->set.proxyauth = auth; - } - break; - - case CURLOPT_PROXY: - /* - * Set proxy server:port to use as HTTP proxy. - * - * If the proxy is set to "" we explicitly say that we don't want to use a - * proxy (even though there might be environment variables saying so). - * - * Setting it to NULL, means no proxy but allows the environment variables - * to decide for us. - */ - result = setstropt(&data->set.str[STRING_PROXY], - va_arg(param, char *)); - break; - - case CURLOPT_PROXYTYPE: - /* - * Set proxy type. HTTP/HTTP_1_0/SOCKS4/SOCKS4a/SOCKS5/SOCKS5_HOSTNAME - */ - data->set.proxytype = (curl_proxytype)va_arg(param, long); - break; - - case CURLOPT_PROXY_TRANSFER_MODE: - /* - * set transfer mode (;type=) when doing FTP via an HTTP proxy - */ - switch (va_arg(param, long)) { - case 0: - data->set.proxy_transfer_mode = FALSE; - break; - case 1: - data->set.proxy_transfer_mode = TRUE; - break; - default: - /* reserve other values for future use */ - result = CURLE_UNKNOWN_OPTION; - break; - } - break; -#endif /* CURL_DISABLE_PROXY */ - -#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 = (0 != va_arg(param, long))?TRUE:FALSE; - break; -#endif - - case CURLOPT_WRITEHEADER: - /* - * Custom pointer to pass the header write callback function - */ - data->set.writeheader = (void *)va_arg(param, void *); - break; - case CURLOPT_ERRORBUFFER: - /* - * Error buffer provided by the caller to get the human readable - * error string in. - */ - data->set.errorbuffer = va_arg(param, char *); - break; - case CURLOPT_FILE: - /* - * FILE pointer to write to or include in the data write callback - */ - data->set.out = va_arg(param, FILE *); - break; - case CURLOPT_FTPPORT: - /* - * Use FTP PORT, this also specifies which IP address to use - */ - result = setstropt(&data->set.str[STRING_FTPPORT], - va_arg(param, char *)); - data->set.ftp_use_port = (NULL != data->set.str[STRING_FTPPORT]) ? - TRUE:FALSE; - break; - - case CURLOPT_FTP_USE_EPRT: - data->set.ftp_use_eprt = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_FTP_USE_EPSV: - data->set.ftp_use_epsv = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_FTP_USE_PRET: - data->set.ftp_use_pret = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_FTP_SSL_CCC: - data->set.ftp_ccc = (curl_ftpccc)va_arg(param, long); - break; - - case CURLOPT_FTP_SKIP_PASV_IP: - /* - * Enable or disable FTP_SKIP_PASV_IP, which will disable/enable the - * bypass of the IP address in PASV responses. - */ - data->set.ftp_skip_ip = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_INFILE: - /* - * FILE pointer to read the file to be uploaded from. Or possibly - * used as argument to the read callback. - */ - data->set.in = va_arg(param, FILE *); - break; - case CURLOPT_INFILESIZE: - /* - * If known, this should inform curl about the file size of the - * to-be-uploaded file. - */ - data->set.infilesize = va_arg(param, long); - break; - case CURLOPT_INFILESIZE_LARGE: - /* - * If known, this should inform curl about the file size of the - * to-be-uploaded file. - */ - data->set.infilesize = va_arg(param, curl_off_t); - break; - case CURLOPT_LOW_SPEED_LIMIT: - /* - * The low speed limit that if transfers are below this for - * CURLOPT_LOW_SPEED_TIME, the transfer is aborted. - */ - data->set.low_speed_limit=va_arg(param, long); - break; - case CURLOPT_MAX_SEND_SPEED_LARGE: - /* - * When transfer uploads are faster then CURLOPT_MAX_SEND_SPEED_LARGE - * bytes per second the transfer is throttled.. - */ - data->set.max_send_speed=va_arg(param, curl_off_t); - break; - case CURLOPT_MAX_RECV_SPEED_LARGE: - /* - * When receiving data faster than CURLOPT_MAX_RECV_SPEED_LARGE bytes per - * second the transfer is throttled.. - */ - data->set.max_recv_speed=va_arg(param, curl_off_t); - break; - case CURLOPT_LOW_SPEED_TIME: - /* - * The low speed time that if transfers are below the set - * CURLOPT_LOW_SPEED_LIMIT during this time, the transfer is aborted. - */ - data->set.low_speed_time=va_arg(param, long); - break; - case CURLOPT_URL: - /* - * The URL to fetch. - */ - if(data->change.url_alloc) { - /* the already set URL is allocated, free it first! */ - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - result = setstropt(&data->set.str[STRING_SET_URL], - va_arg(param, char *)); - data->change.url = data->set.str[STRING_SET_URL]; - break; - case CURLOPT_PORT: - /* - * The port number to use when getting the URL - */ - data->set.use_port = va_arg(param, long); - break; - case CURLOPT_TIMEOUT: - /* - * The maximum time you allow curl to use for a single transfer - * operation. - */ - data->set.timeout = va_arg(param, long) * 1000L; - break; - - case CURLOPT_TIMEOUT_MS: - data->set.timeout = va_arg(param, long); - break; - - case CURLOPT_CONNECTTIMEOUT: - /* - * The maximum time you allow curl to use to connect. - */ - data->set.connecttimeout = va_arg(param, long) * 1000L; - break; - - case CURLOPT_CONNECTTIMEOUT_MS: - data->set.connecttimeout = va_arg(param, long); - break; - - case CURLOPT_ACCEPTTIMEOUT_MS: - /* - * The maximum time you allow curl to wait for server connect - */ - data->set.accepttimeout = va_arg(param, long); - break; - - case CURLOPT_USERPWD: - /* - * user:password to use in the operation - */ - result = setstropt_userpwd(va_arg(param, char *), - &data->set.str[STRING_USERNAME], - &data->set.str[STRING_PASSWORD]); - break; - case CURLOPT_USERNAME: - /* - * authentication user name to use in the operation - */ - result = setstropt(&data->set.str[STRING_USERNAME], - va_arg(param, char *)); - break; - case CURLOPT_PASSWORD: - /* - * authentication password to use in the operation - */ - result = setstropt(&data->set.str[STRING_PASSWORD], - va_arg(param, char *)); - break; - case CURLOPT_POSTQUOTE: - /* - * List of RAW FTP commands to use after a transfer - */ - data->set.postquote = va_arg(param, struct curl_slist *); - break; - case CURLOPT_PREQUOTE: - /* - * List of RAW FTP commands to use prior to RETR (Wesley Laxton) - */ - data->set.prequote = va_arg(param, struct curl_slist *); - break; - case CURLOPT_QUOTE: - /* - * List of RAW FTP commands to use before a transfer - */ - data->set.quote = va_arg(param, struct curl_slist *); - break; - case CURLOPT_RESOLVE: - /* - * List of NAME:[address] names to populate the DNS cache with - * Prefix the NAME with dash (-) to _remove_ the name from the cache. - * - * Names added with this API will remain in the cache until explicitly - * removed or the handle is cleaned up. - * - * This API can remove any name from the DNS cache, but only entries - * that aren't actually in use right now will be pruned immediately. - */ - data->set.resolve = va_arg(param, struct curl_slist *); - data->change.resolve = data->set.resolve; - break; - case CURLOPT_PROGRESSFUNCTION: - /* - * Progress callback function - */ - data->set.fprogress = va_arg(param, curl_progress_callback); - if(data->set.fprogress) - data->progress.callback = TRUE; /* no longer internal */ - else - data->progress.callback = FALSE; /* NULL enforces internal */ - - break; - case CURLOPT_PROGRESSDATA: - /* - * Custom client data to pass to the progress callback - */ - data->set.progress_client = va_arg(param, void *); - break; - -#ifndef CURL_DISABLE_PROXY - case CURLOPT_PROXYUSERPWD: - /* - * user:password needed to use the proxy - */ - result = setstropt_userpwd(va_arg(param, char *), - &data->set.str[STRING_PROXYUSERNAME], - &data->set.str[STRING_PROXYPASSWORD]); - break; - case CURLOPT_PROXYUSERNAME: - /* - * authentication user name to use in the operation - */ - result = setstropt(&data->set.str[STRING_PROXYUSERNAME], - va_arg(param, char *)); - break; - case CURLOPT_PROXYPASSWORD: - /* - * authentication password to use in the operation - */ - result = setstropt(&data->set.str[STRING_PROXYPASSWORD], - va_arg(param, char *)); - break; - case CURLOPT_NOPROXY: - /* - * proxy exception list - */ - result = setstropt(&data->set.str[STRING_NOPROXY], - va_arg(param, char *)); - break; -#endif - - case CURLOPT_RANGE: - /* - * What range of the file you want to transfer - */ - result = setstropt(&data->set.str[STRING_SET_RANGE], - va_arg(param, char *)); - break; - case CURLOPT_RESUME_FROM: - /* - * Resume transfer at the give file position - */ - data->set.set_resume_from = va_arg(param, long); - break; - case CURLOPT_RESUME_FROM_LARGE: - /* - * Resume transfer at the give file position - */ - data->set.set_resume_from = va_arg(param, curl_off_t); - break; - case CURLOPT_DEBUGFUNCTION: - /* - * stderr write callback. - */ - data->set.fdebug = va_arg(param, curl_debug_callback); - /* - * if the callback provided is NULL, it'll use the default callback - */ - break; - case CURLOPT_DEBUGDATA: - /* - * Set to a void * that should receive all error writes. This - * defaults to CURLOPT_STDERR for normal operations. - */ - data->set.debugdata = va_arg(param, void *); - break; - case CURLOPT_STDERR: - /* - * Set to a FILE * that should receive all error writes. This - * defaults to stderr for normal operations. - */ - data->set.err = va_arg(param, FILE *); - if(!data->set.err) - data->set.err = stderr; - break; - case CURLOPT_HEADERFUNCTION: - /* - * Set header write callback - */ - data->set.fwrite_header = va_arg(param, curl_write_callback); - break; - case CURLOPT_WRITEFUNCTION: - /* - * Set data write callback - */ - data->set.fwrite_func = va_arg(param, curl_write_callback); - if(!data->set.fwrite_func) { - data->set.is_fwrite_set = 0; - /* When set to NULL, reset to our internal default function */ - data->set.fwrite_func = (curl_write_callback)fwrite; - } - else - data->set.is_fwrite_set = 1; - break; - case CURLOPT_READFUNCTION: - /* - * Read data callback - */ - data->set.fread_func = va_arg(param, curl_read_callback); - if(!data->set.fread_func) { - data->set.is_fread_set = 0; - /* When set to NULL, reset to our internal default function */ - data->set.fread_func = (curl_read_callback)fread; - } - else - data->set.is_fread_set = 1; - break; - case CURLOPT_SEEKFUNCTION: - /* - * Seek callback. Might be NULL. - */ - data->set.seek_func = va_arg(param, curl_seek_callback); - break; - case CURLOPT_SEEKDATA: - /* - * Seek control callback. Might be NULL. - */ - data->set.seek_client = va_arg(param, void *); - break; - case CURLOPT_CONV_FROM_NETWORK_FUNCTION: - /* - * "Convert from network encoding" callback - */ - data->set.convfromnetwork = va_arg(param, curl_conv_callback); - break; - case CURLOPT_CONV_TO_NETWORK_FUNCTION: - /* - * "Convert to network encoding" callback - */ - data->set.convtonetwork = va_arg(param, curl_conv_callback); - break; - case CURLOPT_CONV_FROM_UTF8_FUNCTION: - /* - * "Convert from UTF-8 encoding" callback - */ - data->set.convfromutf8 = va_arg(param, curl_conv_callback); - break; - case CURLOPT_IOCTLFUNCTION: - /* - * I/O control callback. Might be NULL. - */ - data->set.ioctl_func = va_arg(param, curl_ioctl_callback); - break; - case CURLOPT_IOCTLDATA: - /* - * I/O control data pointer. Might be NULL. - */ - data->set.ioctl_client = va_arg(param, void *); - break; - case CURLOPT_SSLCERT: - /* - * String that holds file name of the SSL certificate to use - */ - result = setstropt(&data->set.str[STRING_CERT], - va_arg(param, char *)); - break; - case CURLOPT_SSLCERTTYPE: - /* - * String that holds file type of the SSL certificate to use - */ - result = setstropt(&data->set.str[STRING_CERT_TYPE], - va_arg(param, char *)); - break; - case CURLOPT_SSLKEY: - /* - * String that holds file name of the SSL key to use - */ - result = setstropt(&data->set.str[STRING_KEY], - va_arg(param, char *)); - break; - case CURLOPT_SSLKEYTYPE: - /* - * String that holds file type of the SSL key to use - */ - result = setstropt(&data->set.str[STRING_KEY_TYPE], - va_arg(param, char *)); - break; - case CURLOPT_KEYPASSWD: - /* - * String that holds the SSL or SSH private key password. - */ - result = setstropt(&data->set.str[STRING_KEY_PASSWD], - va_arg(param, char *)); - break; - case CURLOPT_SSLENGINE: - /* - * String that holds the SSL crypto engine. - */ - argptr = va_arg(param, char *); - if(argptr && argptr[0]) - result = Curl_ssl_set_engine(data, argptr); - break; - - case CURLOPT_SSLENGINE_DEFAULT: - /* - * flag to set engine as default. - */ - result = Curl_ssl_set_engine_default(data); - break; - case CURLOPT_CRLF: - /* - * Kludgy option to enable CRLF conversions. Subject for removal. - */ - data->set.crlf = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_INTERFACE: - /* - * Set what interface or address/hostname to bind the socket to when - * performing an operation and thus what from-IP your connection will use. - */ - result = setstropt(&data->set.str[STRING_DEVICE], - va_arg(param, char *)); - break; - case CURLOPT_LOCALPORT: - /* - * Set what local port to bind the socket to when performing an operation. - */ - data->set.localport = curlx_sltous(va_arg(param, long)); - break; - case CURLOPT_LOCALPORTRANGE: - /* - * Set number of local ports to try, starting with CURLOPT_LOCALPORT. - */ - data->set.localportrange = curlx_sltosi(va_arg(param, long)); - break; - case CURLOPT_KRBLEVEL: - /* - * A string that defines the kerberos security level. - */ - result = setstropt(&data->set.str[STRING_KRB_LEVEL], - va_arg(param, char *)); - data->set.krb = (NULL != data->set.str[STRING_KRB_LEVEL])?TRUE:FALSE; - break; - case CURLOPT_GSSAPI_DELEGATION: - /* - * GSSAPI credential delegation - */ - data->set.gssapi_delegation = va_arg(param, long); - break; - case CURLOPT_SSL_VERIFYPEER: - /* - * Enable peer SSL verifying. - */ - data->set.ssl.verifypeer = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_SSL_VERIFYHOST: - /* - * Enable verification of the host name in the peer certificate - */ - arg = va_arg(param, long); - - /* Obviously people are not reading documentation and too many thought - this argument took a boolean when it wasn't and misused it. We thus ban - 1 as a sensible input and we warn about its use. Then we only have the - 2 action internally stored as TRUE. */ - - if(1 == arg) { - failf(data, "CURLOPT_SSL_VERIFYHOST no longer supports 1 as value!"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - data->set.ssl.verifyhost = (0 != arg)?TRUE:FALSE; - break; -#ifdef USE_SSLEAY - /* since these two options are only possible to use on an OpenSSL- - powered libcurl we #ifdef them on this condition so that libcurls - built against other SSL libs will return a proper error when trying - to set this option! */ - case CURLOPT_SSL_CTX_FUNCTION: - /* - * Set a SSL_CTX callback - */ - data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); - break; - case CURLOPT_SSL_CTX_DATA: - /* - * Set a SSL_CTX callback parameter pointer - */ - data->set.ssl.fsslctxp = va_arg(param, void *); - break; - case CURLOPT_CERTINFO: - data->set.ssl.certinfo = (0 != va_arg(param, long))?TRUE:FALSE; - break; -#endif - case CURLOPT_CAINFO: - /* - * Set CA info for SSL connection. Specify file name of the CA certificate - */ - result = setstropt(&data->set.str[STRING_SSL_CAFILE], - va_arg(param, char *)); - break; - case CURLOPT_CAPATH: - /* - * Set CA path info for SSL connection. Specify directory name of the CA - * certificates which have been prepared using openssl c_rehash utility. - */ - /* This does not work on windows. */ - result = setstropt(&data->set.str[STRING_SSL_CAPATH], - va_arg(param, char *)); - break; - case CURLOPT_CRLFILE: - /* - * Set CRL file info for SSL connection. Specify file name of the CRL - * to check certificates revocation - */ - result = setstropt(&data->set.str[STRING_SSL_CRLFILE], - va_arg(param, char *)); - break; - case CURLOPT_ISSUERCERT: - /* - * Set Issuer certificate file - * to check certificates issuer - */ - result = setstropt(&data->set.str[STRING_SSL_ISSUERCERT], - va_arg(param, char *)); - break; - case CURLOPT_TELNETOPTIONS: - /* - * Set a linked list of telnet options - */ - data->set.telnet_options = va_arg(param, struct curl_slist *); - break; - - case CURLOPT_BUFFERSIZE: - /* - * The application kindly asks for a differently sized receive buffer. - * If it seems reasonable, we'll use it. - */ - data->set.buffer_size = va_arg(param, long); - - if((data->set.buffer_size> (BUFSIZE -1 )) || - (data->set.buffer_size < 1)) - data->set.buffer_size = 0; /* huge internal default */ - - break; - - case CURLOPT_NOSIGNAL: - /* - * The application asks not to set any signal() or alarm() handlers, - * even when using a timeout. - */ - data->set.no_signal = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_SHARE: - { - struct Curl_share *set; - set = va_arg(param, struct Curl_share *); - - /* disconnect from old share, if any */ - if(data->share) { - Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); - - if(data->dns.hostcachetype == HCACHE_SHARED) { - data->dns.hostcache = NULL; - data->dns.hostcachetype = HCACHE_NONE; - } - -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(data->share->cookies == data->cookies) - data->cookies = NULL; -#endif - - if(data->share->sslsession == data->state.session) - data->state.session = NULL; - - data->share->dirty--; - - Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); - data->share = NULL; - } - - /* use new share if it set */ - data->share = set; - if(data->share) { - - Curl_share_lock(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE); - - data->share->dirty++; - - if(data->share->hostcache) { - /* use shared host cache, first free the private one if any */ - if(data->dns.hostcachetype == HCACHE_PRIVATE) - Curl_hostcache_destroy(data); - - data->dns.hostcache = data->share->hostcache; - data->dns.hostcachetype = HCACHE_SHARED; - } -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) - if(data->share->cookies) { - /* use shared cookie list, first free own one if any */ - if(data->cookies) - Curl_cookie_cleanup(data->cookies); - /* enable cookies since we now use a share that uses cookies! */ - data->cookies = data->share->cookies; - } -#endif /* CURL_DISABLE_HTTP */ - if(data->share->sslsession) { - data->set.ssl.max_ssl_sessions = data->share->max_ssl_sessions; - data->state.session = data->share->sslsession; - } - Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); - - } - /* check for host cache not needed, - * it will be done by curl_easy_perform */ - } - break; - - case CURLOPT_PRIVATE: - /* - * Set private data pointer. - */ - data->set.private_data = va_arg(param, void *); - break; - - case CURLOPT_MAXFILESIZE: - /* - * Set the maximum size of a file to download. - */ - data->set.max_filesize = va_arg(param, long); - break; - -#ifdef USE_SSL - case CURLOPT_USE_SSL: - /* - * Make transfers attempt to use SSL/TLS. - */ - data->set.use_ssl = (curl_usessl)va_arg(param, long); - break; - - case CURLOPT_SSL_OPTIONS: - arg = va_arg(param, long); - data->set.ssl_enable_beast = arg&CURLSSLOPT_ALLOW_BEAST?TRUE:FALSE; - break; - -#endif - case CURLOPT_FTPSSLAUTH: - /* - * Set a specific auth for FTP-SSL transfers. - */ - data->set.ftpsslauth = (curl_ftpauth)va_arg(param, long); - break; - - case CURLOPT_IPRESOLVE: - data->set.ipver = va_arg(param, long); - break; - - case CURLOPT_MAXFILESIZE_LARGE: - /* - * Set the maximum size of a file to download. - */ - data->set.max_filesize = va_arg(param, curl_off_t); - break; - - case CURLOPT_TCP_NODELAY: - /* - * Enable or disable TCP_NODELAY, which will disable/enable the Nagle - * algorithm - */ - data->set.tcp_nodelay = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_FTP_ACCOUNT: - result = setstropt(&data->set.str[STRING_FTP_ACCOUNT], - va_arg(param, char *)); - break; - - case CURLOPT_IGNORE_CONTENT_LENGTH: - data->set.ignorecl = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_CONNECT_ONLY: - /* - * No data transfer, set up connection and let application use the socket - */ - data->set.connect_only = (0 != va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_FTP_ALTERNATIVE_TO_USER: - result = setstropt(&data->set.str[STRING_FTP_ALTERNATIVE_TO_USER], - va_arg(param, char *)); - break; - - case CURLOPT_SOCKOPTFUNCTION: - /* - * socket callback function: called after socket() but before connect() - */ - data->set.fsockopt = va_arg(param, curl_sockopt_callback); - break; - - case CURLOPT_SOCKOPTDATA: - /* - * socket callback data pointer. Might be NULL. - */ - data->set.sockopt_client = va_arg(param, void *); - break; - - case CURLOPT_OPENSOCKETFUNCTION: - /* - * open/create socket callback function: called instead of socket(), - * before connect() - */ - data->set.fopensocket = va_arg(param, curl_opensocket_callback); - break; - - case CURLOPT_OPENSOCKETDATA: - /* - * socket callback data pointer. Might be NULL. - */ - data->set.opensocket_client = va_arg(param, void *); - break; - - case CURLOPT_CLOSESOCKETFUNCTION: - /* - * close socket callback function: called instead of close() - * when shutting down a connection - */ - data->set.fclosesocket = va_arg(param, curl_closesocket_callback); - break; - - case CURLOPT_CLOSESOCKETDATA: - /* - * socket callback data pointer. Might be NULL. - */ - data->set.closesocket_client = va_arg(param, void *); - break; - - case CURLOPT_SSL_SESSIONID_CACHE: - data->set.ssl.sessionid = (0 != va_arg(param, long))?TRUE:FALSE; - break; - -#ifdef USE_LIBSSH2 - /* we only include SSH options if explicitly built to support SSH */ - case CURLOPT_SSH_AUTH_TYPES: - data->set.ssh_auth_types = va_arg(param, long); - break; - - case CURLOPT_SSH_PUBLIC_KEYFILE: - /* - * Use this file instead of the $HOME/.ssh/id_dsa.pub file - */ - result = setstropt(&data->set.str[STRING_SSH_PUBLIC_KEY], - va_arg(param, char *)); - break; - - case CURLOPT_SSH_PRIVATE_KEYFILE: - /* - * Use this file instead of the $HOME/.ssh/id_dsa file - */ - result = setstropt(&data->set.str[STRING_SSH_PRIVATE_KEY], - va_arg(param, char *)); - break; - case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: - /* - * Option to allow for the MD5 of the host public key to be checked - * for validation purposes. - */ - result = setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], - va_arg(param, char *)); - break; -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - case CURLOPT_SSH_KNOWNHOSTS: - /* - * Store the file name to read known hosts from. - */ - result = setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], - va_arg(param, char *)); - break; - - case CURLOPT_SSH_KEYFUNCTION: - /* setting to NULL is fine since the curl_ssh.c functions themselves will - then rever to use the internal default */ - data->set.ssh_keyfunc = va_arg(param, curl_sshkeycallback); - break; - - case CURLOPT_SSH_KEYDATA: - /* - * Custom client data to pass to the SSH keyfunc callback - */ - data->set.ssh_keyfunc_userp = va_arg(param, void *); - break; -#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ - -#endif /* USE_LIBSSH2 */ - - case CURLOPT_HTTP_TRANSFER_DECODING: - /* - * disable libcurl transfer encoding is used - */ - data->set.http_te_skip = (0 == va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_HTTP_CONTENT_DECODING: - /* - * raw data passed to the application when content encoding is used - */ - data->set.http_ce_skip = (0 == va_arg(param, long))?TRUE:FALSE; - break; - - case CURLOPT_NEW_FILE_PERMS: - /* - * Uses these permissions instead of 0644 - */ - data->set.new_file_perms = va_arg(param, long); - break; - - case CURLOPT_NEW_DIRECTORY_PERMS: - /* - * Uses these permissions instead of 0755 - */ - data->set.new_directory_perms = va_arg(param, long); - break; - - case CURLOPT_ADDRESS_SCOPE: - /* - * We always get longs when passed plain numericals, but for this value we - * know that an unsigned int will always hold the value so we blindly - * typecast to this type - */ - data->set.scope = curlx_sltoui(va_arg(param, long)); - break; - - case CURLOPT_PROTOCOLS: - /* set the bitmask for the protocols that are allowed to be used for the - transfer, which thus helps the app which takes URLs from users or other - external inputs and want to restrict what protocol(s) to deal - with. Defaults to CURLPROTO_ALL. */ - data->set.allowed_protocols = va_arg(param, long); - break; - - case CURLOPT_REDIR_PROTOCOLS: - /* set the bitmask for the protocols that libcurl is allowed to follow to, - as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs - to be set in both bitmasks to be allowed to get redirected to. Defaults - to all protocols except FILE and SCP. */ - data->set.redir_protocols = va_arg(param, long); - break; - - case CURLOPT_MAIL_FROM: - result = setstropt(&data->set.str[STRING_MAIL_FROM], - va_arg(param, char *)); - break; - - case CURLOPT_MAIL_AUTH: - result = setstropt(&data->set.str[STRING_MAIL_AUTH], - va_arg(param, char *)); - break; - - case CURLOPT_MAIL_RCPT: - /* get a list of mail recipients */ - data->set.mail_rcpt = va_arg(param, struct curl_slist *); - break; - - case CURLOPT_RTSP_REQUEST: - { - /* - * Set the RTSP request method (OPTIONS, SETUP, PLAY, etc...) - * Would this be better if the RTSPREQ_* were just moved into here? - */ - long curl_rtspreq = va_arg(param, long); - Curl_RtspReq rtspreq = RTSPREQ_NONE; - switch(curl_rtspreq) { - case CURL_RTSPREQ_OPTIONS: - rtspreq = RTSPREQ_OPTIONS; - break; - - case CURL_RTSPREQ_DESCRIBE: - rtspreq = RTSPREQ_DESCRIBE; - break; - - case CURL_RTSPREQ_ANNOUNCE: - rtspreq = RTSPREQ_ANNOUNCE; - break; - - case CURL_RTSPREQ_SETUP: - rtspreq = RTSPREQ_SETUP; - break; - - case CURL_RTSPREQ_PLAY: - rtspreq = RTSPREQ_PLAY; - break; - - case CURL_RTSPREQ_PAUSE: - rtspreq = RTSPREQ_PAUSE; - break; - - case CURL_RTSPREQ_TEARDOWN: - rtspreq = RTSPREQ_TEARDOWN; - break; - - case CURL_RTSPREQ_GET_PARAMETER: - rtspreq = RTSPREQ_GET_PARAMETER; - break; - - case CURL_RTSPREQ_SET_PARAMETER: - rtspreq = RTSPREQ_SET_PARAMETER; - break; - - case CURL_RTSPREQ_RECORD: - rtspreq = RTSPREQ_RECORD; - break; - - case CURL_RTSPREQ_RECEIVE: - rtspreq = RTSPREQ_RECEIVE; - break; - default: - rtspreq = RTSPREQ_NONE; - } - - data->set.rtspreq = rtspreq; - break; - } - - - case CURLOPT_RTSP_SESSION_ID: - /* - * Set the RTSP Session ID manually. Useful if the application is - * resuming a previously established RTSP session - */ - result = setstropt(&data->set.str[STRING_RTSP_SESSION_ID], - va_arg(param, char *)); - break; - - case CURLOPT_RTSP_STREAM_URI: - /* - * Set the Stream URI for the RTSP request. Unless the request is - * for generic server options, the application will need to set this. - */ - result = setstropt(&data->set.str[STRING_RTSP_STREAM_URI], - va_arg(param, char *)); - break; - - case CURLOPT_RTSP_TRANSPORT: - /* - * The content of the Transport: header for the RTSP request - */ - result = setstropt(&data->set.str[STRING_RTSP_TRANSPORT], - va_arg(param, char *)); - break; - - case CURLOPT_RTSP_CLIENT_CSEQ: - /* - * Set the CSEQ number to issue for the next RTSP request. Useful if the - * application is resuming a previously broken connection. The CSEQ - * will increment from this new number henceforth. - */ - data->state.rtsp_next_client_CSeq = va_arg(param, long); - break; - - case CURLOPT_RTSP_SERVER_CSEQ: - /* Same as the above, but for server-initiated requests */ - data->state.rtsp_next_client_CSeq = va_arg(param, long); - break; - - case CURLOPT_INTERLEAVEDATA: - data->set.rtp_out = va_arg(param, void *); - break; - case CURLOPT_INTERLEAVEFUNCTION: - /* Set the user defined RTP write function */ - data->set.fwrite_rtp = va_arg(param, curl_write_callback); - break; - - case CURLOPT_WILDCARDMATCH: - data->set.wildcardmatch = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_CHUNK_BGN_FUNCTION: - data->set.chunk_bgn = va_arg(param, curl_chunk_bgn_callback); - break; - case CURLOPT_CHUNK_END_FUNCTION: - data->set.chunk_end = va_arg(param, curl_chunk_end_callback); - break; - case CURLOPT_FNMATCH_FUNCTION: - data->set.fnmatch = va_arg(param, curl_fnmatch_callback); - break; - case CURLOPT_CHUNK_DATA: - data->wildcard.customptr = va_arg(param, void *); - break; - case CURLOPT_FNMATCH_DATA: - data->set.fnmatch_data = va_arg(param, void *); - break; -#ifdef USE_TLS_SRP - case CURLOPT_TLSAUTH_USERNAME: - result = setstropt(&data->set.str[STRING_TLSAUTH_USERNAME], - va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME] && !data->set.ssl.authtype) - data->set.ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ - break; - case CURLOPT_TLSAUTH_PASSWORD: - result = setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD], - va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME] && !data->set.ssl.authtype) - data->set.ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ - break; - case CURLOPT_TLSAUTH_TYPE: - if(strnequal((char *)va_arg(param, char *), "SRP", strlen("SRP"))) - data->set.ssl.authtype = CURL_TLSAUTH_SRP; - else - data->set.ssl.authtype = CURL_TLSAUTH_NONE; - break; -#endif - case CURLOPT_DNS_SERVERS: - result = Curl_set_dns_servers(data, va_arg(param, char *)); - break; - - case CURLOPT_TCP_KEEPALIVE: - data->set.tcp_keepalive = (0 != va_arg(param, long))?TRUE:FALSE; - break; - case CURLOPT_TCP_KEEPIDLE: - data->set.tcp_keepidle = va_arg(param, long); - break; - case CURLOPT_TCP_KEEPINTVL: - data->set.tcp_keepintvl = va_arg(param, long); - break; - - default: - /* unknown tag and its companion, just ignore: */ - result = CURLE_UNKNOWN_OPTION; - break; - } - - return result; -} - -static void conn_free(struct connectdata *conn) -{ - if(!conn) - return; - - /* possible left-overs from the async name resolvers */ - Curl_resolver_cancel(conn); - - /* close the SSL stuff before we close any sockets since they will/may - write to the sockets */ - Curl_ssl_close(conn, FIRSTSOCKET); - Curl_ssl_close(conn, SECONDARYSOCKET); - - /* close possibly still open sockets */ - if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) - Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); - if(CURL_SOCKET_BAD != conn->sock[FIRSTSOCKET]) - Curl_closesocket(conn, conn->sock[FIRSTSOCKET]); - -#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) - Curl_ntlm_wb_cleanup(conn); -#endif - - Curl_safefree(conn->user); - Curl_safefree(conn->passwd); - Curl_safefree(conn->proxyuser); - Curl_safefree(conn->proxypasswd); - Curl_safefree(conn->allocptr.proxyuserpwd); - Curl_safefree(conn->allocptr.uagent); - Curl_safefree(conn->allocptr.userpwd); - Curl_safefree(conn->allocptr.accept_encoding); - Curl_safefree(conn->allocptr.te); - Curl_safefree(conn->allocptr.rangeline); - Curl_safefree(conn->allocptr.ref); - Curl_safefree(conn->allocptr.host); - Curl_safefree(conn->allocptr.cookiehost); - Curl_safefree(conn->allocptr.rtsp_transport); - Curl_safefree(conn->trailer); - Curl_safefree(conn->host.rawalloc); /* host name buffer */ - Curl_safefree(conn->proxy.rawalloc); /* proxy name buffer */ - Curl_safefree(conn->master_buffer); - - Curl_llist_destroy(conn->send_pipe, NULL); - Curl_llist_destroy(conn->recv_pipe, NULL); - Curl_llist_destroy(conn->pend_pipe, NULL); - Curl_llist_destroy(conn->done_pipe, NULL); - - conn->send_pipe = NULL; - conn->recv_pipe = NULL; - conn->pend_pipe = NULL; - conn->done_pipe = NULL; - - Curl_safefree(conn->localdev); - Curl_free_ssl_config(&conn->ssl_config); - - free(conn); /* free all the connection oriented data */ -} - -CURLcode Curl_disconnect(struct connectdata *conn, bool dead_connection) -{ - struct SessionHandle *data; - if(!conn) - return CURLE_OK; /* this is closed and fine already */ - data = conn->data; - - if(!data) { - DEBUGF(fprintf(stderr, "DISCONNECT without easy handle, ignoring\n")); - return CURLE_OK; - } - - if(conn->dns_entry != NULL) { - Curl_resolv_unlock(data, conn->dns_entry); - conn->dns_entry = NULL; - } - - Curl_hostcache_prune(data); /* kill old DNS cache entries */ - - { - int has_host_ntlm = (conn->ntlm.state != NTLMSTATE_NONE); - int has_proxy_ntlm = (conn->proxyntlm.state != NTLMSTATE_NONE); - - /* Authentication data is a mix of connection-related and sessionhandle- - related stuff. NTLM is connection-related so when we close the shop - we shall forget. */ - - if(has_host_ntlm) { - data->state.authhost.done = FALSE; - data->state.authhost.picked = - data->state.authhost.want; - } - - if(has_proxy_ntlm) { - data->state.authproxy.done = FALSE; - data->state.authproxy.picked = - data->state.authproxy.want; - } - - if(has_host_ntlm || has_proxy_ntlm) { - data->state.authproblem = FALSE; - - Curl_http_ntlm_cleanup(conn); - } - } - - /* Cleanup possible redirect junk */ - if(data->req.newurl) { - free(data->req.newurl); - data->req.newurl = NULL; - } - - if(conn->handler->disconnect) - /* This is set if protocol-specific cleanups should be made */ - conn->handler->disconnect(conn, dead_connection); - - /* unlink ourselves! */ - infof(data, "Closing connection %d\n", conn->connection_id); - Curl_conncache_remove_conn(data->state.conn_cache, conn); - -#if defined(USE_LIBIDN) - if(conn->host.encalloc) - idn_free(conn->host.encalloc); /* encoded host name buffer, must be freed - with idn_free() since this was allocated - by libidn */ - if(conn->proxy.encalloc) - idn_free(conn->proxy.encalloc); /* encoded proxy name buffer, must be - freed with idn_free() since this was - allocated by libidn */ -#elif defined(USE_WIN32_IDN) - free(conn->host.encalloc); /* encoded host name buffer, must be freed with - idn_free() since this was allocated by - curl_win32_idn_to_ascii */ - if(conn->proxy.encalloc) - free(conn->proxy.encalloc); /* encoded proxy name buffer, must be freed - with idn_free() since this was allocated by - curl_win32_idn_to_ascii */ -#endif - - Curl_ssl_close(conn, FIRSTSOCKET); - - /* Indicate to all handles on the pipe that we're dead */ - if(Curl_isPipeliningEnabled(data)) { - signalPipeClose(conn->send_pipe, TRUE); - signalPipeClose(conn->recv_pipe, TRUE); - signalPipeClose(conn->pend_pipe, TRUE); - signalPipeClose(conn->done_pipe, FALSE); - } - - conn_free(conn); - data->state.current_conn = NULL; - Curl_speedinit(data); - - return CURLE_OK; -} - -/* - * This function should return TRUE if the socket is to be assumed to - * be dead. Most commonly this happens when the server has closed the - * connection due to inactivity. - */ -static bool SocketIsDead(curl_socket_t sock) -{ - int sval; - bool ret_val = TRUE; - - sval = Curl_socket_ready(sock, CURL_SOCKET_BAD, 0); - if(sval == 0) - /* timeout */ - ret_val = FALSE; - - return ret_val; -} - -static bool IsPipeliningPossible(const struct SessionHandle *handle, - const struct connectdata *conn) -{ - if((conn->handler->protocol & CURLPROTO_HTTP) && - handle->multi && Curl_multi_canPipeline(handle->multi) && - (handle->set.httpreq == HTTPREQ_GET || - handle->set.httpreq == HTTPREQ_HEAD) && - handle->set.httpversion != CURL_HTTP_VERSION_1_0) - return TRUE; - - return FALSE; -} - -bool Curl_isPipeliningEnabled(const struct SessionHandle *handle) -{ - if(handle->multi && Curl_multi_canPipeline(handle->multi)) - return TRUE; - - return FALSE; -} - -CURLcode Curl_addHandleToPipeline(struct SessionHandle *data, - struct curl_llist *pipeline) -{ - if(!Curl_llist_insert_next(pipeline, pipeline->tail, data)) - return CURLE_OUT_OF_MEMORY; - return CURLE_OK; -} - -int Curl_removeHandleFromPipeline(struct SessionHandle *handle, - struct curl_llist *pipeline) -{ - struct curl_llist_element *curr; - - curr = pipeline->head; - while(curr) { - if(curr->ptr == handle) { - Curl_llist_remove(pipeline, curr, NULL); - return 1; /* we removed a handle */ - } - curr = curr->next; - } - - return 0; -} - -#if 0 /* this code is saved here as it is useful for debugging purposes */ -static void Curl_printPipeline(struct curl_llist *pipeline) -{ - struct curl_llist_element *curr; - - curr = pipeline->head; - while(curr) { - struct SessionHandle *data = (struct SessionHandle *) curr->ptr; - infof(data, "Handle in pipeline: %s\n", data->state.path); - curr = curr->next; - } -} -#endif - -static struct SessionHandle* gethandleathead(struct curl_llist *pipeline) -{ - struct curl_llist_element *curr = pipeline->head; - if(curr) { - return (struct SessionHandle *) curr->ptr; - } - - return NULL; -} - -/* remove the specified connection from all (possible) pipelines and related - queues */ -void Curl_getoff_all_pipelines(struct SessionHandle *data, - struct connectdata *conn) -{ - bool recv_head = (conn->readchannel_inuse && - (gethandleathead(conn->recv_pipe) == data)) ? TRUE : FALSE; - - bool send_head = (conn->writechannel_inuse && - (gethandleathead(conn->send_pipe) == data)) ? TRUE : FALSE; - - if(Curl_removeHandleFromPipeline(data, conn->recv_pipe) && recv_head) - conn->readchannel_inuse = FALSE; - if(Curl_removeHandleFromPipeline(data, conn->send_pipe) && send_head) - conn->writechannel_inuse = FALSE; - Curl_removeHandleFromPipeline(data, conn->pend_pipe); - Curl_removeHandleFromPipeline(data, conn->done_pipe); -} - -static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke) -{ - struct curl_llist_element *curr; - - if(!pipeline) - return; - - curr = pipeline->head; - while(curr) { - struct curl_llist_element *next = curr->next; - struct SessionHandle *data = (struct SessionHandle *) curr->ptr; - -#ifdef DEBUGBUILD /* debug-only code */ - if(data->magic != CURLEASY_MAGIC_NUMBER) { - /* MAJOR BADNESS */ - infof(data, "signalPipeClose() found BAAD easy handle\n"); - } -#endif - - if(pipe_broke) - data->state.pipe_broke = TRUE; - Curl_multi_handlePipeBreak(data); - Curl_llist_remove(pipeline, curr, NULL); - curr = next; - } -} - - -/* - * Given one filled in connection struct (named needle), this function should - * detect if there already is one that has all the significant details - * exactly the same and thus should be used instead. - * - * If there is a match, this function returns TRUE - and has marked the - * connection as 'in-use'. It must later be called with ConnectionDone() to - * return back to 'idle' (unused) state. - */ -static bool -ConnectionExists(struct SessionHandle *data, - struct connectdata *needle, - struct connectdata **usethis) -{ - struct connectdata *check; - struct connectdata *chosen = 0; - bool canPipeline = IsPipeliningPossible(data, needle); - bool wantNTLM = (data->state.authhost.want==CURLAUTH_NTLM) || - (data->state.authhost.want==CURLAUTH_NTLM_WB) ? TRUE : FALSE; - struct connectbundle *bundle; - - /* Look up the bundle with all the connections to this - particular host */ - bundle = Curl_conncache_find_bundle(data->state.conn_cache, - needle->host.name); - if(bundle) { - struct curl_llist_element *curr; - - infof(data, "Found bundle for host %s: %p\n", needle->host.name, bundle); - - curr = bundle->conn_list->head; - while(curr) { - bool match = FALSE; - bool credentialsMatch = FALSE; - size_t pipeLen; - - /* - * Note that if we use a HTTP proxy, we check connections to that - * proxy and not to the actual remote server. - */ - check = curr->ptr; - curr = curr->next; - - pipeLen = check->send_pipe->size + check->recv_pipe->size; - - if(!pipeLen && !check->inuse) { - /* The check for a dead socket makes sense only if there are no - handles in pipeline and the connection isn't already marked in - use */ - bool dead; - if(check->handler->protocol & CURLPROTO_RTSP) - /* RTSP is a special case due to RTP interleaving */ - dead = Curl_rtsp_connisdead(check); - else - dead = SocketIsDead(check->sock[FIRSTSOCKET]); - - if(dead) { - check->data = data; - infof(data, "Connection %d seems to be dead!\n", - check->connection_id); - - /* disconnect resources */ - Curl_disconnect(check, /* dead_connection */ TRUE); - continue; - } - } - - if(canPipeline) { - /* Make sure the pipe has only GET requests */ - struct SessionHandle* sh = gethandleathead(check->send_pipe); - struct SessionHandle* rh = gethandleathead(check->recv_pipe); - if(sh) { - if(!IsPipeliningPossible(sh, check)) - continue; - } - else if(rh) { - if(!IsPipeliningPossible(rh, check)) - continue; - } -#ifdef DEBUGBUILD - if(pipeLen > MAX_PIPELINE_LENGTH) { - infof(data, "BAD! Connection #%ld has too big pipeline!\n", - check->connection_id); - } -#endif - } - else { - if(pipeLen > 0) { - /* can only happen within multi handles, and means that another easy - handle is using this connection */ - continue; - } - - if(Curl_resolver_asynch()) { - /* ip_addr_str[0] is NUL only if the resolving of the name hasn't - completed yet and until then we don't re-use this connection */ - if(!check->ip_addr_str[0]) { - infof(data, - "Connection #%ld is still name resolving, can't reuse\n", - check->connection_id); - continue; - } - } - - if((check->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) || - check->bits.close) { - /* Don't pick a connection that hasn't connected yet or that is going - to get closed. */ - infof(data, "Connection #%ld isn't open enough, can't reuse\n", - check->connection_id); -#ifdef DEBUGBUILD - if(check->recv_pipe->size > 0) { - infof(data, - "BAD! Unconnected #%ld has a non-empty recv pipeline!\n", - check->connection_id); - } -#endif - continue; - } - } - - if((needle->handler->flags&PROTOPT_SSL) != - (check->handler->flags&PROTOPT_SSL)) - /* don't do mixed SSL and non-SSL connections */ - if(!(needle->handler->protocol & check->handler->protocol)) - /* except protocols that have been upgraded via TLS */ - continue; - - if(needle->handler->flags&PROTOPT_SSL) { - if((data->set.ssl.verifypeer != check->verifypeer) || - (data->set.ssl.verifyhost != check->verifyhost)) - continue; - } - - if(needle->bits.proxy != check->bits.proxy) - /* don't do mixed proxy and non-proxy connections */ - continue; - - if(!canPipeline && check->inuse) - /* this request can't be pipelined but the checked connection is - already in use so we skip it */ - continue; - - if(needle->localdev || needle->localport) { - /* If we are bound to a specific local end (IP+port), we must not - re-use a random other one, although if we didn't ask for a - particular one we can reuse one that was bound. - - This comparison is a bit rough and too strict. Since the input - parameters can be specified in numerous ways and still end up the - same it would take a lot of processing to make it really accurate. - Instead, this matching will assume that re-uses of bound connections - will most likely also re-use the exact same binding parameters and - missing out a few edge cases shouldn't hurt anyone very much. - */ - if((check->localport != needle->localport) || - (check->localportrange != needle->localportrange) || - !check->localdev || - !needle->localdev || - strcmp(check->localdev, needle->localdev)) - continue; - } - - if(!needle->bits.httpproxy || needle->handler->flags&PROTOPT_SSL || - (needle->bits.httpproxy && check->bits.httpproxy && - needle->bits.tunnel_proxy && check->bits.tunnel_proxy && - Curl_raw_equal(needle->proxy.name, check->proxy.name) && - (needle->port == check->port))) { - /* The requested connection does not use a HTTP proxy or it uses SSL or - it is a non-SSL protocol tunneled over the same http proxy name and - port number or it is a non-SSL protocol which is allowed to be - upgraded via TLS */ - - if((Curl_raw_equal(needle->handler->scheme, check->handler->scheme) || - needle->handler->protocol & check->handler->protocol) && - Curl_raw_equal(needle->host.name, check->host.name) && - needle->remote_port == check->remote_port) { - if(needle->handler->flags & PROTOPT_SSL) { - /* This is a SSL connection so verify that we're using the same - SSL options as well */ - if(!Curl_ssl_config_matches(&needle->ssl_config, - &check->ssl_config)) { - DEBUGF(infof(data, - "Connection #%ld has different SSL parameters, " - "can't reuse\n", - check->connection_id)); - continue; - } - else if(check->ssl[FIRSTSOCKET].state != ssl_connection_complete) { - DEBUGF(infof(data, - "Connection #%ld has not started SSL connect, " - "can't reuse\n", - check->connection_id)); - continue; - } - } - if((needle->handler->protocol & CURLPROTO_FTP) || - ((needle->handler->protocol & CURLPROTO_HTTP) && wantNTLM)) { - /* This is FTP or HTTP+NTLM, verify that we're using the same name - and password as well */ - if(!strequal(needle->user, check->user) || - !strequal(needle->passwd, check->passwd)) { - /* one of them was different */ - continue; - } - credentialsMatch = TRUE; - } - match = TRUE; - } - } - else { /* The requested needle connection is using a proxy, - is the checked one using the same host, port and type? */ - if(check->bits.proxy && - (needle->proxytype == check->proxytype) && - (needle->bits.tunnel_proxy == check->bits.tunnel_proxy) && - Curl_raw_equal(needle->proxy.name, check->proxy.name) && - needle->port == check->port) { - /* This is the same proxy connection, use it! */ - match = TRUE; - } - } - - if(match) { - chosen = check; - - /* If we are not looking for an NTLM connection, we can choose this one - immediately. */ - if(!wantNTLM) - break; - - /* Otherwise, check if this is already authenticating with the right - credentials. If not, keep looking so that we can reuse NTLM - connections if possible. (Especially we must reuse the same - connection if partway through a handshake!) */ - if(credentialsMatch && chosen->ntlm.state != NTLMSTATE_NONE) - break; - } - } - } - - if(chosen) { - chosen->inuse = TRUE; /* mark this as being in use so that no other - handle in a multi stack may nick it */ - *usethis = chosen; - return TRUE; /* yes, we found one to use! */ - } - - return FALSE; /* no matching connecting exists */ -} - -/* - * This function kills and removes an existing connection in the connection - * cache. The connection that has been unused for the longest time. - * - * Returns FALSE if it can't find any unused connection to kill. - */ -static bool -ConnectionKillOne(struct SessionHandle *data) -{ - struct conncache *bc = data->state.conn_cache; - struct curl_hash_iterator iter; - struct curl_llist_element *curr; - struct curl_hash_element *he; - long highscore=-1; - long score; - struct timeval now; - struct connectdata *conn_candidate = NULL; - struct connectbundle *bundle; - - now = Curl_tvnow(); - - Curl_hash_start_iterate(bc->hash, &iter); - - he = Curl_hash_next_element(&iter); - while(he) { - struct connectdata *conn; - - bundle = he->ptr; - - curr = bundle->conn_list->head; - while(curr) { - conn = curr->ptr; - - if(!conn->inuse) { - /* Set higher score for the age passed since the connection was used */ - score = Curl_tvdiff(now, conn->now); - - if(score > highscore) { - highscore = score; - conn_candidate = conn; - } - } - curr = curr->next; - } - - he = Curl_hash_next_element(&iter); - } - - if(conn_candidate) { - /* Set the connection's owner correctly */ - conn_candidate->data = data; - - bundle = conn_candidate->bundle; - - /* the winner gets the honour of being disconnected */ - (void)Curl_disconnect(conn_candidate, /* dead_connection */ FALSE); - - return TRUE; - } - - return FALSE; -} - -/* this connection can now be marked 'idle' */ -static void -ConnectionDone(struct connectdata *conn) -{ - conn->inuse = FALSE; -} - -/* - * The given input connection struct pointer is to be stored in the connection - * cache. If the cache is already full, least interesting existing connection - * (if any) gets closed. - * - * The given connection should be unique. That must've been checked prior to - * this call. - */ -static CURLcode ConnectionStore(struct SessionHandle *data, - struct connectdata *conn) -{ - static int connection_id_counter = 0; - - CURLcode result; - - /* Assign a number to the connection for easier tracking in the log - output */ - conn->connection_id = connection_id_counter++; - - result = Curl_conncache_add_conn(data->state.conn_cache, conn); - if(result != CURLE_OK) - conn->connection_id = -1; - - return result; -} - -/* after a TCP connection to the proxy has been verified, this function does - the next magic step. - - Note: this function's sub-functions call failf() - -*/ -CURLcode Curl_connected_proxy(struct connectdata *conn) -{ - switch(conn->proxytype) { -#ifndef CURL_DISABLE_PROXY - case CURLPROXY_SOCKS5: - case CURLPROXY_SOCKS5_HOSTNAME: - return Curl_SOCKS5(conn->proxyuser, conn->proxypasswd, - conn->host.name, conn->remote_port, - FIRSTSOCKET, conn); - - case CURLPROXY_SOCKS4: - return Curl_SOCKS4(conn->proxyuser, conn->host.name, - conn->remote_port, FIRSTSOCKET, conn, FALSE); - - case CURLPROXY_SOCKS4A: - return Curl_SOCKS4(conn->proxyuser, conn->host.name, - conn->remote_port, FIRSTSOCKET, conn, TRUE); - -#endif /* CURL_DISABLE_PROXY */ - case CURLPROXY_HTTP: - case CURLPROXY_HTTP_1_0: - /* do nothing here. handled later. */ - break; - default: - break; - } /* switch proxytype */ - - return CURLE_OK; -} - -static CURLcode ConnectPlease(struct SessionHandle *data, - struct connectdata *conn, - bool *connected) -{ - CURLcode result; - Curl_addrinfo *addr; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - char *hostname = conn->bits.proxy?conn->proxy.name:conn->host.name; - - infof(data, "About to connect() to %s%s port %ld (#%ld)\n", - conn->bits.proxy?"proxy ":"", - hostname, conn->port, conn->connection_id); -#else - (void)data; -#endif - - /************************************************************* - * Connect to server/proxy - *************************************************************/ - result= Curl_connecthost(conn, - conn->dns_entry, - &conn->sock[FIRSTSOCKET], - &addr, - connected); - if(CURLE_OK == result) { - /* All is cool, we store the current information */ - conn->ip_addr = addr; - - if(*connected) { - result = Curl_connected_proxy(conn); - if(!result) { - conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - } - } - } - - if(result) - *connected = FALSE; /* mark it as not connected */ - - return result; -} - -/* - * verboseconnect() displays verbose information after a connect - */ -#ifndef CURL_DISABLE_VERBOSE_STRINGS -void Curl_verboseconnect(struct connectdata *conn) -{ - if(conn->data->set.verbose) - infof(conn->data, "Connected to %s (%s) port %ld (#%ld)\n", - conn->bits.proxy ? conn->proxy.dispname : conn->host.dispname, - conn->ip_addr_str, conn->port, conn->connection_id); -} -#endif - -int Curl_protocol_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - if(conn->handler->proto_getsock) - return conn->handler->proto_getsock(conn, socks, numsocks); - return GETSOCK_BLANK; -} - -int Curl_doing_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - if(conn && conn->handler->doing_getsock) - return conn->handler->doing_getsock(conn, socks, numsocks); - return GETSOCK_BLANK; -} - -/* - * We are doing protocol-specific connecting and this is being called over and - * over from the multi interface until the connection phase is done on - * protocol layer. - */ - -CURLcode Curl_protocol_connecting(struct connectdata *conn, - bool *done) -{ - CURLcode result=CURLE_OK; - - if(conn && conn->handler->connecting) { - *done = FALSE; - result = conn->handler->connecting(conn, done); - } - else - *done = TRUE; - - return result; -} - -/* - * We are DOING this is being called over and over from the multi interface - * until the DOING phase is done on protocol layer. - */ - -CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done) -{ - CURLcode result=CURLE_OK; - - if(conn && conn->handler->doing) { - *done = FALSE; - result = conn->handler->doing(conn, done); - } - else - *done = TRUE; - - return result; -} - -/* - * We have discovered that the TCP connection has been successful, we can now - * proceed with some action. - * - */ -CURLcode Curl_protocol_connect(struct connectdata *conn, - bool *protocol_done) -{ - CURLcode result=CURLE_OK; - struct SessionHandle *data = conn->data; - - *protocol_done = FALSE; - - if(conn->bits.tcpconnect[FIRSTSOCKET] && conn->bits.protoconnstart) { - /* We already are connected, get back. This may happen when the connect - worked fine in the first call, like when we connect to a local server - or proxy. Note that we don't know if the protocol is actually done. - - Unless this protocol doesn't have any protocol-connect callback, as - then we know we're done. */ - if(!conn->handler->connecting) - *protocol_done = TRUE; - - return CURLE_OK; - } - - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - Curl_verboseconnect(conn); - - if(!conn->bits.protoconnstart) { - - /* Set start time here for timeout purposes in the connect procedure, it - is later set again for the progress meter purpose */ - conn->now = Curl_tvnow(); - - result = Curl_proxy_connect(conn); - if(result) - return result; - - if(conn->handler->connect_it) { - /* is there a protocol-specific connect() procedure? */ - - /* Call the protocol-specific connect function */ - result = conn->handler->connect_it(conn, protocol_done); - } - else - *protocol_done = TRUE; - - /* it has started, possibly even completed but that knowledge isn't stored - in this bit! */ - if(!result) - conn->bits.protoconnstart = TRUE; - } - - return result; /* pass back status */ -} - -/* - * Helpers for IDNA convertions. - */ -static bool is_ASCII_name(const char *hostname) -{ - const unsigned char *ch = (const unsigned char*)hostname; - - while(*ch) { - if(*ch++ & 0x80) - return FALSE; - } - return TRUE; -} - -#ifdef USE_LIBIDN -/* - * Check if characters in hostname is allowed in Top Level Domain. - */ -static bool tld_check_name(struct SessionHandle *data, - const char *ace_hostname) -{ - size_t err_pos; - char *uc_name = NULL; - int rc; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - const char *tld_errmsg = ""; -#else - (void)data; -#endif - - /* Convert (and downcase) ACE-name back into locale's character set */ - rc = idna_to_unicode_lzlz(ace_hostname, &uc_name, 0); - if(rc != IDNA_SUCCESS) - return FALSE; - - rc = tld_check_lz(uc_name, &err_pos, NULL); -#ifndef CURL_DISABLE_VERBOSE_STRINGS -#ifdef HAVE_TLD_STRERROR - if(rc != TLD_SUCCESS) - tld_errmsg = tld_strerror((Tld_rc)rc); -#endif - if(rc == TLD_INVALID) - infof(data, "WARNING: %s; pos %u = `%c'/0x%02X\n", - tld_errmsg, err_pos, uc_name[err_pos], - uc_name[err_pos] & 255); - else if(rc != TLD_SUCCESS) - infof(data, "WARNING: TLD check for %s failed; %s\n", - uc_name, tld_errmsg); -#endif /* CURL_DISABLE_VERBOSE_STRINGS */ - if(uc_name) - idn_free(uc_name); - if(rc != TLD_SUCCESS) - return FALSE; - - return TRUE; -} -#endif - -/* - * Perform any necessary IDN conversion of hostname - */ -static void fix_hostname(struct SessionHandle *data, - struct connectdata *conn, struct hostname *host) -{ -#ifndef USE_LIBIDN - (void)data; - (void)conn; -#elif defined(CURL_DISABLE_VERBOSE_STRINGS) - (void)conn; -#endif - - /* set the name we use to display the host name */ - host->dispname = host->name; - if(!is_ASCII_name(host->name)) { -#ifdef USE_LIBIDN - /************************************************************* - * Check name for non-ASCII and convert hostname to ACE form. - *************************************************************/ - if(stringprep_check_version(LIBIDN_REQUIRED_VERSION)) { - char *ace_hostname = NULL; - int rc = idna_to_ascii_lz(host->name, &ace_hostname, 0); - infof (data, "Input domain encoded as `%s'\n", - stringprep_locale_charset ()); - if(rc != IDNA_SUCCESS) - infof(data, "Failed to convert %s to ACE; %s\n", - host->name, Curl_idn_strerror(conn,rc)); - else { - /* tld_check_name() displays a warning if the host name contains - "illegal" characters for this TLD */ - (void)tld_check_name(data, ace_hostname); - - host->encalloc = ace_hostname; - /* change the name pointer to point to the encoded hostname */ - host->name = host->encalloc; - } - } -#elif defined(USE_WIN32_IDN) - /************************************************************* - * Check name for non-ASCII and convert hostname to ACE form. - *************************************************************/ - char *ace_hostname = NULL; - int rc = curl_win32_idn_to_ascii(host->name, &ace_hostname); - if(rc == 0) - infof(data, "Failed to convert %s to ACE;\n", - host->name); - else { - host->encalloc = ace_hostname; - /* change the name pointer to point to the encoded hostname */ - host->name = host->encalloc; - } -#else - infof(data, "IDN support not present, can't parse Unicode domains\n"); -#endif - } -} - -static void llist_dtor(void *user, void *element) -{ - (void)user; - (void)element; - /* Do nothing */ -} - -/* - * Allocate and initialize a new connectdata object. - */ -static struct connectdata *allocate_conn(struct SessionHandle *data) -{ - struct connectdata *conn = calloc(1, sizeof(struct connectdata)); - if(!conn) - return NULL; - - conn->handler = &Curl_handler_dummy; /* Be sure we have a handler defined - already from start to avoid NULL - situations and checks */ - - /* and we setup a few fields in case we end up actually using this struct */ - - conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ - conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ - conn->connection_id = -1; /* no ID */ - conn->port = -1; /* unknown at this point */ - - /* Default protocol-independent behavior doesn't support persistent - connections, so we set this to force-close. Protocols that support - this need to set this to FALSE in their "curl_do" functions. */ - conn->bits.close = TRUE; - - /* Store creation time to help future close decision making */ - conn->created = Curl_tvnow(); - - conn->data = data; /* Setup the association between this connection - and the SessionHandle */ - - conn->proxytype = data->set.proxytype; /* type */ - -#ifdef CURL_DISABLE_PROXY - - conn->bits.proxy = FALSE; - conn->bits.httpproxy = FALSE; - conn->bits.proxy_user_passwd = FALSE; - conn->bits.tunnel_proxy = FALSE; - -#else /* CURL_DISABLE_PROXY */ - - /* note that these two proxy bits are now just on what looks to be - requested, they may be altered down the road */ - conn->bits.proxy = (data->set.str[STRING_PROXY] && - *data->set.str[STRING_PROXY])?TRUE:FALSE; - conn->bits.httpproxy = (conn->bits.proxy && - (conn->proxytype == CURLPROXY_HTTP || - conn->proxytype == CURLPROXY_HTTP_1_0))?TRUE:FALSE; - conn->bits.proxy_user_passwd = - (NULL != data->set.str[STRING_PROXYUSERNAME])?TRUE:FALSE; - conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy; - -#endif /* CURL_DISABLE_PROXY */ - - conn->bits.user_passwd = (NULL != data->set.str[STRING_USERNAME])?TRUE:FALSE; - conn->bits.ftp_use_epsv = data->set.ftp_use_epsv; - conn->bits.ftp_use_eprt = data->set.ftp_use_eprt; - - conn->verifypeer = data->set.ssl.verifypeer; - conn->verifyhost = data->set.ssl.verifyhost; - - conn->ip_version = data->set.ipver; - -#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) - conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - conn->ntlm_auth_hlpr_pid = 0; - conn->challenge_header = NULL; - conn->response_header = NULL; -#endif - - if(data->multi && Curl_multi_canPipeline(data->multi) && - !conn->master_buffer) { - /* Allocate master_buffer to be used for pipelining */ - conn->master_buffer = calloc(BUFSIZE, sizeof (char)); - if(!conn->master_buffer) - goto error; - } - - /* Initialize the pipeline lists */ - conn->send_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - conn->recv_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - conn->pend_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - conn->done_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - if(!conn->send_pipe || !conn->recv_pipe || !conn->pend_pipe || - !conn->done_pipe) - goto error; - -#if defined(HAVE_KRB4) || defined(HAVE_GSSAPI) - conn->data_prot = PROT_CLEAR; -#endif - - /* Store the local bind parameters that will be used for this connection */ - if(data->set.str[STRING_DEVICE]) { - conn->localdev = strdup(data->set.str[STRING_DEVICE]); - if(!conn->localdev) - goto error; - } - conn->localportrange = data->set.localportrange; - conn->localport = data->set.localport; - - /* the close socket stuff needs to be copied to the connection struct as - it may live on without (this specific) SessionHandle */ - conn->fclosesocket = data->set.fclosesocket; - conn->closesocket_client = data->set.closesocket_client; - - return conn; - error: - - Curl_llist_destroy(conn->send_pipe, NULL); - Curl_llist_destroy(conn->recv_pipe, NULL); - Curl_llist_destroy(conn->pend_pipe, NULL); - Curl_llist_destroy(conn->done_pipe, NULL); - - conn->send_pipe = NULL; - conn->recv_pipe = NULL; - conn->pend_pipe = NULL; - conn->done_pipe = NULL; - - Curl_safefree(conn->master_buffer); - Curl_safefree(conn->localdev); - Curl_safefree(conn); - return NULL; -} - -static CURLcode findprotocol(struct SessionHandle *data, - struct connectdata *conn, - const char *protostr) -{ - const struct Curl_handler * const *pp; - const struct Curl_handler *p; - - /* Scan protocol handler table and match against 'protostr' to set a few - variables based on the URL. Now that the handler may be changed later - when the protocol specific setup function is called. */ - for(pp = protocols; (p = *pp) != NULL; pp++) { - if(Curl_raw_equal(p->scheme, protostr)) { - /* Protocol found in table. Check if allowed */ - if(!(data->set.allowed_protocols & p->protocol)) - /* nope, get out */ - break; - - /* it is allowed for "normal" request, now do an extra check if this is - the result of a redirect */ - if(data->state.this_is_a_follow && - !(data->set.redir_protocols & p->protocol)) - /* nope, get out */ - break; - - /* Perform setup complement if some. */ - conn->handler = conn->given = p; - - /* 'port' and 'remote_port' are set in setup_connection_internals() */ - return CURLE_OK; - } - } - - - /* The protocol was not found in the table, but we don't have to assign it - to anything since it is already assigned to a dummy-struct in the - create_conn() function when the connectdata struct is allocated. */ - failf(data, "Protocol %s not supported or disabled in " LIBCURL_NAME, - protostr); - - return CURLE_UNSUPPORTED_PROTOCOL; -} - -/* - * Parse URL and fill in the relevant members of the connection struct. - */ -static CURLcode parseurlandfillconn(struct SessionHandle *data, - struct connectdata *conn, - bool *prot_missing, - char *user, - char *passwd) -{ - char *at; - char *fragment; - char *path = data->state.path; - char *query; - int rc; - char protobuf[16]; - const char *protop; - CURLcode result; - - *prot_missing = FALSE; - - /************************************************************* - * Parse the URL. - * - * We need to parse the url even when using the proxy, because we will need - * the hostname and port in case we are trying to SSL connect through the - * proxy -- and we don't know if we will need to use SSL until we parse the - * url ... - ************************************************************/ - if((2 == sscanf(data->change.url, "%15[^:]:%[^\n]", - protobuf, path)) && - Curl_raw_equal(protobuf, "file")) { - if(path[0] == '/' && path[1] == '/') { - /* Allow omitted hostname (e.g. file:/). This is not strictly - * speaking a valid file: URL by RFC 1738, but treating file:/ as - * file://localhost/ is similar to how other schemes treat missing - * hostnames. See RFC 1808. */ - - /* This cannot be done with strcpy() in a portable manner, since the - memory areas overlap! */ - memmove(path, path + 2, strlen(path + 2)+1); - } - /* - * we deal with file:/// differently since it supports no - * hostname other than "localhost" and "127.0.0.1", which is unique among - * the URL protocols specified in RFC 1738 - */ - if(path[0] != '/') { - /* the URL included a host name, we ignore host names in file:// URLs - as the standards don't define what to do with them */ - char *ptr=strchr(path, '/'); - if(ptr) { - /* there was a slash present - - RFC1738 (section 3.1, page 5) says: - - The rest of the locator consists of data specific to the scheme, - and is known as the "url-path". It supplies the details of how the - specified resource can be accessed. Note that the "/" between the - host (or port) and the url-path is NOT part of the url-path. - - As most agents use file://localhost/foo to get '/foo' although the - slash preceding foo is a separator and not a slash for the path, - a URL as file://localhost//foo must be valid as well, to refer to - the same file with an absolute path. - */ - - if(ptr[1] && ('/' == ptr[1])) - /* if there was two slashes, we skip the first one as that is then - used truly as a separator */ - ptr++; - - /* This cannot be made with strcpy, as the memory chunks overlap! */ - memmove(path, ptr, strlen(ptr)+1); - } - } - - protop = "file"; /* protocol string */ - } - else { - /* clear path */ - path[0]=0; - - if(2 > sscanf(data->change.url, - "%15[^\n:]://%[^\n/?]%[^\n]", - protobuf, - conn->host.name, path)) { - - /* - * The URL was badly formatted, let's try the browser-style _without_ - * protocol specified like 'http://'. - */ - rc = sscanf(data->change.url, "%[^\n/?]%[^\n]", conn->host.name, path); - if(1 > rc) { - /* - * We couldn't even get this format. - * djgpp 2.04 has a sscanf() bug where 'conn->host.name' is - * assigned, but the return value is EOF! - */ -#if defined(__DJGPP__) && (DJGPP_MINOR == 4) - if(!(rc == -1 && *conn->host.name)) -#endif - { - failf(data, " malformed"); - return CURLE_URL_MALFORMAT; - } - } - - /* - * Since there was no protocol part specified, we guess what protocol it - * is based on the first letters of the server name. - */ - - /* Note: if you add a new protocol, please update the list in - * lib/curl_version.c too! */ - - if(checkprefix("FTP.", conn->host.name)) - protop = "ftp"; - else if(checkprefix("DICT.", conn->host.name)) - protop = "DICT"; - else if(checkprefix("LDAP.", conn->host.name)) - protop = "LDAP"; - else if(checkprefix("IMAP.", conn->host.name)) - protop = "IMAP"; - else { - protop = "http"; - } - - *prot_missing = TRUE; /* not given in URL */ - } - else - protop = protobuf; - } - - /* We search for '?' in the host name (but only on the right side of a - * @-letter to allow ?-letters in username and password) to handle things - * like http://example.com?param= (notice the missing '/'). - */ - at = strchr(conn->host.name, '@'); - if(at) - query = strchr(at+1, '?'); - else - query = strchr(conn->host.name, '?'); - - if(query) { - /* We must insert a slash before the '?'-letter in the URL. If the URL had - a slash after the '?', that is where the path currently begins and the - '?string' is still part of the host name. - - We must move the trailing part from the host name and put it first in - the path. And have it all prefixed with a slash. - */ - - size_t hostlen = strlen(query); - size_t pathlen = strlen(path); - - /* move the existing path plus the zero byte forward, to make room for - the host-name part */ - memmove(path+hostlen+1, path, pathlen+1); - - /* now copy the trailing host part in front of the existing path */ - memcpy(path+1, query, hostlen); - - path[0]='/'; /* prepend the missing slash */ - - *query=0; /* now cut off the hostname at the ? */ - } - else if(!path[0]) { - /* if there's no path set, use a single slash */ - strcpy(path, "/"); - } - - /* If the URL is malformatted (missing a '/' after hostname before path) we - * insert a slash here. The only letter except '/' we accept to start a path - * is '?'. - */ - if(path[0] == '?') { - /* We need this function to deal with overlapping memory areas. We know - that the memory area 'path' points to is 'urllen' bytes big and that - is bigger than the path. Use +1 to move the zero byte too. */ - memmove(&path[1], path, strlen(path)+1); - path[0] = '/'; - } - - /************************************************************* - * Parse a user name and password in the URL and strip it out - * of the host name - *************************************************************/ - result = parse_url_userpass(data, conn, user, passwd); - if(result != CURLE_OK) - return result; - - if(conn->host.name[0] == '[') { - /* This looks like an IPv6 address literal. See if there is an address - scope. */ - char *percent = strstr (conn->host.name, "%25"); - if(percent) { - char *endp; - unsigned long scope = strtoul (percent + 3, &endp, 10); - if(*endp == ']') { - /* The address scope was well formed. Knock it out of the - hostname. */ - memmove(percent, endp, strlen(endp)+1); - if(!data->state.this_is_a_follow) - /* Don't honour a scope given in a Location: header */ - conn->scope = (unsigned int)scope; - } - else - infof(data, "Invalid IPv6 address format\n"); - } - } - - if(data->set.scope) - /* Override any scope that was set above. */ - conn->scope = data->set.scope; - - /* Remove the fragment part of the path. Per RFC 2396, this is always the - last part of the URI. We are looking for the first '#' so that we deal - gracefully with non conformant URI such as http://example.com#foo#bar. */ - fragment = strchr(path, '#'); - if(fragment) { - *fragment = 0; - - /* we know the path part ended with a fragment, so we know the full URL - string does too and we need to cut it off from there so it isn't used - over proxy */ - fragment = strchr(data->change.url, '#'); - if(fragment) - *fragment = 0; - } - - /* - * So if the URL was A://B/C#D, - * protop is A - * conn->host.name is B - * data->state.path is /C - */ - - return findprotocol(data, conn, protop); -} - -/* - * If we're doing a resumed transfer, we need to setup our stuff - * properly. - */ -static CURLcode setup_range(struct SessionHandle *data) -{ - struct UrlState *s = &data->state; - s->resume_from = data->set.set_resume_from; - if(s->resume_from || data->set.str[STRING_SET_RANGE]) { - if(s->rangestringalloc) - free(s->range); - - if(s->resume_from) - s->range = aprintf("%" FORMAT_OFF_TU "-", s->resume_from); - else - s->range = strdup(data->set.str[STRING_SET_RANGE]); - - s->rangestringalloc = (s->range)?TRUE:FALSE; - - if(!s->range) - return CURLE_OUT_OF_MEMORY; - - /* tell ourselves to fetch this range */ - s->use_range = TRUE; /* enable range download */ - } - else - s->use_range = FALSE; /* disable range download */ - - return CURLE_OK; -} - - -/*************************************************************** -* Setup connection internals specific to the requested protocol. -* This MUST get called after proxy magic has been figured out. -***************************************************************/ -static CURLcode setup_connection_internals(struct connectdata *conn) -{ - const struct Curl_handler * p; - CURLcode result; - - conn->socktype = SOCK_STREAM; /* most of them are TCP streams */ - - /* Scan protocol handler table. */ - - /* Perform setup complement if some. */ - p = conn->handler; - - if(p->setup_connection) { - result = (*p->setup_connection)(conn); - - if(result != CURLE_OK) - return result; - - p = conn->handler; /* May have changed. */ - } - - if(conn->port < 0) - /* we check for -1 here since if proxy was detected already, this - was very likely already set to the proxy port */ - conn->port = p->defport; - conn->remote_port = (unsigned short)conn->given->defport; - - return CURLE_OK; -} - -#ifndef CURL_DISABLE_PROXY -/**************************************************************** -* Checks if the host is in the noproxy list. returns true if it matches -* and therefore the proxy should NOT be used. -****************************************************************/ -static bool check_noproxy(const char* name, const char* no_proxy) -{ - /* no_proxy=domain1.dom,host.domain2.dom - * (a comma-separated list of hosts which should - * not be proxied, or an asterisk to override - * all proxy variables) - */ - size_t tok_start; - size_t tok_end; - const char* separator = ", "; - size_t no_proxy_len; - size_t namelen; - char *endptr; - - if(no_proxy && no_proxy[0]) { - if(Curl_raw_equal("*", no_proxy)) { - return TRUE; - } - - /* NO_PROXY was specified and it wasn't just an asterisk */ - - no_proxy_len = strlen(no_proxy); - endptr = strchr(name, ':'); - if(endptr) - namelen = endptr - name; - else - namelen = strlen(name); - - for(tok_start = 0; tok_start < no_proxy_len; tok_start = tok_end + 1) { - while(tok_start < no_proxy_len && - strchr(separator, no_proxy[tok_start]) != NULL) { - /* Look for the beginning of the token. */ - ++tok_start; - } - - if(tok_start == no_proxy_len) - break; /* It was all trailing separator chars, no more tokens. */ - - for(tok_end = tok_start; tok_end < no_proxy_len && - strchr(separator, no_proxy[tok_end]) == NULL; ++tok_end) - /* Look for the end of the token. */ - ; - - /* To match previous behaviour, where it was necessary to specify - * ".local.com" to prevent matching "notlocal.com", we will leave - * the '.' off. - */ - if(no_proxy[tok_start] == '.') - ++tok_start; - - if((tok_end - tok_start) <= namelen) { - /* Match the last part of the name to the domain we are checking. */ - const char *checkn = name + namelen - (tok_end - tok_start); - if(Curl_raw_nequal(no_proxy + tok_start, checkn, - tok_end - tok_start)) { - if((tok_end - tok_start) == namelen || *(checkn - 1) == '.') { - /* We either have an exact match, or the previous character is a . - * so it is within the same domain, so no proxy for this host. - */ - return TRUE; - } - } - } /* if((tok_end - tok_start) <= namelen) */ - } /* for(tok_start = 0; tok_start < no_proxy_len; - tok_start = tok_end + 1) */ - } /* NO_PROXY was specified and it wasn't just an asterisk */ - - return FALSE; -} - -/**************************************************************** -* Detect what (if any) proxy to use. Remember that this selects a host -* name and is not limited to HTTP proxies only. -* The returned pointer must be freed by the caller (unless NULL) -****************************************************************/ -static char *detect_proxy(struct connectdata *conn) -{ - char *proxy = NULL; - -#ifndef CURL_DISABLE_HTTP - /* If proxy was not specified, we check for default proxy environment - * variables, to enable i.e Lynx compliance: - * - * http_proxy=http://some.server.dom:port/ - * https_proxy=http://some.server.dom:port/ - * ftp_proxy=http://some.server.dom:port/ - * no_proxy=domain1.dom,host.domain2.dom - * (a comma-separated list of hosts which should - * not be proxied, or an asterisk to override - * all proxy variables) - * all_proxy=http://some.server.dom:port/ - * (seems to exist for the CERN www lib. Probably - * the first to check for.) - * - * For compatibility, the all-uppercase versions of these variables are - * checked if the lowercase versions don't exist. - */ - char *no_proxy=NULL; - char proxy_env[128]; - - no_proxy=curl_getenv("no_proxy"); - if(!no_proxy) - no_proxy=curl_getenv("NO_PROXY"); - - if(!check_noproxy(conn->host.name, no_proxy)) { - /* It was not listed as without proxy */ - const char *protop = conn->handler->scheme; - char *envp = proxy_env; - char *prox; - - /* Now, build _proxy and check for such a one to use */ - while(*protop) - *envp++ = (char)tolower((int)*protop++); - - /* append _proxy */ - strcpy(envp, "_proxy"); - - /* read the protocol proxy: */ - prox=curl_getenv(proxy_env); - - /* - * We don't try the uppercase version of HTTP_PROXY because of - * security reasons: - * - * When curl is used in a webserver application - * environment (cgi or php), this environment variable can - * be controlled by the web server user by setting the - * http header 'Proxy:' to some value. - * - * This can cause 'internal' http/ftp requests to be - * arbitrarily redirected by any external attacker. - */ - if(!prox && !Curl_raw_equal("http_proxy", proxy_env)) { - /* There was no lowercase variable, try the uppercase version: */ - Curl_strntoupper(proxy_env, proxy_env, sizeof(proxy_env)); - prox=curl_getenv(proxy_env); - } - - if(prox && *prox) { /* don't count "" strings */ - proxy = prox; /* use this */ - } - else { - proxy = curl_getenv("all_proxy"); /* default proxy to use */ - if(!proxy) - proxy=curl_getenv("ALL_PROXY"); - } - } /* if(!check_noproxy(conn->host.name, no_proxy)) - it wasn't specified - non-proxy */ - if(no_proxy) - free(no_proxy); - -#else /* !CURL_DISABLE_HTTP */ - - (void)conn; -#endif /* CURL_DISABLE_HTTP */ - - return proxy; -} - -/* - * If this is supposed to use a proxy, we need to figure out the proxy - * host name, so that we can re-use an existing connection - * that may exist registered to the same proxy host. - * proxy will be freed before this function returns. - */ -static CURLcode parse_proxy(struct SessionHandle *data, - struct connectdata *conn, char *proxy) -{ - char *prox_portno; - char *endofprot; - - /* We use 'proxyptr' to point to the proxy name from now on... */ - char *proxyptr; - char *portptr; - char *atsign; - - /* We do the proxy host string parsing here. We want the host name and the - * port name. Accept a protocol:// prefix - */ - - /* Parse the protocol part if present */ - endofprot = strstr(proxy, "://"); - if(endofprot) { - proxyptr = endofprot+3; - if(checkprefix("socks5h", proxy)) - conn->proxytype = CURLPROXY_SOCKS5_HOSTNAME; - else if(checkprefix("socks5", proxy)) - conn->proxytype = CURLPROXY_SOCKS5; - else if(checkprefix("socks4a", proxy)) - conn->proxytype = CURLPROXY_SOCKS4A; - else if(checkprefix("socks4", proxy) || checkprefix("socks", proxy)) - conn->proxytype = CURLPROXY_SOCKS4; - /* Any other xxx:// : change to http proxy */ - } - else - proxyptr = proxy; /* No xxx:// head: It's a HTTP proxy */ - - /* Is there a username and password given in this proxy url? */ - atsign = strchr(proxyptr, '@'); - if(atsign) { - char proxyuser[MAX_CURL_USER_LENGTH]; - char proxypasswd[MAX_CURL_PASSWORD_LENGTH]; - proxypasswd[0] = 0; - - if(1 <= sscanf(proxyptr, - "%" MAX_CURL_USER_LENGTH_TXT"[^:@]:" - "%" MAX_CURL_PASSWORD_LENGTH_TXT "[^@]", - proxyuser, proxypasswd)) { - CURLcode res = CURLE_OK; - - /* found user and password, rip them out. note that we are - unescaping them, as there is otherwise no way to have a - username or password with reserved characters like ':' in - them. */ - Curl_safefree(conn->proxyuser); - conn->proxyuser = curl_easy_unescape(data, proxyuser, 0, NULL); - - if(!conn->proxyuser) - res = CURLE_OUT_OF_MEMORY; - else { - Curl_safefree(conn->proxypasswd); - conn->proxypasswd = curl_easy_unescape(data, proxypasswd, 0, NULL); - - if(!conn->proxypasswd) - res = CURLE_OUT_OF_MEMORY; - } - - if(CURLE_OK == res) { - conn->bits.proxy_user_passwd = TRUE; /* enable it */ - atsign++; /* the right side of the @-letter */ - - if(atsign) - proxyptr = atsign; /* now use this instead */ - else - res = CURLE_OUT_OF_MEMORY; - } - - if(res) - return res; - } - } - - /* start scanning for port number at this point */ - portptr = proxyptr; - - /* detect and extract RFC2732-style IPv6-addresses */ - if(*proxyptr == '[') { - char *ptr = ++proxyptr; /* advance beyond the initial bracket */ - while(*ptr && (ISXDIGIT(*ptr) || (*ptr == ':') || (*ptr == '%') || - (*ptr == '.'))) - ptr++; - if(*ptr == ']') - /* yeps, it ended nicely with a bracket as well */ - *ptr++ = 0; - else - infof(data, "Invalid IPv6 address format\n"); - portptr = ptr; - /* Note that if this didn't end with a bracket, we still advanced the - * proxyptr first, but I can't see anything wrong with that as no host - * name nor a numeric can legally start with a bracket. - */ - } - - /* Get port number off proxy.server.com:1080 */ - prox_portno = strchr(portptr, ':'); - if(prox_portno) { - *prox_portno = 0x0; /* cut off number from host name */ - prox_portno ++; - /* now set the local port number */ - conn->port = strtol(prox_portno, NULL, 10); - } - else { - if(proxyptr[0]=='/') - /* If the first character in the proxy string is a slash, fail - immediately. The following code will otherwise clear the string which - will lead to code running as if no proxy was set! */ - return CURLE_COULDNT_RESOLVE_PROXY; - - /* without a port number after the host name, some people seem to use - a slash so we strip everything from the first slash */ - atsign = strchr(proxyptr, '/'); - if(atsign) - *atsign = 0x0; /* cut off path part from host name */ - - if(data->set.proxyport) - /* None given in the proxy string, then get the default one if it is - given */ - conn->port = data->set.proxyport; - } - - /* now, clone the cleaned proxy host name */ - conn->proxy.rawalloc = strdup(proxyptr); - conn->proxy.name = conn->proxy.rawalloc; - - if(!conn->proxy.rawalloc) - return CURLE_OUT_OF_MEMORY; - - return CURLE_OK; -} - -/* - * Extract the user and password from the authentication string - */ -static CURLcode parse_proxy_auth(struct SessionHandle *data, - struct connectdata *conn) -{ - char proxyuser[MAX_CURL_USER_LENGTH]=""; - char proxypasswd[MAX_CURL_PASSWORD_LENGTH]=""; - - if(data->set.str[STRING_PROXYUSERNAME] != NULL) { - strncpy(proxyuser, data->set.str[STRING_PROXYUSERNAME], - MAX_CURL_USER_LENGTH); - proxyuser[MAX_CURL_USER_LENGTH-1] = '\0'; /*To be on safe side*/ - } - if(data->set.str[STRING_PROXYPASSWORD] != NULL) { - strncpy(proxypasswd, data->set.str[STRING_PROXYPASSWORD], - MAX_CURL_PASSWORD_LENGTH); - proxypasswd[MAX_CURL_PASSWORD_LENGTH-1] = '\0'; /*To be on safe side*/ - } - - conn->proxyuser = curl_easy_unescape(data, proxyuser, 0, NULL); - if(!conn->proxyuser) - return CURLE_OUT_OF_MEMORY; - - conn->proxypasswd = curl_easy_unescape(data, proxypasswd, 0, NULL); - if(!conn->proxypasswd) - return CURLE_OUT_OF_MEMORY; - - return CURLE_OK; -} -#endif /* CURL_DISABLE_PROXY */ - -/* - * - * Parse a user name and password in the URL and strip it out of the host name - * - * Inputs: data->set.use_netrc (CURLOPT_NETRC) - * conn->host.name - * - * Outputs: (almost :- all currently undefined) - * conn->bits.user_passwd - non-zero if non-default passwords exist - * user - non-zero length if defined - * passwd - ditto - * conn->host.name - remove user name and password - */ -static CURLcode parse_url_userpass(struct SessionHandle *data, - struct connectdata *conn, - char *user, char *passwd) -{ - /* At this point, we're hoping all the other special cases have - * been taken care of, so conn->host.name is at most - * [user[:password]]@]hostname - * - * We need somewhere to put the embedded details, so do that first. - */ - - char *ptr=strchr(conn->host.name, '@'); - char *userpass = conn->host.name; - - user[0] =0; /* to make everything well-defined */ - passwd[0]=0; - - /* We will now try to extract the - * possible user+password pair in a string like: - * ftp://user:password@ftp.my.site:8021/README */ - if(ptr != NULL) { - /* there's a user+password given here, to the left of the @ */ - - conn->host.name = ++ptr; - - /* So the hostname is sane. Only bother interpreting the - * results if we could care. It could still be wasted - * work because it might be overtaken by the programmatically - * set user/passwd, but doing that first adds more cases here :-( - */ - - conn->bits.userpwd_in_url = TRUE; - if(data->set.use_netrc != CURL_NETRC_REQUIRED) { - /* We could use the one in the URL */ - - conn->bits.user_passwd = TRUE; /* enable user+password */ - - if(*userpass != ':') { - /* the name is given, get user+password */ - sscanf(userpass, "%" MAX_CURL_USER_LENGTH_TXT "[^:@]:" - "%" MAX_CURL_PASSWORD_LENGTH_TXT "[^@]", - user, passwd); - } - else - /* no name given, get the password only */ - sscanf(userpass, ":%" MAX_CURL_PASSWORD_LENGTH_TXT "[^@]", passwd); - - if(user[0]) { - char *newname=curl_easy_unescape(data, user, 0, NULL); - if(!newname) - return CURLE_OUT_OF_MEMORY; - if(strlen(newname) < MAX_CURL_USER_LENGTH) - strcpy(user, newname); - - /* if the new name is longer than accepted, then just use - the unconverted name, it'll be wrong but what the heck */ - free(newname); - } - if(passwd[0]) { - /* we have a password found in the URL, decode it! */ - char *newpasswd=curl_easy_unescape(data, passwd, 0, NULL); - if(!newpasswd) - return CURLE_OUT_OF_MEMORY; - if(strlen(newpasswd) < MAX_CURL_PASSWORD_LENGTH) - strcpy(passwd, newpasswd); - - free(newpasswd); - } - } - } - return CURLE_OK; -} - -/************************************************************* - * Figure out the remote port number and fix it in the URL - * - * No matter if we use a proxy or not, we have to figure out the remote - * port number of various reasons. - * - * To be able to detect port number flawlessly, we must not confuse them - * IPv6-specified addresses in the [0::1] style. (RFC2732) - * - * The conn->host.name is currently [user:passwd@]host[:port] where host - * could be a hostname, IPv4 address or IPv6 address. - * - * The port number embedded in the URL is replaced, if necessary. - *************************************************************/ -static CURLcode parse_remote_port(struct SessionHandle *data, - struct connectdata *conn) -{ - char *portptr; - char endbracket; - - /* Note that at this point, the IPv6 address cannot contain any scope - suffix as that has already been removed in the parseurlandfillconn() - function */ - if((1 == sscanf(conn->host.name, "[%*45[0123456789abcdefABCDEF:.]%c", - &endbracket)) && - (']' == endbracket)) { - /* this is a RFC2732-style specified IP-address */ - conn->bits.ipv6_ip = TRUE; - - conn->host.name++; /* skip over the starting bracket */ - portptr = strchr(conn->host.name, ']'); - if(portptr) { - *portptr++ = '\0'; /* zero terminate, killing the bracket */ - if(':' != *portptr) - portptr = NULL; /* no port number available */ - } - } - else { -#ifdef ENABLE_IPV6 - struct in6_addr in6; - if(Curl_inet_pton(AF_INET6, conn->host.name, &in6) > 0) { - /* This is a numerical IPv6 address, meaning this is a wrongly formatted - URL */ - failf(data, "IPv6 numerical address used in URL without brackets"); - return CURLE_URL_MALFORMAT; - } -#endif - - portptr = strrchr(conn->host.name, ':'); - } - - if(data->set.use_port && data->state.allow_port) { - /* if set, we use this and ignore the port possibly given in the URL */ - conn->remote_port = (unsigned short)data->set.use_port; - if(portptr) - *portptr = '\0'; /* cut off the name there anyway - if there was a port - number - since the port number is to be ignored! */ - if(conn->bits.httpproxy) { - /* we need to create new URL with the new port number */ - char *url; - char type[12]=""; - - if(conn->bits.type_set) - snprintf(type, sizeof(type), ";type=%c", - data->set.prefer_ascii?'A': - (data->set.ftp_list_only?'D':'I')); - - /* - * This synthesized URL isn't always right--suffixes like ;type=A are - * stripped off. It would be better to work directly from the original - * URL and simply replace the port part of it. - */ - url = aprintf("%s://%s%s%s:%hu%s%s%s", conn->given->scheme, - conn->bits.ipv6_ip?"[":"", conn->host.name, - conn->bits.ipv6_ip?"]":"", conn->remote_port, - data->state.slash_removed?"/":"", data->state.path, - type); - if(!url) - return CURLE_OUT_OF_MEMORY; - - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - - data->change.url = url; - data->change.url_alloc = TRUE; - } - } - else if(portptr) { - /* no CURLOPT_PORT given, extract the one from the URL */ - - char *rest; - unsigned long port; - - port=strtoul(portptr+1, &rest, 10); /* Port number must be decimal */ - - if(rest != (portptr+1) && *rest == '\0') { - /* The colon really did have only digits after it, - * so it is either a port number or a mistake */ - - if(port > 0xffff) { /* Single unix standard says port numbers are - * 16 bits long */ - failf(data, "Port number too large: %lu", port); - return CURLE_URL_MALFORMAT; - } - - *portptr = '\0'; /* cut off the name there */ - conn->remote_port = curlx_ultous(port); - } - else if(!port) - /* Browser behavior adaptation. If there's a colon with no digits after, - just cut off the name there which makes us ignore the colon and just - use the default port. Firefox and Chrome both do that. */ - *portptr = '\0'; - } - return CURLE_OK; -} - -/* - * Override a user name and password from the URL with that in the - * CURLOPT_USERPWD option or a .netrc file, if applicable. - */ -static void override_userpass(struct SessionHandle *data, - struct connectdata *conn, - char *user, char *passwd) -{ - if(data->set.str[STRING_USERNAME] != NULL) { - strncpy(user, data->set.str[STRING_USERNAME], MAX_CURL_USER_LENGTH); - user[MAX_CURL_USER_LENGTH-1] = '\0'; /*To be on safe side*/ - } - if(data->set.str[STRING_PASSWORD] != NULL) { - strncpy(passwd, data->set.str[STRING_PASSWORD], MAX_CURL_PASSWORD_LENGTH); - passwd[MAX_CURL_PASSWORD_LENGTH-1] = '\0'; /*To be on safe side*/ - } - - conn->bits.netrc = FALSE; - if(data->set.use_netrc != CURL_NETRC_IGNORED) { - if(Curl_parsenetrc(conn->host.name, - user, passwd, - data->set.str[STRING_NETRC_FILE])) { - infof(data, "Couldn't find host %s in the " - DOT_CHAR "netrc file; using defaults\n", - conn->host.name); - } - else { - /* set bits.netrc TRUE to remember that we got the name from a .netrc - file, so that it is safe to use even if we followed a Location: to a - different host or similar. */ - conn->bits.netrc = TRUE; - - conn->bits.user_passwd = TRUE; /* enable user+password */ - } - } -} - -/* - * Set password so it's available in the connection. - */ -static CURLcode set_userpass(struct connectdata *conn, - const char *user, const char *passwd) -{ - /* If our protocol needs a password and we have none, use the defaults */ - if((conn->handler->flags & PROTOPT_NEEDSPWD) && - !conn->bits.user_passwd) { - - conn->user = strdup(CURL_DEFAULT_USER); - if(conn->user) - conn->passwd = strdup(CURL_DEFAULT_PASSWORD); - else - conn->passwd = NULL; - /* This is the default password, so DON'T set conn->bits.user_passwd */ - } - else { - /* store user + password, zero-length if not set */ - conn->user = strdup(user); - if(conn->user) - conn->passwd = strdup(passwd); - else - conn->passwd = NULL; - } - if(!conn->user || !conn->passwd) - return CURLE_OUT_OF_MEMORY; - - return CURLE_OK; -} - -/************************************************************* - * Resolve the address of the server or proxy - *************************************************************/ -static CURLcode resolve_server(struct SessionHandle *data, - struct connectdata *conn, - bool *async) -{ - CURLcode result=CURLE_OK; - long timeout_ms = Curl_timeleft(data, NULL, TRUE); - - /************************************************************* - * Resolve the name of the server or proxy - *************************************************************/ - if(conn->bits.reuse) - /* We're reusing the connection - no need to resolve anything, and - fix_hostname() was called already in create_conn() for the re-use - case. */ - *async = FALSE; - - else { - /* this is a fresh connect */ - int rc; - struct Curl_dns_entry *hostaddr; - - /* set a pointer to the hostname we display */ - fix_hostname(data, conn, &conn->host); - - if(!conn->proxy.name || !*conn->proxy.name) { - /* If not connecting via a proxy, extract the port from the URL, if it is - * there, thus overriding any defaults that might have been set above. */ - conn->port = conn->remote_port; /* it is the same port */ - - /* Resolve target host right on */ - rc = Curl_resolv_timeout(conn, conn->host.name, (int)conn->port, - &hostaddr, timeout_ms); - if(rc == CURLRESOLV_PENDING) - *async = TRUE; - - else if(rc == CURLRESOLV_TIMEDOUT) - result = CURLE_OPERATION_TIMEDOUT; - - else if(!hostaddr) { - failf(data, "Couldn't resolve host '%s'", conn->host.dispname); - result = CURLE_COULDNT_RESOLVE_HOST; - /* don't return yet, we need to clean up the timeout first */ - } - } - else { - /* This is a proxy that hasn't been resolved yet. */ - - /* IDN-fix the proxy name */ - fix_hostname(data, conn, &conn->proxy); - - /* resolve proxy */ - rc = Curl_resolv_timeout(conn, conn->proxy.name, (int)conn->port, - &hostaddr, timeout_ms); - - if(rc == CURLRESOLV_PENDING) - *async = TRUE; - - else if(rc == CURLRESOLV_TIMEDOUT) - result = CURLE_OPERATION_TIMEDOUT; - - else if(!hostaddr) { - failf(data, "Couldn't resolve proxy '%s'", conn->proxy.dispname); - result = CURLE_COULDNT_RESOLVE_PROXY; - /* don't return yet, we need to clean up the timeout first */ - } - } - DEBUGASSERT(conn->dns_entry == NULL); - conn->dns_entry = hostaddr; - } - - return result; -} - -/* - * Cleanup the connection just allocated before we can move along and use the - * previously existing one. All relevant data is copied over and old_conn is - * ready for freeing once this function returns. - */ -static void reuse_conn(struct connectdata *old_conn, - struct connectdata *conn) -{ - if(old_conn->proxy.rawalloc) - free(old_conn->proxy.rawalloc); - - /* free the SSL config struct from this connection struct as this was - allocated in vain and is targeted for destruction */ - Curl_free_ssl_config(&old_conn->ssl_config); - - conn->data = old_conn->data; - - /* get the user+password information from the old_conn struct since it may - * be new for this request even when we re-use an existing connection */ - conn->bits.user_passwd = old_conn->bits.user_passwd; - if(conn->bits.user_passwd) { - /* use the new user name and password though */ - Curl_safefree(conn->user); - Curl_safefree(conn->passwd); - conn->user = old_conn->user; - conn->passwd = old_conn->passwd; - old_conn->user = NULL; - old_conn->passwd = NULL; - } - - conn->bits.proxy_user_passwd = old_conn->bits.proxy_user_passwd; - if(conn->bits.proxy_user_passwd) { - /* use the new proxy user name and proxy password though */ - Curl_safefree(conn->proxyuser); - Curl_safefree(conn->proxypasswd); - conn->proxyuser = old_conn->proxyuser; - conn->proxypasswd = old_conn->proxypasswd; - old_conn->proxyuser = NULL; - old_conn->proxypasswd = NULL; - } - - /* host can change, when doing keepalive with a proxy or if the case is - different this time etc */ - Curl_safefree(conn->host.rawalloc); - conn->host=old_conn->host; - - /* persist connection info in session handle */ - Curl_persistconninfo(conn); - - /* re-use init */ - conn->bits.reuse = TRUE; /* yes, we're re-using here */ - - Curl_safefree(old_conn->user); - Curl_safefree(old_conn->passwd); - Curl_safefree(old_conn->proxyuser); - Curl_safefree(old_conn->proxypasswd); - Curl_safefree(old_conn->localdev); - - Curl_llist_destroy(old_conn->send_pipe, NULL); - Curl_llist_destroy(old_conn->recv_pipe, NULL); - Curl_llist_destroy(old_conn->pend_pipe, NULL); - Curl_llist_destroy(old_conn->done_pipe, NULL); - - old_conn->send_pipe = NULL; - old_conn->recv_pipe = NULL; - old_conn->pend_pipe = NULL; - old_conn->done_pipe = NULL; - - Curl_safefree(old_conn->master_buffer); -} - -/** - * create_conn() sets up a new connectdata struct, or re-uses an already - * existing one, and resolves host name. - * - * if this function returns CURLE_OK and *async is set to TRUE, the resolve - * response will be coming asynchronously. If *async is FALSE, the name is - * already resolved. - * - * @param data The sessionhandle pointer - * @param in_connect is set to the next connection data pointer - * @param async is set TRUE when an async DNS resolution is pending - * @see Curl_setup_conn() - * - * *NOTE* this function assigns the conn->data pointer! - */ - -static CURLcode create_conn(struct SessionHandle *data, - struct connectdata **in_connect, - bool *async) -{ - CURLcode result=CURLE_OK; - struct connectdata *conn; - struct connectdata *conn_temp = NULL; - size_t urllen; - char user[MAX_CURL_USER_LENGTH]; - char passwd[MAX_CURL_PASSWORD_LENGTH]; - bool reuse; - char *proxy = NULL; - bool prot_missing = FALSE; - - *async = FALSE; - - /************************************************************* - * Check input data - *************************************************************/ - - if(!data->change.url) - return CURLE_URL_MALFORMAT; - - /* First, split up the current URL in parts so that we can use the - parts for checking against the already present connections. In order - to not have to modify everything at once, we allocate a temporary - connection data struct and fill in for comparison purposes. */ - conn = allocate_conn(data); - - if(!conn) - return CURLE_OUT_OF_MEMORY; - - /* We must set the return variable as soon as possible, so that our - parent can cleanup any possible allocs we may have done before - any failure */ - *in_connect = conn; - - /* This initing continues below, see the comment "Continue connectdata - * initialization here" */ - - /*********************************************************** - * We need to allocate memory to store the path in. We get the size of the - * full URL to be sure, and we need to make it at least 256 bytes since - * other parts of the code will rely on this fact - ***********************************************************/ -#define LEAST_PATH_ALLOC 256 - urllen=strlen(data->change.url); - if(urllen < LEAST_PATH_ALLOC) - urllen=LEAST_PATH_ALLOC; - - /* - * We malloc() the buffers below urllen+2 to make room for 2 possibilities: - * 1 - an extra terminating zero - * 2 - an extra slash (in case a syntax like "www.host.com?moo" is used) - */ - - Curl_safefree(data->state.pathbuffer); - data->state.path = NULL; - - data->state.pathbuffer = malloc(urllen+2); - if(NULL == data->state.pathbuffer) - return CURLE_OUT_OF_MEMORY; /* really bad error */ - data->state.path = data->state.pathbuffer; - - conn->host.rawalloc = malloc(urllen+2); - if(NULL == conn->host.rawalloc) { - Curl_safefree(data->state.pathbuffer); - data->state.path = NULL; - return CURLE_OUT_OF_MEMORY; - } - - conn->host.name = conn->host.rawalloc; - conn->host.name[0] = 0; - - result = parseurlandfillconn(data, conn, &prot_missing, user, passwd); - if(result != CURLE_OK) - return result; - - /************************************************************* - * No protocol part in URL was used, add it! - *************************************************************/ - if(prot_missing) { - /* We're guessing prefixes here and if we're told to use a proxy or if - we're gonna follow a Location: later or... then we need the protocol - part added so that we have a valid URL. */ - char *reurl; - - reurl = aprintf("%s://%s", conn->handler->scheme, data->change.url); - - if(!reurl) { - Curl_safefree(proxy); - return CURLE_OUT_OF_MEMORY; - } - - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - - data->change.url = reurl; - data->change.url_alloc = TRUE; /* free this later */ - } - - /************************************************************* - * If the protocol can't handle url query strings, then cut - * of the unhandable part - *************************************************************/ - if((conn->given->flags&PROTOPT_NOURLQUERY)) { - char *path_q_sep = strchr(conn->data->state.path, '?'); - if(path_q_sep) { - /* according to rfc3986, allow the query (?foo=bar) - also on protocols that can't handle it. - - cut the string-part after '?' - */ - - /* terminate the string */ - path_q_sep[0] = 0; - } - } - -#ifndef CURL_DISABLE_PROXY - /************************************************************* - * Extract the user and password from the authentication string - *************************************************************/ - if(conn->bits.proxy_user_passwd) { - result = parse_proxy_auth(data, conn); - if(result != CURLE_OK) - return result; - } - - /************************************************************* - * Detect what (if any) proxy to use - *************************************************************/ - if(data->set.str[STRING_PROXY]) { - proxy = strdup(data->set.str[STRING_PROXY]); - /* if global proxy is set, this is it */ - if(NULL == proxy) { - failf(data, "memory shortage"); - return CURLE_OUT_OF_MEMORY; - } - } - - if(data->set.str[STRING_NOPROXY] && - check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY])) { - if(proxy) { - free(proxy); /* proxy is in exception list */ - proxy = NULL; - } - } - else if(!proxy) - proxy = detect_proxy(conn); - - if(proxy && (!*proxy || (conn->handler->flags & PROTOPT_NONETWORK))) { - free(proxy); /* Don't bother with an empty proxy string or if the - protocol doesn't work with network */ - proxy = NULL; - } - - /*********************************************************************** - * If this is supposed to use a proxy, we need to figure out the proxy host - * name, proxy type and port number, so that we can re-use an existing - * connection that may exist registered to the same proxy host. - ***********************************************************************/ - if(proxy) { - result = parse_proxy(data, conn, proxy); - - free(proxy); /* parse_proxy copies the proxy string */ - - if(result) - return result; - - if((conn->proxytype == CURLPROXY_HTTP) || - (conn->proxytype == CURLPROXY_HTTP_1_0)) { -#ifdef CURL_DISABLE_HTTP - /* asking for a HTTP proxy is a bit funny when HTTP is disabled... */ - return CURLE_UNSUPPORTED_PROTOCOL; -#else - /* force this connection's protocol to become HTTP if not already - compatible - if it isn't tunneling through */ - if(!(conn->handler->protocol & CURLPROTO_HTTP) && - !conn->bits.tunnel_proxy) - conn->handler = &Curl_handler_http; - - conn->bits.httpproxy = TRUE; -#endif - } - else - conn->bits.httpproxy = FALSE; /* not a HTTP proxy */ - conn->bits.proxy = TRUE; - } - else { - /* we aren't using the proxy after all... */ - conn->bits.proxy = FALSE; - conn->bits.httpproxy = FALSE; - conn->bits.proxy_user_passwd = FALSE; - conn->bits.tunnel_proxy = FALSE; - } - -#endif /* CURL_DISABLE_PROXY */ - - /************************************************************* - * Setup internals depending on protocol. Needs to be done after - * we figured out what/if proxy to use. - *************************************************************/ - result = setup_connection_internals(conn); - if(result != CURLE_OK) { - Curl_safefree(proxy); - return result; - } - - conn->recv[FIRSTSOCKET] = Curl_recv_plain; - conn->send[FIRSTSOCKET] = Curl_send_plain; - conn->recv[SECONDARYSOCKET] = Curl_recv_plain; - conn->send[SECONDARYSOCKET] = Curl_send_plain; - - /*********************************************************************** - * file: is a special case in that it doesn't need a network connection - ***********************************************************************/ -#ifndef CURL_DISABLE_FILE - if(conn->handler->flags & PROTOPT_NONETWORK) { - bool done; - /* this is supposed to be the connect function so we better at least check - that the file is present here! */ - DEBUGASSERT(conn->handler->connect_it); - result = conn->handler->connect_it(conn, &done); - - /* Setup a "faked" transfer that'll do nothing */ - if(CURLE_OK == result) { - conn->data = data; - conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; /* we are "connected */ - - ConnectionStore(data, conn); - - /* - * Setup whatever necessary for a resumed transfer - */ - result = setup_range(data); - if(result) { - DEBUGASSERT(conn->handler->done); - /* we ignore the return code for the protocol-specific DONE */ - (void)conn->handler->done(conn, result, FALSE); - return result; - } - - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ - -1, NULL); /* no upload */ - } - - return result; - } -#endif - - /************************************************************* - * If the protocol is using SSL and HTTP proxy is used, we set - * the tunnel_proxy bit. - *************************************************************/ - if((conn->given->flags&PROTOPT_SSL) && conn->bits.httpproxy) - conn->bits.tunnel_proxy = TRUE; - - /************************************************************* - * Figure out the remote port number and fix it in the URL - *************************************************************/ - result = parse_remote_port(data, conn); - if(result != CURLE_OK) - return result; - - /************************************************************* - * Check for an overridden user name and password, then set it - * for use - *************************************************************/ - override_userpass(data, conn, user, passwd); - result = set_userpass(conn, user, passwd); - if(result != CURLE_OK) - return result; - - /* Get a cloned copy of the SSL config situation stored in the - connection struct. But to get this going nicely, we must first make - sure that the strings in the master copy are pointing to the correct - strings in the session handle strings array! - - Keep in mind that the pointers in the master copy are pointing to strings - that will be freed as part of the SessionHandle struct, but all cloned - copies will be separately allocated. - */ - data->set.ssl.CApath = data->set.str[STRING_SSL_CAPATH]; - data->set.ssl.CAfile = data->set.str[STRING_SSL_CAFILE]; - data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE]; - data->set.ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; - data->set.ssl.random_file = data->set.str[STRING_SSL_RANDOM_FILE]; - data->set.ssl.egdsocket = data->set.str[STRING_SSL_EGDSOCKET]; - data->set.ssl.cipher_list = data->set.str[STRING_SSL_CIPHER_LIST]; -#ifdef USE_TLS_SRP - data->set.ssl.username = data->set.str[STRING_TLSAUTH_USERNAME]; - data->set.ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD]; -#endif - - if(!Curl_clone_ssl_config(&data->set.ssl, &conn->ssl_config)) - return CURLE_OUT_OF_MEMORY; - - /************************************************************* - * Check the current list of connections to see if we can - * re-use an already existing one or if we have to create a - * new one. - *************************************************************/ - - /* reuse_fresh is TRUE if we are told to use a new connection by force, but - we only acknowledge this option if this is not a re-used connection - already (which happens due to follow-location or during a HTTP - authentication phase). */ - if(data->set.reuse_fresh && !data->state.this_is_a_follow) - reuse = FALSE; - else - reuse = ConnectionExists(data, conn, &conn_temp); - - if(reuse) { - /* - * We already have a connection for this, we got the former connection - * in the conn_temp variable and thus we need to cleanup the one we - * just allocated before we can move along and use the previously - * existing one. - */ - reuse_conn(conn, conn_temp); - free(conn); /* we don't need this anymore */ - conn = conn_temp; - *in_connect = conn; - - /* set a pointer to the hostname we display */ - fix_hostname(data, conn, &conn->host); - - infof(data, "Re-using existing connection! (#%ld) with host %s\n", - conn->connection_id, - conn->proxy.name?conn->proxy.dispname:conn->host.dispname); - } - else { - /* - * This is a brand new connection, so let's store it in the connection - * cache of ours! - */ - ConnectionStore(data, conn); - } - - /* Setup and init stuff before DO starts, in preparing for the transfer. */ - do_init(conn); - - /* - * Setup whatever necessary for a resumed transfer - */ - result = setup_range(data); - if(result) - return result; - - /* Continue connectdata initialization here. */ - - /* - * Inherit the proper values from the urldata struct AFTER we have arranged - * the persistent connection stuff - */ - conn->fread_func = data->set.fread_func; - conn->fread_in = data->set.in; - conn->seek_func = data->set.seek_func; - conn->seek_client = data->set.seek_client; - - /************************************************************* - * Resolve the address of the server or proxy - *************************************************************/ - result = resolve_server(data, conn, async); - - return result; -} - -/* Curl_setup_conn() is called after the name resolve initiated in - * create_conn() is all done. - * - * Curl_setup_conn() also handles reused connections - * - * conn->data MUST already have been setup fine (in create_conn) - */ - -CURLcode Curl_setup_conn(struct connectdata *conn, - bool *protocol_done) -{ - CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; - - Curl_pgrsTime(data, TIMER_NAMELOOKUP); - - if(conn->handler->flags & PROTOPT_NONETWORK) { - /* nothing to setup when not using a network */ - *protocol_done = TRUE; - return result; - } - *protocol_done = FALSE; /* default to not done */ - - /* set proxy_connect_closed to false unconditionally already here since it - is used strictly to provide extra information to a parent function in the - case of proxy CONNECT failures and we must make sure we don't have it - lingering set from a previous invoke */ - conn->bits.proxy_connect_closed = FALSE; - - /* - * Set user-agent. Used for HTTP, but since we can attempt to tunnel - * basically anything through a http proxy we can't limit this based on - * protocol. - */ - if(data->set.str[STRING_USERAGENT]) { - Curl_safefree(conn->allocptr.uagent); - conn->allocptr.uagent = - aprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); - if(!conn->allocptr.uagent) - return CURLE_OUT_OF_MEMORY; - } - - data->req.headerbytecount = 0; - -#ifdef CURL_DO_LINEEND_CONV - data->state.crlf_conversions = 0; /* reset CRLF conversion counter */ -#endif /* CURL_DO_LINEEND_CONV */ - - for(;;) { - /* loop for CURL_SERVER_CLOSED_CONNECTION */ - - if(CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET]) { - /* Try to connect only if not already connected */ - bool connected = FALSE; - - result = ConnectPlease(data, conn, &connected); - - if(result && !conn->ip_addr) { - /* transport connection failure not related with authentication */ - conn->bits.tcpconnect[FIRSTSOCKET] = FALSE; - return result; - } - - if(connected) { - result = Curl_protocol_connect(conn, protocol_done); - if(CURLE_OK == result) - conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; - } - else - conn->bits.tcpconnect[FIRSTSOCKET] = FALSE; - - /* if the connection was closed by the server while exchanging - authentication informations, retry with the new set - authentication information */ - if(conn->bits.proxy_connect_closed) { - /* reset the error buffer */ - if(data->set.errorbuffer) - data->set.errorbuffer[0] = '\0'; - data->state.errorbuf = FALSE; - continue; - } - - if(CURLE_OK != result) - return result; - } - else { - Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */ - Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ - conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; - *protocol_done = TRUE; - Curl_verboseconnect(conn); - Curl_updateconninfo(conn, conn->sock[FIRSTSOCKET]); - } - /* Stop the loop now */ - break; - } - - conn->now = Curl_tvnow(); /* time this *after* the connect is done, we - set this here perhaps a second time */ - -#ifdef __EMX__ - /* - * This check is quite a hack. We're calling _fsetmode to fix the problem - * with fwrite converting newline characters (you get mangled text files, - * and corrupted binary files when you download to stdout and redirect it to - * a file). - */ - - if((data->set.out)->_handle == NULL) { - _fsetmode(stdout, "b"); - } -#endif - - return result; -} - -CURLcode Curl_connect(struct SessionHandle *data, - struct connectdata **in_connect, - bool *asyncp, - bool *protocol_done) -{ - CURLcode code; - - *asyncp = FALSE; /* assume synchronous resolves by default */ - - /* call the stuff that needs to be called */ - code = create_conn(data, in_connect, asyncp); - - if(CURLE_OK == code) { - /* no error */ - if((*in_connect)->send_pipe->size || (*in_connect)->recv_pipe->size) - /* pipelining */ - *protocol_done = TRUE; - else if(!*asyncp) { - /* DNS resolution is done: that's either because this is a reused - connection, in which case DNS was unnecessary, or because DNS - really did finish already (synch resolver/fast async resolve) */ - code = Curl_setup_conn(*in_connect, protocol_done); - } - } - - if(code && *in_connect) { - /* We're not allowed to return failure with memory left allocated - in the connectdata struct, free those here */ - Curl_disconnect(*in_connect, FALSE); /* close the connection */ - *in_connect = NULL; /* return a NULL */ - } - - return code; -} - -CURLcode Curl_done(struct connectdata **connp, - CURLcode status, /* an error if this is called after an - error was detected */ - bool premature) -{ - CURLcode result; - struct connectdata *conn; - struct SessionHandle *data; - - DEBUGASSERT(*connp); - - conn = *connp; - data = conn->data; - - if(conn->bits.done) - /* Stop if Curl_done() has already been called */ - return CURLE_OK; - - Curl_getoff_all_pipelines(data, conn); - - if((conn->send_pipe->size + conn->recv_pipe->size != 0 && - !data->set.reuse_forbid && - !conn->bits.close)) - /* Stop if pipeline is not empty and we do not have to close - connection. */ - return CURLE_OK; - - conn->bits.done = TRUE; /* called just now! */ - - /* Cleanup possible redirect junk */ - if(data->req.newurl) { - free(data->req.newurl); - data->req.newurl = NULL; - } - if(data->req.location) { - free(data->req.location); - data->req.location = NULL; - } - - Curl_resolver_cancel(conn); - - if(conn->dns_entry) { - Curl_resolv_unlock(data, conn->dns_entry); /* done with this */ - conn->dns_entry = NULL; - } - - /* this calls the protocol-specific function pointer previously set */ - if(conn->handler->done) - result = conn->handler->done(conn, status, premature); - else - result = CURLE_OK; - - if(Curl_pgrsDone(conn) && !result) - result = CURLE_ABORTED_BY_CALLBACK; - - /* if the transfer was completed in a paused state there can be buffered - data left to write and then kill */ - if(data->state.tempwrite) { - free(data->state.tempwrite); - data->state.tempwrite = NULL; - } - - /* if data->set.reuse_forbid is TRUE, it means the libcurl client has - forced us to close this no matter what we think. - - if conn->bits.close is TRUE, it means that the connection should be - closed in spite of all our efforts to be nice, due to protocol - restrictions in our or the server's end - - if premature is TRUE, it means this connection was said to be DONE before - the entire request operation is complete and thus we can't know in what - state it is for re-using, so we're forced to close it. In a perfect world - we can add code that keep track of if we really must close it here or not, - but currently we have no such detail knowledge. - - connection_id == -1 here means that the connection has not been added - to the connection cache (OOM) and thus we must disconnect it here. - */ - if(data->set.reuse_forbid || conn->bits.close || premature || - (-1 == conn->connection_id)) { - CURLcode res2 = Curl_disconnect(conn, premature); /* close connection */ - - /* If we had an error already, make sure we return that one. But - if we got a new error, return that. */ - if(!result && res2) - result = res2; - } - else { - ConnectionDone(conn); /* the connection is no longer in use */ - - /* remember the most recently used connection */ - data->state.lastconnect = conn; - - infof(data, "Connection #%ld to host %s left intact\n", - conn->connection_id, - conn->bits.httpproxy?conn->proxy.dispname:conn->host.dispname); - } - - *connp = NULL; /* to make the caller of this function better detect that - this was either closed or handed over to the connection - cache here, and therefore cannot be used from this point on - */ - - return result; -} - -/* - * do_init() inits the readwrite session. This is inited each time (in the DO - * function before the protocol-specific DO functions are invoked) for a - * transfer, sometimes multiple times on the same SessionHandle. Make sure - * nothing in here depends on stuff that are setup dynamically for the - * transfer. - */ - -static CURLcode do_init(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - struct SingleRequest *k = &data->req; - - conn->bits.done = FALSE; /* Curl_done() is not called yet */ - conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to use */ - data->state.expect100header = FALSE; - - if(data->set.opt_no_body) - /* in HTTP lingo, no body means using the HEAD request... */ - data->set.httpreq = HTTPREQ_HEAD; - else if(HTTPREQ_HEAD == data->set.httpreq) - /* ... but if unset there really is no perfect method that is the - "opposite" of HEAD but in reality most people probably think GET - then. The important thing is that we can't let it remain HEAD if the - opt_no_body is set FALSE since then we'll behave wrong when getting - HTTP. */ - data->set.httpreq = HTTPREQ_GET; - - /* NB: the content encoding software depends on this initialization */ - Curl_easy_initHandleData(data); - - k->start = Curl_tvnow(); /* start time */ - k->now = k->start; /* current time is now */ - k->header = TRUE; /* assume header */ - - k->bytecount = 0; - - k->buf = data->state.buffer; - k->uploadbuf = data->state.uploadbuffer; - k->hbufp = data->state.headerbuff; - k->ignorebody=FALSE; - - Curl_speedinit(data); - - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - - return CURLE_OK; -} - -/* - * do_complete is called when the DO actions are complete. - * - * We init chunking and trailer bits to their default values here immediately - * before receiving any header data for the current request in the pipeline. - */ -static void do_complete(struct connectdata *conn) -{ - conn->data->req.chunk=FALSE; - conn->data->req.maxfd = (conn->sockfd>conn->writesockfd? - conn->sockfd:conn->writesockfd)+1; - Curl_pgrsTime(conn->data, TIMER_PRETRANSFER); -} - -CURLcode Curl_do(struct connectdata **connp, bool *done) -{ - CURLcode result=CURLE_OK; - struct connectdata *conn = *connp; - struct SessionHandle *data = conn->data; - - if(conn->handler->do_it) { - /* generic protocol-specific function pointer set in curl_connect() */ - result = conn->handler->do_it(conn, done); - - /* This was formerly done in curl_transfer.c, but we better do it here */ - if((CURLE_SEND_ERROR == result) && conn->bits.reuse) { - /* - * If the connection is using an easy handle, call reconnect - * to re-establish the connection. Otherwise, let the multi logic - * figure out how to re-establish the connection. - */ - if(!data->multi) { - result = Curl_reconnect_request(connp); - - if(result == CURLE_OK) { - /* ... finally back to actually retry the DO phase */ - conn = *connp; /* re-assign conn since Curl_reconnect_request - creates a new connection */ - result = conn->handler->do_it(conn, done); - } - } - else - return result; - } - - if((result == CURLE_OK) && *done) - /* do_complete must be called after the protocol-specific DO function */ - do_complete(conn); - } - return result; -} - -/* - * Curl_do_more() is called during the DO_MORE multi state. It is basically a - * second stage DO state which (wrongly) was introduced to support FTP's - * second connection. - * - * TODO: A future libcurl should be able to work away this state. - * - */ - -CURLcode Curl_do_more(struct connectdata *conn, bool *completed) -{ - CURLcode result=CURLE_OK; - - *completed = FALSE; - - if(conn->handler->do_more) - result = conn->handler->do_more(conn, completed); - - if(!result && *completed) - /* do_complete must be called after the protocol-specific DO function */ - do_complete(conn); - - return result; -} - -/* Called on connect, and if there's already a protocol-specific struct - allocated for a different connection, this frees it that it can be setup - properly later on. */ -void Curl_reset_reqproto(struct connectdata *conn) -{ - struct SessionHandle *data = conn->data; - if(data->state.proto.generic && data->state.current_conn != conn) { - free(data->state.proto.generic); - data->state.proto.generic = NULL; - } - data->state.current_conn = conn; -} diff --git a/lib/version.c b/lib/version.c deleted file mode 100644 index fe1f73660..000000000 --- a/lib/version.c +++ /dev/null @@ -1,343 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include -#include "curl_urldata.h" -#include "curl_sslgen.h" - -#define _MPRINTF_REPLACE /* use the internal *printf() functions */ -#include - -#ifdef USE_ARES -# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ - (defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__)) -# define CARES_STATICLIB -# endif -# include -#endif - -#ifdef USE_LIBIDN -#include -#endif - -#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) -#include -#endif - -#ifdef USE_LIBRTMP -#include -#endif - -#ifdef USE_LIBSSH2 -#include -#endif - -#ifdef HAVE_LIBSSH2_VERSION -/* get it run-time if possible */ -#define CURL_LIBSSH2_VERSION libssh2_version(0) -#else -/* use build-time if run-time not possible */ -#define CURL_LIBSSH2_VERSION LIBSSH2_VERSION -#endif - -char *curl_version(void) -{ - static char version[200]; - char *ptr = version; - size_t len; - size_t left = sizeof(version); - - strcpy(ptr, LIBCURL_NAME "/" LIBCURL_VERSION); - len = strlen(ptr); - left -= len; - ptr += len; - - if(left > 1) { - len = Curl_ssl_version(ptr + 1, left - 1); - - if(len > 0) { - *ptr = ' '; - left -= ++len; - ptr += len; - } - } - -#ifdef HAVE_LIBZ - len = snprintf(ptr, left, " zlib/%s", zlibVersion()); - left -= len; - ptr += len; -#endif -#ifdef USE_ARES - /* this function is only present in c-ares, not in the original ares */ - len = snprintf(ptr, left, " c-ares/%s", ares_version(NULL)); - left -= len; - ptr += len; -#endif -#ifdef USE_LIBIDN - if(stringprep_check_version(LIBIDN_REQUIRED_VERSION)) { - len = snprintf(ptr, left, " libidn/%s", stringprep_check_version(NULL)); - left -= len; - ptr += len; - } -#endif -#ifdef USE_WIN32_IDN - len = snprintf(ptr, left, " WinIDN"); - left -= len; - ptr += len; -#endif -#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) -#ifdef _LIBICONV_VERSION - len = snprintf(ptr, left, " iconv/%d.%d", - _LIBICONV_VERSION >> 8, _LIBICONV_VERSION & 255); -#else - /* version unknown */ - len = snprintf(ptr, left, " iconv"); -#endif /* _LIBICONV_VERSION */ - left -= len; - ptr += len; -#endif -#ifdef USE_LIBSSH2 - len = snprintf(ptr, left, " libssh2/%s", CURL_LIBSSH2_VERSION); - left -= len; - ptr += len; -#endif -#ifdef USE_LIBRTMP - { - char suff[2]; - if(RTMP_LIB_VERSION & 0xff) { - suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; - suff[1] = '\0'; - } - else - suff[0] = '\0'; - - snprintf(ptr, left, " librtmp/%d.%d%s", - RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, - suff); -/* - If another lib version is added below this one, this code would - also have to do: - - len = what snprintf() returned - - left -= len; - ptr += len; -*/ - } -#endif - - return version; -} - -/* data for curl_version_info - - Keep the list sorted alphabetically. It is also written so that each - protocol line has its own #if line to make things easier on the eye. - */ - -static const char * const protocols[] = { -#ifndef CURL_DISABLE_DICT - "dict", -#endif -#ifndef CURL_DISABLE_FILE - "file", -#endif -#ifndef CURL_DISABLE_FTP - "ftp", -#endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) - "ftps", -#endif -#ifndef CURL_DISABLE_GOPHER - "gopher", -#endif -#ifndef CURL_DISABLE_HTTP - "http", -#endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) - "https", -#endif -#ifndef CURL_DISABLE_IMAP - "imap", -#endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_IMAP) - "imaps", -#endif -#ifndef CURL_DISABLE_LDAP - "ldap", -#if !defined(CURL_DISABLE_LDAPS) && \ - ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ - (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) - "ldaps", -#endif -#endif -#ifndef CURL_DISABLE_POP3 - "pop3", -#endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_POP3) - "pop3s", -#endif -#ifdef USE_LIBRTMP - "rtmp", -#endif -#ifndef CURL_DISABLE_RTSP - "rtsp", -#endif -#ifdef USE_LIBSSH2 - "scp", -#endif -#ifdef USE_LIBSSH2 - "sftp", -#endif -#ifndef CURL_DISABLE_SMTP - "smtp", -#endif -#if defined(USE_SSL) && !defined(CURL_DISABLE_SMTP) - "smtps", -#endif -#ifndef CURL_DISABLE_TELNET - "telnet", -#endif -#ifndef CURL_DISABLE_TFTP - "tftp", -#endif - - NULL -}; - -static curl_version_info_data version_info = { - CURLVERSION_NOW, - LIBCURL_VERSION, - LIBCURL_VERSION_NUM, - OS, /* as found by configure or set by hand at build-time */ - 0 /* features is 0 by default */ -#ifdef ENABLE_IPV6 - | CURL_VERSION_IPV6 -#endif -#ifdef HAVE_KRB4 - | CURL_VERSION_KERBEROS4 -#endif -#ifdef USE_SSL - | CURL_VERSION_SSL -#endif -#ifdef USE_NTLM - | CURL_VERSION_NTLM -#endif -#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) - | CURL_VERSION_NTLM_WB -#endif -#ifdef USE_WINDOWS_SSPI - | CURL_VERSION_SSPI -#endif -#ifdef HAVE_LIBZ - | CURL_VERSION_LIBZ -#endif -#ifdef USE_HTTP_NEGOTIATE - | CURL_VERSION_GSSNEGOTIATE -#endif -#ifdef DEBUGBUILD - | CURL_VERSION_DEBUG -#endif -#ifdef CURLDEBUG - | CURL_VERSION_CURLDEBUG -#endif -#ifdef CURLRES_ASYNCH - | CURL_VERSION_ASYNCHDNS -#endif -#ifdef HAVE_SPNEGO - | CURL_VERSION_SPNEGO -#endif -#if (CURL_SIZEOF_CURL_OFF_T > 4) && \ - ( (SIZEOF_OFF_T > 4) || defined(USE_WIN32_LARGE_FILES) ) - | CURL_VERSION_LARGEFILE -#endif -#if defined(CURL_DOES_CONVERSIONS) - | CURL_VERSION_CONV -#endif -#if defined(USE_TLS_SRP) - | CURL_VERSION_TLSAUTH_SRP -#endif - , - NULL, /* ssl_version */ - 0, /* ssl_version_num, this is kept at zero */ - NULL, /* zlib_version */ - protocols, - NULL, /* c-ares version */ - 0, /* c-ares version numerical */ - NULL, /* libidn version */ - 0, /* iconv version */ - NULL, /* ssh lib version */ -}; - -curl_version_info_data *curl_version_info(CURLversion stamp) -{ -#ifdef USE_LIBSSH2 - static char ssh_buffer[80]; -#endif - -#ifdef USE_SSL - static char ssl_buffer[80]; - Curl_ssl_version(ssl_buffer, sizeof(ssl_buffer)); - version_info.ssl_version = ssl_buffer; -#endif - -#ifdef HAVE_LIBZ - version_info.libz_version = zlibVersion(); - /* libz left NULL if non-existing */ -#endif -#ifdef USE_ARES - { - int aresnum; - version_info.ares = ares_version(&aresnum); - version_info.ares_num = aresnum; - } -#endif -#ifdef USE_LIBIDN - /* This returns a version string if we use the given version or later, - otherwise it returns NULL */ - version_info.libidn = stringprep_check_version(LIBIDN_REQUIRED_VERSION); - if(version_info.libidn) - version_info.features |= CURL_VERSION_IDN; -#elif defined(USE_WIN32_IDN) - version_info.features |= CURL_VERSION_IDN; -#endif - -#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) -#ifdef _LIBICONV_VERSION - version_info.iconv_ver_num = _LIBICONV_VERSION; -#else - /* version unknown */ - version_info.iconv_ver_num = -1; -#endif /* _LIBICONV_VERSION */ -#endif - -#ifdef USE_LIBSSH2 - snprintf(ssh_buffer, sizeof(ssh_buffer), "libssh2/%s", LIBSSH2_VERSION); - version_info.libssh_version = ssh_buffer; -#endif - - (void)stamp; /* avoid compiler warnings, we don't use this */ - - return &version_info; -} diff --git a/lib/warnless.c b/lib/warnless.c deleted file mode 100644 index 30cdbe6f0..000000000 --- a/lib/warnless.c +++ /dev/null @@ -1,431 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(__INTEL_COMPILER) && defined(__unix__) - -#ifdef HAVE_NETINET_IN_H -# include -#endif -#ifdef HAVE_ARPA_INET_H -# include -#endif - -#endif /* __INTEL_COMPILER && __unix__ */ - -#define BUILDING_WARNLESS_C 1 - -#include "curl_warnless.h" - -#define CURL_MASK_SCHAR 0x7F -#define CURL_MASK_UCHAR 0xFF - -#if (SIZEOF_SHORT == 2) -# define CURL_MASK_SSHORT 0x7FFF -# define CURL_MASK_USHORT 0xFFFF -#elif (SIZEOF_SHORT == 4) -# define CURL_MASK_SSHORT 0x7FFFFFFF -# define CURL_MASK_USHORT 0xFFFFFFFF -#elif (SIZEOF_SHORT == 8) -# define CURL_MASK_SSHORT 0x7FFFFFFFFFFFFFFF -# define CURL_MASK_USHORT 0xFFFFFFFFFFFFFFFF -#else -# error "SIZEOF_SHORT not defined" -#endif - -#if (SIZEOF_INT == 2) -# define CURL_MASK_SINT 0x7FFF -# define CURL_MASK_UINT 0xFFFF -#elif (SIZEOF_INT == 4) -# define CURL_MASK_SINT 0x7FFFFFFF -# define CURL_MASK_UINT 0xFFFFFFFF -#elif (SIZEOF_INT == 8) -# define CURL_MASK_SINT 0x7FFFFFFFFFFFFFFF -# define CURL_MASK_UINT 0xFFFFFFFFFFFFFFFF -#elif (SIZEOF_INT == 16) -# define CURL_MASK_SINT 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -# define CURL_MASK_UINT 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -#else -# error "SIZEOF_INT not defined" -#endif - -#if (CURL_SIZEOF_LONG == 2) -# define CURL_MASK_SLONG 0x7FFFL -# define CURL_MASK_ULONG 0xFFFFUL -#elif (CURL_SIZEOF_LONG == 4) -# define CURL_MASK_SLONG 0x7FFFFFFFL -# define CURL_MASK_ULONG 0xFFFFFFFFUL -#elif (CURL_SIZEOF_LONG == 8) -# define CURL_MASK_SLONG 0x7FFFFFFFFFFFFFFFL -# define CURL_MASK_ULONG 0xFFFFFFFFFFFFFFFFUL -#elif (CURL_SIZEOF_LONG == 16) -# define CURL_MASK_SLONG 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL -# define CURL_MASK_ULONG 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFUL -#else -# error "CURL_SIZEOF_LONG not defined" -#endif - -#if (CURL_SIZEOF_CURL_OFF_T == 2) -# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFF) -# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFF) -#elif (CURL_SIZEOF_CURL_OFF_T == 4) -# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFF) -# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFF) -#elif (CURL_SIZEOF_CURL_OFF_T == 8) -# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFFFFFFFFFF) -# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFFFFFFFFFF) -#elif (CURL_SIZEOF_CURL_OFF_T == 16) -# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) -# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) -#else -# error "CURL_SIZEOF_CURL_OFF_T not defined" -#endif - -#if (SIZEOF_SIZE_T == SIZEOF_SHORT) -# define CURL_MASK_SSIZE_T CURL_MASK_SSHORT -# define CURL_MASK_USIZE_T CURL_MASK_USHORT -#elif (SIZEOF_SIZE_T == SIZEOF_INT) -# define CURL_MASK_SSIZE_T CURL_MASK_SINT -# define CURL_MASK_USIZE_T CURL_MASK_UINT -#elif (SIZEOF_SIZE_T == CURL_SIZEOF_LONG) -# define CURL_MASK_SSIZE_T CURL_MASK_SLONG -# define CURL_MASK_USIZE_T CURL_MASK_ULONG -#elif (SIZEOF_SIZE_T == CURL_SIZEOF_CURL_OFF_T) -# define CURL_MASK_SSIZE_T CURL_MASK_SCOFFT -# define CURL_MASK_USIZE_T CURL_MASK_UCOFFT -#else -# error "SIZEOF_SIZE_T not defined" -#endif - -/* -** unsigned long to unsigned short -*/ - -unsigned short curlx_ultous(unsigned long ulnum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_USHORT); - return (unsigned short)(ulnum & (unsigned long) CURL_MASK_USHORT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** unsigned long to unsigned char -*/ - -unsigned char curlx_ultouc(unsigned long ulnum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_UCHAR); - return (unsigned char)(ulnum & (unsigned long) CURL_MASK_UCHAR); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** unsigned long to signed int -*/ - -int curlx_ultosi(unsigned long ulnum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(ulnum <= (unsigned long) CURL_MASK_SINT); - return (int)(ulnum & (unsigned long) CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** unsigned size_t to signed int -*/ - -int curlx_uztosi(size_t uznum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(uznum <= (size_t) CURL_MASK_SINT); - return (int)(uznum & (size_t) CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** unsigned size_t to unsigned long -*/ - -unsigned long curlx_uztoul(size_t uznum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - -#if (CURL_SIZEOF_LONG < SIZEOF_SIZE_T) - DEBUGASSERT(uznum <= (size_t) CURL_MASK_ULONG); -#endif - return (unsigned long)(uznum & (size_t) CURL_MASK_ULONG); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** unsigned size_t to unsigned int -*/ - -unsigned int curlx_uztoui(size_t uznum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - -#if (SIZEOF_INT < SIZEOF_SIZE_T) - DEBUGASSERT(uznum <= (size_t) CURL_MASK_UINT); -#endif - return (unsigned int)(uznum & (size_t) CURL_MASK_UINT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** signed long to signed int -*/ - -int curlx_sltosi(long slnum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(slnum >= 0); -#if (SIZEOF_INT < CURL_SIZEOF_LONG) - DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_SINT); -#endif - return (int)(slnum & (long) CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** signed long to unsigned int -*/ - -unsigned int curlx_sltoui(long slnum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(slnum >= 0); -#if (SIZEOF_INT < CURL_SIZEOF_LONG) - DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_UINT); -#endif - return (unsigned int)(slnum & (long) CURL_MASK_UINT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** signed long to unsigned short -*/ - -unsigned short curlx_sltous(long slnum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(slnum >= 0); - DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_USHORT); - return (unsigned short)(slnum & (long) CURL_MASK_USHORT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** unsigned size_t to signed ssize_t -*/ - -ssize_t curlx_uztosz(size_t uznum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(uznum <= (size_t) CURL_MASK_SSIZE_T); - return (ssize_t)(uznum & (size_t) CURL_MASK_SSIZE_T); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** signed curl_off_t to unsigned size_t -*/ - -size_t curlx_sotouz(curl_off_t sonum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(sonum >= 0); - return (size_t)(sonum & (curl_off_t) CURL_MASK_USIZE_T); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** signed ssize_t to signed int -*/ - -int curlx_sztosi(ssize_t sznum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(sznum >= 0); -#if (SIZEOF_INT < SIZEOF_SIZE_T) - DEBUGASSERT((size_t) sznum <= (size_t) CURL_MASK_SINT); -#endif - return (int)(sznum & (ssize_t) CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** signed int to unsigned size_t -*/ - -size_t curlx_sitouz(int sinum) -{ -#ifdef __INTEL_COMPILER -# pragma warning(push) -# pragma warning(disable:810) /* conversion may lose significant bits */ -#endif - - DEBUGASSERT(sinum >= 0); - return (size_t) sinum; - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -#if defined(__INTEL_COMPILER) && defined(__unix__) - -int curlx_FD_ISSET(int fd, fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:1469) /* clobber ignored */ - return FD_ISSET(fd, fdset); - #pragma warning(pop) -} - -void curlx_FD_SET(int fd, fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:1469) /* clobber ignored */ - FD_SET(fd, fdset); - #pragma warning(pop) -} - -void curlx_FD_ZERO(fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:593) /* variable was set but never used */ - FD_ZERO(fdset); - #pragma warning(pop) -} - -unsigned short curlx_htons(unsigned short usnum) -{ -#if (__INTEL_COMPILER == 910) && defined(__i386__) - return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); -#else - #pragma warning(push) - #pragma warning(disable:810) /* conversion may lose significant bits */ - return htons(usnum); - #pragma warning(pop) -#endif -} - -unsigned short curlx_ntohs(unsigned short usnum) -{ -#if (__INTEL_COMPILER == 910) && defined(__i386__) - return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); -#else - #pragma warning(push) - #pragma warning(disable:810) /* conversion may lose significant bits */ - return ntohs(usnum); - #pragma warning(pop) -#endif -} - -#endif /* __INTEL_COMPILER && __unix__ */ diff --git a/lib/wildcard.c b/lib/wildcard.c deleted file mode 100644 index d6ba2b276..000000000 --- a/lib/wildcard.c +++ /dev/null @@ -1,77 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2010, 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 - * 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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "curl_wildcard.h" -#include "curl_llist.h" -#include "curl_fileinfo.h" - -#define _MPRINTF_REPLACE /* use our functions only */ -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "curl_memdebug.h" - -CURLcode Curl_wildcard_init(struct WildcardData *wc) -{ - DEBUGASSERT(wc->filelist == NULL); - /* now allocate only wc->filelist, everything else - will be allocated if it is needed. */ - wc->filelist = Curl_llist_alloc(Curl_fileinfo_dtor); - if(!wc->filelist) {; - return CURLE_OUT_OF_MEMORY; - } - return CURLE_OK; -} - -void Curl_wildcard_dtor(struct WildcardData *wc) -{ - if(!wc) - return; - - if(wc->tmp_dtor) { - wc->tmp_dtor(wc->tmp); - wc->tmp_dtor = ZERO_NULL; - wc->tmp = NULL; - } - DEBUGASSERT(wc->tmp == NULL); - - if(wc->filelist) { - Curl_llist_destroy(wc->filelist, NULL); - wc->filelist = NULL; - } - - if(wc->path) { - free(wc->path); - wc->path = NULL; - } - - if(wc->pattern) { - free(wc->pattern); - wc->pattern = NULL; - } - - wc->customptr = NULL; - wc->state = CURLWC_INIT; -} -- cgit v1.2.1