Status Webserver::Start()

in src/kudu/server/webserver.cc [304:475]


Status Webserver::Start() {
  vector<string> options;
  if (static_pages_available()) {
    options.emplace_back("document_root");
    options.push_back(opts_.doc_root);
    options.emplace_back("enable_directory_listing");
    options.emplace_back("no");
  }

  if (IsSecure()) {
    // Initialize OpenSSL, and prevent Squeasel from also performing global
    // OpenSSL initialization.
    security::InitializeOpenSSL();
    options.emplace_back("ssl_global_init");
    options.emplace_back("false");

    options.emplace_back("ssl_certificate");
    options.push_back(opts_.certificate_file);

    if (!opts_.private_key_file.empty()) {
      options.emplace_back("ssl_private_key");
      options.push_back(opts_.private_key_file);

      string key_password;
      if (!opts_.private_key_password_cmd.empty()) {
        RETURN_NOT_OK(security::GetPasswordFromShellCommand(opts_.private_key_password_cmd,
                                                            &key_password));
      }
      options.emplace_back("ssl_private_key_password");
      options.push_back(key_password); // May be empty if not configured.
    }

    options.emplace_back("ssl_ciphers");
    options.emplace_back(opts_.tls_ciphers);
    options.emplace_back("ssl_min_version");
    options.emplace_back(opts_.tls_min_protocol);
  }

  if (!opts_.authentication_domain.empty()) {
    options.emplace_back("authentication_domain");
    options.push_back(opts_.authentication_domain);
  }

  if (!opts_.password_file.empty()) {
#if OPENSSL_VERSION_NUMBER < 0x30000000L
  int fips_mode = FIPS_mode();
#else
  int fips_mode = EVP_default_properties_is_fips_enabled(NULL);
#endif
    if (fips_mode) {
      return Status::IllegalState(
          "Webserver cannot be started with Digest authentication in FIPS approved mode");
    }
    // Mongoose doesn't log anything if it can't stat the password file (but
    // will if it can't open it, which it tries to do during a request).
    if (!Env::Default()->FileExists(opts_.password_file)) {
      ostringstream ss;
      ss << "Webserver: Password file does not exist: " << opts_.password_file;
      return Status::InvalidArgument(ss.str());
    }
    options.emplace_back("global_auth_file");
    options.push_back(opts_.password_file);
  }

  if (opts_.require_spnego) {
    // If the spnego_keytab_file flag is empty, GSSAPI will find the keytab path from
    // KRB5_KTNAME environment variable which is set by InitKerberosForServer().
    // Setting spnego_keytab_file flag will make GSSAPI to use spnego dedicated keytab
    // instead of KRB5_KTNAME.
    const char* kt_file = FLAGS_spnego_keytab_file.empty() ?
      getenv("KRB5_KTNAME") :
      FLAGS_spnego_keytab_file.c_str();
    if (!kt_file || !Env::Default()->FileExists(kt_file)) {
      return Status::InvalidArgument("Unable to configure web server for SPNEGO authentication: "
                                     "must configure a keytab file for the server");
    }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    // NOTE: this call is wrapped into 'ignored' pragma to suppress compilation
    //       warnings on macOS with Xcode where many gssapi_krb5 functions are
    //       deprecated in favor of GSS.framework.
    krb5_gss_register_acceptor_identity(kt_file);
#pragma GCC diagnostic pop
  }

  options.emplace_back("listening_ports");
  string listening_str;
  RETURN_NOT_OK(BuildListenSpec(&listening_str));
  options.push_back(listening_str);

  // initialize the advertised addresses
  if (!opts_.webserver_advertised_addresses.empty()) {
    RETURN_NOT_OK(ParseAddressList(opts_.webserver_advertised_addresses,
                                   opts_.port,
                                   &webserver_advertised_addresses_));
    for (const Sockaddr& addr : webserver_advertised_addresses_) {
      if (addr.port() == 0) {
        return Status::InvalidArgument("advertising an ephemeral webserver port is not supported",
                                       addr.ToString());
      }
    }
  }

  // Num threads
  options.emplace_back("num_threads");
  options.push_back(std::to_string(opts_.num_worker_threads));

  options.emplace_back("enable_keep_alive");
  options.emplace_back("yes");

  // mongoose ignores SIGCHLD and we need it to run kinit. This means that since
  // mongoose does not reap its own children CGI programs must be avoided.
  // Save the signal handler so we can restore it after mongoose sets it to be ignored.
  sighandler_t sig_chld = signal(SIGCHLD, SIG_DFL);

  sq_callbacks callbacks;
  memset(&callbacks, 0, sizeof(callbacks));
  callbacks.begin_request = &Webserver::BeginRequestCallbackStatic;
  callbacks.log_message = &Webserver::LogMessageCallbackStatic;

  // Options must be a NULL-terminated list of C strings.
  vector<const char*> c_options;
  for (const auto& opt : options) {
    c_options.push_back(opt.c_str());
  }
  c_options.push_back(nullptr);

  // To work around not being able to pass member functions as C callbacks, we store a
  // pointer to this server in the per-server state, and register a static method as the
  // default callback. That method unpacks the pointer to this and calls the real
  // callback.
  context_ = sq_start(&callbacks, reinterpret_cast<void*>(this), &c_options[0]);

  // Restore the child signal handler so wait() works properly.
  signal(SIGCHLD, sig_chld);

  if (context_ == nullptr) {
    Sockaddr addr = Sockaddr::Wildcard();
    addr.set_port(opts_.port);
    TryRunLsof(addr);
    string err_msg = Substitute("Webserver: could not start on address $0", http_address_);
    if (!kWebserverLastErrMsg.empty()) {
      err_msg = Substitute("$0: $1", err_msg, kWebserverLastErrMsg);
    }
    return Status::RuntimeError(err_msg);
  }

  RegisterPathHandler("/", "Home",
                      [this](const WebRequest& req, WebResponse* resp) {
                        this->RootHandler(req, resp);
                      },
                      StyleMode::STYLED, /*is_on_nav_bar=*/true);

  vector<Sockaddr> addrs;
  RETURN_NOT_OK(GetBoundAddresses(&addrs));
  string bound_addresses_str;
  for (const Sockaddr& addr : addrs) {
    if (!bound_addresses_str.empty()) {
      bound_addresses_str += ", ";
    }
    bound_addresses_str += Substitute("$0$1/",
                                      IsSecure() ? "https://" : "http://",
                                      addr.ToString());
  }

  LOG(INFO) << Substitute(
      "Webserver started at $0 using document root $1 and password file $2",
      bound_addresses_str,
      static_pages_available() ? opts_.doc_root : "<none>",
      opts_.password_file.empty() ? "<none>" : opts_.password_file);
  return Status::OK();
}