// Copyright 2010-2020, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ipc/ipc_path_manager.h"
#include "absl/strings/str_format.h"
#if defined(OS_ANDROID) || defined(OS_NACL)
#error "This platform is not supported."
#endif // OS_ANDROID || OS_NACL
#include <errno.h>
#ifdef OS_WIN
// clang-format off
#include <windows.h>
#include <psapi.h> // GetModuleFileNameExW
// clang-format on
#else
// For stat system call
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef __APPLE__
#include <sys/sysctl.h>
#endif // __APPLE__
#endif // OS_WIN
#include <cstdlib>
#include <map>
#ifdef OS_WIN
#include <memory> // for std::unique_ptr
#endif
#include "base/const.h"
#include "base/file_stream.h"
#include "base/file_util.h"
#ifdef OS_WIN
#include "base/unverified_sha1.h"
#endif // OS_WIN
#include "base/logging.h"
#if defined(__APPLE__) || defined(OS_IOS)
#include "base/mac_util.h"
#endif // __APPLE__ || OS_IOS
#include "base/mutex.h"
#include "base/port.h"
#include "base/process_mutex.h"
#include "base/scoped_handle.h"
#include "base/singleton.h"
#include "base/system_util.h"
#include "base/util.h"
#include "base/version.h"
#ifdef OS_WIN
#include "base/win_util.h"
#endif // OS_WIN
#include "ipc/ipc.h"
#include "ipc/ipc.pb.h"
namespace mozc {
namespace {
// size of key
const size_t kKeySize = 32;
// Do not use ConfigFileStream, since client won't link
// to the embedded resource files
std::string GetIPCKeyFileName(const std::string &name) {
#ifdef OS_WIN
string basename;
#else
std::string basename = "."; // hidden file
#endif
basename += name + ".ipc";
return FileUtil::JoinPath(SystemUtil::GetUserProfileDirectory(), basename);
}
bool IsValidKey(const std::string &name) {
if (kKeySize != name.size()) {
LOG(ERROR) << "IPCKey is invalid length";
return false;
}
for (size_t i = 0; i < name.size(); ++i) {
if ((name[i] >= '0' && name[i] <= '9') ||
(name[i] >= 'a' && name[i] <= 'f')) {
// do nothing
} else {
LOG(ERROR) << "key name is invalid: " << name[i];
return false;
}
}
return true;
}
std::string CreateIPCKey() {
char buf[16] = {}; // key is 128 bit
char value[kKeySize + 1] = {};
#ifdef OS_WIN
const string sid = SystemUtil::GetUserSidAsString();
const string sha1 = internal::UnverifiedSHA1::MakeDigest(sid);
for (int i = 0; i < sizeof(buf) && i < sha1.size(); ++i) {
buf[i] = sha1.at(i);
}
#else
// get 128 bit key: Note that collision will happen.
Util::GetRandomSequence(buf, sizeof(buf));
#endif
// escape
for (size_t i = 0; i < sizeof(buf); ++i) {
const int hi = ((static_cast<int>(buf[i]) & 0xF0) >> 4);
const int lo = (static_cast<int>(buf[i]) & 0x0F);
value[2 * i] = static_cast<char>(hi >= 10 ? hi - 10 + 'a' : hi + '0');
value[2 * i + 1] = static_cast<char>(lo >= 10 ? lo - 10 + 'a' : lo + '0');
}
value[kKeySize] = '\0';
return std::string(value);
}
class IPCPathManagerMap {
public:
IPCPathManager *GetIPCPathManager(const std::string &name) {
scoped_lock l(&mutex_);
std::map<std::string, IPCPathManager *>::const_iterator it =
manager_map_.find(name);
if (it != manager_map_.end()) {
return it->second;
}
IPCPathManager *manager = new IPCPathManager(name);
manager_map_.insert(std::make_pair(name, manager));
return manager;
}
IPCPathManagerMap() {}
~IPCPathManagerMap() {
scoped_lock l(&mutex_);
for (std::map<std::string, IPCPathManager *>::iterator it =
manager_map_.begin();
it != manager_map_.end(); ++it) {
delete it->second;
}
manager_map_.clear();
}
private:
std::map<std::string, IPCPathManager *> manager_map_;
Mutex mutex_;
};
} // namespace
IPCPathManager::IPCPathManager(const std::string &name)
: mutex_(new Mutex),
ipc_path_info_(new ipc::IPCPathInfo),
name_(name),
server_pid_(0),
last_modified_(-1) {}
IPCPathManager::~IPCPathManager() {}
IPCPathManager *IPCPathManager::GetIPCPathManager(const std::string &name) {
IPCPathManagerMap *manager_map = Singleton<IPCPathManagerMap>::get();
DCHECK(manager_map != nullptr);
return manager_map->GetIPCPathManager(name);
}
bool IPCPathManager::CreateNewPathName() {
scoped_lock l(mutex_.get());
if (ipc_path_info_->key().empty()) {
ipc_path_info_->set_key(CreateIPCKey());
}
return true;
}
bool IPCPathManager::SavePathName() {
scoped_lock l(mutex_.get());
if (path_mutex_ != nullptr) {
return true;
}
path_mutex_.reset(new ProcessMutex("ipc"));
path_mutex_->set_lock_filename(GetIPCKeyFileName(name_));
// a little tricky as CreateNewPathName() tries mutex lock
CreateNewPathName();
// set the server version
ipc_path_info_->set_protocol_version(IPC_PROTOCOL_VERSION);
ipc_path_info_->set_product_version(Version::GetMozcVersion());
#ifdef OS_WIN
ipc_path_info_->set_process_id(static_cast<uint32>(::GetCurrentProcessId()));
ipc_path_info_->set_thread_id(static_cast<uint32>(::GetCurrentThreadId()));
#else
ipc_path_info_->set_process_id(static_cast<uint32>(getpid()));
ipc_path_info_->set_thread_id(0);
#endif
std::string buf;
if (!ipc_path_info_->SerializeToString(&buf)) {
LOG(ERROR) << "SerializeToString failed";
return false;
}
if (!path_mutex_->LockAndWrite(buf)) {
LOG(ERROR) << "ipc key file is already locked";
return false;
}
VLOG(1) << "ServerIPCKey: " << ipc_path_info_->key();
last_modified_ = GetIPCFileTimeStamp();
return true;
}
bool IPCPathManager::LoadPathName() {
// On Windows, ShouldReload() always returns false.
// On other platform, it returns true when timestamp of the file is different
// from that of previous one.
const bool should_load = (ShouldReload() || ipc_path_info_->key().empty());
if (!should_load) {
return true;
}
if (LoadPathNameInternal()) {
return true;
}
#if defined(OS_WIN)
// Fill the default values as fallback.
// Applications conerted by Desktop App Converter (DAC) does not read
// a file of ipc session name in the LocalLow directory.
// For a workaround, let applications to connect the named pipe directly.
// See: b/71338191.
CreateNewPathName();
DCHECK(!ipc_path_info_->key().empty());
ipc_path_info_->set_protocol_version(IPC_PROTOCOL_VERSION);
ipc_path_info_->set_product_version(Version::GetMozcVersion());
return true;
#else
LOG(ERROR) << "LoadPathName failed";
return false;
#endif
}
bool IPCPathManager::GetPathName(std::string *ipc_name) const {
if (ipc_name == nullptr) {
LOG(ERROR) << "ipc_name is nullptr";
return false;
}
if (ipc_path_info_->key().empty()) {
LOG(ERROR) << "ipc_path_info_ is empty";
return false;
}
#ifdef OS_WIN
*ipc_name = mozc::kIPCPrefix;
#elif defined(__APPLE__)
ipc_name->assign(MacUtil::GetLabelForSuffix(""));
#else // not OS_WIN nor __APPLE__
// GetUserIPCName("<name>") => "/tmp/.mozc.<key>.<name>"
const char kIPCPrefix[] = "/tmp/.mozc.";
*ipc_name = kIPCPrefix;
#endif // OS_WIN
#ifdef OS_LINUX
// On Linux, use abstract namespace which is independent of the file system.
(*ipc_name)[0] = '\0';
#endif
ipc_name->append(ipc_path_info_->key());
ipc_name->append(".");
ipc_name->append(name_);
return true;
}
uint32 IPCPathManager::GetServerProtocolVersion() const {
return ipc_path_info_->protocol_version();
}
const std::string &IPCPathManager::GetServerProductVersion() const {
return ipc_path_info_->product_version();
}
uint32 IPCPathManager::GetServerProcessId() const {
return ipc_path_info_->process_id();
}
void IPCPathManager::Clear() {
scoped_lock l(mutex_.get());
ipc_path_info_->Clear();
}
bool IPCPathManager::IsValidServer(uint32 pid, const std::string &server_path) {
scoped_lock l(mutex_.get());
if (pid == 0) {
// For backward compatibility.
return true;
}
if (server_path.empty()) {
// This means that we do not check the server path.
return true;
}
if (pid == static_cast<uint32>(-1)) {
VLOG(1) << "pid is -1. so assume that it is an invalid program";
return false;
}
// compare path name
if (pid == server_pid_) {
return (server_path == server_path_);
}
server_pid_ = 0;
server_path_.clear();
#ifdef OS_WIN
{
std::wstring expected_server_ntpath;
const std::map<string, std::wstring>::const_iterator it =
expected_server_ntpath_cache_.find(server_path);
if (it != expected_server_ntpath_cache_.end()) {
expected_server_ntpath = it->second;
} else {
std::wstring wide_server_path;
Util::UTF8ToWide(server_path, &wide_server_path);
if (WinUtil::GetNtPath(wide_server_path, &expected_server_ntpath)) {
// Caches the relationship from |server_path| to
// |expected_server_ntpath| in case |server_path| is renamed later.
// (This can happen during the updating).
expected_server_ntpath_cache_[server_path] = expected_server_ntpath;
}
}
if (expected_server_ntpath.empty()) {
return false;
}
std::wstring actual_server_ntpath;
if (!WinUtil::GetProcessInitialNtPath(pid, &actual_server_ntpath)) {
return false;
}
if (expected_server_ntpath != actual_server_ntpath) {
return false;
}
// Here we can safely assume that |server_path| (expected one) should be
// the same to |server_path_| (actual one).
server_path_ = server_path;
server_pid_ = pid;
}
#endif // OS_WIN
#ifdef __APPLE__
int name[] = {CTL_KERN, KERN_PROCARGS, static_cast<int>(pid)};
size_t data_len = 0;
if (sysctl(name, arraysize(name), nullptr, &data_len, nullptr, 0) < 0) {
LOG(ERROR) << "sysctl KERN_PROCARGS failed";
return false;
}
server_path_.resize(data_len);
if (sysctl(name, arraysize(name), &server_path_[0], &data_len, nullptr, 0) <
0) {
LOG(ERROR) << "sysctl KERN_PROCARGS failed";
return false;
}
server_pid_ = pid;
#endif // __APPLE__
#ifdef OS_LINUX
// load from /proc/<pid>/exe
char proc[128];
char filename[512];
absl::SNPrintF(proc, sizeof(proc) - 1, "/proc/%u/exe", pid);
const ssize_t size = readlink(proc, filename, sizeof(filename) - 1);
if (size == -1) {
LOG(ERROR) << "readlink failed: " << strerror(errno);
return false;
}
filename[size] = '\0';
server_path_ = filename;
server_pid_ = pid;
#endif // OS_LINUX
VLOG(1) << "server path: " << server_path << " " << server_path_;
if (server_path == server_path_) {
return true;
}
#ifdef OS_LINUX
if ((server_path + " (deleted)") == server_path_) {
LOG(WARNING) << server_path << " on disk is modified";
// If a user updates the server binary on disk during the server is running,
// "readlink /proc/<pid>/exe" returns a path with the " (deleted)" suffix.
// We allow the special case.
server_path_ = server_path;
return true;
}
#endif // OS_LINUX
return false;
}
bool IPCPathManager::ShouldReload() const {
#ifdef OS_WIN
// In windows, no reloading mechanism is necessary because IPC files
// are automatically removed.
return false;
#else
scoped_lock l(mutex_.get());
time_t last_modified = GetIPCFileTimeStamp();
if (last_modified == last_modified_) {
return false;
}
return true;
#endif // OS_WIN
}
time_t IPCPathManager::GetIPCFileTimeStamp() const {
#ifdef OS_WIN
// In windows, we don't need to get the exact file timestamp, so
// just returns -1 at this time.
return static_cast<time_t>(-1);
#else
const std::string filename = GetIPCKeyFileName(name_);
struct stat filestat;
if (::stat(filename.c_str(), &filestat) == -1) {
VLOG(2) << "stat(2) failed. Skipping reload";
return static_cast<time_t>(-1);
}
return filestat.st_mtime;
#endif // OS_WIN
}
bool IPCPathManager::LoadPathNameInternal() {
scoped_lock l(mutex_.get());
// try the new file name first.
const std::string filename = GetIPCKeyFileName(name_);
// Special code for Windows,
// we want to pass FILE_SHRED_DELETE flag for CreateFile.
#ifdef OS_WIN
std::wstring wfilename;
Util::UTF8ToWide(filename, &wfilename);
{
ScopedHandle handle(
::CreateFileW(wfilename.c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, 0, nullptr));
// ScopedHandle does not receive INVALID_HANDLE_VALUE and
// nullptr check is appropriate here.
if (nullptr == handle.get()) {
LOG(ERROR) << "cannot open: " << filename << " " << ::GetLastError();
return false;
}
const DWORD size = ::GetFileSize(handle.get(), nullptr);
if (-1 == static_cast<int>(size)) {
LOG(ERROR) << "GetFileSize failed: " << ::GetLastError();
return false;
}
const DWORD kMaxFileSize = 2096;
if (size == 0 || size >= kMaxFileSize) {
LOG(ERROR) << "Invalid file size: " << kMaxFileSize;
return false;
}
std::unique_ptr<char[]> buf(new char[size]);
DWORD read_size = 0;
if (!::ReadFile(handle.get(), buf.get(), size, &read_size, nullptr)) {
LOG(ERROR) << "ReadFile failed: " << ::GetLastError();
return false;
}
if (read_size != size) {
LOG(ERROR) << "read_size != size";
return false;
}
if (!ipc_path_info_->ParseFromArray(buf.get(), static_cast<int>(size))) {
LOG(ERROR) << "ParseFromStream failed";
return false;
}
}
#else
InputFileStream is(filename.c_str(), std::ios::binary | std::ios::in);
if (!is) {
LOG(ERROR) << "cannot open: " << filename;
return false;
}
if (!ipc_path_info_->ParseFromIstream(&is)) {
LOG(ERROR) << "ParseFromStream failed";
return false;
}
#endif
if (!IsValidKey(ipc_path_info_->key())) {
LOG(ERROR) << "IPCServer::key is invalid";
return false;
}
VLOG(1) << "ClientIPCKey: " << ipc_path_info_->key();
VLOG(1) << "ProtocolVersion: " << ipc_path_info_->protocol_version();
last_modified_ = GetIPCFileTimeStamp();
return true;
}
} // namespace mozc