blob: 32ac8a234c7a7b0efbc602d6ce6ca5cd591e77fa [file] [log] [blame]
// Copyright (c) 2012 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 "shill/openvpn_management_server.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <base/bind.h>
#include <base/string_number_conversions.h>
#include <base/string_split.h>
#include <base/string_util.h>
#include <base/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include "shill/event_dispatcher.h"
#include "shill/glib.h"
#include "shill/logging.h"
#include "shill/openvpn_driver.h"
#include "shill/sockets.h"
using base::Bind;
using base::IntToString;
using base::SplitString;
using base::StringPrintf;
using std::string;
using std::vector;
namespace shill {
OpenVPNManagementServer::OpenVPNManagementServer(OpenVPNDriver *driver,
GLib *glib)
: driver_(driver),
glib_(glib),
weak_ptr_factory_(this),
ready_callback_(Bind(&OpenVPNManagementServer::OnReady,
weak_ptr_factory_.GetWeakPtr())),
input_callback_(Bind(&OpenVPNManagementServer::OnInput,
weak_ptr_factory_.GetWeakPtr())),
sockets_(NULL),
socket_(-1),
dispatcher_(NULL),
connected_socket_(-1),
hold_waiting_(false),
hold_release_(false) {}
OpenVPNManagementServer::~OpenVPNManagementServer() {
OpenVPNManagementServer::Stop();
}
bool OpenVPNManagementServer::Start(EventDispatcher *dispatcher,
Sockets *sockets,
vector<string> *options) {
SLOG(VPN, 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(VPN, 2) << "Listening socket: " << socket;
sockets_ = sockets;
socket_ = socket;
ready_handler_.reset(
dispatcher->CreateReadyHandler(
socket, IOHandler::kModeInput, ready_callback_));
dispatcher_ = dispatcher;
// Append openvpn management API options.
options->push_back("--management");
options->push_back(inet_ntoa(addr.sin_addr));
options->push_back(IntToString(ntohs(addr.sin_port)));
options->push_back("--management-client");
options->push_back("--management-hold");
hold_release_ = false;
hold_waiting_ = false;
options->push_back("--management-query-passwords");
if (driver_->AppendValueOption(flimflam::kOpenVPNStaticChallengeProperty,
"--static-challenge",
options)) {
options->push_back("1"); // Force echo.
}
return true;
}
void OpenVPNManagementServer::Stop() {
SLOG(VPN, 2) << __func__;
if (!IsStarted()) {
return;
}
input_handler_.reset();
if (connected_socket_ >= 0) {
sockets_->Close(connected_socket_);
connected_socket_ = -1;
}
dispatcher_ = NULL;
ready_handler_.reset();
if (socket_ >= 0) {
sockets_->Close(socket_);
socket_ = -1;
}
sockets_ = NULL;
}
void OpenVPNManagementServer::ReleaseHold() {
SLOG(VPN, 2) << __func__;
hold_release_ = true;
if (!hold_waiting_) {
return;
}
LOG(INFO) << "Releasing hold.";
hold_waiting_ = false;
SendHoldRelease();
}
void OpenVPNManagementServer::Hold() {
SLOG(VPN, 2) << __func__;
hold_release_ = false;
}
void OpenVPNManagementServer::OnReady(int fd) {
SLOG(VPN, 2) << __func__ << "(" << fd << ")";
connected_socket_ = sockets_->Accept(fd, NULL, NULL);
if (connected_socket_ < 0) {
PLOG(ERROR) << "Connected socket accept failed.";
return;
}
ready_handler_.reset();
input_handler_.reset(
dispatcher_->CreateInputHandler(connected_socket_, input_callback_));
SendState("on");
}
void OpenVPNManagementServer::OnInput(InputData *data) {
SLOG(VPN, 2) << __func__ << "(" << data->len << ")";
vector<string> messages;
SplitString(
string(reinterpret_cast<char *>(data->buf), data->len), '\n', &messages);
for (vector<string>::const_iterator it = messages.begin();
it != messages.end() && IsStarted(); ++it) {
ProcessMessage(*it);
}
}
void OpenVPNManagementServer::ProcessMessage(const string &message) {
SLOG(VPN, 2) << __func__ << "(" << message << ")";
if (message.empty()) {
return;
}
if (!ProcessInfoMessage(message) &&
!ProcessNeedPasswordMessage(message) &&
!ProcessFailedPasswordMessage(message) &&
!ProcessStateMessage(message) &&
!ProcessHoldMessage(message)) {
LOG(WARNING) << "OpenVPN management message ignored: " << message;
}
}
bool OpenVPNManagementServer::ProcessInfoMessage(const string &message) {
if (!StartsWithASCII(message, ">INFO:", true)) {
return false;
}
LOG(INFO) << "Processing info message.";
return true;
}
bool OpenVPNManagementServer::ProcessNeedPasswordMessage(
const string &message) {
if (!StartsWithASCII(message, ">PASSWORD:Need ", true)) {
return false;
}
LOG(INFO) << "Processing need-password message.";
string tag = ParseNeedPasswordTag(message);
if (tag == "Auth") {
if (message.find("SC:") != string::npos) {
PerformStaticChallenge(tag);
} else {
PerformAuthentication(tag);
}
} else if (StartsWithASCII(tag, "User-Specific TPM Token", true)) {
SupplyTPMToken(tag);
} else {
NOTIMPLEMENTED() << ": Unsupported need-password message: " << message;
driver_->Cleanup(Service::kStateFailure);
}
return true;
}
// static
string OpenVPNManagementServer::ParseNeedPasswordTag(const string &message) {
SLOG(VPN, 2) << __func__ << "(" << message << ")";
size_t start = message.find('\'');
if (start == string::npos) {
return string();
}
size_t end = message.find('\'', start + 1);
if (end == string::npos) {
return string();
}
return message.substr(start + 1, end - start - 1);
}
void OpenVPNManagementServer::PerformStaticChallenge(const string &tag) {
LOG(INFO) << "Perform static challenge: " << tag;
string user =
driver_->args()->LookupString(flimflam::kOpenVPNUserProperty, "");
string password =
driver_->args()->LookupString(flimflam::kOpenVPNPasswordProperty, "");
string otp =
driver_->args()->LookupString(flimflam::kOpenVPNOTPProperty, "");
if (user.empty() || password.empty() || otp.empty()) {
NOTIMPLEMENTED() << ": Missing credentials:"
<< (user.empty() ? " no-user" : "")
<< (password.empty() ? " no-password" : "")
<< (otp.empty() ? " no-otp" : "");
driver_->Cleanup(Service::kStateFailure);
return;
}
gchar *b64_password =
glib_->Base64Encode(reinterpret_cast<const guchar *>(password.data()),
password.size());
gchar *b64_otp =
glib_->Base64Encode(reinterpret_cast<const guchar *>(otp.data()),
otp.size());
if (!b64_password || !b64_otp) {
LOG(ERROR) << "Unable to base64-encode credentials.";
return;
}
SendUsername(tag, user);
SendPassword(tag, StringPrintf("SCRV1:%s:%s", b64_password, b64_otp));
glib_->Free(b64_otp);
glib_->Free(b64_password);
// Don't reuse OTP.
driver_->args()->RemoveString(flimflam::kOpenVPNOTPProperty);
}
void OpenVPNManagementServer::PerformAuthentication(const string &tag) {
LOG(INFO) << "Perform authentication: " << tag;
string user =
driver_->args()->LookupString(flimflam::kOpenVPNUserProperty, "");
string password =
driver_->args()->LookupString(flimflam::kOpenVPNPasswordProperty, "");
if (user.empty() || password.empty()) {
NOTIMPLEMENTED() << ": Missing credentials:"
<< (user.empty() ? " no-user" : "")
<< (password.empty() ? " no-password" : "");
driver_->Cleanup(Service::kStateFailure);
return;
}
SendUsername(tag, user);
SendPassword(tag, password);
}
void OpenVPNManagementServer::SupplyTPMToken(const string &tag) {
SLOG(VPN, 2) << __func__ << "(" << tag << ")";
string pin = driver_->args()->LookupString(flimflam::kOpenVPNPinProperty, "");
if (pin.empty()) {
NOTIMPLEMENTED() << ": Missing PIN.";
driver_->Cleanup(Service::kStateFailure);
return;
}
SendPassword(tag, pin);
}
bool OpenVPNManagementServer::ProcessFailedPasswordMessage(
const string &message) {
if (!StartsWithASCII(message, ">PASSWORD:Verification Failed:", true)) {
return false;
}
NOTIMPLEMENTED();
driver_->Cleanup(Service::kStateFailure);
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 (!StartsWithASCII(message, ">STATE:", true)) {
return false;
}
LOG(INFO) << "Processing state message.";
vector<string> details;
SplitString(message, ',', &details);
if (details.size() > 1) {
if (details[1] == "RECONNECTING") {
driver_->OnReconnecting();
}
// The rest of the states are currently ignored.
}
return true;
}
bool OpenVPNManagementServer::ProcessHoldMessage(const string &message) {
if (!StartsWithASCII(message, ">HOLD:Waiting for hold release", true)) {
return false;
}
LOG(INFO) << "Processing hold message.";
hold_waiting_ = true;
if (hold_release_) {
ReleaseHold();
}
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(VPN, 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(VPN, 2) << __func__ << "(" << state << ")";
Send(StringPrintf("state %s\n", state.c_str()));
}
void OpenVPNManagementServer::SendUsername(const string &tag,
const string &username) {
SLOG(VPN, 2) << __func__;
Send(StringPrintf("username \"%s\" %s\n", tag.c_str(), username.c_str()));
}
void OpenVPNManagementServer::SendPassword(const string &tag,
const string &password) {
SLOG(VPN, 2) << __func__;
Send(StringPrintf("password \"%s\" \"%s\"\n",
tag.c_str(),
EscapeToQuote(password).c_str()));
}
void OpenVPNManagementServer::SendHoldRelease() {
SLOG(VPN, 2) << __func__;
Send("hold release\n");
}
} // namespace shill