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();
}