471 lines
15 KiB
C++
471 lines
15 KiB
C++
//
|
|
// 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.
|
|
//
|
|
|
|
#include "shill/vpn/openvpn_management_server.h"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include <base/bind.h>
|
|
#include <base/strings/string_number_conversions.h>
|
|
#include <base/strings/string_split.h>
|
|
#include <base/strings/string_util.h>
|
|
#include <base/strings/stringprintf.h>
|
|
#include <brillo/data_encoding.h>
|
|
#if defined(__ANDROID__)
|
|
#include <dbus/service_constants.h>
|
|
#else
|
|
#include <chromeos/dbus/service_constants.h>
|
|
#endif // __ANDROID__
|
|
|
|
#include "shill/error.h"
|
|
#include "shill/event_dispatcher.h"
|
|
#include "shill/logging.h"
|
|
#include "shill/net/sockets.h"
|
|
#include "shill/vpn/openvpn_driver.h"
|
|
|
|
using base::Bind;
|
|
using base::IntToString;
|
|
using base::SplitString;
|
|
using base::StringPrintf;
|
|
using base::Unretained;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
namespace shill {
|
|
|
|
namespace Logging {
|
|
static auto kModuleLogScope = ScopeLogger::kVPN;
|
|
static string ObjectID(OpenVPNManagementServer* o) {
|
|
return o->GetServiceRpcIdentifier();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
const char kPasswordTagAuth[] = "Auth";
|
|
} // namespace
|
|
|
|
const char OpenVPNManagementServer::kStateReconnecting[] = "RECONNECTING";
|
|
const char OpenVPNManagementServer::kStateResolve[] = "RESOLVE";
|
|
|
|
OpenVPNManagementServer::OpenVPNManagementServer(OpenVPNDriver* driver)
|
|
: driver_(driver),
|
|
sockets_(nullptr),
|
|
socket_(-1),
|
|
dispatcher_(nullptr),
|
|
connected_socket_(-1),
|
|
hold_waiting_(false),
|
|
hold_release_(false) {}
|
|
|
|
OpenVPNManagementServer::~OpenVPNManagementServer() {
|
|
OpenVPNManagementServer::Stop();
|
|
}
|
|
|
|
bool OpenVPNManagementServer::Start(EventDispatcher* dispatcher,
|
|
Sockets* sockets,
|
|
vector<vector<string>>* options) {
|
|
SLOG(this, 2) << __func__;
|
|
if (IsStarted()) {
|
|
return true;
|
|
}
|
|
|
|
int socket = sockets->Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (socket < 0) {
|
|
PLOG(ERROR) << "Unable to create management server socket.";
|
|
return false;
|
|
}
|
|
|
|
struct sockaddr_in addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
if (sockets->Bind(
|
|
socket, reinterpret_cast<struct sockaddr*>(&addr), addrlen) < 0 ||
|
|
sockets->Listen(socket, 1) < 0 ||
|
|
sockets->GetSockName(
|
|
socket, reinterpret_cast<struct sockaddr*>(&addr), &addrlen) < 0) {
|
|
PLOG(ERROR) << "Socket setup failed.";
|
|
sockets->Close(socket);
|
|
return false;
|
|
}
|
|
|
|
SLOG(this, 2) << "Listening socket: " << socket;
|
|
sockets_ = sockets;
|
|
socket_ = socket;
|
|
ready_handler_.reset(
|
|
dispatcher->CreateReadyHandler(
|
|
socket, IOHandler::kModeInput,
|
|
Bind(&OpenVPNManagementServer::OnReady, Unretained(this))));
|
|
dispatcher_ = dispatcher;
|
|
|
|
// Append openvpn management API options.
|
|
driver_->AppendOption("management", inet_ntoa(addr.sin_addr),
|
|
IntToString(ntohs(addr.sin_port)), options);
|
|
driver_->AppendOption("management-client", options);
|
|
driver_->AppendOption("management-hold", options);
|
|
hold_release_ = false;
|
|
hold_waiting_ = false;
|
|
|
|
driver_->AppendOption("management-query-passwords", options);
|
|
if (driver_->AppendValueOption(kOpenVPNStaticChallengeProperty,
|
|
"static-challenge",
|
|
options)) {
|
|
options->back().push_back("1"); // Force echo.
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void OpenVPNManagementServer::Stop() {
|
|
SLOG(this, 2) << __func__;
|
|
if (!IsStarted()) {
|
|
return;
|
|
}
|
|
state_.clear();
|
|
input_handler_.reset();
|
|
if (connected_socket_ >= 0) {
|
|
sockets_->Close(connected_socket_);
|
|
connected_socket_ = -1;
|
|
}
|
|
dispatcher_ = nullptr;
|
|
ready_handler_.reset();
|
|
if (socket_ >= 0) {
|
|
sockets_->Close(socket_);
|
|
socket_ = -1;
|
|
}
|
|
sockets_ = nullptr;
|
|
}
|
|
|
|
void OpenVPNManagementServer::ReleaseHold() {
|
|
SLOG(this, 2) << __func__;
|
|
hold_release_ = true;
|
|
if (!hold_waiting_) {
|
|
return;
|
|
}
|
|
LOG(INFO) << "Releasing hold.";
|
|
hold_waiting_ = false;
|
|
SendHoldRelease();
|
|
}
|
|
|
|
void OpenVPNManagementServer::Hold() {
|
|
SLOG(this, 2) << __func__;
|
|
hold_release_ = false;
|
|
}
|
|
|
|
void OpenVPNManagementServer::Restart() {
|
|
LOG(INFO) << "Restart.";
|
|
SendSignal("SIGUSR1");
|
|
}
|
|
|
|
std::string OpenVPNManagementServer::GetServiceRpcIdentifier() {
|
|
return driver_->GetServiceRpcIdentifier();
|
|
}
|
|
|
|
void OpenVPNManagementServer::OnReady(int fd) {
|
|
SLOG(this, 2) << __func__ << "(" << fd << ")";
|
|
connected_socket_ = sockets_->Accept(fd, nullptr, nullptr);
|
|
if (connected_socket_ < 0) {
|
|
PLOG(ERROR) << "Connected socket accept failed.";
|
|
return;
|
|
}
|
|
ready_handler_.reset();
|
|
input_handler_.reset(dispatcher_->CreateInputHandler(
|
|
connected_socket_,
|
|
Bind(&OpenVPNManagementServer::OnInput, Unretained(this)),
|
|
Bind(&OpenVPNManagementServer::OnInputError, Unretained(this))));
|
|
SendState("on");
|
|
}
|
|
|
|
void OpenVPNManagementServer::OnInput(InputData* data) {
|
|
SLOG(this, 2) << __func__ << "(" << data->len << ")";
|
|
vector<string> messages = SplitString(
|
|
string(reinterpret_cast<char*>(data->buf), data->len), "\n",
|
|
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
|
for (vector<string>::const_iterator it = messages.begin();
|
|
it != messages.end() && IsStarted(); ++it) {
|
|
ProcessMessage(*it);
|
|
}
|
|
}
|
|
|
|
void OpenVPNManagementServer::OnInputError(const std::string& error_msg) {
|
|
LOG(ERROR) << error_msg;
|
|
driver_->FailService(Service::kFailureInternal, Service::kErrorDetailsNone);
|
|
}
|
|
|
|
void OpenVPNManagementServer::ProcessMessage(const string& message) {
|
|
SLOG(this, 2) << __func__ << "(" << message << ")";
|
|
if (message.empty()) {
|
|
return;
|
|
}
|
|
if (!ProcessInfoMessage(message) &&
|
|
!ProcessNeedPasswordMessage(message) &&
|
|
!ProcessFailedPasswordMessage(message) &&
|
|
!ProcessAuthTokenMessage(message) &&
|
|
!ProcessStateMessage(message) &&
|
|
!ProcessHoldMessage(message) &&
|
|
!ProcessSuccessMessage(message)) {
|
|
LOG(WARNING) << "Message ignored: " << message;
|
|
}
|
|
}
|
|
|
|
bool OpenVPNManagementServer::ProcessInfoMessage(const string& message) {
|
|
if (!base::StartsWith(message, ">INFO:", base::CompareCase::SENSITIVE)) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << message;
|
|
return true;
|
|
}
|
|
|
|
bool OpenVPNManagementServer::ProcessNeedPasswordMessage(
|
|
const string& message) {
|
|
if (!base::StartsWith(message, ">PASSWORD:Need ",
|
|
base::CompareCase::SENSITIVE)) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << "Processing need-password message.";
|
|
string tag = ParsePasswordTag(message);
|
|
if (tag == kPasswordTagAuth) {
|
|
if (message.find("SC:") != string::npos) {
|
|
PerformStaticChallenge(tag);
|
|
} else {
|
|
PerformAuthentication(tag);
|
|
}
|
|
} else if (base::StartsWith(tag, "User-Specific TPM Token",
|
|
base::CompareCase::SENSITIVE)) {
|
|
SupplyTPMToken(tag);
|
|
} else {
|
|
NOTIMPLEMENTED() << ": Unsupported need-password message: " << message;
|
|
driver_->FailService(Service::kFailureInternal, Service::kErrorDetailsNone);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
string OpenVPNManagementServer::ParseSubstring(const string& message,
|
|
const string& start,
|
|
const string& end) {
|
|
SLOG(VPN, nullptr, 2) << __func__ << "(" << message
|
|
<< ", " << start << ", " << end << ")";
|
|
DCHECK(!start.empty() && !end.empty());
|
|
size_t start_pos = message.find(start);
|
|
if (start_pos == string::npos) {
|
|
return string();
|
|
}
|
|
size_t end_pos = message.find(end, start_pos + start.size());
|
|
if (end_pos == string::npos) {
|
|
return string();
|
|
}
|
|
return message.substr(start_pos + start.size(),
|
|
end_pos - start_pos - start.size());
|
|
}
|
|
|
|
// static
|
|
string OpenVPNManagementServer::ParsePasswordTag(const string& message) {
|
|
return ParseSubstring(message, "'", "'");
|
|
}
|
|
|
|
// static
|
|
string OpenVPNManagementServer::ParsePasswordFailedReason(
|
|
const string& message) {
|
|
return ParseSubstring(message, "['", "']");
|
|
}
|
|
|
|
void OpenVPNManagementServer::PerformStaticChallenge(const string& tag) {
|
|
LOG(INFO) << "Perform static challenge: " << tag;
|
|
string user = driver_->args()->LookupString(kOpenVPNUserProperty, "");
|
|
string password = driver_->args()->LookupString(kOpenVPNPasswordProperty, "");
|
|
string otp = driver_->args()->LookupString(kOpenVPNOTPProperty, "");
|
|
string token = driver_->args()->LookupString(kOpenVPNTokenProperty, "");
|
|
if (user.empty() || (token.empty() && (password.empty() || otp.empty()))) {
|
|
NOTIMPLEMENTED() << ": Missing credentials:"
|
|
<< (user.empty() ? " no-user" : "")
|
|
<< (token.empty() ? " no-token" : "")
|
|
<< (password.empty() ? " no-password" : "")
|
|
<< (otp.empty() ? " no-otp" : "");
|
|
driver_->FailService(Service::kFailureInternal, Service::kErrorDetailsNone);
|
|
return;
|
|
}
|
|
|
|
string password_encoded;
|
|
if (!token.empty()) {
|
|
password_encoded = token;
|
|
// Don't reuse token.
|
|
driver_->args()->RemoveString(kOpenVPNTokenProperty);
|
|
} else {
|
|
string b64_password(brillo::data_encoding::Base64Encode(password));
|
|
string b64_otp(brillo::data_encoding::Base64Encode(otp));
|
|
password_encoded = StringPrintf("SCRV1:%s:%s",
|
|
b64_password.c_str(),
|
|
b64_otp.c_str());
|
|
// Don't reuse OTP.
|
|
driver_->args()->RemoveString(kOpenVPNOTPProperty);
|
|
}
|
|
SendUsername(tag, user);
|
|
SendPassword(tag, password_encoded);
|
|
}
|
|
|
|
void OpenVPNManagementServer::PerformAuthentication(const string& tag) {
|
|
LOG(INFO) << "Perform authentication: " << tag;
|
|
string user = driver_->args()->LookupString(kOpenVPNUserProperty, "");
|
|
string password = driver_->args()->LookupString(kOpenVPNPasswordProperty, "");
|
|
if (user.empty() || password.empty()) {
|
|
NOTIMPLEMENTED() << ": Missing credentials:"
|
|
<< (user.empty() ? " no-user" : "")
|
|
<< (password.empty() ? " no-password" : "");
|
|
driver_->FailService(Service::kFailureInternal, Service::kErrorDetailsNone);
|
|
return;
|
|
}
|
|
SendUsername(tag, user);
|
|
SendPassword(tag, password);
|
|
}
|
|
|
|
void OpenVPNManagementServer::SupplyTPMToken(const string& tag) {
|
|
SLOG(this, 2) << __func__ << "(" << tag << ")";
|
|
string pin = driver_->args()->LookupString(kOpenVPNPinProperty, "");
|
|
if (pin.empty()) {
|
|
NOTIMPLEMENTED() << ": Missing PIN.";
|
|
driver_->FailService(Service::kFailureInternal, Service::kErrorDetailsNone);
|
|
return;
|
|
}
|
|
SendPassword(tag, pin);
|
|
}
|
|
|
|
bool OpenVPNManagementServer::ProcessFailedPasswordMessage(
|
|
const string& message) {
|
|
if (!base::StartsWith(message, ">PASSWORD:Verification Failed:",
|
|
base::CompareCase::SENSITIVE)) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << message;
|
|
string reason;
|
|
if (ParsePasswordTag(message) == kPasswordTagAuth) {
|
|
reason = ParsePasswordFailedReason(message);
|
|
}
|
|
driver_->FailService(Service::kFailureConnect, reason);
|
|
return true;
|
|
}
|
|
|
|
bool OpenVPNManagementServer::ProcessAuthTokenMessage(const string& message) {
|
|
if (!base::StartsWith(message, ">PASSWORD:Auth-Token:",
|
|
base::CompareCase::SENSITIVE)) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << "Auth-Token message ignored.";
|
|
return true;
|
|
}
|
|
|
|
// >STATE:* message support. State messages are of the form:
|
|
// >STATE:<date>,<state>,<detail>,<local-ip>,<remote-ip>
|
|
// where:
|
|
// <date> is the current time (since epoch) in seconds
|
|
// <state> is one of:
|
|
// INITIAL, CONNECTING, WAIT, AUTH, GET_CONFIG, ASSIGN_IP, ADD_ROUTES,
|
|
// CONNECTED, RECONNECTING, EXITING, RESOLVE, TCP_CONNECT
|
|
// <detail> is a free-form string giving details about the state change
|
|
// <local-ip> is a dotted-quad for the local IPv4 address (when available)
|
|
// <remote-ip> is a dotted-quad for the remote IPv4 address (when available)
|
|
bool OpenVPNManagementServer::ProcessStateMessage(const string& message) {
|
|
if (!base::StartsWith(message, ">STATE:", base::CompareCase::SENSITIVE)) {
|
|
return false;
|
|
}
|
|
vector<string> details = SplitString(message, ",", base::TRIM_WHITESPACE,
|
|
base::SPLIT_WANT_ALL);
|
|
if (details.size() > 1) {
|
|
state_ = details[1];
|
|
LOG(INFO) << "OpenVPN state: " << state_;
|
|
if (state_ == kStateReconnecting) {
|
|
OpenVPNDriver::ReconnectReason reason =
|
|
OpenVPNDriver::kReconnectReasonUnknown;
|
|
if (details.size() > 2 && details[2] == "tls-error") {
|
|
reason = OpenVPNDriver::kReconnectReasonTLSError;
|
|
}
|
|
driver_->OnReconnecting(reason);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OpenVPNManagementServer::ProcessHoldMessage(const string& message) {
|
|
if (!base::StartsWith(message, ">HOLD:Waiting for hold release",
|
|
base::CompareCase::SENSITIVE)) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << "Client waiting for hold release.";
|
|
hold_waiting_ = true;
|
|
if (hold_release_) {
|
|
ReleaseHold();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool OpenVPNManagementServer::ProcessSuccessMessage(const string& message) {
|
|
if (!base::StartsWith(message, "SUCCESS: ", base::CompareCase::SENSITIVE)) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << message;
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
string OpenVPNManagementServer::EscapeToQuote(const string& str) {
|
|
string escaped;
|
|
for (string::const_iterator it = str.begin(); it != str.end(); ++it) {
|
|
if (*it == '\\' || *it == '"') {
|
|
escaped += '\\';
|
|
}
|
|
escaped += *it;
|
|
}
|
|
return escaped;
|
|
}
|
|
|
|
void OpenVPNManagementServer::Send(const string& data) {
|
|
SLOG(this, 2) << __func__;
|
|
ssize_t len = sockets_->Send(connected_socket_, data.data(), data.size(), 0);
|
|
PLOG_IF(ERROR, len < 0 || static_cast<size_t>(len) != data.size())
|
|
<< "Send failed.";
|
|
}
|
|
|
|
void OpenVPNManagementServer::SendState(const string& state) {
|
|
SLOG(this, 2) << __func__ << "(" << state << ")";
|
|
Send(StringPrintf("state %s\n", state.c_str()));
|
|
}
|
|
|
|
void OpenVPNManagementServer::SendUsername(const string& tag,
|
|
const string& username) {
|
|
SLOG(this, 2) << __func__;
|
|
Send(StringPrintf("username \"%s\" %s\n", tag.c_str(), username.c_str()));
|
|
}
|
|
|
|
void OpenVPNManagementServer::SendPassword(const string& tag,
|
|
const string& password) {
|
|
SLOG(this, 2) << __func__;
|
|
Send(StringPrintf("password \"%s\" \"%s\"\n",
|
|
tag.c_str(),
|
|
EscapeToQuote(password).c_str()));
|
|
}
|
|
|
|
void OpenVPNManagementServer::SendSignal(const string& signal) {
|
|
SLOG(this, 2) << __func__ << "(" << signal << ")";
|
|
Send(StringPrintf("signal %s\n", signal.c_str()));
|
|
}
|
|
|
|
void OpenVPNManagementServer::SendHoldRelease() {
|
|
SLOG(this, 2) << __func__;
|
|
Send("hold release\n");
|
|
}
|
|
|
|
} // namespace shill
|