* Copyright (C) 2013 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include <gio/gio.h>
#include <glib-unix.h>
#include <glib/gstdio.h>
#include <dirent.h>
#include <string.h>
#include <systemd/sd-daemon.h>
#include "cockpitws.h"
#include "cockpithandlers.h"
#include "cockpitbranding.h"
#include "common/cockpitconf.h"
#include "common/cockpitmemory.h"
#include "cockpitsystem.h"
static gint opt_port = 9090;
static gchar *opt_address = NULL;
static gboolean opt_no_tls = FALSE;
static gboolean opt_for_tls_proxy = FALSE;
static gboolean opt_local_ssh = FALSE;
static gchar *opt_local_session = NULL;
static gboolean opt_version = FALSE;
static GOptionEntry cmd_entries[] = {
{"port", 'p', 0, G_OPTION_ARG_INT, &opt_port, "Local port to bind to (9090 if unset)", NULL},
{"address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "Address to bind to (binds on all addresses if unset)", "ADDRESS"},
{"no-tls", 0, 0, G_OPTION_ARG_NONE, &opt_no_tls, "Disable http to https redirection", NULL},
{"for-tls-proxy", 0, 0, G_OPTION_ARG_NONE, &opt_for_tls_proxy,
"Act behind a https-terminating proxy: accept only https:// origins by default",
NULL},
{"local-ssh", 0, 0, G_OPTION_ARG_NONE, &opt_local_ssh, "Log in locally via SSH", NULL },
{"local-session", 0, 0, G_OPTION_ARG_STRING, &opt_local_session,
"Launch a bridge in the local session (path to cockpit-bridge or '-' for stdin/out); implies --no-tls",
"BRIDGE" },
{"version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Print version information", NULL },
{NULL}
};
static void
print_version (void)
{
g_print ("Version: %s\n", PACKAGE_VERSION);
g_print ("Protocol: 1\n");
g_print ("Authorization: basic\n");
}
static gchar **
setup_static_roots (GHashTable *os_release)
{
gchar **roots;
const gchar *os_variant_id;
const gchar *os_version_id;
const gchar *os_id;
const gchar *os_id_like;
if (os_release)
{
os_id = g_hash_table_lookup (os_release, "ID");
os_variant_id = g_hash_table_lookup (os_release, "VARIANT_ID");
os_version_id = g_hash_table_lookup (os_release, "VERSION_ID");
os_id_like = g_hash_table_lookup (os_release, "ID_LIKE");
}
else
{
os_id = NULL;
os_variant_id = NULL;
os_version_id = NULL;
os_id_like = NULL;
}
roots = cockpit_branding_calculate_static_roots (os_id, os_variant_id, os_version_id, os_id_like, TRUE);
return roots;
}
static void
on_local_ready (GObject *object,
GAsyncResult *result,
gpointer data)
{
cockpit_web_server_start (COCKPIT_WEB_SERVER (data));
g_object_unref (data);
}
int
main (int argc,
char *argv[])
{
gint ret = 1;
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GError) error = NULL;
g_auto(GStrv) roots = NULL;
g_autoptr(GMainLoop) loop = NULL;
g_autofree gchar *login_html = NULL;
g_autofree gchar *login_po_js = NULL;
g_autoptr(CockpitWebServer) server = NULL;
CockpitWebServerFlags server_flags = COCKPIT_WEB_SERVER_NONE;
CockpitHandlerData data;
signal (SIGPIPE, SIG_IGN);
cockpit_setenv_check ("GSETTINGS_BACKEND", "memory", TRUE);
cockpit_setenv_check ("GIO_USE_PROXY_RESOLVER", "dummy", TRUE);
cockpit_setenv_check ("GIO_USE_VFS", "local", TRUE);
cockpit_setenv_check ("KRB5CCNAME", "FILE:/dev/null", TRUE);
memset (&data, 0, sizeof (data));
context = g_option_context_new (NULL);
g_option_context_add_main_entries (context, cmd_entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error))
goto out;
if (opt_for_tls_proxy && opt_no_tls)
{
g_printerr ("--for-tls-proxy and --no-tls are mutually exclusive\n");
goto out;
}
if (opt_version)
{
print_version ();
ret = 0;
goto out;
}
if (opt_for_tls_proxy || cockpit_conf_bool ("WebService", "X-For-CockpitClient", FALSE))
opt_no_tls = TRUE;
g_log_writer_default_set_use_stderr (TRUE);
loop = g_main_loop_new (NULL, FALSE);
data.os_release = cockpit_system_load_os_release ();
data.auth = cockpit_auth_new (opt_local_ssh, opt_for_tls_proxy ? COCKPIT_AUTH_FOR_TLS_PROXY : COCKPIT_AUTH_NONE);
roots = setup_static_roots (data.os_release);
const gchar *static_dir = cockpit_conf_string ("WebService", "CustomLoginPage");
const gchar *root_dir = static_dir ? static_dir : DATADIR "/cockpit/static";
data.branding_roots = (const gchar **)roots;
login_html = g_strconcat (root_dir, "/login.html", NULL);
data.login_html = (const gchar *)login_html;
login_po_js = g_strconcat (root_dir, "/po.js", NULL);
data.login_po_js = (const gchar *)login_po_js;
if (opt_for_tls_proxy)
server_flags |= COCKPIT_WEB_SERVER_FOR_TLS_PROXY;
if (!opt_no_tls && !cockpit_conf_bool ("WebService", "AllowUnencrypted", FALSE))
server_flags |= COCKPIT_WEB_SERVER_REDIRECT_TLS;
server = cockpit_web_server_new (server_flags);
const gint n_listen_fds = sd_listen_fds (true);
if (n_listen_fds)
{
for (gint fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n_listen_fds; fd++)
if (!cockpit_web_server_add_fd_listener (server, fd, &error))
{
g_prefix_error (&error, "Unable to acquire LISTEN_FDS: ");
goto out;
}
g_signal_connect_swapped (data.auth, "idling", G_CALLBACK (g_main_loop_quit), loop);
}
else
{
if (!cockpit_web_server_add_inet_listener (server, opt_address, opt_port, &error))
{
g_prefix_error (&error, "Error starting web server: ");
goto out;
}
}
if (cockpit_conf_string ("WebService", "UrlRoot"))
{
g_object_set (server, "url-root",
cockpit_conf_string ("WebService", "UrlRoot"),
NULL);
}
cockpit_web_server_set_protocol_header (server, cockpit_conf_string ("WebService", "ProtocolHeader"));
cockpit_web_server_set_forwarded_for_header (server, cockpit_conf_string ("WebService", "ForwardedForHeader"));
g_signal_connect (server, "handle-stream",
G_CALLBACK (cockpit_handler_socket), &data);
g_signal_connect (server, "handle-stream",
G_CALLBACK (cockpit_handler_external), &data);
g_object_set (server, "ssl-exception-prefix", "/ping", NULL);
g_signal_connect (server, "handle-resource::/ping",
G_CALLBACK (cockpit_handler_ping), &data);
g_signal_connect (server, "handle-resource::/favicon.ico",
G_CALLBACK (cockpit_handler_root), &data);
g_signal_connect (server, "handle-resource::/apple-touch-icon.png",
G_CALLBACK (cockpit_handler_root), &data);
g_signal_connect (server, "handle-resource::/ca.cer",
G_CALLBACK (cockpit_handler_ca_cert), &data);
g_signal_connect (server, "handle-resource",
G_CALLBACK (cockpit_handler_default), &data);
if (opt_local_session)
{
g_autoptr(CockpitPipe) pipe = NULL;
struct passwd *pwd;
if (g_str_equal (opt_local_session, "-"))
{
pipe = cockpit_pipe_new (opt_local_session, 0, 1);
}
else
{
g_auto(GStrv) args = NULL;
if (!g_shell_parse_argv (opt_local_session, NULL, &args, &error))
{
g_prefix_error (&error, "--local-session: ");
goto out;
}
pipe = cockpit_pipe_spawn ((const gchar **) args, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
}
pwd = getpwuid (geteuid ());
if (!pwd)
{
g_printerr ("Failed to resolve current user id %u\n", geteuid ());
goto out;
}
cockpit_auth_local_async (data.auth, pwd->pw_name, pipe, on_local_ready, g_object_ref (server));
}
else
{
cockpit_web_server_start (server);
}
g_main_loop_run (loop);
ret = 0;
out:
if (error)
g_printerr ("cockpit-ws: %s\n", error->message);
g_clear_object (&data.auth);
if (data.os_release)
g_hash_table_unref (data.os_release);
g_free (opt_address);
g_free (opt_local_session);
cockpit_conf_cleanup ();
return ret;
}