//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2017 OpenVPN Technologies, Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

// These classes encapsulate the basic setup of the various objects needed to
// create an OpenVPN client session.  The basic idea here is to look at both
// compile time settings (i.e. crypto/SSL/random libraries), and run-time
// (such as transport layer using UDP, TCP, or HTTP-proxy), and
// build the actual objects that will be used to construct a client session.

#ifndef OPENVPN_CLIENT_CLIOPT_H
#define OPENVPN_CLIENT_CLIOPT_H

#include <string>

#include <openvpn/error/excode.hpp>

#include <openvpn/common/size.hpp>
#include <openvpn/common/platform.hpp>
#include <openvpn/common/options.hpp>
#include <openvpn/common/stop.hpp>
#include <openvpn/frame/frame_init.hpp>
#include <openvpn/pki/epkibase.hpp>
#include <openvpn/crypto/cryptodcsel.hpp>
#include <openvpn/ssl/mssparms.hpp>
#include <openvpn/tun/tunmtu.hpp>
#include <openvpn/tun/ipv6_setting.hpp>
#include <openvpn/netconf/hwaddr.hpp>

#include <openvpn/transport/socket_protect.hpp>
#include <openvpn/transport/reconnect_notify.hpp>
#include <openvpn/transport/client/udpcli.hpp>
#include <openvpn/transport/client/tcpcli.hpp>
#include <openvpn/transport/client/httpcli.hpp>
#include <openvpn/transport/altproxy.hpp>
#include <openvpn/transport/dco.hpp>
#include <openvpn/client/cliproto.hpp>
#include <openvpn/client/cliopthelper.hpp>
#include <openvpn/client/optfilt.hpp>
#include <openvpn/client/clilife.hpp>

#include <openvpn/ssl/sslchoose.hpp>

#ifdef OPENVPN_GREMLIN
#include <openvpn/transport/gremlin.hpp>
#endif

#if defined(OPENVPN_PLATFORM_ANDROID)
#include <openvpn/client/cliemuexr.hpp>
#endif

#if defined(OPENVPN_EXTERNAL_TUN_FACTORY)
// requires that client implements ExternalTun::Factory::new_tun_factory
#include <openvpn/tun/extern/config.hpp>
#elif defined(USE_TUN_BUILDER)
#include <openvpn/tun/builder/client.hpp>
#elif defined(OPENVPN_PLATFORM_LINUX) && !defined(OPENVPN_FORCE_TUN_NULL)
#include <openvpn/tun/linux/client/tuncli.hpp>
#ifdef OPENVPN_COMMAND_AGENT
#include <openvpn/client/unix/cmdagent.hpp>
#endif
#elif defined(OPENVPN_PLATFORM_MAC) && !defined(OPENVPN_FORCE_TUN_NULL)
#include <openvpn/tun/mac/client/tuncli.hpp>
#include <openvpn/apple/maclife.hpp>
#ifdef OPENVPN_COMMAND_AGENT
#include <openvpn/client/unix/cmdagent.hpp>
#endif
#elif defined(OPENVPN_PLATFORM_WIN) && !defined(OPENVPN_FORCE_TUN_NULL)
#include <openvpn/tun/win/client/tuncli.hpp>
#ifdef OPENVPN_COMMAND_AGENT
#include <openvpn/client/win/cmdagent.hpp>
#endif
#else
#include <openvpn/tun/client/tunnull.hpp>
#endif

#ifdef PRIVATE_TUNNEL_PROXY
#include <openvpn/pt/ptproxy.hpp>
#endif

#if defined(ENABLE_DCO)
#include <openvpn/dco/dcocli.hpp>
#endif

#ifndef OPENVPN_UNUSED_OPTIONS
#define OPENVPN_UNUSED_OPTIONS "UNUSED OPTIONS"
#endif

namespace openvpn {

  class ClientOptions : public RC<thread_unsafe_refcount>
  {
  public:
    typedef RCPtr<ClientOptions> Ptr;

    typedef ClientProto::Session Client;

    struct Config
    {
      std::string gui_version;
      std::string server_override;
      Protocol proto_override;
      IPv6Setting ipv6;
      int conn_timeout = 0;
      SessionStats::Ptr cli_stats;
      ClientEvent::Queue::Ptr cli_events;
      ProtoContextOptions::Ptr proto_context_options;
      HTTPProxyTransport::Options::Ptr http_proxy_options;
      bool alt_proxy = false;
      bool dco = false;
      bool echo = false;
      bool info = false;
      bool tun_persist = false;
      bool google_dns_fallback = false;
      std::string private_key_password;
      bool disable_client_cert = false;
      int ssl_debug_level = 0;
      int default_key_direction = -1;
      bool force_aes_cbc_ciphersuites = false;
      bool autologin_sessions = false;
      std::string tls_version_min_override;
      std::string tls_cert_profile_override;
      PeerInfo::Set::Ptr extra_peer_info;
#ifdef OPENVPN_GREMLIN
      Gremlin::Config::Ptr gremlin_config;
#endif
      Stop* stop = nullptr;

      // callbacks -- must remain in scope for lifetime of ClientOptions object
      ExternalPKIBase* external_pki = nullptr;
      SocketProtect* socket_protect = nullptr;
      ReconnectNotify* reconnect_notify = nullptr;
      RemoteList::RemoteOverride* remote_override = nullptr;

#if defined(USE_TUN_BUILDER)
      TunBuilderBase* builder = nullptr;
#endif

#if defined(OPENVPN_EXTERNAL_TUN_FACTORY)
      ExternalTun::Factory* extern_tun_factory = nullptr;
#endif
    };

    ClientOptions(const OptionList& opt,   // only needs to remain in scope for duration of constructor call
		  const Config& config)
      : server_addr_float(false),
        socket_protect(config.socket_protect),
	reconnect_notify(config.reconnect_notify),
	cli_stats(config.cli_stats),
	cli_events(config.cli_events),
	server_poll_timeout_(10),
	server_override(config.server_override),
	proto_override(config.proto_override),
	conn_timeout_(config.conn_timeout),
	tcp_queue_limit(64),
	proto_context_options(config.proto_context_options),
	http_proxy_options(config.http_proxy_options),
#ifdef OPENVPN_GREMLIN
	gremlin_config(config.gremlin_config),
#endif
	echo(config.echo),
	info(config.info),
	autologin(false),
	autologin_sessions(false),
	creds_locked(false)
    {
      // parse general client options
      const ParseClientConfig pcc(opt);

      // creds
      userlocked_username = pcc.userlockedUsername();
      autologin = pcc.autologin();
      autologin_sessions = (autologin && config.autologin_sessions);

      // digest factory
      DigestFactory::Ptr digest_factory(new CryptoDigestFactory<SSLLib::CryptoAPI>());

      // initialize RNG/PRNG
      rng.reset(new SSLLib::RandomAPI(false));
      prng.reset(new SSLLib::RandomAPI(true));

#if defined(ENABLE_DCO) && !defined(OPENVPN_FORCE_TUN_NULL) && !defined(OPENVPN_EXTERNAL_TUN_FACTORY)
      if (config.dco)
	dco = DCOTransport::new_controller();
#else
      if (config.dco)
	throw option_error("DCO not enabled in this build");
#endif

      // frame
      const unsigned int tun_mtu = parse_tun_mtu(opt, 0); // get tun-mtu parameter from config
      const MSSCtrlParms mc(opt);
      frame = frame_init(true, tun_mtu, mc.mssfix_ctrl, true);

      // TCP queue limit
      tcp_queue_limit = opt.get_num<decltype(tcp_queue_limit)>("tcp-queue-limit", 1, tcp_queue_limit, 1, 65536);

      // route-nopull
      pushed_options_filter.reset(new PushedOptionsFilter(opt.exists("route-nopull")));

      // OpenVPN Protocol context (including SSL)
      cp_main = proto_config(opt, config, pcc, false);
      cp_relay = proto_config(opt, config, pcc, true); // may be null
      layer = cp_main->layer;

#ifdef PRIVATE_TUNNEL_PROXY
      if (config.alt_proxy && !dco)
	alt_proxy = PTProxy::new_proxy(opt, rng);
#endif

      // If HTTP proxy parameters are not supplied by API, try to get them from config
      if (!http_proxy_options)
	http_proxy_options = HTTPProxyTransport::Options::parse(opt);

      // load remote list
      if (config.remote_override)
	  remote_list.reset(new RemoteList(config.remote_override));
	else
	  remote_list.reset(new RemoteList(opt, "", RemoteList::WARN_UNSUPPORTED, nullptr));
      if (!remote_list->defined())
	throw option_error("no remote option specified");

      // Set remote list prng
      remote_list->set_random(prng);

      // If running in tun_persist mode, we need to do basic DNS caching so that
      // we can avoid emitting DNS requests while the tunnel is blocked during
      // reconnections.
      remote_list->set_enable_cache(config.tun_persist);

      // process server override
      remote_list->set_server_override(config.server_override);

      // process protocol override, should be called after set_enable_cache
      remote_list->handle_proto_override(config.proto_override,
					 http_proxy_options || (alt_proxy && alt_proxy->requires_tcp()));

      // process remote-random
      if (opt.exists("remote-random"))
	remote_list->randomize();

      // get "float" option
      server_addr_float = opt.exists("float");

      // special remote cache handling for proxies
      if (alt_proxy)
	{
	  remote_list->set_enable_cache(false); // remote server addresses will be resolved by proxy
	  alt_proxy->set_enable_cache(config.tun_persist);
	}
      else if (http_proxy_options)
	{
	  remote_list->set_enable_cache(false); // remote server addresses will be resolved by proxy
	  http_proxy_options->proxy_server_set_enable_cache(config.tun_persist);
	}

      // secret option not supported
      if (opt.exists("secret"))
	throw option_error("sorry, static key encryption mode (non-SSL/TLS) is not supported");

      // fragment option not supported
      if (opt.exists("fragment"))
	throw option_error("sorry, 'fragment' directive is not supported, nor is connecting to a server that uses 'fragment' directive");

      // init transport config
      const std::string session_name = load_transport_config();

      // initialize tun/tap
      if (dco)
	{
	  DCO::TunConfig tunconf;
	  tunconf.tun_prop.layer = layer;
	  tunconf.tun_prop.session_name = session_name;
	  if (tun_mtu)
	    tunconf.tun_prop.mtu = tun_mtu;
	  tunconf.tun_prop.google_dns_fallback = config.google_dns_fallback;
	  tunconf.tun_prop.remote_list = remote_list;
	  tunconf.stop = config.stop;
	  tun_factory = dco->new_tun_factory(tunconf, opt);
	}
      else
	{
#if defined(OPENVPN_EXTERNAL_TUN_FACTORY)
	  {
	    ExternalTun::Config tunconf;
	    tunconf.tun_prop.layer = layer;
	    tunconf.tun_prop.session_name = session_name;
	    tunconf.tun_prop.google_dns_fallback = config.google_dns_fallback;
	    if (tun_mtu)
	      tunconf.tun_prop.mtu = tun_mtu;
	    tunconf.frame = frame;
	    tunconf.stats = cli_stats;
	    tunconf.tun_prop.remote_list = remote_list;
	    tunconf.tun_persist = config.tun_persist;
	    tunconf.stop = config.stop;
	    tun_factory.reset(config.extern_tun_factory->new_tun_factory(tunconf, opt));
	    if (!tun_factory)
	      throw option_error("OPENVPN_EXTERNAL_TUN_FACTORY: no tun factory");
	  }
#elif defined(USE_TUN_BUILDER)
	  {
	    TunBuilderClient::ClientConfig::Ptr tunconf = TunBuilderClient::ClientConfig::new_obj();
	    tunconf->builder = config.builder;
	    tunconf->tun_prop.session_name = session_name;
	    tunconf->tun_prop.google_dns_fallback = config.google_dns_fallback;
	    if (tun_mtu)
	      tunconf->tun_prop.mtu = tun_mtu;
	    tunconf->frame = frame;
	    tunconf->stats = cli_stats;
	    tunconf->tun_prop.remote_list = remote_list;
	    tun_factory = tunconf;
#if defined(OPENVPN_PLATFORM_IPHONE)
	    tunconf->retain_sd = true;
	    tunconf->tun_prefix = true;
	    if (config.tun_persist)
	      tunconf->tun_prop.remote_bypass = true;
#endif
#if defined(OPENVPN_PLATFORM_ANDROID)
	    // Android VPN API doesn't support excluded routes, so we must emulate them
	    tunconf->eer_factory.reset(new EmulateExcludeRouteFactoryImpl(false));
#endif
	    if (config.tun_persist)
	      tunconf->tun_persist.reset(new TunBuilderClient::TunPersist(true, tunconf->retain_sd, config.builder));
	    tun_factory = tunconf;
	  }
#elif defined(OPENVPN_PLATFORM_LINUX) && !defined(OPENVPN_FORCE_TUN_NULL)
	  {
	    TunLinux::ClientConfig::Ptr tunconf = TunLinux::ClientConfig::new_obj();
	    tunconf->tun_prop.layer = layer;
	    tunconf->tun_prop.session_name = session_name;
	    if (tun_mtu)
	      tunconf->tun_prop.mtu = tun_mtu;
	    tunconf->tun_prop.google_dns_fallback = config.google_dns_fallback;
	    tunconf->tun_prop.remote_list = remote_list;
	    tunconf->frame = frame;
	    tunconf->stats = cli_stats;
	    tunconf->load(opt);
	    tun_factory = tunconf;
	  }
#elif defined(OPENVPN_PLATFORM_MAC) && !defined(OPENVPN_FORCE_TUN_NULL)
	  {
	    TunMac::ClientConfig::Ptr tunconf = TunMac::ClientConfig::new_obj();
	    tunconf->tun_prop.layer = layer;
	    tunconf->tun_prop.session_name = session_name;
	    tunconf->tun_prop.google_dns_fallback = config.google_dns_fallback;
	    if (tun_mtu)
	      tunconf->tun_prop.mtu = tun_mtu;
	    tunconf->frame = frame;
	    tunconf->stats = cli_stats;
	    tunconf->stop = config.stop;
	    if (config.tun_persist)
	      tunconf->tun_persist.reset(new TunMac::TunPersist(true, false, nullptr));
	    client_lifecycle.reset(new MacLifeCycle);
#ifdef OPENVPN_COMMAND_AGENT
	    tunconf->tun_setup_factory = UnixCommandAgent::new_agent(opt);
#endif
	    tun_factory = tunconf;
	  }
#elif defined(OPENVPN_PLATFORM_WIN) && !defined(OPENVPN_FORCE_TUN_NULL)
	  {
	    TunWin::ClientConfig::Ptr tunconf = TunWin::ClientConfig::new_obj();
	    tunconf->tun_prop.layer = layer;
	    tunconf->tun_prop.session_name = session_name;
	    tunconf->tun_prop.google_dns_fallback = config.google_dns_fallback;
	    if (tun_mtu)
	      tunconf->tun_prop.mtu = tun_mtu;
	    tunconf->frame = frame;
	    tunconf->stats = cli_stats;
	    tunconf->stop = config.stop;
	    if (config.tun_persist)
	      tunconf->tun_persist.reset(new TunWin::TunPersist(true, false, nullptr));
#ifdef OPENVPN_COMMAND_AGENT
	    tunconf->tun_setup_factory = WinCommandAgent::new_agent(opt);
#endif
	    tun_factory = tunconf;
	  }
#else
	  {
	    TunNull::ClientConfig::Ptr tunconf = TunNull::ClientConfig::new_obj();
	    tunconf->frame = frame;
	    tunconf->stats = cli_stats;
	    tun_factory = tunconf;
	  }
#endif
	}

      // verify that tun implementation can handle OSI layer declared by config
      if (layer == Layer(Layer::OSI_LAYER_2) && !tun_factory->layer_2_supported())
	throw ErrorCode(Error::TAP_NOT_SUPPORTED, true, "OSI layer 2 tunnels are not currently supported");

      // server-poll-timeout
      {
	const Option *o = opt.get_ptr("server-poll-timeout");
	if (o)
	  server_poll_timeout_ = parse_number_throw<unsigned int>(o->get(1, 16), "server-poll-timeout");
      }

      // create default creds object in case submit_creds is not called,
      // and populate it with embedded creds, if available
      {
	ClientCreds::Ptr cc = new ClientCreds();
	if (pcc.hasEmbeddedPassword())
	  {
	    cc->set_username(userlocked_username);
	    cc->set_password(pcc.embeddedPassword());
	    cc->enable_password_cache(true);
	    cc->set_replace_password_with_session_id(true);
	    submit_creds(cc);
	    creds_locked = true;
	  }
	else if (autologin_sessions)
	  {
	    // autologin sessions require replace_password_with_session_id
	    cc->set_replace_password_with_session_id(true);
	    submit_creds(cc);
	    creds_locked = true;
	  }
	else
	  {
	    submit_creds(cc);
	  }
      }

      // configure push_base, a set of base options that will be combined with
      // options pushed by server.
      {
	push_base.reset(new PushOptionsBase());

	// base options where multiple options of the same type can aggregate
	push_base->multi.extend(opt, "route");
	push_base->multi.extend(opt, "route-ipv6");
	push_base->multi.extend(opt, "redirect-gateway");
	push_base->multi.extend(opt, "redirect-private");
	push_base->multi.extend(opt, "dhcp-option");

	// base options where only a single instance of each option makes sense
	push_base->singleton.extend(opt, "redirect-dns");
	push_base->singleton.extend(opt, "inactive");
	push_base->singleton.extend(opt, "route-metric");

	// IPv6
	{
	  const unsigned int n = push_base->singleton.extend(opt, "block-ipv6");
	  if (!n && config.ipv6() == IPv6Setting::No)
	    push_base->singleton.emplace_back("block-ipv6");
	}
      }

      // show unused options
      if (opt.n_unused())
	OPENVPN_LOG(OPENVPN_UNUSED_OPTIONS << std::endl << opt.render(Option::RENDER_TRUNC_64|Option::RENDER_NUMBER|Option::RENDER_BRACKET|Option::RENDER_UNUSED));
    }

    static PeerInfo::Set::Ptr build_peer_info(const Config& config, const ParseClientConfig& pcc, const bool autologin_sessions)
    {
      PeerInfo::Set::Ptr pi(new PeerInfo::Set);

      // IPv6
      if (config.ipv6() == IPv6Setting::No)
	pi->emplace_back("IV_IPv6", "0");
      else if (config.ipv6() == IPv6Setting::Yes)
	pi->emplace_back("IV_IPv6", "1");

      // autologin sessions
      if (autologin_sessions)
	pi->emplace_back("IV_AUTO_SESS", "1");

      // Config::peerInfo
      pi->append_foreign_set_ptr(config.extra_peer_info.get());

      // setenv UV_ options
      pi->append_foreign_set_ptr(pcc.peerInfoUV());

      // MAC address
      if (pcc.pushPeerInfo())
	{
	  std::string hwaddr = get_hwaddr();
	  if (!hwaddr.empty())
	    pi->emplace_back("IV_HWADDR", hwaddr);
	}

      return pi;
    }

    void next()
    {
      bool omit_next = false;

      if (alt_proxy)
	omit_next = alt_proxy->next();
      if (!omit_next)
	remote_list->next();
      load_transport_config();
    }

    void remote_reset_cache_item()
    {
      remote_list->reset_cache_item();
    }

    bool pause_on_connection_timeout()
    {
      if (reconnect_notify)
	return reconnect_notify->pause_on_connection_timeout();
      else
	return false;
    }

    Client::Config::Ptr client_config(const bool relay_mode)
    {
      Client::Config::Ptr cli_config = new Client::Config;

      // Copy ProtoConfig so that modifications due to server push will
      // not persist across client instantiations.
      cli_config->proto_context_config.reset(new Client::ProtoConfig(proto_config_cached(relay_mode)));

      cli_config->proto_context_options = proto_context_options;
      cli_config->push_base = push_base;
      cli_config->transport_factory = transport_factory;
      cli_config->tun_factory = tun_factory;
      cli_config->cli_stats = cli_stats;
      cli_config->cli_events = cli_events;
      cli_config->creds = creds;
      cli_config->pushed_options_filter = pushed_options_filter;
      cli_config->tcp_queue_limit = tcp_queue_limit;
      cli_config->echo = echo;
      cli_config->info = info;
      cli_config->autologin_sessions = autologin_sessions;
      return cli_config;
    }

    bool need_creds() const
    {
      return !autologin;
    }

    void submit_creds(const ClientCreds::Ptr& creds_arg)
    {
      if (creds_arg && !creds_locked)
	{
	  // if no username is defined in creds and userlocked_username is defined
	  // in profile, set the creds username to be the userlocked_username
	  if (!creds_arg->username_defined() && !userlocked_username.empty())
	    creds_arg->set_username(userlocked_username);
	  creds = creds_arg;
	}
    }

    bool server_poll_timeout_enabled() const
    {
      return !http_proxy_options;
    }

    Time::Duration server_poll_timeout() const
    {
      return Time::Duration::seconds(server_poll_timeout_);
    }

    SessionStats& stats() { return *cli_stats; }
    const SessionStats::Ptr& stats_ptr() const { return cli_stats; }
    ClientEvent::Queue& events() { return *cli_events; }
    ClientLifeCycle* lifecycle() { return client_lifecycle.get(); }

    int conn_timeout() const { return conn_timeout_; }

    RemoteList::Ptr remote_list_precache() const
    {
      RemoteList::Ptr r;
      if (alt_proxy)
	{
	  alt_proxy->precache(r);
	  if (r)
	    return r;
	}
      if (http_proxy_options)
	{
	  http_proxy_options->proxy_server_precache(r);
	  if (r)
	    return r;
	}
      return remote_list;
    }

    void update_now()
    {
      now_.update();
    }

    void finalize(const bool disconnected)
    {
      if (tun_factory)
	tun_factory->finalize(disconnected);
    }

  private:
    Client::ProtoConfig& proto_config_cached(const bool relay_mode)
    {
      if (relay_mode && cp_relay)
	return *cp_relay;
      else
	return *cp_main;
    }

    Client::ProtoConfig::Ptr proto_config(const OptionList& opt,
					  const Config& config,
					  const ParseClientConfig& pcc,
					  const bool relay_mode)
    {
      // relay mode is null unless one of the below directives is defined
      if (relay_mode && !opt.exists("relay-mode"))
	return Client::ProtoConfig::Ptr();

      // load flags
      unsigned int lflags = SSLConfigAPI::LF_PARSE_MODE;
      if (relay_mode)
	lflags |= SSLConfigAPI::LF_RELAY_MODE;

      // client SSL config
      SSLLib::SSLAPI::Config::Ptr cc(new SSLLib::SSLAPI::Config());
      cc->set_external_pki_callback(config.external_pki);
      cc->set_frame(frame);
      cc->set_flags(SSLConst::LOG_VERIFY_STATUS);
      cc->set_debug_level(config.ssl_debug_level);
      cc->set_rng(rng);
      cc->set_local_cert_enabled(pcc.clientCertEnabled() && !config.disable_client_cert);
      cc->set_private_key_password(config.private_key_password);
      cc->set_force_aes_cbc_ciphersuites(config.force_aes_cbc_ciphersuites);
      cc->load(opt, lflags);
      cc->set_tls_version_min_override(config.tls_version_min_override);
      cc->set_tls_cert_profile_override(config.tls_cert_profile_override);
      if (!cc->get_mode().is_client())
	throw option_error("only client configuration supported");

      // client ProtoContext config
      Client::ProtoConfig::Ptr cp(new Client::ProtoConfig());
      cp->relay_mode = relay_mode;
      cp->dc.set_factory(new CryptoDCSelect<SSLLib::CryptoAPI>(frame, cli_stats, prng));
      cp->dc_deferred = true; // defer data channel setup until after options pull
      cp->tls_auth_factory.reset(new CryptoOvpnHMACFactory<SSLLib::CryptoAPI>());
      cp->tlsprf_factory.reset(new CryptoTLSPRFFactory<SSLLib::CryptoAPI>());
      cp->ssl_factory = cc->new_factory();
      cp->load(opt, *proto_context_options, config.default_key_direction, false);
      cp->set_xmit_creds(!autologin || pcc.hasEmbeddedPassword() || autologin_sessions);
      cp->gui_version = config.gui_version;
      cp->force_aes_cbc_ciphersuites = config.force_aes_cbc_ciphersuites; // also used to disable proto V2
      cp->extra_peer_info = build_peer_info(config, pcc, autologin_sessions);
      cp->frame = frame;
      cp->now = &now_;
      cp->rng = rng;
      cp->prng = prng;

      return cp;
    }

    std::string load_transport_config()
    {
      // get current transport protocol
      const Protocol& transport_protocol = remote_list->current_transport_protocol();

      // If we are connecting over a proxy, and TCP protocol is required, but current
      // transport protocol is NOT TCP, we will throw an internal error because this
      // should have been caught earlier in RemoteList::handle_proto_override.

      // construct transport object
      if (dco)
	{
	  DCO::TransportConfig transconf;
	  transconf.protocol = transport_protocol;
	  transconf.remote_list = remote_list;
	  transconf.frame = frame;
	  transconf.stats = cli_stats;
	  transconf.server_addr_float = server_addr_float;
	  transport_factory = dco->new_transport_factory(transconf);
	}
      else if (alt_proxy)
	{
	  if (alt_proxy->requires_tcp() && !transport_protocol.is_tcp())
	    throw option_error("internal error: no TCP server entries for " + alt_proxy->name() + " transport");
	  AltProxy::Config conf;
	  conf.remote_list = remote_list;
	  conf.frame = frame;
	  conf.stats = cli_stats;
	  conf.digest_factory.reset(new CryptoDigestFactory<SSLLib::CryptoAPI>());
	  conf.socket_protect = socket_protect;
	  conf.rng = rng;
	  transport_factory = alt_proxy->new_transport_client_factory(conf);
	}
      else if (http_proxy_options)
	{
	  if (!transport_protocol.is_tcp())
	    throw option_error("internal error: no TCP server entries for HTTP proxy transport");

	  // HTTP Proxy transport
	  HTTPProxyTransport::ClientConfig::Ptr httpconf = HTTPProxyTransport::ClientConfig::new_obj();
	  httpconf->remote_list = remote_list;
	  httpconf->frame = frame;
	  httpconf->stats = cli_stats;
	  httpconf->digest_factory.reset(new CryptoDigestFactory<SSLLib::CryptoAPI>());
	  httpconf->socket_protect = socket_protect;
	  httpconf->http_proxy_options = http_proxy_options;
	  httpconf->rng = rng;
#ifdef PRIVATE_TUNNEL_PROXY
	  httpconf->skip_html = true;
#endif
	  transport_factory = httpconf;
	}
      else
	{
	  if (transport_protocol.is_udp())
	    {
	      // UDP transport
	      UDPTransport::ClientConfig::Ptr udpconf = UDPTransport::ClientConfig::new_obj();
	      udpconf->remote_list = remote_list;
	      udpconf->frame = frame;
	      udpconf->stats = cli_stats;
	      udpconf->socket_protect = socket_protect;
	      udpconf->server_addr_float = server_addr_float;
#ifdef OPENVPN_GREMLIN
	      udpconf->gremlin_config = gremlin_config;
#endif
	      transport_factory = udpconf;
	    }
	  else if (transport_protocol.is_tcp())
	    {
	      // TCP transport
	      TCPTransport::ClientConfig::Ptr tcpconf = TCPTransport::ClientConfig::new_obj();
	      tcpconf->remote_list = remote_list;
	      tcpconf->frame = frame;
	      tcpconf->stats = cli_stats;
	      tcpconf->socket_protect = socket_protect;
#ifdef OPENVPN_GREMLIN
	      tcpconf->gremlin_config = gremlin_config;
#endif
	      transport_factory = tcpconf;
	    }
	  else
	    throw option_error("internal error: unknown transport protocol");
	}
      return remote_list->current_server_host();
    }

    Time now_; // current time
    RandomAPI::Ptr rng;
    RandomAPI::Ptr prng;
    Frame::Ptr frame;
    Layer layer;
    Client::ProtoConfig::Ptr cp_main;
    Client::ProtoConfig::Ptr cp_relay;
    RemoteList::Ptr remote_list;
    bool server_addr_float;
    TransportClientFactory::Ptr transport_factory;
    TunClientFactory::Ptr tun_factory;
    SocketProtect* socket_protect;
    ReconnectNotify* reconnect_notify;
    SessionStats::Ptr cli_stats;
    ClientEvent::Queue::Ptr cli_events;
    ClientCreds::Ptr creds;
    unsigned int server_poll_timeout_;
    std::string server_override;
    Protocol proto_override;
    int conn_timeout_;
    unsigned int tcp_queue_limit;
    ProtoContextOptions::Ptr proto_context_options;
    HTTPProxyTransport::Options::Ptr http_proxy_options;
#ifdef OPENVPN_GREMLIN
    Gremlin::Config::Ptr gremlin_config;
#endif
    std::string userlocked_username;
    bool echo;
    bool info;
    bool autologin;
    bool autologin_sessions;
    bool creds_locked;
    PushOptionsBase::Ptr push_base;
    OptionList::FilterBase::Ptr pushed_options_filter;
    ClientLifeCycle::Ptr client_lifecycle;
    AltProxy::Ptr alt_proxy;
    DCO::Ptr dco;
  };
}

#endif
