| // Copyright (c) 2011 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. |
| |
| #include "vpn-manager/l2tp_manager.h" |
| |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <stdlib.h> // for setenv() |
| #include <sys/prctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <optional> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/strings/pattern.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <brillo/process/process.h> |
| |
| using ::base::FilePath; |
| using ::base::StringPrintf; |
| using ::brillo::ProcessImpl; |
| |
| namespace vpn_manager { |
| |
| namespace { |
| |
| const char kL2tpConnectionName[] = "managed"; |
| // Environment variable available to ppp plugin to know the resolved address |
| // of the L2TP server. |
| const char kLnsAddress[] = "LNS_ADDRESS"; |
| const char kPppInterfacePath[] = "/sys/class/net/ppp0"; |
| const char kPppLogPrefix[] = "pppd: "; |
| const char kPppAuthenticationFailurePattern[] = "*authentication failed*"; |
| const char kBpsParameter[] = "1000000"; |
| const char kRedialParameter[] = "yes"; |
| const char kRedialTimeoutParameter[] = "2"; |
| const char kMaxRedialsParameter[] = "30"; |
| // Path to pid file that contains pid for xl2tpd process. |
| const char kXl2tpdPidFilePath[] = "/run/l2tpipsec_vpn/xl2tpd.pid"; |
| |
| // xl2tpd (1.3.12 at the time of writing) uses fgets with a size 1024 buffer to |
| // get configuration lines. If a configuration line was longer than that and |
| // didn't contain the comment delimiter ';', it could be used to populate |
| // multiple configuration options. |
| constexpr size_t kXl2tpdMaxConfigurationLength = 1023; |
| |
| bool AddString(std::string* config, const char* key, const std::string& value) { |
| if (value.find('\n') != value.npos) { |
| std::string escaped_value = value; |
| // Escape newlines prior to logging. |
| size_t pos; |
| while ((pos = escaped_value.find('\n')) != escaped_value.npos) { |
| escaped_value.replace(pos, 1, "\\n"); |
| } |
| LOG(ERROR) << key << " value may not contain a newline: '" << escaped_value |
| << "'"; |
| return false; |
| } |
| |
| auto line = StringPrintf("%s = %s\n", key, value.c_str()); |
| if (line.size() > kXl2tpdMaxConfigurationLength) { |
| LOG(ERROR) << "Line may not exceed " << kXl2tpdMaxConfigurationLength |
| << " characters: '" << line << "'"; |
| return false; |
| } |
| |
| config->append(line); |
| return true; |
| } |
| |
| void AddBool(std::string* config, const char* key, bool value) { |
| config->append(StringPrintf("%s = %s\n", key, value ? "yes" : "no")); |
| } |
| |
| } // namespace |
| |
| L2tpManager::L2tpManager(bool default_route, |
| bool length_bit, |
| bool require_chap, |
| bool refuse_pap, |
| bool require_authentication, |
| const std::string& password, |
| bool ppp_lcp_echo, |
| int ppp_setup_timeout, |
| const std::string& pppd_plugin, |
| bool use_peer_dns, |
| const std::string& user, |
| bool system_config, |
| const base::FilePath& temp_path) |
| : ServiceManager("l2tp", temp_path), |
| default_route_(default_route), |
| length_bit_(length_bit), |
| require_chap_(require_chap), |
| refuse_pap_(refuse_pap), |
| require_authentication_(require_authentication), |
| password_(password), |
| ppp_lcp_echo_(ppp_lcp_echo), |
| ppp_setup_timeout_(ppp_setup_timeout), |
| pppd_plugin_(pppd_plugin), |
| use_peer_dns_(use_peer_dns), |
| user_(user), |
| system_config_(system_config), |
| was_initiated_(false), |
| output_fd_(-1), |
| ppp_output_fd_(-1), |
| ppp_interface_path_(kPppInterfacePath), |
| l2tpd_(new ProcessImpl) {} |
| |
| int L2tpManager::GetPppSetupTimeoutForTesting() { |
| return ppp_setup_timeout_; |
| } |
| |
| void L2tpManager::SetDefaultRouteForTesting(bool default_route) { |
| default_route_ = default_route; |
| } |
| |
| void L2tpManager::SetPasswordForTesting(const std::string& password) { |
| password_ = password; |
| } |
| |
| void L2tpManager::SetPppdPluginForTesting(const std::string& pppd_plugin) { |
| pppd_plugin_ = pppd_plugin; |
| } |
| |
| void L2tpManager::SetPppLcpEchoForTesting(bool ppp_lcp_echo) { |
| ppp_lcp_echo_ = ppp_lcp_echo; |
| } |
| |
| void L2tpManager::SetUsePeerDnsForTesting(bool use_peer_dns) { |
| use_peer_dns_ = use_peer_dns; |
| } |
| |
| void L2tpManager::SetUserForTesting(const std::string& user) { |
| user_ = user; |
| } |
| |
| void L2tpManager::SetSystemConfigForTesting(bool system_config) { |
| system_config_ = system_config; |
| } |
| |
| bool L2tpManager::Initialize(const sockaddr_storage& remote_address) { |
| if (!ConvertSockAddrToIPString(remote_address, &remote_address_text_)) { |
| LOG(ERROR) << "Unable to convert sockaddr to name for remote host"; |
| RegisterError(kServiceErrorInternal); |
| return false; |
| } |
| remote_address_ = remote_address; |
| |
| if (user_.empty()) { |
| LOG(ERROR) << "l2tp layer requires user name"; |
| RegisterError(kServiceErrorInvalidArgument); |
| return false; |
| } |
| if (!pppd_plugin_.empty() && !base::PathExists(FilePath(pppd_plugin_))) { |
| LOG(WARNING) << "pppd_plugin (" << pppd_plugin_ << ") does not exist"; |
| } |
| if (!password_.empty()) { |
| LOG(WARNING) << "Passing a password on the command-line is insecure"; |
| } |
| return true; |
| } |
| |
| bool L2tpManager::CreatePppLogFifo() { |
| ppp_output_path_ = temp_path().Append("pppd.log"); |
| const char* fifo_path = ppp_output_path_.value().c_str(); |
| if (HANDLE_EINTR(mkfifo(fifo_path, S_IRUSR | S_IWUSR)) == 0) { |
| ppp_output_fd_ = HANDLE_EINTR(open(fifo_path, O_RDONLY | O_NONBLOCK)); |
| if (ppp_output_fd_ != -1) |
| return true; |
| } |
| return false; |
| } |
| |
| std::optional<std::string> L2tpManager::FormatL2tpdConfiguration( |
| const std::string& ppp_config_path) { |
| std::string l2tpd_config; |
| bool success = true; |
| l2tpd_config.append(StringPrintf("[lac %s]\n", kL2tpConnectionName)); |
| success &= AddString(&l2tpd_config, "lns", remote_address_text_); |
| AddBool(&l2tpd_config, "require chap", require_chap_); |
| AddBool(&l2tpd_config, "refuse pap", refuse_pap_); |
| AddBool(&l2tpd_config, "require authentication", require_authentication_); |
| success &= AddString(&l2tpd_config, "name", user_); |
| if (VLOG_IS_ON(4)) { |
| AddBool(&l2tpd_config, "ppp debug", true); |
| } |
| success &= AddString(&l2tpd_config, "pppoptfile", ppp_config_path); |
| AddBool(&l2tpd_config, "length bit", length_bit_); |
| success &= AddString(&l2tpd_config, "bps", kBpsParameter); |
| success &= AddString(&l2tpd_config, "redial", kRedialParameter); |
| success &= |
| AddString(&l2tpd_config, "redial timeout", kRedialTimeoutParameter); |
| success &= AddString(&l2tpd_config, "max redials", kMaxRedialsParameter); |
| |
| if (!success) { |
| return std::nullopt; |
| } |
| return l2tpd_config; |
| } |
| |
| std::string L2tpManager::FormatPppdConfiguration() { |
| std::string pppd_config( |
| "ipcp-accept-local\n" |
| "ipcp-accept-remote\n" |
| "refuse-eap\n" |
| "noccp\n" |
| "noauth\n" |
| "crtscts\n" |
| "mtu 1410\n" |
| "mru 1410\n" |
| "lock\n" |
| "connect-delay 5000\n"); |
| pppd_config.append( |
| StringPrintf("%sdefaultroute\n", default_route_ ? "" : "no")); |
| if (ppp_lcp_echo_) { |
| pppd_config.append( |
| "lcp-echo-failure 4\n" |
| "lcp-echo-interval 30\n"); |
| } |
| if (ppp_output_fd_ != -1) { |
| pppd_config.append( |
| StringPrintf("logfile %s\n", ppp_output_path_.value().c_str())); |
| } |
| if (use_peer_dns_) { |
| pppd_config.append("usepeerdns\n"); |
| } |
| if (!system_config_) { |
| // nosystemconfig is only supported by the chromiumos patched |
| // version of pppd. |
| pppd_config.append("nosystemconfig\n"); |
| } |
| if (!pppd_plugin_.empty()) { |
| DLOG(INFO) << "Using pppd plugin " << pppd_plugin_; |
| pppd_config.append(StringPrintf("plugin %s\n", pppd_plugin_.c_str())); |
| } |
| if (VLOG_IS_ON(2)) { |
| pppd_config.append("debug\n"); |
| } |
| return pppd_config; |
| } |
| |
| bool L2tpManager::Initiate() { |
| std::string control_string; |
| control_string = StringPrintf("c %s", kL2tpConnectionName); |
| if (pppd_plugin_.empty()) { |
| control_string.append( |
| StringPrintf(" %s %s\n", user_.c_str(), password_.c_str())); |
| } else { |
| // otherwise the plugin must specify username and password. |
| control_string.append("\n"); |
| } |
| if (!base::WriteFile(l2tpd_control_path_, control_string.c_str(), |
| control_string.size())) { |
| return false; |
| } |
| was_initiated_ = true; |
| return true; |
| } |
| |
| bool L2tpManager::Terminate() { |
| std::string control_string = StringPrintf("d %s\n", kL2tpConnectionName); |
| if (!base::WriteFile(l2tpd_control_path_, control_string.c_str(), |
| control_string.size())) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool L2tpManager::Start() { |
| FilePath pppd_config_path = temp_path().Append("pppd.conf"); |
| auto l2tpd_config = FormatL2tpdConfiguration(pppd_config_path.value()); |
| if (!l2tpd_config) { |
| LOG(ERROR) << "Failed to write xl2tpd configuration"; |
| RegisterError(kServiceErrorInvalidArgument); |
| return false; |
| } |
| FilePath l2tpd_config_path = temp_path().Append("l2tpd.conf"); |
| if (!base::WriteFile(l2tpd_config_path, l2tpd_config.value().c_str(), |
| l2tpd_config.value().size())) { |
| LOG(ERROR) << "Unable to write l2tpd config to " |
| << l2tpd_config_path.value(); |
| RegisterError(kServiceErrorInternal); |
| return false; |
| } |
| |
| if (!CreatePppLogFifo()) { |
| PLOG(ERROR) << "Unable to create ppp log fifo"; |
| RegisterError(kServiceErrorInternal); |
| return false; |
| } |
| std::string pppd_config = FormatPppdConfiguration(); |
| if (!base::WriteFile(pppd_config_path, pppd_config.c_str(), |
| pppd_config.size())) { |
| LOG(ERROR) << "Unable to write pppd config to " << pppd_config_path.value(); |
| RegisterError(kServiceErrorInternal); |
| return false; |
| } |
| l2tpd_control_path_ = temp_path().Append("l2tpd.control"); |
| base::DeleteFile(l2tpd_control_path_); |
| |
| if (!pppd_plugin_.empty()) { |
| // Pass the resolved LNS address to the plugin. |
| setenv(kLnsAddress, remote_address_text_.c_str(), 1); |
| } |
| |
| // crbug/1046396 - Kill existing xl2tpd process first. |
| base::FilePath pidFilePath(kXl2tpdPidFilePath); |
| if (base::PathExists(pidFilePath)) { |
| std::string pid_str; |
| int pid = 0; |
| base::ReadFileToString(pidFilePath, &pid_str); |
| base::StringToInt(pid_str, &pid); |
| if (pid != 0) { |
| LOG(INFO) << "Killing existing xl2tpd process " << pid; |
| l2tpd_->Reset(pid); |
| l2tpd_->Kill(SIGKILL, 0); |
| } else { |
| LOG(ERROR) << "Unable to parse pid file " << kXl2tpdPidFilePath; |
| } |
| } |
| |
| l2tpd_->Reset(0); |
| l2tpd_->AddArg(L2TPD); |
| l2tpd_->AddStringOption("-c", l2tpd_config_path.value()); |
| l2tpd_->AddStringOption("-C", l2tpd_control_path_.value()); |
| l2tpd_->AddArg("-D"); |
| l2tpd_->AddStringOption("-p", kXl2tpdPidFilePath); |
| l2tpd_->RedirectUsingPipe(STDERR_FILENO, false); |
| l2tpd_->SetPreExecCallback(base::BindOnce([]() { |
| // The xl2tpd process will get a SIGTERM when the parent (this) process |
| // dies. |
| prctl(PR_SET_PDEATHSIG, SIGTERM); |
| return true; |
| })); |
| l2tpd_->Start(); |
| output_fd_ = l2tpd_->GetPipe(STDERR_FILENO); |
| start_ticks_ = base::TimeTicks::Now(); |
| return true; |
| } |
| |
| int L2tpManager::Poll() { |
| if (is_running()) |
| return -1; |
| if (start_ticks_.is_null()) |
| return -1; |
| if (!was_initiated_ && base::PathExists(l2tpd_control_path_)) { |
| if (!Initiate()) { |
| LOG(ERROR) << "Unable to initiate connection"; |
| RegisterError(kServiceErrorL2tpConnectionFailed); |
| Terminate(); |
| OnStopped(false); |
| return -1; |
| } |
| // With the connection initated, check if it's up in 1s. |
| return 1000; |
| } |
| if (was_initiated_ && base::PathExists(FilePath(ppp_interface_path_))) { |
| LOG(INFO) << "L2TP connection now up"; |
| OnStarted(); |
| return -1; |
| } |
| // Check for the ppp setup timeout. This includes the time |
| // to start pppd, it to set up its control file, l2tp connection |
| // setup, ppp connection setup. Authentication happens after |
| // the ppp device is created. |
| if (base::TimeTicks::Now() - start_ticks_ > |
| base::Seconds(ppp_setup_timeout_)) { |
| RegisterError(kServiceErrorPppConnectionFailed); |
| LOG(ERROR) << "PPP setup timed out"; |
| // Cleanly terminate if the control file exists. |
| if (was_initiated_) |
| Terminate(); |
| OnStopped(false); |
| // Poll in 1 second in order to check if clean shutdown worked. |
| } |
| return 1000; |
| } |
| |
| void L2tpManager::ProcessOutput() { |
| WriteFdToSyslog(output_fd_, "", &partial_output_line_); |
| } |
| |
| void L2tpManager::ProcessPppOutput() { |
| WriteFdToSyslog(ppp_output_fd_, kPppLogPrefix, &partial_ppp_output_line_); |
| } |
| |
| bool L2tpManager::IsChild(pid_t pid) { |
| return pid == l2tpd_->pid(); |
| } |
| |
| void L2tpManager::Stop() { |
| if (l2tpd_->pid()) { |
| LOG(INFO) << "Shutting down L2TP"; |
| Terminate(); |
| } |
| OnStopped(false); |
| } |
| |
| void L2tpManager::OnSyslogOutput(const std::string& prefix, |
| const std::string& line) { |
| if (prefix == kPppLogPrefix && |
| base::MatchPattern(line, kPppAuthenticationFailurePattern)) { |
| LOG(ERROR) << "PPP authentication failed"; |
| RegisterError(kServiceErrorPppAuthenticationFailed); |
| Stop(); |
| } |
| } |
| |
| } // namespace vpn_manager |