//
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// The term "L2TP / IPSec" refers to a pair of layered protocols used
// together to establish a tunneled VPN connection.  First, an "IPSec"
// link is created, which secures a single IP traffic pair between the
// client and server.  For this link to complete, one or two levels of
// authentication are performed.  The first, inner mandatory authentication
// ensures the two parties establishing the IPSec link are correct.  This
// can use a certificate exchange or a less secure "shared group key"
// (PSK) authentication.  An optional outer IPSec authentication can also be
// performed, which is not fully supported by shill's implementation.
// In order to support "tunnel groups" from some vendor VPNs shill supports
// supplying the authentication realm portion during the outer authentication.
// Notably, XAUTH and other forms of user authentication on this outer link
// are not supported.
//
// When IPSec authentication completes, traffic is tunneled through a
// layer 2 tunnel, called "L2TP".  Using the secured link, we tunnel a
// PPP link, through which a second layer of authentication is performed,
// using the provided "user" and "password" properties.

#include "shill/vpn/l2tp_ipsec_driver.h"

#include <base/bind.h>
#include <base/files/file_util.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#if defined(__ANDROID__)
#include <dbus/service_constants.h>
#else
#include <chromeos/dbus/service_constants.h>
#endif  // __ANDROID__
#include <vpn-manager/service_error.h>

#include "shill/certificate_file.h"
#include "shill/device_info.h"
#include "shill/error.h"
#include "shill/external_task.h"
#include "shill/ipconfig.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/ppp_daemon.h"
#include "shill/ppp_device.h"
#include "shill/ppp_device_factory.h"
#include "shill/process_manager.h"
#include "shill/vpn/vpn_service.h"

using base::Bind;
using base::Closure;
using base::FilePath;
using std::map;
using std::string;
using std::vector;

namespace shill {

namespace Logging {
static auto kModuleLogScope = ScopeLogger::kVPN;
static string ObjectID(L2TPIPSecDriver* l) {
  return l->GetServiceRpcIdentifier();
}
}

namespace {
const char kL2TPIPSecIPSecTimeoutProperty[] = "L2TPIPsec.IPsecTimeout";
const char kL2TPIPSecLeftProtoPortProperty[] = "L2TPIPsec.LeftProtoPort";
const char kL2TPIPSecLengthBitProperty[] = "L2TPIPsec.LengthBit";
const char kL2TPIPSecPFSProperty[] = "L2TPIPsec.PFS";
const char kL2TPIPSecRefusePapProperty[] = "L2TPIPsec.RefusePap";
const char kL2TPIPSecRekeyProperty[] = "L2TPIPsec.Rekey";
const char kL2TPIPSecRequireAuthProperty[] = "L2TPIPsec.RequireAuth";
const char kL2TPIPSecRequireChapProperty[] = "L2TPIPsec.RequireChap";
const char kL2TPIPSecRightProtoPortProperty[] = "L2TPIPsec.RightProtoPort";
}  // namespace

// static
const char L2TPIPSecDriver::kL2TPIPSecVPNPath[] = "/usr/sbin/l2tpipsec_vpn";
// static
const VPNDriver::Property L2TPIPSecDriver::kProperties[] = {
  { kL2tpIpsecAuthenticationType, 0 },
  { kL2tpIpsecCaCertNssProperty, 0 },
  { kL2tpIpsecClientCertIdProperty, 0 },
  { kL2tpIpsecClientCertSlotProperty, 0 },
  { kL2tpIpsecIkeVersion, 0 },
  { kL2tpIpsecPasswordProperty, Property::kCredential | Property::kWriteOnly },
  { kL2tpIpsecPinProperty, Property::kCredential },
  { kL2tpIpsecPskProperty, Property::kCredential | Property::kWriteOnly },
  { kL2tpIpsecUserProperty, 0 },
  { kProviderHostProperty, 0 },
  { kProviderTypeProperty, 0 },
  { kL2tpIpsecCaCertPemProperty, Property::kArray },
  { kL2tpIpsecTunnelGroupProperty, 0 },
  { kL2TPIPSecIPSecTimeoutProperty, 0 },
  { kL2TPIPSecLeftProtoPortProperty, 0 },
  { kL2TPIPSecLengthBitProperty, 0 },
  { kL2TPIPSecPFSProperty, 0 },
  { kL2TPIPSecRefusePapProperty, 0 },
  { kL2TPIPSecRekeyProperty, 0 },
  { kL2TPIPSecRequireAuthProperty, 0 },
  { kL2TPIPSecRequireChapProperty, 0 },
  { kL2TPIPSecRightProtoPortProperty, 0 },
  { kL2tpIpsecXauthUserProperty, Property::kCredential | Property::kWriteOnly },
  { kL2tpIpsecXauthPasswordProperty,
    Property::kCredential | Property::kWriteOnly },
  { kL2tpIpsecLcpEchoDisabledProperty, 0 },
};

L2TPIPSecDriver::L2TPIPSecDriver(ControlInterface* control,
                                 EventDispatcher* dispatcher,
                                 Metrics* metrics,
                                 Manager* manager,
                                 DeviceInfo* device_info,
                                 ProcessManager* process_manager)
    : VPNDriver(dispatcher, manager, kProperties, arraysize(kProperties)),
      control_(control),
      metrics_(metrics),
      device_info_(device_info),
      process_manager_(process_manager),
      ppp_device_factory_(PPPDeviceFactory::GetInstance()),
      certificate_file_(new CertificateFile()),
      weak_ptr_factory_(this) {}

L2TPIPSecDriver::~L2TPIPSecDriver() {
  IdleService();
}

std::string L2TPIPSecDriver::GetServiceRpcIdentifier() {
  if (service_ == nullptr)
    return "(l2tp_ipsec_driver)";
  return service_->GetRpcIdentifier();
}

bool L2TPIPSecDriver::ClaimInterface(const string& link_name,
                                     int interface_index) {
  // TODO(petkov): crbug.com/212446.
  NOTIMPLEMENTED();
  return false;
}

void L2TPIPSecDriver::Connect(const VPNServiceRefPtr& service, Error* error) {
  StartConnectTimeout(kDefaultConnectTimeoutSeconds);
  service_ = service;
  service_->SetState(Service::kStateConfiguring);
  if (!SpawnL2TPIPSecVPN(error)) {
    FailService(Service::kFailureInternal);
  }
}

void L2TPIPSecDriver::Disconnect() {
  SLOG(this, 2) << __func__;
  IdleService();
}

void L2TPIPSecDriver::OnConnectionDisconnected() {
  LOG(INFO) << "Underlying connection disconnected.";
  IdleService();
}

void L2TPIPSecDriver::OnConnectTimeout() {
  VPNDriver::OnConnectTimeout();
  FailService(Service::kFailureConnect);
}

string L2TPIPSecDriver::GetProviderType() const {
  return kProviderL2tpIpsec;
}

void L2TPIPSecDriver::IdleService() {
  Cleanup(Service::kStateIdle, Service::kFailureUnknown);
}

void L2TPIPSecDriver::FailService(Service::ConnectFailure failure) {
  Cleanup(Service::kStateFailure, failure);
}

void L2TPIPSecDriver::Cleanup(Service::ConnectState state,
                              Service::ConnectFailure failure) {
  SLOG(this, 2) << __func__ << "("
                << Service::ConnectStateToString(state) << ", "
                << Service::ConnectFailureToString(failure) << ")";
  StopConnectTimeout();
  DeleteTemporaryFiles();
  external_task_.reset();
  if (device_) {
    device_->DropConnection();
    device_->SetEnabled(false);
    device_ = nullptr;
  }
  if (service_) {
    if (state == Service::kStateFailure) {
      service_->SetFailure(failure);
    } else {
      service_->SetState(state);
    }
    service_ = nullptr;
  }
}

void L2TPIPSecDriver::DeleteTemporaryFile(base::FilePath* temporary_file) {
  if (!temporary_file->empty()) {
    base::DeleteFile(*temporary_file, false);
    temporary_file->clear();
  }
}

void L2TPIPSecDriver::DeleteTemporaryFiles() {
  DeleteTemporaryFile(&psk_file_);
  DeleteTemporaryFile(&xauth_credentials_file_);
}

bool L2TPIPSecDriver::SpawnL2TPIPSecVPN(Error* error) {
  SLOG(this, 2) << __func__;
  std::unique_ptr<ExternalTask> external_task_local(
      new ExternalTask(control_,
                       process_manager_,
                       weak_ptr_factory_.GetWeakPtr(),
                       Bind(&L2TPIPSecDriver::OnL2TPIPSecVPNDied,
                            weak_ptr_factory_.GetWeakPtr())));

  vector<string> options;
  map<string, string> environment;  // No env vars passed.
  if (!InitOptions(&options, error)) {
    return false;
  }
  LOG(INFO) << "L2TP/IPSec VPN process options: "
            << base::JoinString(options, " ");

  if (external_task_local->Start(
          FilePath(kL2TPIPSecVPNPath), options, environment, true, error)) {
    external_task_ = std::move(external_task_local);
    return true;
  }
  return false;
}

bool L2TPIPSecDriver::InitOptions(vector<string>* options, Error* error) {
  string vpnhost = args()->LookupString(kProviderHostProperty, "");
  if (vpnhost.empty()) {
    Error::PopulateAndLog(
        FROM_HERE, error, Error::kInvalidArguments, "VPN host not specified.");
    return false;
  }

  if (!InitPSKOptions(options, error)) {
    return false;
  }

  if (!InitXauthOptions(options, error)) {
    return false;
  }

  options->push_back(base::StringPrintf("--remote_host=%s", vpnhost.c_str()));
  options->push_back(base::StringPrintf("--pppd_plugin=%s",
                                        PPPDaemon::kShimPluginPath));
  // Disable pppd from configuring IP addresses, routes, DNS.
  options->push_back("--nosystemconfig");

  // Accept a PEM CA certificate.
  InitPEMOptions(options);

  AppendValueOption(kL2tpIpsecClientCertIdProperty,
                    "--client_cert_id", options);
  AppendValueOption(kL2tpIpsecClientCertSlotProperty,
                    "--client_cert_slot", options);
  AppendValueOption(kL2tpIpsecPinProperty, "--user_pin", options);
  AppendValueOption(kL2tpIpsecUserProperty, "--user", options);
  AppendValueOption(kL2TPIPSecIPSecTimeoutProperty, "--ipsec_timeout", options);
  AppendValueOption(kL2TPIPSecLeftProtoPortProperty,
                    "--leftprotoport", options);
  AppendFlag(kL2TPIPSecPFSProperty, "--pfs", "--nopfs", options);
  AppendFlag(kL2TPIPSecRekeyProperty, "--rekey", "--norekey", options);
  AppendValueOption(kL2TPIPSecRightProtoPortProperty,
                    "--rightprotoport", options);
  AppendFlag(kL2TPIPSecRequireChapProperty,
             "--require_chap", "--norequire_chap", options);
  AppendFlag(kL2TPIPSecRefusePapProperty,
             "--refuse_pap", "--norefuse_pap", options);
  AppendFlag(kL2TPIPSecRequireAuthProperty,
             "--require_authentication", "--norequire_authentication", options);
  AppendFlag(kL2TPIPSecLengthBitProperty,
             "--length_bit", "--nolength_bit", options);
  AppendFlag(kL2tpIpsecLcpEchoDisabledProperty,
             "--noppp_lcp_echo", "--ppp_lcp_echo", options);
  AppendValueOption(kL2tpIpsecTunnelGroupProperty, "--tunnel_group", options);
  if (SLOG_IS_ON(VPN, 0)) {
    options->push_back("--debug");
  }
  return true;
}

bool L2TPIPSecDriver::InitPSKOptions(vector<string>* options, Error* error) {
  string psk = args()->LookupString(kL2tpIpsecPskProperty, "");
  if (!psk.empty()) {
    if (!base::CreateTemporaryFileInDir(manager()->run_path(), &psk_file_) ||
        chmod(psk_file_.value().c_str(), S_IRUSR | S_IWUSR) ||
        base::WriteFile(psk_file_, psk.data(), psk.size()) !=
            static_cast<int>(psk.size())) {
      Error::PopulateAndLog(
          FROM_HERE, error, Error::kInternalError, "Unable to setup psk file.");
      return false;
    }
    options->push_back(base::StringPrintf("--psk_file=%s",
                                          psk_file_.value().c_str()));
  }
  return true;
}

bool L2TPIPSecDriver::InitPEMOptions(vector<string>* options) {
  vector<string> ca_certs;
  if (args()->ContainsStrings(kL2tpIpsecCaCertPemProperty)) {
    ca_certs = args()->GetStrings(kL2tpIpsecCaCertPemProperty);
  }
  if (ca_certs.empty()) {
    return false;
  }
  FilePath certfile = certificate_file_->CreatePEMFromStrings(ca_certs);
  if (certfile.empty()) {
    LOG(ERROR) << "Unable to extract certificates from PEM string.";
    return false;
  }
  options->push_back(base::StringPrintf("--server_ca_file=%s",
                                        certfile.value().c_str()));
  return true;
}

bool L2TPIPSecDriver::InitXauthOptions(vector<string>* options, Error* error) {
  string user = args()->LookupString(kL2tpIpsecXauthUserProperty, "");
  string password = args()->LookupString(kL2tpIpsecXauthPasswordProperty, "");
  if (user.empty() && password.empty()) {
    // Xauth credentials not configured.
    return true;
  }
  if (user.empty() || password.empty()) {
      Error::PopulateAndLog(
          FROM_HERE, error, Error::kInvalidArguments,
          "XAUTH credentials are partially configured.");
    return false;
  }
  string xauth_credentials = user + "\n" + password + "\n";
  if (!base::CreateTemporaryFileInDir(manager()->run_path(),
                                      &xauth_credentials_file_) ||
      chmod(xauth_credentials_file_.value().c_str(), S_IRUSR | S_IWUSR) ||
      base::WriteFile(xauth_credentials_file_, xauth_credentials.data(),
                      xauth_credentials.size()) !=
          static_cast<int>(xauth_credentials.size())) {
    Error::PopulateAndLog(
        FROM_HERE, error, Error::kInternalError,
        "Unable to setup XAUTH credentials file.");
    return false;
  }
  options->push_back(base::StringPrintf("--xauth_credentials_file=%s",
                         xauth_credentials_file_.value().c_str()));
  return true;
}

bool L2TPIPSecDriver::AppendValueOption(
    const string& property, const string& option, vector<string>* options) {
  string value = args()->LookupString(property, "");
  if (!value.empty()) {
    options->push_back(base::StringPrintf("%s=%s", option.c_str(),
                                          value.c_str()));
    return true;
  }
  return false;
}

bool L2TPIPSecDriver::AppendFlag(const string& property,
                                 const string& true_option,
                                 const string& false_option,
                                 vector<string>* options) {
  string value = args()->LookupString(property, "");
  if (!value.empty()) {
    options->push_back(value == "true" ? true_option : false_option);
    return true;
  }
  return false;
}

void L2TPIPSecDriver::OnL2TPIPSecVPNDied(pid_t /*pid*/, int status) {
  FailService(TranslateExitStatusToFailure(status));
  // TODO(petkov): Figure if we need to restart the connection.
}

// static
Service::ConnectFailure L2TPIPSecDriver::TranslateExitStatusToFailure(
    int status) {
  if (!WIFEXITED(status)) {
    return Service::kFailureInternal;
  }
  switch (WEXITSTATUS(status)) {
    case vpn_manager::kServiceErrorResolveHostnameFailed:
      return Service::kFailureDNSLookup;
    case vpn_manager::kServiceErrorIpsecConnectionFailed:
    case vpn_manager::kServiceErrorL2tpConnectionFailed:
    case vpn_manager::kServiceErrorPppConnectionFailed:
      return Service::kFailureConnect;
    case vpn_manager::kServiceErrorIpsecPresharedKeyAuthenticationFailed:
      return Service::kFailureIPSecPSKAuth;
    case vpn_manager::kServiceErrorIpsecCertificateAuthenticationFailed:
      return Service::kFailureIPSecCertAuth;
    case vpn_manager::kServiceErrorPppAuthenticationFailed:
      return Service::kFailurePPPAuth;
    default:
      break;
  }
  return Service::kFailureUnknown;
}

void L2TPIPSecDriver::GetLogin(string* user, string* password) {
  LOG(INFO) << "Login requested.";
  string user_property =
      args()->LookupString(kL2tpIpsecUserProperty, "");
  if (user_property.empty()) {
    LOG(ERROR) << "User not set.";
    return;
  }
  string password_property =
      args()->LookupString(kL2tpIpsecPasswordProperty, "");
  if (password_property.empty()) {
    LOG(ERROR) << "Password not set.";
    return;
  }
  *user = user_property;
  *password = password_property;
}

void L2TPIPSecDriver::Notify(
    const string& reason, const map<string, string>& dict) {
  LOG(INFO) << "IP configuration received: " << reason;

  if (reason == kPPPReasonAuthenticating ||
      reason == kPPPReasonAuthenticated) {
    // These are uninteresting intermediate states that do not indicate failure.
    return;
  }

  if (reason != kPPPReasonConnect) {
    DCHECK_EQ(kPPPReasonDisconnect, reason);
    // DestroyLater, rather than while on stack.
    external_task_.release()->DestroyLater(dispatcher());
    FailService(Service::kFailureUnknown);
    return;
  }

  DeleteTemporaryFiles();

  string interface_name = PPPDevice::GetInterfaceName(dict);
  int interface_index = device_info_->GetIndex(interface_name);
  if (interface_index < 0) {
    // TODO(petkov): Consider handling the race when the RTNL notification about
    // the new PPP device has not been received yet. We can keep the IP
    // configuration and apply it when ClaimInterface is
    // invoked. crbug.com/212446.
    NOTIMPLEMENTED() << ": No device info for " << interface_name << ".";
    return;
  }

  // There is no IPv6 support for L2TP/IPsec VPN at this moment, so create a
  // blackhole route for IPv6 traffic after establishing a IPv4 VPN.
  // TODO(benchan): Generalize this when IPv6 support is added.
  bool blackhole_ipv6 = true;

  if (!device_) {
    device_ = ppp_device_factory_->CreatePPPDevice(
        control_, dispatcher(), metrics_, manager(), interface_name,
        interface_index);
  }
  device_->SetEnabled(true);
  device_->SelectService(service_);

  // Reduce MTU to the minimum viable for IPv6, since the IPSec layer consumes
  // some variable portion of the payload.  Although this system does not yet
  // support IPv6, it is a reasonable value to start with, since the minimum
  // IPv6 packet size will plausibly be a size any gateway would support, and
  // is also larger than the IPv4 minimum size.
  device_->UpdateIPConfigFromPPPWithMTU(
      dict, blackhole_ipv6, IPConfig::kMinIPv6MTU);

  ReportConnectionMetrics();
  StopConnectTimeout();
}

bool L2TPIPSecDriver::IsPskRequired() const {
  return
    const_args()->LookupString(kL2tpIpsecPskProperty, "").empty() &&
    const_args()->LookupString(kL2tpIpsecClientCertIdProperty, "").empty();
}

KeyValueStore L2TPIPSecDriver::GetProvider(Error* error) {
  SLOG(this, 2) << __func__;
  KeyValueStore props = VPNDriver::GetProvider(error);
  props.SetBool(kPassphraseRequiredProperty,
                args()->LookupString(kL2tpIpsecPasswordProperty, "").empty());
  props.SetBool(kL2tpIpsecPskRequiredProperty, IsPskRequired());
  return props;
}

void L2TPIPSecDriver::ReportConnectionMetrics() {
  metrics_->SendEnumToUMA(
      Metrics::kMetricVpnDriver,
      Metrics::kVpnDriverL2tpIpsec,
      Metrics::kMetricVpnDriverMax);

  // We output an enum for each of the authentication types specified,
  // even if more than one is set at the same time.
  bool has_remote_authentication = false;
  if (args()->LookupString(kL2tpIpsecPskProperty, "") != "") {
    metrics_->SendEnumToUMA(
        Metrics::kMetricVpnRemoteAuthenticationType,
        Metrics::kVpnRemoteAuthenticationTypeL2tpIpsecPsk,
        Metrics::kMetricVpnRemoteAuthenticationTypeMax);
    has_remote_authentication = true;
  }
  if (!has_remote_authentication) {
    metrics_->SendEnumToUMA(
        Metrics::kMetricVpnRemoteAuthenticationType,
        Metrics::kVpnRemoteAuthenticationTypeL2tpIpsecDefault,
        Metrics::kMetricVpnRemoteAuthenticationTypeMax);
  }

  bool has_user_authentication = false;
  if (args()->LookupString(kL2tpIpsecClientCertIdProperty,
                           "") != "") {
    metrics_->SendEnumToUMA(
        Metrics::kMetricVpnUserAuthenticationType,
        Metrics::kVpnUserAuthenticationTypeL2tpIpsecCertificate,
        Metrics::kMetricVpnUserAuthenticationTypeMax);
    has_user_authentication = true;
  }
  if (args()->LookupString(kL2tpIpsecPasswordProperty, "") != "") {
    metrics_->SendEnumToUMA(
        Metrics::kMetricVpnUserAuthenticationType,
        Metrics::kVpnUserAuthenticationTypeL2tpIpsecUsernamePassword,
        Metrics::kMetricVpnUserAuthenticationTypeMax);
    has_user_authentication = true;
  }
  if (!has_user_authentication) {
    metrics_->SendEnumToUMA(
        Metrics::kMetricVpnUserAuthenticationType,
        Metrics::kVpnUserAuthenticationTypeL2tpIpsecNone,
        Metrics::kMetricVpnUserAuthenticationTypeMax);
  }
}

}  // namespace shill