| // 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 |