/* Copyright (C) 2015 ABRT team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include "abrt_problems2_session.h" #include "libabrt.h" static PolkitAuthority *s_pk_authority; PolkitAuthority *abrt_p2_session_class_set_polkit_authority(PolkitAuthority *pk_authority) { if (s_pk_authority != NULL) { log_warning("Session: polkit Authority already initialized"); /* * Introduce something like this to libreport if (g_verbose > 3) abort(); */ return s_pk_authority; } s_pk_authority = pk_authority; return s_pk_authority; } PolkitAuthority *abrt_p2_session_class_polkit_authority(void) { if (s_pk_authority == NULL) log_debug("Session: Polkit Authority not-yet initialized"); return s_pk_authority; } PolkitAuthority *abrt_p2_session_class_release_polkit_authority(void) { PolkitAuthority *pk_authority = s_pk_authority; s_pk_authority = NULL; return pk_authority; } typedef struct { char *p2s_caller; uid_t p2s_uid; int p2s_state; GList *p2s_tasks; uint32_t p2s_task_indexer; GHashTable *p2s_tokens; struct check_auth_cb_params *p2s_auth_rq; PolkitSubject *p2s_pk_subject; } AbrtP2SessionPrivate; enum { ABRT_P2_SESSION_STATE_INIT, ABRT_P2_SESSION_STATE_PENDING, ABRT_P2_SESSION_STATE_AUTH, }; struct _AbrtP2Session { GObject parent_instance; AbrtP2SessionPrivate *pv; }; G_DEFINE_TYPE_WITH_PRIVATE(AbrtP2Session, abrt_p2_session, G_TYPE_OBJECT) struct check_auth_cb_params { AbrtP2Session *session; GCancellable *cancellable; PolkitDetails *details; }; enum { SN_AUTHORIZATION_CHANGED, SN_LAST_SIGNAL } SignalNumber; static guint s_signals[SN_LAST_SIGNAL] = { 0 }; static void abrt_p2_session_finalize(GObject *gobject) { AbrtP2SessionPrivate *pv = abrt_p2_session_get_instance_private(ABRT_P2_SESSION(gobject)); free(pv->p2s_caller); g_hash_table_destroy(pv->p2s_tokens); /* If there is ongoing authorization, */ /* tell the callback that it must not touch session.*/ if (pv->p2s_auth_rq != NULL) pv->p2s_auth_rq->session = NULL; if (pv->p2s_pk_subject != NULL) g_object_unref(pv->p2s_pk_subject); } static void abrt_p2_session_class_init(AbrtP2SessionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = abrt_p2_session_finalize; s_signals[SN_AUTHORIZATION_CHANGED] = g_signal_new ("authorization-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(AbrtP2SessionClass, authorization_changed), /*accumulator*/NULL, /*accu_data*/NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, /*n_params*/1, G_TYPE_INT); } static void abrt_p2_session_init(AbrtP2Session *self) { self->pv = abrt_p2_session_get_instance_private(self); } static void emit_authorization_changed(AbrtP2Session *session, AbrtP2SessionAuthChangedStatus status) { g_signal_emit(session, s_signals[SN_AUTHORIZATION_CHANGED], 0, (gint32)status); } static void change_state(AbrtP2Session *session, int new_state) { if (session->pv->p2s_state == new_state) return; AbrtP2SessionAuthChangedStatus value = -1; int old_state = session->pv->p2s_state; session->pv->p2s_state = new_state; if (old_state == ABRT_P2_SESSION_STATE_INIT && new_state == ABRT_P2_SESSION_STATE_PENDING) { log_debug("Authorization request is pending"); value = ABRT_P2_SESSION_CHANGED_PENDING; } else if (old_state == ABRT_P2_SESSION_STATE_INIT && new_state == ABRT_P2_SESSION_STATE_AUTH) { log_debug("Authorization has been granted"); value = ABRT_P2_SESSION_CHANGED_AUTHORIZED; } else if (old_state == ABRT_P2_SESSION_STATE_PENDING && new_state == ABRT_P2_SESSION_STATE_AUTH) { log_debug("Authorization has been acquired"); value = ABRT_P2_SESSION_CHANGED_AUTHORIZED; } else if (old_state == ABRT_P2_SESSION_STATE_AUTH && new_state == ABRT_P2_SESSION_STATE_INIT) { log_debug("Authorization request has been lost"); value = ABRT_P2_SESSION_CHANGED_NOT_AUTHORIZED; } else if (old_state == ABRT_P2_SESSION_STATE_PENDING && new_state == ABRT_P2_SESSION_STATE_INIT) { log_debug("Authorization request has failed"); value = ABRT_P2_SESSION_CHANGED_FAILED; } else goto forgotten_state; emit_authorization_changed(session, value); return; forgotten_state: error_msg("BUG: unsupported state, current : %d, new : %d", session->pv->p2s_state, new_state); } #ifdef HAVE_POLKIT static void check_authorization_callback(GObject *source, GAsyncResult *res, gpointer user_data) { GError *error = NULL; PolkitAuthorizationResult *result = NULL; result = polkit_authority_check_authorization_finish(POLKIT_AUTHORITY(source), res, &error); int new_state = ABRT_P2_SESSION_STATE_INIT; if (result == NULL) { error_msg("Polkit authorization failed: %s", error->message); g_error_free(error); } else { if (polkit_authorization_result_get_is_authorized(result)) new_state = ABRT_P2_SESSION_STATE_AUTH; else /* We do not support polkit_authorization_result_get_is_challenge */ log_debug("Not authorized"); g_object_unref(result); } struct check_auth_cb_params *params = (struct check_auth_cb_params *)user_data; AbrtP2Session *session = params->session; g_object_unref(params->cancellable); if (params->details != NULL) g_object_unref(params->details); free(params); /* It might happen that the session had been destroyed before the callback * was called (e.g. if you programmatically cancel the operation, this * callback is called in the next main loop iteration). */ if (session != NULL) { change_state(session, new_state); session->pv->p2s_auth_rq = NULL; } else log_debug("Operation finished after the session had been destroyed"); } #endif static void authorization_request_initialize(AbrtP2Session *session, GVariant *parameters) { #ifdef HAVE_POLKIT struct check_auth_cb_params *auth_rq = xmalloc(sizeof(*auth_rq)); auth_rq->session = session; auth_rq->cancellable = g_cancellable_new(); auth_rq->details = NULL; if (parameters != NULL) { GVariant *message = g_variant_lookup_value(parameters, "message", G_VARIANT_TYPE_STRING); if (message != NULL) { auth_rq->details = polkit_details_new(); polkit_details_insert(auth_rq->details, "polkit.message", g_variant_get_string(message, NULL)); g_variant_unref(message); } } session->pv->p2s_auth_rq = auth_rq; change_state(session, ABRT_P2_SESSION_STATE_PENDING); /* http://www.freedesktop.org/software/polkit/docs/latest/polkit-apps.html */ if (session->pv->p2s_pk_subject == NULL) session->pv->p2s_pk_subject = polkit_system_bus_name_new(session->pv->p2s_caller); /* If you cancel this operation programmatically, you might get the * following warning message: WARNING **: Error cancelling authorization check: GDBus.Error:org.freedesktop.PolicyKit1.Error.Failed: No such cancellation_id `cancellation-id-1' for name :1.257 The message is returned by polkitbackend and it just means that there were nothing to cancel. Probably because the operation was already finished or not-yet started. */ polkit_authority_check_authorization(abrt_p2_session_class_polkit_authority(), session->pv->p2s_pk_subject, "org.freedesktop.problems.getall", auth_rq->details, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, auth_rq->cancellable, check_authorization_callback, auth_rq); #else change_state(session, ABRT_P2_SESSION_STATE_AUTH); #endif } AbrtP2Session *abrt_p2_session_new(char *caller, uid_t uid) { AbrtP2Session *session = g_object_new(TYPE_ABRT_P2_SESSION, NULL); session->pv->p2s_caller = caller; session->pv->p2s_uid = uid; if (session->pv->p2s_uid == 0) session->pv->p2s_state = ABRT_P2_SESSION_STATE_AUTH; else session->pv->p2s_state = ABRT_P2_SESSION_STATE_INIT; session->pv->p2s_tokens = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); return session; } const char *abrt_p2_session_generate_token(AbrtP2Session *session, unsigned int duration, GError **error) { if (session->pv->p2s_state != ABRT_P2_SESSION_STATE_AUTH) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Session is not authorized"); return NULL; } #define SESSION_TOKEN_LENGTH 16 static const char *const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; if (duration == 0) duration = 5; FILE *urandom = fopen("/dev/urandom", "rb"); if (urandom == NULL) { perror_msg("fopen(/dev/urandom, rb)"); g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to open /dev/urandom for reading"); return NULL; } unsigned int seed = 0; const size_t r = fread(&seed, 1, sizeof(seed), urandom); fclose(urandom); if (sizeof(seed) != r) { perror_msg("fread(unsigned int, /dev/urandom)"); g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Failed to read 'unsigned int' from /dev/urandom"); return NULL; } char *token = xmalloc((SESSION_TOKEN_LENGTH + 1) * sizeof(char)); for (char *iter = token; iter < token + SESSION_TOKEN_LENGTH; ++iter) *iter = alphabet[(int)(strlen(alphabet) * (rand_r(&seed) / (double)RAND_MAX))]; token[SESSION_TOKEN_LENGTH] = '\0'; const time_t curtime = time(NULL); if (curtime == ((time_t) -1)) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Cannot get current time"); free(token); return NULL; } g_hash_table_insert(session->pv->p2s_tokens, token, GINT_TO_POINTER(curtime + duration)); return token; #undef SESSION_TOKEN_LENGTH } int abrt_p2_session_revoke_token(AbrtP2Session *session, const char *token) { return g_hash_table_remove(session->pv->p2s_tokens, token) ? 0 : 1; } static AbrtP2SessionAuthRequestRet abrt_p2_session_authorize_peer_with_token( AbrtP2Session *session, AbrtP2Session *peer_session, const char *token, GError **error) { if (session->pv->p2s_state != ABRT_P2_SESSION_STATE_AUTH) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Not authorized session cannot pass authorization"); return ABRT_P2_SESSION_AUTHORIZE_FAILED; } if (session->pv->p2s_uid != peer_session->pv->p2s_uid) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Session owners do not match"); return ABRT_P2_SESSION_AUTHORIZE_FAILED; } const gpointer expire = g_hash_table_lookup(session->pv->p2s_tokens, token); if (expire == NULL) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "No such token"); return ABRT_P2_SESSION_AUTHORIZE_FAILED; } const time_t curtime = time(NULL); if (curtime == ((time_t) -1)) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Cannot get current time"); return ABRT_P2_SESSION_AUTHORIZE_FAILED; } if (curtime > (time_t)(GPOINTER_TO_INT(expire))) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED, "Token has already expired"); return ABRT_P2_SESSION_AUTHORIZE_FAILED; } g_hash_table_remove(session->pv->p2s_tokens, token); abrt_p2_session_grant_authorization(peer_session); log_info("Granted authorization to peer session on '%s' bus", peer_session->pv->p2s_caller); return ABRT_P2_SESSION_AUTHORIZE_GRANTED; } static int abrt_p2_session_cmp_caller(AbrtP2Session *lhs, const char *bus_name) { return strcmp(lhs->pv->p2s_caller, bus_name); } static AbrtP2SessionAuthRequestRet _abrt_p2_session_begin_auth(AbrtP2Session *session, GVariant *parameters, GList *peers, GError **error) { AbrtP2SessionAuthRequestRet ret = ABRT_P2_SESSION_AUTHORIZE_FAILED; GVariant *peer_bus = g_variant_lookup_value(parameters, "problems2.peer-bus", G_VARIANT_TYPE_STRING); GVariant *peer_token = g_variant_lookup_value(parameters, "problems2.peer-token", G_VARIANT_TYPE_STRING); if (!peer_bus && !peer_token) { authorization_request_initialize(session, parameters); ret = ABRT_P2_SESSION_AUTHORIZE_ACCEPTED; } else if (peer_bus && peer_token) { const gchar *bus = g_variant_get_string(peer_bus, NULL); GList *tmp = g_list_find_custom(peers, bus, (GCompareFunc)abrt_p2_session_cmp_caller); if (tmp == NULL) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "No peer session for bus '%s'", g_variant_get_string(peer_bus, NULL)); goto finito; } AbrtP2Session *peer_session = (AbrtP2Session *)tmp->data; const gchar *token = g_variant_get_string(peer_token, NULL); ret = abrt_p2_session_authorize_peer_with_token(peer_session, session, token, error); } else /* if ((peer_bus && !peer_token) || (!peer_bus && peer_token)) */ { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Invalid parameters peer-bus and peer-token."); } finito: if (peer_bus != NULL) g_variant_unref(peer_bus); if (peer_token != NULL) g_variant_unref(peer_token); return ret; } AbrtP2SessionAuthRequestRet abrt_p2_session_authorize(AbrtP2Session *session, GVariant *parameters, GList *peers, GError **error) { switch(session->pv->p2s_state) { case ABRT_P2_SESSION_STATE_INIT: return _abrt_p2_session_begin_auth(session, parameters, peers, error); case ABRT_P2_SESSION_STATE_PENDING: return ABRT_P2_SESSION_AUTHORIZE_PENDING; case ABRT_P2_SESSION_STATE_AUTH: return ABRT_P2_SESSION_AUTHORIZE_GRANTED; default: error_msg("BUG: %s: forgotten state -> %d", __func__, session->pv->p2s_state); return ABRT_P2_SESSION_AUTHORIZE_FAILED; } } void abrt_p2_session_revoke_authorization(AbrtP2Session *session) { if (session->pv->p2s_uid == 0) return; switch(session->pv->p2s_state) { case ABRT_P2_SESSION_STATE_AUTH: change_state(session, ABRT_P2_SESSION_STATE_INIT); break; case ABRT_P2_SESSION_STATE_PENDING: g_cancellable_cancel(session->pv->p2s_auth_rq->cancellable); change_state(session, ABRT_P2_SESSION_STATE_INIT); break; case ABRT_P2_SESSION_STATE_INIT: /* pass */ break; } } AbrtP2SessionAuthRequestRet abrt_p2_session_grant_authorization(AbrtP2Session *session) { switch(session->pv->p2s_state) { case ABRT_P2_SESSION_STATE_AUTH: /* pass */ break; case ABRT_P2_SESSION_STATE_PENDING: g_cancellable_cancel(session->pv->p2s_auth_rq->cancellable); change_state(session, ABRT_P2_SESSION_STATE_AUTH); break; case ABRT_P2_SESSION_STATE_INIT: change_state(session, ABRT_P2_SESSION_STATE_AUTH); break; } return ABRT_P2_SESSION_AUTHORIZE_GRANTED; } uid_t abrt_p2_session_uid(AbrtP2Session *session) { return session->pv->p2s_uid; } const char *abrt_p2_session_caller(AbrtP2Session *session) { return session->pv->p2s_caller; } int abrt_p2_session_is_authorized(AbrtP2Session *session) { return session->pv->p2s_state == ABRT_P2_SESSION_STATE_AUTH; } int abrt_p2_session_check_sanity(AbrtP2Session *session, const char *caller, uid_t caller_uid, GError **error) { if (strcmp(session->pv->p2s_caller, caller) == 0 && session->pv->p2s_uid == caller_uid) /* the session node is sane */ return 0; log_warning("Problems2 Session object does not belong to UID %d", caller_uid); g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Your Problems2 Session is broken. Check system logs for more details."); return -1; } uint32_t abrt_p2_session_add_task(AbrtP2Session *session, AbrtP2Task *task, GError **error) { if (session->pv->p2s_task_indexer == (UINT32_MAX - 1)) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Reached the limit of task per session."); return UINT32_MAX; } if (abrt_p2_session_owns_task(session, task) == 0) { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "Task is already owned by the session"); return UINT32_MAX; } session->pv->p2s_tasks = g_list_prepend(session->pv->p2s_tasks, task); return session->pv->p2s_task_indexer++; } void abrt_p2_session_remove_task(AbrtP2Session *session, AbrtP2Task *task, GError **error) { session->pv->p2s_tasks = g_list_remove(session->pv->p2s_tasks, task); } GList *abrt_p2_session_tasks(AbrtP2Session *session) { return session->pv->p2s_tasks; } int abrt_p2_session_owns_task(AbrtP2Session *session, AbrtP2Task *task) { return !(g_list_find(session->pv->p2s_tasks, task)); } int abrt_p2_session_tasks_count(AbrtP2Session *session) { return g_list_length(session->pv->p2s_tasks); } static void abrt_p2_session_dispose_task(AbrtP2Task *task, gint32 status) { switch(status) { case ABRT_P2_TASK_STATUS_STOPPED: { GError *local_error = NULL; abrt_p2_task_cancel(task, &local_error); if (local_error != NULL) { error_msg("Task garbage collector failed to cancel task: %s", local_error->message); g_error_free(local_error); } /* In case of errors, this could cause problems, but I * don't have better plan yet. */ log_debug("Disposed new/stopped task: %p", task); g_object_unref(task); } break; case ABRT_P2_TASK_STATUS_NEW: log_debug("Disposed new task: %p", task); g_object_unref(task); break; case ABRT_P2_TASK_STATUS_FAILED: log_debug("Disposed failed task: %p", task); g_object_unref(task); break; case ABRT_P2_TASK_STATUS_CANCELED: log_debug("Disposed canceled task: %p", task); g_object_unref(task); break; case ABRT_P2_TASK_STATUS_DONE: log_debug("Disposed done task: %p", task); g_object_unref(task); break; case ABRT_P2_TASK_STATUS_RUNNING: error_msg("BUG: cannot dispose RUNNING task"); abort(); break; } } static void abrt_p2_session_delayed_dispose_task(AbrtP2Task *task, gint32 status, gpointer user_data) { if (status == ABRT_P2_TASK_STATUS_RUNNING) { error_msg("BUG: task to dispose must not change state to RUNNING"); abort(); } log_debug("Going to dispose delayed task: %p: %d", task, status); abrt_p2_session_dispose_task(task, status); } void abrt_p2_session_clean_tasks(AbrtP2Session *session) { GList *task = session->pv->p2s_tasks; session->pv->p2s_tasks = NULL; while (task != NULL) { AbrtP2Task *t = ABRT_P2_TASK(task->data); task = g_list_delete_link(task, task); const AbrtP2TaskStatus status = abrt_p2_task_status(t); if (status != ABRT_P2_TASK_STATUS_RUNNING) { abrt_p2_session_dispose_task(t, status); continue; } log_debug("Delaying disposal of running task: %p", t); g_signal_connect(t, "status-changed", G_CALLBACK(abrt_p2_session_delayed_dispose_task), NULL); GError *local_error = NULL; abrt_p2_task_cancel(t, &local_error); if (local_error != NULL) { error_msg("Task garbage collector failed to cancel running task: %s", local_error->message); g_error_free(local_error); } } }