| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // 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. |
| // |
| // 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 <memory> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/check_op.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <chromeos/dbus/service_constants.h> |
| #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/process_manager.h" |
| #include "shill/scope_logger.h" |
| #include "shill/vpn/vpn_service.h" |
| #include "shill/vpn/vpn_util.h" |
| |
| namespace shill { |
| |
| namespace Logging { |
| static auto kModuleLogScope = ScopeLogger::kVPN; |
| static std::string ObjectID(const L2TPIPsecDriver*) { |
| return "(l2tp_ipsec_driver)"; |
| } |
| } // namespace Logging |
| |
| namespace { |
| |
| const char kL2TPIPsecIPsecTimeoutProperty[] = "L2TPIPsec.IPsecTimeout"; |
| const char kL2TPIPsecLeftProtoPortProperty[] = "L2TPIPsec.LeftProtoPort"; |
| const char kL2TPIPsecLengthBitProperty[] = "L2TPIPsec.LengthBit"; |
| 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"; |
| |
| constexpr base::TimeDelta kConnectTimeout = base::TimeDelta::FromMinutes(1); |
| |
| Service::ConnectFailure ExitStatusToFailure(int status) { |
| switch (status) { |
| case vpn_manager::kServiceErrorNoError: |
| return Service::kFailureNone; |
| case vpn_manager::kServiceErrorInternal: |
| case vpn_manager::kServiceErrorInvalidArgument: |
| return Service::kFailureInternal; |
| 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: |
| return Service::kFailureUnknown; |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| const char L2TPIPsecDriver::kL2TPIPsecVPNPath[] = "/usr/sbin/l2tpipsec_vpn"; |
| // static |
| const VPNDriver::Property L2TPIPsecDriver::kProperties[] = { |
| {kL2TPIPsecClientCertIdProperty, 0}, |
| {kL2TPIPsecClientCertSlotProperty, 0}, |
| {kL2TPIPsecPasswordProperty, Property::kCredential | Property::kWriteOnly}, |
| {kL2TPIPsecPinProperty, Property::kCredential}, |
| {kL2TPIPsecPskProperty, Property::kCredential | Property::kWriteOnly}, |
| {kL2TPIPsecUseLoginPasswordProperty, 0}, |
| {kL2TPIPsecUserProperty, 0}, |
| {kProviderHostProperty, 0}, |
| {kProviderTypeProperty, 0}, |
| {kL2TPIPsecCaCertPemProperty, Property::kArray}, |
| {kL2TPIPsecTunnelGroupProperty, 0}, |
| {kL2TPIPsecIPsecTimeoutProperty, 0}, |
| {kL2TPIPsecLeftProtoPortProperty, 0}, |
| {kL2TPIPsecLengthBitProperty, 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(Manager* manager, |
| ProcessManager* process_manager) |
| : VPNDriver(manager, process_manager, kProperties, base::size(kProperties)), |
| certificate_file_(new CertificateFile()), |
| password_provider_( |
| std::make_unique<password_provider::PasswordProvider>()) {} |
| |
| L2TPIPsecDriver::~L2TPIPsecDriver() { |
| Cleanup(); |
| } |
| |
| base::TimeDelta L2TPIPsecDriver::ConnectAsync(EventHandler* handler) { |
| event_handler_ = handler; |
| Error error; |
| if (!SpawnL2TPIPsecVPN(&error)) { |
| dispatcher()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&L2TPIPsecDriver::FailService, |
| weak_factory_.GetWeakPtr(), Service::kFailureInternal)); |
| return kTimeoutNone; |
| } |
| return kConnectTimeout; |
| } |
| |
| void L2TPIPsecDriver::Disconnect() { |
| SLOG(this, 2) << __func__; |
| Cleanup(); |
| event_handler_ = nullptr; |
| } |
| |
| IPConfig::Properties L2TPIPsecDriver::GetIPProperties() const { |
| return ip_properties_; |
| } |
| |
| void L2TPIPsecDriver::OnConnectTimeout() { |
| FailService(Service::kFailureConnect); |
| } |
| |
| std::string L2TPIPsecDriver::GetProviderType() const { |
| return kProviderL2tpIpsec; |
| } |
| |
| void L2TPIPsecDriver::FailService(Service::ConnectFailure failure) { |
| SLOG(this, 2) << __func__ << "(" << Service::ConnectFailureToString(failure) |
| << ")"; |
| Cleanup(); |
| if (event_handler_) { |
| event_handler_->OnDriverFailure(failure, Service::kErrorDetailsNone); |
| event_handler_ = nullptr; |
| } |
| } |
| |
| void L2TPIPsecDriver::Cleanup() { |
| DeleteTemporaryFiles(); |
| external_task_.reset(); |
| } |
| |
| void L2TPIPsecDriver::OnBeforeSuspend(const ResultCallback& callback) { |
| if (event_handler_) { |
| FailService(Service::kFailureDisconnect); |
| } |
| callback.Run(Error(Error::kSuccess)); |
| } |
| |
| void L2TPIPsecDriver::OnDefaultPhysicalServiceEvent( |
| DefaultPhysicalServiceEvent event) { |
| if (!event_handler_) { |
| return; |
| } |
| if (event == kDefaultPhysicalServiceUp) { |
| return; |
| } |
| FailService(Service::kFailureDisconnect); |
| } |
| |
| void L2TPIPsecDriver::DeleteTemporaryFile(base::FilePath* temporary_file) { |
| if (!temporary_file->empty()) { |
| base::DeleteFile(*temporary_file); |
| temporary_file->clear(); |
| } |
| } |
| |
| void L2TPIPsecDriver::DeleteTemporaryFiles() { |
| DeleteTemporaryFile(&psk_file_); |
| DeleteTemporaryFile(&xauth_credentials_file_); |
| } |
| |
| bool L2TPIPsecDriver::SpawnL2TPIPsecVPN(Error* error) { |
| SLOG(this, 2) << __func__; |
| auto external_task_local = std::make_unique<ExternalTask>( |
| control_interface(), process_manager(), weak_factory_.GetWeakPtr(), |
| base::Bind(&L2TPIPsecDriver::OnL2TPIPsecVPNDied, |
| weak_factory_.GetWeakPtr())); |
| |
| std::vector<std::string> options; |
| const std::map<std::string, std::string> environment; // No env vars passed. |
| if (!InitOptions(&options, error)) { |
| return false; |
| } |
| LOG(INFO) << "L2TP/IPsec VPN process options: " |
| << base::JoinString(options, " "); |
| |
| uint64_t capmask = CAP_TO_MASK(CAP_NET_ADMIN) | CAP_TO_MASK(CAP_NET_RAW) | |
| CAP_TO_MASK(CAP_NET_BIND_SERVICE) | |
| CAP_TO_MASK(CAP_SETUID) | CAP_TO_MASK(CAP_SETGID); |
| if (!external_task_local->StartInMinijail( |
| base::FilePath(kL2TPIPsecVPNPath), &options, environment, |
| VPNUtil::kVPNUser, VPNUtil::kVPNGroup, capmask, true, true, error)) { |
| return false; |
| } |
| external_task_ = std::move(external_task_local); |
| return true; |
| } |
| |
| bool L2TPIPsecDriver::InitOptions(std::vector<std::string>* options, |
| Error* error) { |
| const auto vpnhost = args()->Lookup<std::string>(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(kL2TPIPsecRekeyProperty, "--rekey", "--norekey", options); |
| AppendValueOption(kL2TPIPsecRightProtoPortProperty, "--rightprotoport", |
| options); |
| AppendFlag(kL2TPIPsecRequireChapProperty, "--require_chap", |
| "--norequire_chap", options); |
| // b/187984628: When UseLoginPassword is enabled, PAP must be refused to |
| // prevent potential password leak to a malicious server. |
| if (args()->Lookup<std::string>(kL2TPIPsecUseLoginPasswordProperty, "") == |
| "true") { |
| args()->Set<std::string>(kL2TPIPsecRefusePapProperty, "true"); |
| } |
| 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(base::StringPrintf( |
| "--log_level=%d", -ScopeLogger::GetInstance()->verbose_level())); |
| } |
| return true; |
| } |
| |
| bool L2TPIPsecDriver::InitPSKOptions(std::vector<std::string>* options, |
| Error* error) { |
| const auto psk = args()->Lookup<std::string>(kL2TPIPsecPskProperty, ""); |
| if (!psk.empty()) { |
| if (!base::CreateTemporaryFileInDir(manager()->run_path(), &psk_file_) || |
| chmod(psk_file_.value().c_str(), S_IRUSR | S_IWUSR | S_IRGRP) || |
| 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(std::vector<std::string>* options) { |
| std::vector<std::string> ca_certs; |
| if (args()->Contains<Strings>(kL2TPIPsecCaCertPemProperty)) { |
| ca_certs = args()->Get<Strings>(kL2TPIPsecCaCertPemProperty); |
| } |
| if (ca_certs.empty()) { |
| return false; |
| } |
| base::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(std::vector<std::string>* options, |
| Error* error) { |
| const auto user = |
| args()->Lookup<std::string>(kL2TPIPsecXauthUserProperty, ""); |
| const auto password = |
| args()->Lookup<std::string>(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; |
| } |
| const std::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 | S_IRGRP) || |
| 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 std::string& property, |
| const std::string& option, |
| std::vector<std::string>* options) { |
| const auto value = args()->Lookup<std::string>(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 std::string& property, |
| const std::string& true_option, |
| const std::string& false_option, |
| std::vector<std::string>* options) { |
| const auto value = args()->Lookup<std::string>(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(ExitStatusToFailure(status)); |
| // TODO(petkov): Figure if we need to restart the connection. |
| } |
| |
| void L2TPIPsecDriver::GetLogin(std::string* user, std::string* password) { |
| LOG(INFO) << "Login requested."; |
| const auto user_property = |
| args()->Lookup<std::string>(kL2TPIPsecUserProperty, ""); |
| if (user_property.empty()) { |
| LOG(ERROR) << "User not set."; |
| return; |
| } |
| const std::string use_login_password = |
| args()->Lookup<std::string>(kL2TPIPsecUseLoginPasswordProperty, ""); |
| if (use_login_password == "true") { |
| std::unique_ptr<password_provider::Password> login_password = |
| password_provider_->GetPassword(); |
| if (login_password == nullptr || login_password->size() == 0) { |
| LOG(ERROR) << "Unable to retrieve user password"; |
| return; |
| } |
| *user = user_property; |
| *password = std::string(login_password->GetRaw(), login_password->size()); |
| return; |
| } |
| const auto password_property = |
| args()->Lookup<std::string>(kL2TPIPsecPasswordProperty, ""); |
| if (password_property.empty()) { |
| LOG(ERROR) << "Password not set."; |
| return; |
| } |
| *user = user_property; |
| *password = password_property; |
| } |
| |
| void L2TPIPsecDriver::Notify(const std::string& reason, |
| const std::map<std::string, std::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); |
| // TODO(crbug.com/989361) We should move into a disconnecting state, stop |
| // this task if it exists, and wait for the task to fully shut down before |
| // completing the disconnection. This should wait for the VPNDriver code to |
| // be refactored, as the disconnect flow is a mess as it stands. |
| external_task_.reset(); |
| FailService(Service::kFailureUnknown); |
| return; |
| } |
| |
| DeleteTemporaryFiles(); |
| |
| std::string interface_name = PPPDevice::GetInterfaceName(dict); |
| ip_properties_ = PPPDevice::ParseIPConfiguration(dict); |
| metrics()->SendSparseToUMA(Metrics::kMetricPPPMTUValue, ip_properties_.mtu); |
| |
| // 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. |
| ip_properties_.blackhole_ipv6 = true; |
| |
| // 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. |
| ip_properties_.mtu = IPConfig::kMinIPv6MTU; |
| |
| ip_properties_.method = kTypeVPN; |
| |
| ReportConnectionMetrics(); |
| |
| // Make sure DeviceInfo is aware of this interface before invoking the |
| // connection success callback. |
| int interface_index = manager()->device_info()->GetIndex(interface_name); |
| if (interface_index != -1) { |
| OnLinkReady(interface_name, interface_index); |
| } else { |
| manager()->device_info()->AddVirtualInterfaceReadyCallback( |
| interface_name, base::BindOnce(&L2TPIPsecDriver::OnLinkReady, |
| weak_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void L2TPIPsecDriver::OnLinkReady(const std::string& link_name, |
| int interface_index) { |
| if (!event_handler_) { |
| LOG(ERROR) << "OnLinkReady() triggered in illegal service state"; |
| return; |
| } |
| event_handler_->OnDriverConnected(link_name, interface_index); |
| } |
| |
| bool L2TPIPsecDriver::IsPskRequired() const { |
| return const_args()->Lookup<std::string>(kL2TPIPsecPskProperty, "").empty() && |
| const_args() |
| ->Lookup<std::string>(kL2TPIPsecClientCertIdProperty, "") |
| .empty(); |
| } |
| |
| KeyValueStore L2TPIPsecDriver::GetProvider(Error* error) { |
| SLOG(this, 2) << __func__; |
| KeyValueStore props = VPNDriver::GetProvider(error); |
| props.Set<bool>( |
| kPassphraseRequiredProperty, |
| args()->Lookup<std::string>(kL2TPIPsecPasswordProperty, "").empty()); |
| props.Set<bool>(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()->Contains<Strings>(kL2TPIPsecCaCertPemProperty) && |
| !args()->Get<Strings>(kL2TPIPsecCaCertPemProperty).empty()) { |
| metrics()->SendEnumToUMA( |
| Metrics::kMetricVpnRemoteAuthenticationType, |
| Metrics::kVpnRemoteAuthenticationTypeL2tpIpsecCertificate, |
| Metrics::kMetricVpnRemoteAuthenticationTypeMax); |
| has_remote_authentication = true; |
| } |
| if (args()->Lookup<std::string>(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()->Lookup<std::string>(kL2TPIPsecClientCertIdProperty, "") != "") { |
| metrics()->SendEnumToUMA( |
| Metrics::kMetricVpnUserAuthenticationType, |
| Metrics::kVpnUserAuthenticationTypeL2tpIpsecCertificate, |
| Metrics::kMetricVpnUserAuthenticationTypeMax); |
| has_user_authentication = true; |
| } |
| if (args()->Lookup<std::string>(kL2TPIPsecPasswordProperty, "") != "" || |
| args()->Lookup<std::string>(kL2TPIPsecUseLoginPasswordProperty, "") == |
| "true") { |
| 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 |