DRILL-7795: Add support for overriding hostname to C++ client

* Add the hostnameOverride property to specify the expected hostname in the SSL certificate.
* Set the TLS SNI property to the host field even if the hostname wasn't overridden.
* Update querySubmitter example to include this new property.
* Change querySubmitter so that the number of options is detected from the options array
rather than being a constant that requires manual updates.
diff --git a/contrib/native/client/example/querySubmitter.cpp b/contrib/native/client/example/querySubmitter.cpp
index 1ca7668..7c66613 100644
--- a/contrib/native/client/example/querySubmitter.cpp
+++ b/contrib/native/client/example/querySubmitter.cpp
@@ -23,8 +23,6 @@
 #include <boost/algorithm/string/join.hpp>
 #include "drill/drillc.hpp"
 
-int nOptions=27;
-
 struct Option{
     char name[32];
     char desc[128];
@@ -56,7 +54,8 @@
     {"disableCertVerification", "disable certificate verification", false},
     {"useSystemTrustStore", "[Windows only]. Use the system truststore.", false},
     {"CustomSSLCtxOptions", "The custom SSL CTX Options", false},
-    {"supportComplexTypes", "Toggle for supporting complex types", false}
+    {"supportComplexTypes", "Toggle for supporting complex types", false},
+    {"hostnameOverride", "Override the SSL server hostname", false}
 };
 
 std::map<std::string, std::string> qsOptionValues;
@@ -165,7 +164,7 @@
 
 void printUsage(){
     std::cerr<<"Usage: querySubmitter ";
-    for(int j=0; j<nOptions ;j++){
+    for(int j=0; j<sizeof(qsOptions)/sizeof(qsOptions[0]) ;j++){
         std::cerr<< " "<< qsOptions[j].name <<"="  << "[" <<qsOptions[j].desc <<"]" ;
     }
     std::cerr<<std::endl;
@@ -179,7 +178,7 @@
         char*v=strtok(NULL, "");
 
         bool found=false;
-        for(int j=0; j<nOptions ;j++){
+        for(int j=0; j<sizeof(qsOptions)/sizeof(qsOptions[0]) ;j++){
             if(!strcmp(qsOptions[j].name, o)){
                 found=true; break;
             }
@@ -196,7 +195,7 @@
         qsOptionValues[o]=v;
     }
 
-    for(int j=0; j<nOptions ;j++){
+    for(int j=0; j<sizeof(qsOptions)/sizeof(qsOptions[0]) ;j++){
         if(qsOptions[j].required ){
             if(qsOptionValues.find(qsOptions[j].name) == qsOptionValues.end()){
                 std::cerr<< ""<< qsOptions[j].name << " [" <<qsOptions[j].desc <<"] " << "is required." << std::endl;
@@ -318,6 +317,7 @@
         std::string useSystemTrustStore = qsOptionValues["useSystemTrustStore"];
         std::string customSSLOptions = qsOptionValues["CustomSSLCtxOptions"];
         std::string supportComplexTypes = qsOptionValues["supportComplexTypes"];
+        std::string hostnameOverride = qsOptionValues["hostnameOverride"];
 
         Drill::QueryType type;
 
@@ -426,6 +426,9 @@
         if (supportComplexTypes.length() > 0){
             props.setProperty(USERPROP_SUPPORT_COMPLEX_TYPES, supportComplexTypes);
         }
+        if (hostnameOverride.length() > 0) {
+            props.setProperty(USERPROP_HOSTNAME_OVERRIDE, hostnameOverride);
+        }
 
         if(client.connect(connectStr.c_str(), &props)!=Drill::CONN_SUCCESS){
             std::cerr<< "Failed to connect with error: "<< client.getError() << " (Using:"<<connectStr<<")"<<std::endl;
diff --git a/contrib/native/client/src/clientlib/channel.cpp b/contrib/native/client/src/clientlib/channel.cpp
index 5e96388..53c9f0a 100644
--- a/contrib/native/client/src/clientlib/channel.cpp
+++ b/contrib/native/client/src/clientlib/channel.cpp
@@ -210,6 +210,9 @@
                 verifyMode = boost::asio::ssl::context::verify_none;
             }
 
+            std::string hostnameOverride;
+            props->getProp(USERPROP_HOSTNAME_OVERRIDE, hostnameOverride);
+
             long customSSLCtxOptions = SSLChannelContext::ApplyMinTLSRestriction(protocol);
             std::string sslOptions;
             props->getProp(USERPROP_CUSTOM_SSLCTXOPTIONS, sslOptions);
@@ -222,7 +225,7 @@
                  }
             }
 
-            pChannelContext = new SSLChannelContext(props, tlsVersion, verifyMode, customSSLCtxOptions);
+            pChannelContext = new SSLChannelContext(props, tlsVersion, verifyMode, hostnameOverride, customSSLCtxOptions);
         }
             break;
 #endif
@@ -276,20 +279,23 @@
 }
 
 connectionStatus_t Channel::connect(){
-    connectionStatus_t ret=CONN_FAILURE;
-    if(this->m_state==CHANNEL_INITIALIZED){
-        ret=m_pEndpoint->getDrillbitEndpoint();
-        if(ret==CONN_SUCCESS){
+    connectionStatus_t ret = CONN_FAILURE;
+    if (this->m_state == CHANNEL_INITIALIZED){
+        ret = m_pEndpoint->getDrillbitEndpoint();
+        if (ret == CONN_SUCCESS){
             DRILL_LOG(LOG_TRACE) << "Connecting to drillbit: " 
                 << m_pEndpoint->getHost() 
                 << ":" << m_pEndpoint->getPort() 
                 << "." << std::endl;
-            ret=this->connectInternal();
-        }else{
+            ret = this->setSocketInformation();
+            if (ret == CONN_SUCCESS) {
+                ret = this->connectInternal();
+            }
+        } else {
             handleError(ret, m_pEndpoint->getError()->msg);
         }
     }
-    this->m_state=(ret==CONN_SUCCESS)?CHANNEL_CONNECTED:this->m_state;
+    this->m_state = (ret == CONN_SUCCESS) ? CHANNEL_CONNECTED : this->m_state;
     return ret;
 }
 
diff --git a/contrib/native/client/src/clientlib/channel.hpp b/contrib/native/client/src/clientlib/channel.hpp
index 7d4ad60..f7dfe3e 100644
--- a/contrib/native/client/src/clientlib/channel.hpp
+++ b/contrib/native/client/src/clientlib/channel.hpp
@@ -94,7 +94,7 @@
             /// @brief Applies Minimum TLS protocol restrictions. 
             ///         tlsv11+ means restrict to TLS version 1.1 and higher.
             ///         tlsv12+ means restrict to TLS version 1.2 and higher.
-            ///  Please note that SSL_OP_NO_TLSv tags are depreecated in openSSL 1.1.0.
+            ///  Please note that SSL_OP_NO_TLSv tags are deprecated in openSSL 1.1.0.
             /// 
             /// @param in_ver               The protocol version.
             /// 
@@ -113,9 +113,11 @@
         SSLChannelContext(DrillUserProperties *props,
                           boost::asio::ssl::context::method tlsVersion,
                           boost::asio::ssl::verify_mode verifyMode,
+                          const std::string& hostnameOverride,
                           const long customSSLCtxOptions = 0) :
                     ChannelContext(props),
                     m_SSLContext(tlsVersion),
+                    m_hostnameOverride(hostnameOverride),
                     m_certHostnameVerificationStatus(true) 
             {
                 m_SSLContext.set_default_verify_paths();
@@ -142,9 +144,17 @@
             /// @param in_result                The host name verification status.
             void SetCertHostnameVerificationStatus(bool in_result) { m_certHostnameVerificationStatus = in_result; }
 
+            /// @brief Returns the overridden hostname used for certificate verification
+            ///
+            /// @return the hostname override, or empty if the hostname should not be overridden.
+            const std::string& GetHostnameOverride() { return m_hostnameOverride; }
+
         private:
             boost::asio::ssl::context m_SSLContext;
 
+            // The hostname to verify. Unused if empty.
+            std::string m_hostnameOverride;
+
             // The flag to indicate the host name verification result.
             bool m_certHostnameVerificationStatus;
     };
@@ -210,6 +220,10 @@
                 return handleError(CONN_HANDSHAKE_FAILED, in_err.what());
             }
 
+            virtual connectionStatus_t setSocketInformation() {
+                return CONN_SUCCESS;
+            }
+
             boost::asio::io_service& m_ioService;
             boost::asio::io_service m_ioServiceFallback; // used if m_ioService is not provided
             AsioStreamSocket* m_pSocket;
@@ -291,6 +305,21 @@
                         getMessage(ERR_CONN_SSL_GENERAL, in_err.what()));
                 }
             }
+
+            connectionStatus_t setSocketInformation() {
+                const char* sniProperty;
+                SSLChannelContext_t& context = *((SSLChannelContext_t *)m_pContext);
+                if (!context.GetHostnameOverride().empty()){
+                    sniProperty = context.GetHostnameOverride().c_str();
+                }
+                else{
+                    sniProperty = m_pEndpoint->getHost().c_str();
+                }
+                if (!SSL_set_tlsext_host_name(((SslSocket *)m_pSocket)->getSocketStream().native_handle(), sniProperty)) {
+                    return handleError(CONN_SSLERROR, getMessage(ERR_CONN_SSL_SNI, sniProperty, ERR_func_error_string(ERR_get_error())));
+                }
+                return CONN_SUCCESS;
+            }
 #endif
     };
 
@@ -332,9 +361,16 @@
                 // Gets the channel context.
                 SSLChannelContext_t* context = (SSLChannelContext_t*)(m_channel->getChannelContext());
 
-                // Retrieve the host before we perform Host name verification.
-                // This is because host with ZK mode is selected after the connect() function is called.
-                boost::asio::ssl::rfc2818_verification verifier(m_channel->getEndpoint()->getHost().c_str());
+                const char* hostname;
+                if (context->GetHostnameOverride().empty()) {
+                    // Retrieve the host before we perform Host name verification.
+                    // This is because host with ZK mode is selected after the connect() function is called.
+                    hostname = m_channel->getEndpoint()->getHost().c_str();
+                } else {
+                    hostname = context->GetHostnameOverride().c_str();
+                }
+
+                boost::asio::ssl::rfc2818_verification verifier(hostname);
 
                 // Perform verification.
                 bool verified = verifier(in_preverified, in_ctx);
diff --git a/contrib/native/client/src/clientlib/errmsgs.cpp b/contrib/native/client/src/clientlib/errmsgs.cpp
index 5ab8d8e..f3daa4d 100644
--- a/contrib/native/client/src/clientlib/errmsgs.cpp
+++ b/contrib/native/client/src/clientlib/errmsgs.cpp
@@ -61,6 +61,7 @@
     {ERR_CONN_SSL_CN, ERR_CATEGORY_CONN, 0, "SSL certificate host name verification failure. [Details: %s]" },
     {ERR_CONN_SSL_CERTVERIFY, ERR_CATEGORY_CONN, 0, "SSL certificate verification failed. [Details: %s]"},
     {ERR_CONN_SSL_PROTOVER, ERR_CATEGORY_CONN, 0, "Unsupported TLS protocol version. [Details: %s]" },
+    {ERR_CONN_SSL_SNI, ERR_CATEGORY_CONN, 0, "Failed to set TLS SNI. Host: %s [Details: %s]"},
     {ERR_QRY_OUTOFMEM, ERR_CATEGORY_QRY, 0, "Out of memory."},
     {ERR_QRY_COMMERR, ERR_CATEGORY_QRY, 0, "Communication error. %s"},
     {ERR_QRY_INVREADLEN, ERR_CATEGORY_QRY, 0, "Internal Error: Received a message with an invalid read length."},
diff --git a/contrib/native/client/src/clientlib/errmsgs.hpp b/contrib/native/client/src/clientlib/errmsgs.hpp
index 7230611..465adb5 100644
--- a/contrib/native/client/src/clientlib/errmsgs.hpp
+++ b/contrib/native/client/src/clientlib/errmsgs.hpp
@@ -59,7 +59,10 @@
 #define ERR_CONN_SSL_CN         DRILL_ERR_START+27
 #define ERR_CONN_SSL_CERTVERIFY DRILL_ERR_START+28
 #define ERR_CONN_SSL_PROTOVER   DRILL_ERR_START+29
-#define ERR_CONN_MAX            DRILL_ERR_START+29
+#define ERR_CONN_SSL_SNI        DRILL_ERR_START+30
+
+// This should be the same as the largest ERR_CONN_* code.
+#define ERR_CONN_MAX            DRILL_ERR_START+30
 
 #define ERR_QRY_OUTOFMEM    ERR_CONN_MAX+1
 #define ERR_QRY_COMMERR     ERR_CONN_MAX+2
@@ -81,6 +84,8 @@
 #define ERR_QRY_18          ERR_CONN_MAX+18
 #define ERR_QRY_19          ERR_CONN_MAX+19
 #define ERR_QRY_20          ERR_CONN_MAX+20
+
+// This should be the same as the largest ERR_QRY_* code.
 #define ERR_QRY_MAX         ERR_QRY_20
 
     // Use only Plain Old Data types in this struc. We will declare
diff --git a/contrib/native/client/src/clientlib/userProperties.cpp b/contrib/native/client/src/clientlib/userProperties.cpp
index 0ad8af1..d5d96ea 100644
--- a/contrib/native/client/src/clientlib/userProperties.cpp
+++ b/contrib/native/client/src/clientlib/userProperties.cpp
@@ -32,6 +32,7 @@
     ( USERPROP_USESSL,      USERPROP_FLAGS_BOOLEAN|USERPROP_FLAGS_SSLPROP)
     ( USERPROP_TLSPROTOCOL,      USERPROP_FLAGS_STRING|USERPROP_FLAGS_SSLPROP)
     ( USERPROP_CERTFILEPATH,    USERPROP_FLAGS_STRING|USERPROP_FLAGS_SSLPROP|USERPROP_FLAGS_FILEPATH)
+    ( USERPROP_HOSTNAME_OVERRIDE,    USERPROP_FLAGS_STRING|USERPROP_FLAGS_SSLPROP)
     ( USERPROP_DISABLE_HOSTVERIFICATION,    USERPROP_FLAGS_BOOLEAN|USERPROP_FLAGS_SSLPROP)
     ( USERPROP_DISABLE_CERTVERIFICATION,    USERPROP_FLAGS_BOOLEAN|USERPROP_FLAGS_SSLPROP)
     ( USERPROP_USESYSTEMTRUSTSTORE,    USERPROP_FLAGS_BOOLEAN|USERPROP_FLAGS_SSLPROP)
diff --git a/contrib/native/client/src/include/drill/common.hpp b/contrib/native/client/src/include/drill/common.hpp
index 9f57446..8bd15f4 100644
--- a/contrib/native/client/src/include/drill/common.hpp
+++ b/contrib/native/client/src/include/drill/common.hpp
@@ -180,6 +180,7 @@
 // #define USERPROP_CERTPASSWORD "certPassword" // Password for certificate file. 
 #define USERPROP_DISABLE_HOSTVERIFICATION "disableHostVerification"
 #define USERPROP_DISABLE_CERTVERIFICATION "disableCertVerification"
+#define USERPROP_HOSTNAME_OVERRIDE "hostnameOverride" //The hostname to verify in the SSL Certificate.
 #define USERPROP_USESYSTEMTRUSTSTORE "useSystemTrustStore" //Windows only, use the system trust store
 #define USERPROP_IMPERSONATION_TARGET "impersonation_target"
 #define USERPROP_AUTH_MECHANISM "auth"