static const gchar * action_for_type (const gchar *type, const gchar *application, gboolean force_ssh) { const gchar *action; const gchar *host; g_return_val_if_fail (type != NULL, NULL); g_return_val_if_fail (application != NULL, NULL); host = application_parse_host (application); if (g_strcmp0 (type, ACTION_CONVERSATION) == 0) action = ACTION_CONVERSATION; else if (host) action = ACTION_SSH; /* Only force_ssh for basic */ else if (force_ssh && g_strcmp0 (type, "basic") == 0) action = ACTION_SSH; else if (type && cockpit_conf_string (type, "action")) action = cockpit_conf_string (type, "action"); else if (g_strcmp0 (type, "basic") == 0 || g_strcmp0 (type, "negotiate") == 0) action = ACTION_SPAWN_DECODE; else action = ACTION_NONE; return action; }
static const gchar * type_option (const gchar *type, const gchar *option, const gchar *default_str) { if (type && cockpit_conf_string (type, option)) return cockpit_conf_string (type, option); return default_str; }
static void handle_shell (CockpitHandlerData *data, CockpitWebService *service, const gchar *path, GHashTable *headers, CockpitWebResponse *response) { gboolean valid; const gchar *shell_path; /* Check if a valid path for a shell to be served at */ valid = g_str_equal (path, "/") || g_str_has_prefix (path, "/@") || strspn (path + 1, COCKPIT_RESOURCE_PACKAGE_VALID) == strcspn (path + 1, "/"); if (g_str_has_prefix (path, "/@/") || g_str_has_prefix (path, "//")) valid = FALSE; if (!valid) { cockpit_web_response_error (response, 404, NULL, NULL); } else if (service) { shell_path = cockpit_conf_string ("WebService", "Shell"); cockpit_channel_response_serve (service, headers, response, NULL, shell_path ? shell_path : cockpit_ws_shell_component); } else { send_login_html (response, data); } }
WebSocketConnection * cockpit_web_service_create_socket (const gchar **protocols, const gchar *path, GIOStream *io_stream, GHashTable *headers, GByteArray *input_buffer) { WebSocketConnection *connection; const gchar *host = NULL; const gchar *protocol = NULL; const gchar **origins; gchar *allocated = NULL; gchar *origin = NULL; gchar *defaults[2]; gboolean secure; gchar *url; g_return_val_if_fail (path != NULL, NULL); if (headers) host = g_hash_table_lookup (headers, "Host"); if (!host) host = cockpit_ws_default_host_header; /* No headers case for tests */ if (cockpit_ws_default_protocol_header && !headers && cockpit_conf_string ("WebService", "ProtocolHeader")) { protocol = cockpit_ws_default_protocol_header; } else { protocol = cockpit_web_response_get_protocol (io_stream, headers); } secure = g_strcmp0 (protocol, "https") == 0; url = g_strdup_printf ("%s://%s%s", secure ? "wss" : "ws", host ? host : "localhost", path); origins = cockpit_conf_strv ("WebService", "Origins", ' '); if (origins == NULL) { origin = g_strdup_printf ("%s://%s", secure ? "https" : "http", host); defaults[0] = origin; defaults[1] = NULL; origins = (const gchar **)defaults; } connection = web_socket_server_new_for_stream (url, origins, protocols, io_stream, headers, input_buffer); g_free (allocated); g_free (url); g_free (origin); return connection; }
static void add_oauth_to_environment (JsonObject *environment) { static const gchar *url; JsonObject *object; url = cockpit_conf_string ("OAuth", "URL"); if (url) { object = json_object_new (); json_object_set_string_member (object, "URL", url); json_object_set_string_member (object, "ErrorParam", cockpit_conf_string ("oauth", "ErrorParam")); json_object_set_string_member (object, "TokenParam", cockpit_conf_string ("oauth", "TokenParam")); json_object_set_object_member (environment, "OAuth", object); } }
CockpitAuth * cockpit_auth_new (gboolean login_loopback) { CockpitAuth *self = g_object_new (COCKPIT_TYPE_AUTH, NULL); const gchar *max_startups_conf; gint count = 0; self->login_loopback = login_loopback; if (cockpit_ws_max_startups == NULL) max_startups_conf = cockpit_conf_string ("WebService", "MaxStartups"); else max_startups_conf = cockpit_ws_max_startups; self->max_startups = max_startups; self->max_startups_begin = max_startups; self->max_startups_rate = 100; if (max_startups_conf) { count = sscanf (max_startups_conf, "%u:%u:%u", &self->max_startups_begin, &self->max_startups_rate, &self->max_startups); /* If all three numbers are not given use the * first as a hard limit */ if (count == 1 || count == 2) { self->max_startups = self->max_startups_begin; self->max_startups_rate = 100; } if (count < 1 || count > 3 || self->max_startups_begin > self->max_startups || self->max_startups_rate > 100 || self->max_startups_rate < 1) { g_warning ("Illegal MaxStartups spec: %s. Reverting to defaults", max_startups_conf); self->max_startups = max_startups; self->max_startups_begin = max_startups; self->max_startups_rate = 100; } } return self; }
static GBytes * build_environment (GHashTable *os_release) { static const gchar *prefix = "\n <script>\nvar environment = "; static const gchar *suffix = ";\n </script>"; GByteArray *buffer; GHashTableIter iter; GBytes *bytes; JsonObject *object; const gchar *title; gchar *hostname; gpointer key, value; JsonObject *osr; object = json_object_new (); title = cockpit_conf_string ("WebService", "LoginTitle"); if (title) json_object_set_string_member (object, "title", title); hostname = g_malloc0 (HOST_NAME_MAX + 1); gethostname (hostname, HOST_NAME_MAX); hostname[HOST_NAME_MAX] = '\0'; json_object_set_string_member (object, "hostname", hostname); g_free (hostname); if (os_release) { osr = json_object_new (); g_hash_table_iter_init (&iter, os_release); while (g_hash_table_iter_next (&iter, &key, &value)) json_object_set_string_member (osr, key, value); json_object_set_object_member (object, "os-release", osr); } add_oauth_to_environment (object); bytes = cockpit_json_write_bytes (object); json_object_unref (object); buffer = g_bytes_unref_to_array (bytes); g_byte_array_prepend (buffer, (const guint8 *)prefix, strlen (prefix)); g_byte_array_append (buffer, (const guint8 *)suffix, strlen (suffix)); return g_byte_array_free_to_bytes (buffer); }
static void add_page_to_environment (JsonObject *object) { static gint page_login_to = -1; JsonObject *page; const gchar *value; page = json_object_new (); value = cockpit_conf_string ("WebService", "LoginTitle"); if (value) json_object_set_string_member (page, "title", value); if (page_login_to < 0) { page_login_to = cockpit_conf_bool ("WebService", "LoginTo", g_file_test (cockpit_ws_ssh_program, G_FILE_TEST_IS_EXECUTABLE)); } json_object_set_boolean_member (page, "connect", page_login_to); json_object_set_object_member (object, "page", page); }
int main (int argc, char *argv[]) { gint ret = 1; CockpitWebServer *server = NULL; GOptionContext *context; CockpitHandlerData data; GTlsCertificate *certificate = NULL; GError *local_error = NULL; GError **error = &local_error; gchar **roots = NULL; gchar *cert_path = NULL; GMainLoop *loop = NULL; gchar *login_html = NULL; gchar *login_po_html = NULL; CockpitPipe *pipe = NULL; int outfd = -1; signal (SIGPIPE, SIG_IGN); g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); g_setenv ("GIO_USE_PROXY_RESOLVER", "dummy", TRUE); g_setenv ("GIO_USE_VFS", "local", TRUE); /* Any interaction with a krb5 ccache should be explicit */ g_setenv ("KRB5CCNAME", "FILE:/dev/null", TRUE); g_setenv ("G_TLS_GNUTLS_PRIORITY", "SECURE128:%LATEST_RECORD_VERSION:-VERS-SSL3.0:-VERS-TLS1.0", FALSE); 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_version) { print_version (); ret = 0; goto out; } /* * This process talks on stdin/stdout. However lots of stuff wants to write * to stdout, such as g_debug, and uses fd 1 to do that. Reroute fd 1 so that * it goes to stderr, and use another fd for stdout. */ outfd = dup (1); if (outfd < 0 || dup2 (2, 1) < 1) { g_printerr ("ws couldn't redirect stdout to stderr"); if (outfd > -1) close (outfd); goto out; } cockpit_set_journal_logging (NULL, !isatty (2)); if (opt_local_session || opt_no_tls) { /* no certificate */ } else { cert_path = cockpit_certificate_locate (FALSE, error); if (cert_path != NULL) certificate = cockpit_certificate_load (cert_path, error); if (certificate == NULL) goto out; g_info ("Using certificate: %s", cert_path); } loop = g_main_loop_new (NULL, FALSE); data.os_release = cockpit_system_load_os_release (); data.auth = cockpit_auth_new (opt_local_ssh); roots = setup_static_roots (data.os_release); data.branding_roots = (const gchar **)roots; login_html = g_strdup (DATADIR "/cockpit/static/login.html"); data.login_html = (const gchar *)login_html; login_po_html = g_strdup (DATADIR "/cockpit/static/login.po.html"); data.login_po_html = (const gchar *)login_po_html; server = cockpit_web_server_new (opt_address, opt_port, certificate, NULL, error); if (server == NULL) { g_prefix_error (error, "Error starting web server: "); goto out; } cockpit_web_server_set_redirect_tls (server, !cockpit_conf_bool ("WebService", "AllowUnencrypted", FALSE)); if (cockpit_conf_string ("WebService", "UrlRoot")) { g_object_set (server, "url-root", cockpit_conf_string ("WebService", "UrlRoot"), NULL); } if (cockpit_web_server_get_socket_activated (server)) g_signal_connect_swapped (data.auth, "idling", G_CALLBACK (g_main_loop_quit), loop); /* Ignores stuff it shouldn't handle */ g_signal_connect (server, "handle-stream", G_CALLBACK (cockpit_handler_socket), &data); /* External channels, ignore stuff they shouldn't handle */ g_signal_connect (server, "handle-stream", G_CALLBACK (cockpit_handler_external), &data); /* Don't redirect to TLS for /ping */ g_object_set (server, "ssl-exception-prefix", "/ping", NULL); g_signal_connect (server, "handle-resource::/ping", G_CALLBACK (cockpit_handler_ping), &data); /* Files that cannot be cache-forever, because of well known names */ 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); /* The fallback handler for everything else */ g_signal_connect (server, "handle-resource", G_CALLBACK (cockpit_handler_default), &data); if (opt_local_session) { struct passwd *pwd; if (g_str_equal (opt_local_session, "-")) { pipe = cockpit_pipe_new (opt_local_session, 0, outfd); outfd = -1; } else { const gchar *args[] = { opt_local_session, NULL }; pipe = cockpit_pipe_spawn (args, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE); } /* Spawn a local session as a bridge */ 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)); g_object_unref (pipe); } else { /* When no local bridge, start serving immediately */ cockpit_web_server_start (server); } /* Debugging issues during testing */ #if WITH_DEBUG signal (SIGABRT, cockpit_test_signal_backtrace); signal (SIGSEGV, cockpit_test_signal_backtrace); #endif g_main_loop_run (loop); ret = 0; out: if (outfd >= 0) close (outfd); if (loop) g_main_loop_unref (loop); if (local_error) { g_printerr ("cockpit-ws: %s\n", local_error->message); g_error_free (local_error); } g_clear_object (&server); g_clear_object (&data.auth); if (data.os_release) g_hash_table_unref (data.os_release); g_clear_object (&certificate); g_free (cert_path); g_strfreev (roots); g_free (login_po_html); g_free (login_html); g_free (opt_address); g_free (opt_local_session); cockpit_conf_cleanup (); return ret; }
static void cockpit_auth_remote_login_async (CockpitAuth *self, const gchar *application, const gchar *type, GHashTable *headers, const gchar *remote_peer, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *task; AuthData *ad = NULL; GBytes *input = NULL; GBytes *auth_bytes = NULL; gchar *user = NULL; gchar *password = NULL; /* owned by input */ const gchar *data = NULL; const gchar *command; const gchar *host; const gchar *host_arg; gint next_arg = 1; const gchar *argv[] = { cockpit_ws_ssh_program, NULL, NULL, NULL, NULL, NULL, NULL, }; task = g_simple_async_result_new (G_OBJECT (self), callback, user_data, cockpit_auth_remote_login_async); if (g_strcmp0 (type, "basic") == 0) { input = cockpit_auth_parse_authorization (headers, TRUE); data = g_bytes_get_data (input, NULL); password = strchr (data, ':'); if (password != NULL) { user = g_strndup (data, password - data); password++; auth_bytes = g_bytes_new_static (password, strlen(password)); } } else { input = cockpit_auth_parse_authorization (headers, FALSE); if (input) auth_bytes = g_bytes_ref (input); } if (application && auth_bytes && input) { command = type_option (SSH_SECTION, "command", cockpit_ws_ssh_program); argv[0] = command; host = application_parse_host (application); if (host) { host_arg = host; if (g_strcmp0 (remote_peer, "127.0.0.1") == 0 || g_strcmp0 (remote_peer, "::1") == 0 || cockpit_conf_bool (SSH_SECTION, "allowUnknown", FALSE)) { argv[next_arg++] = "--prompt-unknown-hostkey"; } } else { argv[next_arg++] = "--ignore-hostkey"; if (cockpit_conf_string (SSH_SECTION, "host")) host_arg = cockpit_conf_string (SSH_SECTION, "host"); else host_arg = "localhost"; } argv[next_arg++] = host_arg; argv[next_arg++] = user; ad = create_auth_data (self, host_arg, application, SSH_SECTION, remote_peer, command, input); start_auth_process (self, ad, auth_bytes, task, cockpit_auth_remote_login_async, argv); } else { g_simple_async_result_set_error (task, COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED, "Basic authentication required"); g_simple_async_result_complete_in_idle (task); } if (auth_bytes) g_bytes_unref (auth_bytes); if (input) g_bytes_unref (input); if (ad) auth_data_unref (ad); if (user) g_free (user); g_object_unref (task); }