blob: 13455e6a70d7e6dd6d1706b171b84938bebd9438 [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 "ssh_plugin.h"
#include <stdio.h>
#include <string.h>
#include <resolv.h>
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var_array_buffer.h"
#include "file_system.h"
const char kMessageNameAttr[] = "name";
const char kMessageArgumentsAttr[] = "arguments";
// These are C++ the method names as JavaScript sees them.
const char kStartSessionMethodId[] = "startSession";
const char kOnOpenFileMethodId[] = "onOpenFile";
const char kOnOpenSocketMethodId[] = "onOpenSocket";
const char kOnReadMethodId[] = "onRead";
const char kOnWriteAcknowledgeMethodId[] = "onWriteAcknowledge";
const char kOnCloseMethodId[] = "onClose";
const char kOnReadReadyMethodId[] = "onReadReady";
const char kOnResizeMethodId[] = "onResize";
const char kOnExitAcknowledgeMethodId[] = "onExitAcknowledge";
// Known startSession attributes.
const char kUsernameAttr[] = "username";
const char kHostAttr[] = "host";
const char kPortAttr[] = "port";
const char kTerminalWidthAttr[] = "terminalWidth";
const char kTerminalHeightAttr[] = "terminalHeight";
const char kUseJsSocketAttr[] = "useJsSocket";
const char kEnvironmentAttr[] = "environment";
const char kArgumentsAttr[] = "arguments";
const char kWriteWindowAttr[] = "writeWindow";
const char kAuthAgentAppID[] = "authAgentAppID";
const char kSubsystemAttr[] = "subsystem";
// These are JavaScript method names as C++ code sees them.
const char kPrintLogMethodId[] = "printLog";
const char kExitMethodId[] = "exit";
const char kOpenFileMethodId[] = "openFile";
const char kOpenSocketMethodId[] = "openSocket";
const char kWriteMethodId[] = "write";
const char kReadMethodId[] = "read";
const char kCloseMethodId[] = "close";
const size_t kDefaultWriteWindow = 64 * 1024;
extern "C" int ssh_main(int ac, const char** av, const char *subsystem);
//------------------------------------------------------------------------------
SshPluginInstance* SshPluginInstance::instance_ = NULL;
SshPluginInstance::SshPluginInstance(PP_Instance instance)
: pp::Instance(instance),
core_(pp::Module::Get()->core()),
openssh_thread_(NULL),
factory_(this),
file_system_(this, this) {
instance_ = this;
}
SshPluginInstance::~SshPluginInstance() {
instance_ = NULL;
}
void SshPluginInstance::HandleMessage(const pp::Var& message_data) {
if (message_data.is_dictionary()) {
pp::VarDictionary message(message_data);
pp::Var function_var = message.Get(kMessageNameAttr);
pp::Var args_var = message.Get(kMessageArgumentsAttr);
if (function_var.is_string() && args_var.is_array()) {
const std::string function = function_var.AsString();
if (!function.empty()) {
const pp::VarArray args(args_var);
Invoke(function, args);
}
}
}
}
void SshPluginInstance::Invoke(const std::string& function,
const pp::VarArray& args) {
if (function == kStartSessionMethodId) {
StartSession(args);
} else if (function == kOnOpenFileMethodId ||
function == kOnOpenSocketMethodId) {
OnOpen(args);
} else if (function == kOnReadMethodId) {
OnRead(args);
} else if (function == kOnWriteAcknowledgeMethodId) {
OnWriteAcknowledge(args);
} else if (function == kOnCloseMethodId) {
OnClose(args);
} else if (function == kOnReadReadyMethodId) {
OnReadReady(args);
} else if (function == kOnResizeMethodId) {
OnResize(args);
} else if (function == kOnExitAcknowledgeMethodId) {
OnExitAcknowledge(args);
}
}
void SshPluginInstance::InvokeJS(const std::string& function,
const pp::VarArray& args) {
pp::VarDictionary dict;
dict.Set(kMessageNameAttr, pp::Var(function));
dict.Set(kMessageArgumentsAttr, args);
PostMessage(dict);
}
void SshPluginInstance::PrintLogImpl(int32_t result, const std::string& msg) {
pp::VarArray call_args;
call_args.SetLength(1);
call_args.Set(0, msg);
InvokeJS(kPrintLogMethodId, call_args);
}
void SshPluginInstance::PrintLog(const std::string& msg) {
core_->CallOnMainThread(0, factory_.NewCallback(
&SshPluginInstance::PrintLogImpl, msg));
}
void SshPluginInstance::SendExitCodeImpl(int32_t result, int error) {
pp::VarArray call_args;
call_args.SetLength(1);
call_args.Set(0, error);
InvokeJS(kExitMethodId, call_args);
}
void SshPluginInstance::SendExitCode(int error) {
core_->CallOnMainThread(0, factory_.NewCallback(
&SshPluginInstance::SendExitCodeImpl, error));
openssh_thread_ = NULL;
}
bool SshPluginInstance::OpenFile(int fd, const char* name, int mode,
InputInterface* stream) {
if (name) {
pp::VarArray call_args;
call_args.SetLength(3);
call_args.Set(0, fd);
call_args.Set(1, name);
call_args.Set(2, mode);
InvokeJS(kOpenFileMethodId, call_args);
}
assert(streams_.find(fd) == streams_.end());
streams_[fd] = stream;
return true;
}
bool SshPluginInstance::OpenSocket(int fd, const char* host, uint16_t port,
InputInterface* stream) {
pp::VarArray call_args;
call_args.SetLength(3);
call_args.Set(0, fd);
call_args.Set(1, host);
call_args.Set(2, port);
InvokeJS(kOpenSocketMethodId, call_args);
assert(streams_.find(fd) == streams_.end());
streams_[fd] = stream;
return true;
}
bool SshPluginInstance::Write(int fd, const char* data, size_t size) {
const size_t kMaxWriteSize = 32 * 1024;
pp::VarArray call_args;
size_t start = 0;
call_args.SetLength(2);
call_args.Set(0, fd);
while (start < size) {
size_t chunk_size = ((size - start) <= kMaxWriteSize) ? (size - start)
: kMaxWriteSize;
pp::VarArrayBuffer arr(chunk_size);
char* buf = static_cast<char*>(arr.Map());
memcpy(buf, data + start, chunk_size);
arr.Unmap();
call_args.Set(1, arr);
start += chunk_size;
InvokeJS(kWriteMethodId, call_args);
}
return true;
}
bool SshPluginInstance::Read(int fd, size_t size) {
pp::VarArray call_args;
call_args.SetLength(2);
call_args.Set(0, fd);
call_args.Set(1, (int32_t)size);
InvokeJS(kReadMethodId, call_args);
return true;
}
bool SshPluginInstance::Close(int fd) {
pp::VarArray call_args;
call_args.SetLength(1);
call_args.Set(0, fd);
InvokeJS(kCloseMethodId, call_args);
return true;
}
size_t SshPluginInstance::GetWriteWindow() {
if (session_args_.HasKey(kWriteWindowAttr)) {
const pp::Var arg = session_args_.Get(kWriteWindowAttr);
if (arg.is_number())
return arg.AsInt();
}
return kDefaultWriteWindow;
}
void SshPluginInstance::SessionThreadImpl() {
file_system_.WaitForStdFiles();
// Call renamed ssh main.
std::vector<const std::string> argv;
// argv[0]
argv.push_back("ssh");
if (session_args_.HasKey(kArgumentsAttr)) {
const pp::Var arg = session_args_.Get(kArgumentsAttr);
if (arg.is_array()) {
const pp::VarArray args(arg);
for (size_t i = 0; i < args.GetLength(); i++) {
if (args.Get(i).is_string())
argv.push_back(args.Get(i).AsString());
else
PrintLog("startSession: invalid argument\n");
}
}
}
std::string port;
if (session_args_.HasKey(kPortAttr)) {
char buf[64];
snprintf(buf, sizeof(buf), "-p%d", session_args_.Get(kPortAttr).AsInt());
port = buf;
argv.push_back(port.c_str());
}
std::string username_hostname;
if (session_args_.HasKey(kUsernameAttr) &&
session_args_.HasKey(kHostAttr)) {
username_hostname = session_args_.Get(kUsernameAttr).AsString() + "@" +
session_args_.Get(kHostAttr).AsString();
argv.push_back(username_hostname.c_str());
}
std::string subsystem;
const char *csubsystem = NULL;
if (session_args_.HasKey(kSubsystemAttr)) {
subsystem = session_args_.Get(kSubsystemAttr).AsString();
csubsystem = subsystem.c_str();
}
LOG("ssh main args:\n");
for (size_t i = 0; i < argv.size(); i++)
LOG(" argv[%d] = %s\n", i, argv[i].c_str());
std::vector<const char *> cargv;
for (auto it = argv.begin(); it != argv.end(); ++it)
cargv.push_back(it->c_str());
SendExitCode(ssh_main(cargv.size(), &cargv[0], csubsystem));
}
void* SshPluginInstance::SessionThread(void* arg) {
SshPluginInstance* instance = static_cast<SshPluginInstance*>(arg);
instance->SessionThreadImpl();
return NULL;
}
void SshPluginInstance::StartSession(const pp::VarArray& args) {
if (openssh_thread_) {
PrintLogImpl(0, "startSession: session already started!\n");
return;
}
if (args.GetLength() != 1) {
PrintLogImpl(0, "startSession: args must be one element only\n");
return;
}
const pp::Var session_arg = args.Get(0);
if (!session_arg.is_dictionary()) {
PrintLogImpl(0, "startSession: args[0] must be a dictionary of settings\n");
return;
}
session_args_ = pp::VarDictionary(session_arg);
if (session_args_.HasKey(kTerminalWidthAttr) &&
session_args_.Get(kTerminalWidthAttr).is_number() &&
session_args_.HasKey(kTerminalHeightAttr) &&
session_args_.Get(kTerminalHeightAttr).is_number()) {
file_system_.SetTerminalSize(
session_args_.Get(kTerminalWidthAttr).AsInt(),
session_args_.Get(kTerminalHeightAttr).AsInt());
}
if (session_args_.HasKey(kUseJsSocketAttr) &&
session_args_.Get(kUseJsSocketAttr).is_bool()) {
file_system_.UseJsSocket(session_args_.Get(kUseJsSocketAttr).AsBool());
}
if (session_args_.HasKey(kEnvironmentAttr) &&
session_args_.Get(kEnvironmentAttr).is_dictionary()) {
const pp::VarDictionary env(session_args_.Get(kEnvironmentAttr));
const pp::VarArray keys = env.GetKeys();
for (size_t i = 0; i < keys.GetLength(); ++i) {
const pp::Var key(keys.Get(i));
const pp::Var value(env.Get(key));
if (key.is_string() && value.is_string())
setenv(key.AsString().c_str(), value.AsString().c_str(), 1);
}
}
if (session_args_.HasKey(kAuthAgentAppID) &&
session_args_.Get(kAuthAgentAppID).is_string()) {
const pp::Var agent(session_args_.Get(kAuthAgentAppID));
setenv("SSH_AUTH_SOCK", agent.AsString().c_str(), 1);
}
if (pthread_create(&openssh_thread_, NULL,
&SshPluginInstance::SessionThread, this)) {
SendExitCodeImpl(0, -1);
}
}
void SshPluginInstance::OnOpen(const pp::VarArray& args) {
const pp::Var fd = args.Get(0);
const pp::Var success = args.Get(1);
const pp::Var is_atty = args.Get(2);
if (fd.is_number() && success.is_bool() && is_atty.is_bool()) {
InputStreams::iterator it = streams_.find(fd.AsInt());
if (it != streams_.end()) {
it->second->OnOpen(success.AsBool(), is_atty.AsBool());
if (!success.AsBool())
streams_.erase(it);
} else {
PrintLogImpl(0, "onOpen: for unknown file descriptor\n");
}
} else {
PrintLogImpl(0, "onOpen: invalid arguments\n");
}
}
void SshPluginInstance::OnRead(const pp::VarArray& args) {
const pp::Var fd = args.Get(0);
const pp::Var data = args.Get(1);
if (!fd.is_number()) {
PrintLogImpl(0, "onRead: bad fd argument (non-numeric)\n");
return;
}
InputStreams::iterator it = streams_.find(fd.AsInt());
if (it == streams_.end()) {
PrintLogImpl(0, "onRead: for unknown file descriptor\n");
return;
}
if (data.is_string()) {
const std::string& str = data.AsString();
std::vector<char> buf(str.size() * 3 / 4);
int res = b64_pton(str.c_str(), (unsigned char*)&buf[0], buf.size());
assert(res >= 0);
it->second->OnRead(&buf[0], res);
} else if (data.is_array()) {
const pp::VarArray arr(data);
std::vector<char> buf(arr.GetLength());
for (size_t i = 0; i < buf.size(); ++i)
buf[i] = arr.Get(i).AsInt();
it->second->OnRead(&buf[0], buf.size());
} else if (data.is_array_buffer()) {
pp::VarArrayBuffer arr(data);
const char* buf = static_cast<char*>(arr.Map());
it->second->OnRead(buf, arr.ByteLength());
arr.Unmap();
} else {
PrintLogImpl(0, "onRead: invalid data argument (not string or array)\n");
}
}
void SshPluginInstance::OnWriteAcknowledge(const pp::VarArray& args) {
const pp::Var fd = args.Get(0);
const pp::Var count = args.Get(1);
if (fd.is_number() && count.is_number()) {
InputStreams::iterator it = streams_.find(fd.AsInt());
if (it != streams_.end()) {
// JS tops out at 53-bits for integers, so we can use signed ints here
// even though the underlying OnWriteAcknowledge is using uint64_t.
int64_t cnt;
if (count.is_int()) {
cnt = count.AsInt();
} else {
cnt = static_cast<int64_t>(count.AsDouble());
}
if (cnt < 0) {
PrintLogImpl(0, "onWriteAcknowledge: count is negative\n");
} else if (cnt > 1ULL << 53) {
PrintLogImpl(0, "onWriteAcknowledge: count is too big\n");
} else {
it->second->OnWriteAcknowledge(cnt);
}
} else {
PrintLogImpl(0, "onWriteAcknowledge: for unknown file descriptor\n");
}
} else {
PrintLogImpl(0, "onWriteAcknowledge: invalid arguments\n");
}
}
void SshPluginInstance::OnClose(const pp::VarArray& args) {
const pp::Var fd = args.Get(0);
InputStreams::iterator it = streams_.find(fd.AsInt());
if (it != streams_.end()) {
it->second->OnClose();
streams_.erase(it);
} else {
PrintLogImpl(0, "onClose: for unknown file descriptor\n");
}
}
void SshPluginInstance::OnReadReady(const pp::VarArray& args) {
const pp::Var fd = args.Get(0);
const pp::Var result = args.Get(1);
if (fd.is_number() && result.is_bool()) {
InputStreams::iterator it = streams_.find(fd.AsInt());
if (it != streams_.end()) {
it->second->OnReadReady(result.AsBool());
} else {
PrintLogImpl(0, "onReadReady: for unknown file descriptor\n");
}
} else {
PrintLogImpl(0, "onReadReady: invalid arguments\n");
}
}
void SshPluginInstance::OnResize(const pp::VarArray& args) {
file_system_.SetTerminalSize(args.Get(0).AsInt(),
args.Get(1).AsInt());
}
void SshPluginInstance::OnExitAcknowledge(const pp::VarArray& args) {
file_system_.ExitCodeAcked();
}
//------------------------------------------------------------------------------
namespace pp {
class SshPluginModule : public pp::Module {
public:
SshPluginModule() : pp::Module() {}
virtual ~SshPluginModule() {}
virtual pp::Instance* CreateInstance(PP_Instance instance) {
return new SshPluginInstance(instance);
}
};
Module* CreateModule() {
return new SshPluginModule();
}
} // namespace pp