//    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 Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

// API for OpenVPN Client, may be used standalone or wrapped by swig.
// Use ovpncli.i to wrap the API for swig.
// The crux of the API is defined in OpenVPNClient (below)
// and TunBuilderBase.

#include <string>
#include <vector>
#include <utility>

#include <openvpn/tun/builder/base.hpp>
#include <openvpn/tun/extern/fw.hpp>
#include <openvpn/pki/epkibase.hpp>
#include <openvpn/transport/client/extern/fw.hpp>

namespace openvpn {
  class OptionList;
  class ProfileMerge;
  class Stop;

  namespace ClientAPI {
    // Represents an OpenVPN server and its friendly name
    // (client reads)
    struct ServerEntry
    {
      std::string server;
      std::string friendlyName;
    };

    // return properties of config
    // (client reads)
    struct EvalConfig
    {
      // true if error
      bool error = false;

      // if error, message given here
      std::string message;

      // this username must be used with profile
      std::string userlockedUsername;

      // profile name of config
      std::string profileName;

      // "friendly" name of config
      std::string friendlyName;

      // true: no creds required, false: username/password required
      bool autologin = false;

      // if true, this is an External PKI profile (no cert or key directives)
      bool externalPki = false;

      // static challenge, may be empty, ignored if autologin
      std::string staticChallenge;

      // true if static challenge response should be echoed to UI, ignored if autologin
      bool staticChallengeEcho = false;

      // true if this profile requires a private key password
      bool privateKeyPasswordRequired = false;

      // true if user is allowed to save authentication password in UI
      bool allowPasswordSave = false;

      // information about the first remote item in config
      std::string remoteHost;    // will be overridden by Config::serverOverride if defined
      std::string remotePort;
      std::string remoteProto;

      // optional list of user-selectable VPN servers
      std::vector<ServerEntry> serverList;
    };

    // used to pass credentials to VPN core
    // (client writes)
    struct ProvideCreds
    {
      std::string username;
      std::string password;

      // response to challenge
      std::string response;

      // Dynamic challenge/response cookie
      std::string dynamicChallengeCookie;

      // If true, on successful connect, we will replace the password
      // with the session ID we receive from the server (if provided).
      // If false, the password will be cached for future reconnects
      // and will not be replaced with a session ID, even if the
      // server provides one.
      bool replacePasswordWithSessionID = false;

      // If true, and if replacePasswordWithSessionID is true, and if
      // we actually receive a session ID from the server, cache
      // the user-provided password for future use before replacing
      // the active password with the session ID.
      bool cachePassword = false;
    };

    // used to get session token from VPN core
    // (client reads)
    struct SessionToken
    {
      std::string username;
      std::string session_id; // an OpenVPN Session ID, used as a proxy for password
    };

    // used to query challenge/response from user
    // (client reads)
    struct DynamicChallenge
    {
      std::string challenge;
      bool echo = false;
      bool responseRequired = false;

      std::string stateID;
    };

    // a basic key/value pair, used in Config below when OpenVPN profile is
    // passed as a dictionary
    struct KeyValue
    {
      KeyValue() {}

      KeyValue(std::string key_arg, std::string value_arg)
	: key(std::move(key_arg)),
	  value(std::move(value_arg)) {}

      std::string key;
      std::string value;
    };

    // OpenVPN config-file/profile
    // (client writes)
    struct Config
    {
      // OpenVPN profile as a string
      std::string content;

      // OpenVPN profile as series of key/value pairs (may be provided exclusively
      // or in addition to content string above).
      std::vector<KeyValue> contentList;

      // Set to identity OpenVPN GUI version.
      // Format should be "<gui_identifier><space><version>"
      // Passed to server as IV_GUI_VER.
      std::string guiVersion;

      // Set to a comma seperated list of supported SSO mechanisms that may
      // be signalled via INFO_PRE to the client.
      // "openurl" is to continue authentication by opening an url in a browser
      // "crtext" gives a challenge response in text format that needs to
      // responded via control channel. (
      // Passed to the server as IV_SSO
      std::string ssoMethods;

      // Override the string that is passed as IV_HWADDR to the server
      std::string hwAddrOverride;

      // Set the string that is passed to the server as IV_PLAT_VER
      std::string platformVersion;

      // Use a different server than that specified in "remote"
      // option of profile
      std::string serverOverride;

      // Use a different port than that specified in "remote"
      // option of profile
      std::string portOverride;

      // Force a given transport protocol
      // Should be tcp, udp, or adaptive.
      std::string protoOverride;

      // IPv6 preference
      //  no      -- disable IPv6, so tunnel will be IPv4-only
      //  yes     -- request combined IPv4/IPv6 tunnel
      //  default (or empty string) -- leave decision to server
      std::string ipv6;

      // Connection timeout in seconds, or 0 to retry indefinitely
      int connTimeout = 0;

      // Keep tun interface active during pauses or reconnections
      bool tunPersist = false;

      // If true and a redirect-gateway profile doesn't also define
      // DNS servers, use the standard Google DNS servers.
      bool googleDnsFallback = false;

      // if true, do synchronous DNS lookup.
      bool synchronousDnsLookup = false;

      // Enable autologin sessions
      bool autologinSessions = true;

      // If true, consider AUTH_FAILED to be a non-fatal error,
      // and retry the connection after a pause.
      bool retryOnAuthFailed = false;

      // An ID used for get-certificate and RSA signing callbacks
      // for External PKI profiles.
      std::string externalPkiAlias;

      // If true, don't send client cert/key to peer.
      bool disableClientCert = false;

      // SSL library debug level
      int sslDebugLevel = 0;

      // Compression mode, one of:
      // yes -- allow compression on both uplink and downlink
      // asym -- allow compression on downlink only (i.e. server -> client)
      // no (default if empty) -- support compression stubs only
      std::string compressionMode;

      // private key password (optional)
      std::string privateKeyPassword;

      // Default key direction parameter for tls-auth (0, 1, or
      // -1 (bidirectional -- default)) if no key-direction parameter
      // defined in profile.  Generally should be -1 (bidirectional)
      // for compatibility with 2.x branch
      int defaultKeyDirection = -1;

      // If true, force ciphersuite to be one of:
      // 1. TLS_DHE_RSA_WITH_AES_256_CBC_SHA, or
      // 2. TLS_DHE_RSA_WITH_AES_128_CBC_SHA
      // and disable setting TLS minimum version.
      // This is intended for compatibility with legacy systems.
      bool forceAesCbcCiphersuites = false;

      // Override the minimum TLS version:
      //   disabled -- don't specify a minimum, and disable any minimum
      //               specified in profile
      //   default or ""  -- use profile minimum
      //   tls_1_0  -- use TLS 1.0 minimum (overrides profile)
      //   tls_1_1  -- use TLS 1.1 minimum (overrides profile)
      //   tls_1_2  -- use TLS 1.2 minimum (overrides profile)
      std::string tlsVersionMinOverride;

      // Override or default the tls-cert-profile setting:
      //   default or ""     -- use profile default
      //   legacy            -- allow 1024-bit RSA certs signed with SHA1
      //   preferred         -- require at least 2048-bit RSA certs signed
      //                        with SHA256 or higher
      //   suiteb            -- require NSA Suite-B
      //   legacy-default    -- use legacy as the default if profile
      //                        doesn't specify tls-cert-profile
      //   preferred-default -- use preferred as the default if profile
      //                        doesn't specify tls-cert-profile
      std::string tlsCertProfileOverride;

      // Pass custom key/value pairs to OpenVPN server.
      std::vector<KeyValue> peerInfo;

      // HTTP Proxy parameters (optional)
      std::string proxyHost;         // hostname or IP address of proxy
      std::string proxyPort;         // port number of proxy
      std::string proxyUsername;     // proxy credentials (optional)
      std::string proxyPassword;     // proxy credentials (optional)
      bool proxyAllowCleartextAuth = false;  // enables HTTP Basic auth

      // Custom proxy implementation
      bool altProxy = false;

      // Custom Data Channel Offload implementation
      bool dco = false;

      // pass through pushed "echo" directives via "ECHO" event
      bool echo = false;

      // pass through control channel INFO notifications via "INFO" event
      bool info = false;

      // Allow access to local LAN. This is for platforms like
      // Android that disable local LAN access by default.
      bool allowLocalLanAccess = false;

      // Periodic convenience clock tick in milliseconds.
      // Will call clock_tick() at a frequency defined by this parameter.
      // Set to 0 to disable.
      unsigned int clockTickMS = 0;

      // Gremlin configuration (requires that the core is built with OPENVPN_GREMLIN)
      std::string gremlinConfig;

      // Use wintun instead of tap-windows6 on Windows
      bool wintun = false;
    };

    // used to communicate VPN events such as connect, disconnect, etc.
    // (client reads)
    struct Event
    {
      bool error = false;    // true if error (fatal or nonfatal)
      bool fatal = false;    // true if fatal error (will disconnect)
      std::string name;      // event name
      std::string info;      // additional event info
    };

    // used to communicate extra details about successful connection
    // (client reads)
    struct ConnectionInfo
    {
      bool defined = false;
      std::string user;
      std::string serverHost;
      std::string serverPort;
      std::string serverProto;
      std::string serverIp;
      std::string vpnIp4;
      std::string vpnIp6;
      std::string gw4;
      std::string gw6;
      std::string clientIp;
      std::string tunName;
    };

    // returned by some methods as a status/error indication
    // (client reads)
    struct Status
    {
      bool error = false;   // true if error
      std::string status;   // an optional short error label that identifies the error
      std::string message;  // if error, message given here
    };

    // used to pass log lines
    // (client reads)
    struct LogInfo
    {
      LogInfo() {}
      LogInfo(std::string str)
	: text(std::move(str)) {}
      std::string text;     // log output (usually but not always one line)
    };

    // receives log messages
    struct LogReceiver
    {
      virtual void log(const LogInfo&) = 0;
      virtual ~LogReceiver() {}
    };

    // used to pass stats for an interface
    struct InterfaceStats
    {
      long long bytesIn;
      long long packetsIn;
      long long errorsIn;
      long long bytesOut;
      long long packetsOut;
      long long errorsOut;
    };

    // used to pass basic transport stats
    struct TransportStats
    {
      long long bytesIn;
      long long bytesOut;
      long long packetsIn;
      long long packetsOut;

      // number of binary milliseconds (1/1024th of a second) since
      // last packet was received, or -1 if undefined
      int lastPacketReceived;
    };

    // return value of merge_config methods
    struct MergeConfig
    {
      std::string status;                   // ProfileMerge::Status codes rendered as string
      std::string errorText;                // error string (augments status)
      std::string basename;                 // profile basename
      std::string profileContent;           // unified profile
      std::vector<std::string> refPathList; // list of all reference paths successfully read
    };

    // base class for External PKI queries
    struct ExternalPKIRequestBase
    {
      bool error = false;        // true if error occurred (client writes)
      std::string errorText;     // text describing error (client writes)
      bool invalidAlias = false; // true if the error is caused by an invalid alias (client writes)
      std::string alias;         // the alias string, used to query cert/key (client reads)
    };

    // used to query for External PKI certificate
    struct ExternalPKICertRequest : public ExternalPKIRequestBase
    {
      // leaf cert
      std::string cert; // (client writes)

      // chain of intermediates and root (optional)
      std::string supportingChain; // (client writes)
    };

    // Used to request an RSA signature.
    // algorithm will determinate what signature is expected:
    // RSA_PKCS1_PADDING means that
    // data will be prefixed by an optional PKCS#1 digest prefix
    // per RFC 3447.
    // RSA_NO_PADDING mean so no padding should be done be the callee
    struct ExternalPKISignRequest : public ExternalPKIRequestBase
    {
      std::string data;  // data rendered as base64 (client reads)
      std::string sig;   // RSA signature, rendered as base64 (client writes)
      std::string algorithm;
    };

    // used to override "remote" directives
    struct RemoteOverride
    {
      // components of "remote" directive (client writes),
      std::string host;   // either one of host
      std::string ip;     //   or ip must be defined (or both)
      std::string port;
      std::string proto;
      std::string error;  // if non-empty, indicates an error
    };

    namespace Private {
      class ClientState;
    };

    // Top-level OpenVPN client class.
    class OpenVPNClient : public TunBuilderBase,            // expose tun builder virtual methods
			  public LogReceiver,               // log message notification
			  public ExternalTun::Factory,      // low-level tun override
			  public ExternalTransport::Factory,// low-level transport override
			  private ExternalPKIBase
    {
    public:
      OpenVPNClient();
      virtual ~OpenVPNClient();

      // Call me first, before calling any other method (static or instance methods)
      // in this class.
      static void init_process();

      // Release any resources allocated by init_process.
      static void uninit_process();

      // Read an OpenVPN profile that might contain external
      // file references, returning a unified profile.
      static MergeConfig merge_config_static(const std::string& path, bool follow_references);

      // Read an OpenVPN profile that might contain external
      // file references, returning a unified profile.
      static MergeConfig merge_config_string_static(const std::string& config_content);

      // Parse profile and determine needed credentials statically.
      static EvalConfig eval_config_static(const Config& config);

      // Maximum size of profile that should be allowed
      static long max_profile_size();

      // Parse a dynamic challenge cookie, placing the result in dc.
      // Return true on success or false if parse error.
      static bool parse_dynamic_challenge(const std::string& cookie, DynamicChallenge& dc);

      // Parse OpenVPN configuration file.
      EvalConfig eval_config(const Config&);

      // Provide credentials and other options.  Call before connect().
      Status provide_creds(const ProvideCreds&);

      // Callback to "protect" a socket from being routed through the tunnel.
      // Will be called from the thread executing connect().
      // The remote and ipv6 are the remote host this socket will connect to
      virtual bool socket_protect(int socket, std::string remote, bool ipv6);

      // Primary VPN client connect method, doesn't return until disconnect.
      // Should be called by a worker thread.  This method will make callbacks
      // to event() and log() functions.  Make sure to call eval_config()
      // and possibly provide_creds() as well before this function.
      Status connect();

      // Return information about the most recent connection.  Should be called
      // after an event of type "CONNECTED".
      ConnectionInfo connection_info();

      // Writes current session token to tok and returns true.
      // If session token is unavailable, false is returned and
      // tok is unmodified.
      bool session_token(SessionToken& tok);

      // Stop the client.  Only meaningful when connect() is running.
      // May be called asynchronously from a different thread
      // when connect() is running.
      void stop();

      // Pause the client -- useful to avoid continuous reconnection attempts
      // when network is down.  May be called from a different thread
      // when connect() is running.
      void pause(const std::string& reason);

      // Resume the client after it has been paused.  May be called from a
      // different thread when connect() is running.
      void resume();

      // Do a disconnect/reconnect cycle n seconds from now.  May be called
      // from a different thread when connect() is running.
      void reconnect(int seconds);

      // When a connection is close to timeout, the core will call this
      // method.  If it returns false, the core will disconnect with a
      // CONNECTION_TIMEOUT event.  If true, the core will enter a PAUSE
      // state.
      virtual bool pause_on_connection_timeout() = 0;

      // Get stats/error info.  May be called from a different thread
      // when connect() is running.

      // number of stats
      static int stats_n();

      // return a stats name, index should be >= 0 and < stats_n()
      static std::string stats_name(int index);

      // return a stats value, index should be >= 0 and < stats_n()
      long long stats_value(int index) const;

      // return all stats in a bundle
      std::vector<long long> stats_bundle() const;

      // return tun stats only
      InterfaceStats tun_stats() const;

      // return transport stats only
      TransportStats transport_stats() const;

      // post control channel message
      void post_cc_msg(const std::string& msg);

      // Callback for delivering events during connect() call.
      // Will be called from the thread executing connect().
      virtual void event(const Event&) = 0;

      // Callback for logging.
      // Will be called from the thread executing connect().
      virtual void log(const LogInfo&) = 0;

      // External PKI callbacks
      // Will be called from the thread executing connect().
      virtual void external_pki_cert_request(ExternalPKICertRequest&) = 0;
      virtual void external_pki_sign_request(ExternalPKISignRequest&) = 0;

      // Remote override callback (disabled by default).
      virtual bool remote_override_enabled();
      virtual void remote_override(RemoteOverride&);

      // Periodic convenience clock tick, controlled by Config::clockTickMS
      virtual void clock_tick();

      // Do a crypto library self test
      static std::string crypto_self_test();

      // Returns date/time of app expiration as a unix time value
      static int app_expire();

      // Returns platform description string
      static std::string platform();

      // Returns core copyright
      static std::string copyright();

      // Hide protected methods/data from SWIG
#ifdef SWIGJAVA
    private:
#else
    protected:
#endif

      Status do_connect();

      virtual void connect_attach();
      virtual void connect_pre_run();
      virtual void connect_run();
      virtual void connect_session_stop();

      virtual Stop* get_async_stop();

      Private::ClientState* state;

    private:
      void connect_setup(Status&, bool&);
      void do_connect_async();
      static Status status_from_exception(const std::exception&);
      static void parse_config(const Config&, EvalConfig&, OptionList&);
      void parse_extras(const Config&, EvalConfig&);
      void external_pki_error(const ExternalPKIRequestBase&, const size_t);
      void process_epki_cert_chain(const ExternalPKICertRequest&);
      void check_app_expired();
      static MergeConfig build_merge_config(const ProfileMerge&);

      friend class MyClientEvents;
      void on_disconnect();

      // from ExternalPKIBase
      virtual bool sign(const std::string& data, std::string& sig, const std::string& algorithm);

      // disable copy and assignment
      OpenVPNClient(const OpenVPNClient&) = delete;
      OpenVPNClient& operator=(const OpenVPNClient&) = delete;
    };

  }
}
