static void on_web_service_idling (CockpitWebService *service, gpointer data) { CockpitAuthenticated *authenticated = data; if (authenticated->timeout_tag) g_source_remove (authenticated->timeout_tag); g_debug ("%s: login is idle", cockpit_creds_get_user (authenticated->creds)); /* * The minimum amount of time before a request uses this new web service, * otherwise it will just go away. */ authenticated->timeout_tag = g_timeout_add_seconds (cockpit_ws_service_idle, on_authenticated_timeout, authenticated); /* * Also reset the timer which checks whether anything is going on in the * entire process or not. */ if (authenticated->auth->timeout_tag) g_source_remove (authenticated->auth->timeout_tag); authenticated->auth->timeout_tag = g_timeout_add_seconds (cockpit_ws_process_idle, on_process_timeout, authenticated->auth); }
static void cockpit_ssh_transport_constructed (GObject *object) { CockpitSshTransport *self = COCKPIT_SSH_TRANSPORT (object); CockpitSshData *data; static GSourceFuncs source_funcs = { cockpit_ssh_source_prepare, cockpit_ssh_source_check, cockpit_ssh_source_dispatch, NULL, }; G_OBJECT_CLASS (cockpit_ssh_transport_parent_class)->constructed (object); g_return_if_fail (self->data->creds != NULL); g_warn_if_fail (ssh_options_set (self->data->session, SSH_OPTIONS_USER, cockpit_creds_get_user (self->data->creds)) == 0); self->io = g_source_new (&source_funcs, sizeof (CockpitSshSource)); ((CockpitSshSource *)self->io)->transport = self; g_source_attach (self->io, self->data->context); /* Setup for connect thread */ self->connect_fd = ssh_get_fd (self->data->session); g_atomic_int_set (&self->connecting, 1); self->data->connecting = &self->connecting; data = self->data; self->data = NULL; self->connect_thread = g_thread_new ("ssh-transport-connect", cockpit_ssh_connect_thread, data); g_debug ("%s: constructed", self->logname); }
static CockpitCreds * cookie_to_creds (CockpitAuth *self, const char *cookie) { CockpitCreds *creds = NULL; const char *prefix = "v=2;k="; const gsize n_prefix = 6; const gchar *id; if (!g_str_has_prefix (cookie, prefix)) { g_debug ("invalid or unsupported cookie: %s", cookie); return NULL; } id = cookie + n_prefix; creds = g_hash_table_lookup (self->authenticated, id); if (creds) { g_debug ("received credential id '%s' for user '%s'", id, cockpit_creds_get_user (creds)); cockpit_creds_ref (creds); } else g_debug ("received unknown/invalid credential id '%s'", id); return creds; }
static gboolean authorize_check_user (CockpitCreds *creds, const char *challenge) { char *subject = NULL; gboolean ret = FALSE; gchar *encoded = NULL; const gchar *user; if (!cockpit_authorize_subject (challenge, &subject)) goto out; if (!subject || g_str_equal (subject, "")) { ret = TRUE; } else { user = cockpit_creds_get_user (creds); if (user == NULL) { ret = TRUE; } else { encoded = cockpit_hex_encode (user, -1); ret = g_str_equal (encoded, subject); } } out: g_free (encoded); free (subject); return ret; }
static gboolean on_authenticated_timeout (gpointer data) { CockpitAuthenticated *authenticated = data; CockpitAuth *self = authenticated->auth; authenticated->timeout_tag = 0; if (cockpit_web_service_get_idling (authenticated->service)) { g_info ("%s: timed out", cockpit_creds_get_user (authenticated->creds)); g_hash_table_remove (self->authenticated, authenticated->cookie); } return FALSE; }
/** * cockpit_auth_start_session: * @self: a CockpitAuth * @creds: credentials for the session * * Start a local session process for the given credentials. It may be * that one is hanging around from prior authentication, in which case * that one is used. * * If launching the session fails, then the pipe will be created in a * failed state, and will close shortly. A CockpitPipe is always returned. * * Returns: (transfer full): the new pipe */ CockpitPipe * cockpit_auth_start_session (CockpitAuth *self, CockpitCreds *creds) { CockpitPipe *pipe; CockpitPipe *auth_pipe = NULL; const gchar *password; GBytes *bytes; g_return_val_if_fail (creds != NULL, NULL); pipe = pop_session_process (self, creds); if (pipe == NULL) { password = cockpit_creds_get_password (creds); if (password == NULL) { bytes = NULL; } else { bytes = g_bytes_new_with_free_func (password, strlen (password), cockpit_creds_unref, creds); } pipe = spawn_session_process (cockpit_creds_get_user (creds), bytes, cockpit_creds_get_rhost (creds), &auth_pipe); if (auth_pipe) { /* * Any failure will come from the pipe exit code, but the session * needs our password (if we have one) so let it get sent. */ g_signal_connect (auth_pipe, "close", G_CALLBACK (g_object_unref), NULL); } } if (!pipe) { pipe = g_object_new (COCKPIT_TYPE_PIPE, "problem", "internal-error", NULL); } return pipe; }
static void send_login_response (CockpitWebResponse *response, CockpitCreds *creds, GHashTable *headers) { JsonObject *object; GBytes *content; object = json_object_new (); json_object_set_string_member (object, "user", cockpit_creds_get_user (creds)); content = cockpit_json_write_bytes (object); json_object_unref (object); g_hash_table_replace (headers, g_strdup ("Content-Type"), g_strdup ("application/json")); cockpit_web_response_content (response, headers, content, NULL); g_bytes_unref (content); }
static void on_web_service_idling (CockpitWebService *service, gpointer data) { CockpitAuthenticated *authenticated = data; if (authenticated->timeout_tag) g_source_remove (authenticated->timeout_tag); g_debug ("%s: login is idle", cockpit_creds_get_user (authenticated->creds)); /* * The minimum amount of time before a request uses this new web service, * otherwise it will just go away. */ authenticated->timeout_tag = g_timeout_add_seconds (cockpit_ws_idle_timeout, on_authenticated_timeout, authenticated); }
CockpitWebService * cockpit_auth_check_cookie (CockpitAuth *self, GHashTable *in_headers) { CockpitAuthenticated *authenticated; authenticated = authenticated_for_headers (self, in_headers); if (authenticated) { g_debug ("received credential cookie for user '%s'", cockpit_creds_get_user (authenticated->creds)); return g_object_ref (authenticated->service); } else { g_debug ("received unknown/invalid credential cookie"); return NULL; } }
static char * creds_to_cookie (CockpitAuth *self, CockpitCreds *creds) { guint64 seed; gchar *cookie; char *id; seed = self->nonce_seed++; id = g_compute_hmac_for_data (G_CHECKSUM_SHA256, self->key->data, self->key->len, (guchar *)&seed, sizeof (seed)); cookie = g_strdup_printf ("v=2;k=%s", id); g_hash_table_insert (self->authenticated, id, cockpit_creds_ref (creds)); g_debug ("sending credential id '%s' for user '%s'", id, cockpit_creds_get_user (creds)); return cookie; }
void cockpit_auth_logout (CockpitAuth *self, GHashTable *headers, gboolean secure_req, GHashTable *out_headers) { CockpitAuthenticated *authenticated; gchar *cookie; authenticated = authenticated_for_headers (self, headers); if (authenticated) { g_info ("logged out user %s", cockpit_creds_get_user (authenticated->creds)); g_hash_table_remove (self->authenticated, authenticated->cookie); } if (out_headers) { cookie = g_strdup_printf ("CockpitAuth=blank; Path=/; Expires=Wed, 13-Jan-2021 22:23:01 GMT;%s HttpOnly", secure_req ? " Secure;" : ""); g_hash_table_insert (out_headers, g_strdup ("Set-Cookie"), cookie); } }
static GBytes * build_environment (CockpitWebService *service, JsonObject *modules) { const gchar *user; CockpitCreds *creds; JsonObject *env; JsonObject *localhost; JsonObject *languages; JsonObject *language; struct passwd *pwd; gchar *hostname; GBytes *bytes; guint n; const struct { const gchar *name; const gchar *code; } supported_languages[] = { { NC_("display-language", "English"), "" }, { NC_("display-language", "Danish"), "da" }, { NC_("display-language", "German"), "de" }, }; env = json_object_new (); if (service) { creds = cockpit_web_service_get_creds (service); user = cockpit_creds_get_user (creds); json_object_set_string_member (env, "user", user); pwd = cockpit_getpwnam_a (user, NULL); if (pwd) { json_object_set_string_member (env, "name", pwd->pw_gecos); free (pwd); } } localhost = json_object_new (); /* This awkwardly takes the localhost reference */ json_object_set_object_member (env, "localhost", localhost); hostname = g_malloc0 (HOST_NAME_MAX + 1); gethostname (hostname, HOST_NAME_MAX); hostname[HOST_NAME_MAX] = '\0'; json_object_set_string_member (env, "hostname", hostname); /* Only include version info if logged in */ if (service) { json_object_set_string_member (localhost, "version", PACKAGE_VERSION); json_object_set_string_member (localhost, "build_info", COCKPIT_BUILD_INFO); } languages = json_object_new (); /* This awkwardly takes the languages reference */ json_object_set_object_member (localhost, "languages", languages); for (n = 0; n < G_N_ELEMENTS (supported_languages); n++) { language = json_object_new (); json_object_set_object_member (languages, supported_languages[n].code, language); json_object_set_string_member (language, "name", supported_languages[n].name); } if (modules) json_object_set_object_member (localhost, "modules", json_object_ref (modules)); bytes = cockpit_json_write_bytes (env); json_object_unref (env); return bytes; }
CockpitWebService * cockpit_auth_login_finish (CockpitAuth *self, GAsyncResult *result, CockpitAuthFlags flags, GHashTable *out_headers, GError **error) { CockpitAuthClass *klass = COCKPIT_AUTH_GET_CLASS (self); CockpitAuthenticated *authenticated; CockpitTransport *transport = NULL; CockpitCreds *creds; gchar *cookie_b64 = NULL; gchar *header; guint64 seed; gchar *id; g_return_val_if_fail (klass->login_finish != NULL, FALSE); creds = klass->login_finish (self, result, out_headers, &transport, error); if (creds == NULL) return NULL; seed = self->nonce_seed++; id = g_compute_hmac_for_data (G_CHECKSUM_SHA256, self->key->data, self->key->len, (guchar *)&seed, sizeof (seed)); authenticated = g_new0 (CockpitAuthenticated, 1); authenticated->cookie = g_strdup_printf ("v=2;k=%s", id); authenticated->creds = creds; authenticated->service = cockpit_web_service_new (creds, transport); authenticated->auth = self; authenticated->idling_sig = g_signal_connect (authenticated->service, "idling", G_CALLBACK (on_web_service_idling), authenticated); authenticated->destroy_sig = g_signal_connect (authenticated->service, "destroy", G_CALLBACK (on_web_service_destroy), authenticated); if (transport) g_object_unref (transport); g_object_weak_ref (G_OBJECT (authenticated->service), on_web_service_gone, authenticated); /* Start off in the idling state, and begin a timeout during which caller must do something else */ on_web_service_idling (authenticated->service, authenticated); g_hash_table_insert (self->authenticated, authenticated->cookie, authenticated); g_debug ("sending credential id '%s' for user '%s'", id, cockpit_creds_get_user (creds)); g_free (id); if (out_headers) { gboolean force_secure = !(flags & COCKPIT_AUTH_COOKIE_INSECURE); cookie_b64 = g_base64_encode ((guint8 *)authenticated->cookie, strlen (authenticated->cookie)); header = g_strdup_printf ("cockpit=%s; Path=/; %s HttpOnly", cookie_b64, force_secure ? " Secure;" : ""); g_free (cookie_b64); g_hash_table_insert (out_headers, g_strdup ("Set-Cookie"), header); } g_info ("logged in user: %s", cockpit_creds_get_user (authenticated->creds)); return g_object_ref (authenticated->service); }
static gboolean process_transport_authorize (CockpitWebService *self, CockpitTransport *transport, JsonObject *options) { const gchar *cookie = NULL; GBytes *payload; char *type = NULL; char *alloc = NULL; const char *response = NULL; const gchar *challenge; const gchar *password; const gchar *host; GBytes *data; if (!cockpit_json_get_string (options, "challenge", NULL, &challenge) || !cockpit_json_get_string (options, "cookie", NULL, &cookie) || !cockpit_json_get_string (options, "host", NULL, &host)) { g_warning ("received invalid authorize command"); return FALSE; } if (!challenge || !cookie) { g_message ("unsupported or unknown authorize command"); return FALSE; } if (!cockpit_authorize_type (challenge, &type)) { g_message ("received invalid authorize challenge command"); } else if (g_str_equal (type, "plain1") || g_str_equal (type, "crypt1") || g_str_equal (type, "basic")) { data = cockpit_creds_get_password (self->creds); if (!data) { g_debug ("%s: received \"authorize\" %s \"challenge\", but no password", host, type); } else if (!g_str_equal ("basic", type) && !authorize_check_user (self->creds, challenge)) { g_debug ("received \"authorize\" %s \"challenge\", but for wrong user", type); } else { password = g_bytes_get_data (data, NULL); if (g_str_equal (type, "crypt1")) { alloc = cockpit_compat_reply_crypt1 (challenge, password); if (alloc) response = alloc; else g_message ("failed to \"authorize\" crypt1 \"challenge\""); } else if (g_str_equal (type, "basic")) { response = cockpit_authorize_build_basic (cockpit_creds_get_user (self->creds), password); } else { response = password; } } } /* Tell the frontend that we're reauthorizing */ if (self->init_received) { self->credential_requests++; send_socket_hints (self, "credential", "request"); } if (cookie && !self->sent_done) { payload = cockpit_transport_build_control ("command", "authorize", "cookie", cookie, "response", response ? response : "", "host", host, NULL); cockpit_transport_send (transport, NULL, payload); g_bytes_unref (payload); } free (type); free (alloc); return TRUE; }