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

#ifdef OS_WIN
#include <sddl.h>
#include <shlobj.h>
#include <string.h>
#include <windows.h>
#else
#include <unistd.h>
#endif  // OS_WIN

#include <cstring>
#include <string>

#include "base/const.h"
#include "base/file_stream.h"
#include "base/file_util.h"
#include "base/logging.h"
#if defined(__APPLE__) || defined(OS_IOS)
#include "base/mac_util.h"
#endif  // __APPLE__ || OS_IOS
#include "base/port.h"
#include "base/process.h"
#include "base/run_level.h"
#include "base/system_util.h"
#include "base/util.h"
#include "client/client.h"
#include "client/client_interface.h"
#include "ipc/ipc.h"
#include "ipc/named_event.h"

#ifdef OS_WIN
#include "base/win_sandbox.h"
#endif  // OS_WIN

namespace mozc {
namespace client {
namespace {
const char kServerName[] = "session";

// Wait at most kServerWaitTimeout msec until server gets ready
const uint32 kServerWaitTimeout = 20000;  // 20 sec

// for every 1000m sec, check server
const uint32 kRetryIntervalForServer = 1000;

// Try 20 times to check mozc_server is running
const uint32 kTrial = 20;

#ifdef DEBUG
// Load special flags for server.
// This should be enabled on debug build
const string LoadServerFlags() {
  const char kServerFlagsFile[] = "mozc_server_flags.txt";
  const string filename = FileUtil::JoinPath(
      SystemUtil::GetUserProfileDirectory(), kServerFlagsFile);
  string flags;
  InputFileStream ifs(filename.c_str());
  if (ifs) {
    getline(ifs, flags);
  }
  VLOG(1) << "New server flag: " << flags;
  return flags;
}
#endif  // DEBUG
}  // namespace

// initialize default path
ServerLauncher::ServerLauncher()
    : server_program_(SystemUtil::GetServerPath()),
      restricted_(false),
      suppress_error_dialog_(false) {}

ServerLauncher::~ServerLauncher() {}

bool ServerLauncher::StartServer(ClientInterface *client) {
  if (server_program().empty()) {
    LOG(ERROR) << "Server path is empty";
    return false;
  }

  // ping first
  if (client->PingServer()) {
    return true;
  }

  std::string arg;

#ifdef OS_WIN
  // When mozc is not used as a default IME and some applications (like notepad)
  // are registered in "Start up", mozc_server may not be launched successfully.
  // This is because the Explorer launches start-up processes inside a group job
  // and the process inside a job cannot make our sandboxed child processes.
  // The group job is unregistered after 60 secs (default).
  //
  // Here we relax the sandbox restriction if process is in a job.
  // In order to keep security, the mozc_server is launched
  // with restricted mode.

  const bool process_in_job = RunLevel::IsProcessInJob();
  if (process_in_job || restricted_) {
    LOG(WARNING) << "Parent process is in job. start with restricted mode";
    arg += "--restricted";
  }
#endif

#ifdef DEBUG
  // In oreder to test the Session treatment (timeout/size constratins),
  // Server flags can be configurable on DEBUG build
  if (!arg.empty()) {
    arg += " ";
  }
  arg += LoadServerFlags();
#endif  // DEBUG

  NamedEventListener listener(kServerName);
  const bool listener_is_available = listener.IsAvailable();

  size_t pid = 0;
#ifdef OS_WIN
  mozc::WinSandbox::SecurityInfo info;
  // You cannot use WinSandbox::USER_INTERACTIVE here because restricted token
  // seems to prevent WinHTTP from using SSL. b/5502343
  info.primary_level = WinSandbox::USER_NON_ADMIN;
  info.impersonation_level = WinSandbox::USER_RESTRICTED_SAME_ACCESS;
  info.integrity_level = WinSandbox::INTEGRITY_LEVEL_LOW;
  // If the current process is in a job, you cannot use
  // CREATE_BREAKAWAY_FROM_JOB. b/1571395
  info.use_locked_down_job = !process_in_job;
  info.allow_ui_operation = false;
  info.in_system_dir = true;  // use system dir not to lock current directory
  info.creation_flags = CREATE_DEFAULT_ERROR_MODE | CREATE_NO_WINDOW;

  DWORD tmp_pid = 0;
  const bool result = mozc::WinSandbox::SpawnSandboxedProcess(
      server_program(), arg, info, &tmp_pid);
  pid = static_cast<size_t>(tmp_pid);

  if (!result) {
    LOG(ERROR) << "Can't start process: " << ::GetLastError();
    return false;
  }
#elif defined(__APPLE__)
  // Use launchd API instead of spawning process.  It doesn't use
  // server_program() at all.
  const bool result = MacUtil::StartLaunchdService(
      "Converter", reinterpret_cast<pid_t *>(&pid));
  if (!result) {
    LOG(ERROR) << "Can't start process";
    return false;
  }
#else
  const bool result = mozc::Process::SpawnProcess(server_program(), arg, &pid);
  if (!result) {
    LOG(ERROR) << "Can't start process: " << strerror(result);
    return false;
  }
#endif  // OS_WIN

  // maybe another process will launch mozc_server at the same time.
  if (client->PingServer()) {
    VLOG(1) << "Another process has launched the server";
    return true;
  }

  // Common part:
  // Wait until mozc_server becomes ready to process requests
  if (listener_is_available) {
    const int ret = listener.WaitEventOrProcess(kServerWaitTimeout, pid);
    switch (ret) {
      case NamedEventListener::TIMEOUT:
        LOG(WARNING) << "seems that " << kProductNameInEnglish << " is not "
                     << "ready within " << kServerWaitTimeout << " msec";
        break;
      case NamedEventListener::EVENT_SIGNALED:
        VLOG(1) << kProductNameInEnglish << " is launched successfully "
                << "within " << kServerWaitTimeout << " msec";
        break;
      case NamedEventListener::PROCESS_SIGNALED:
        LOG(ERROR) << "Mozc server is terminated";
        // Mozc may be terminated because another client launches mozc_server
        if (client->PingServer()) {
          return true;
        }
        return false;
    }
  } else {
    // maybe another process is trying to launch mozc_server.
    LOG(ERROR) << "cannot make NamedEventListener ";
    Util::Sleep(kRetryIntervalForServer);
  }

  // Try to connect mozc_server just in case.
  for (int trial = 0; trial < kTrial; ++trial) {
    if (client->PingServer()) {
      return true;
    }
    Util::Sleep(kRetryIntervalForServer);
  }

  LOG(ERROR) << kProductNameInEnglish << " cannot be launched";

  return false;
}

bool ServerLauncher::ForceTerminateServer(const std::string &name) {
  return IPCClient::TerminateServer(name);
}

bool ServerLauncher::WaitServer(uint32 pid) {
  const int kTimeout = 10000;
  return Process::WaitProcess(static_cast<size_t>(pid), kTimeout);
}

void ServerLauncher::OnFatal(ServerLauncherInterface::ServerErrorType type) {
  LOG(ERROR) << "OnFatal is called: " << static_cast<int>(type);

  std::string error_type;
  switch (type) {
    case ServerLauncherInterface::SERVER_TIMEOUT:
      error_type = "server_timeout";
      break;
    case ServerLauncherInterface::SERVER_BROKEN_MESSAGE:
      error_type = "server_broken_message";
      break;
    case ServerLauncherInterface::SERVER_VERSION_MISMATCH:
      error_type = "server_version_mismatch";
      break;
    case ServerLauncherInterface::SERVER_SHUTDOWN:
      error_type = "server_shutdown";
      break;
    case ServerLauncherInterface::SERVER_FATAL:
      error_type = "server_fatal";
      break;
    default:
      LOG(ERROR) << "Unknown error: " << type;
      return;
  }

  if (!suppress_error_dialog_) {
    Process::LaunchErrorMessageDialog(error_type);
  }
}
}  // namespace client
}  // namespace mozc
