/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2007 William Jon McCann * Copyright (C) 2010,2011 Red Hat, Inc. * * 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, see . * */ #include "config.h" #include #include #include "gnome-settings-profile.h" #include "gnome-settings-bus.h" #include "gsd-smartcard-manager.h" #include "gsd-smartcard-service.h" #include "gsd-smartcard-enum-types.h" #include "gsd-smartcard-utils.h" #include #include #include #include #include #include #define GSD_SESSION_MANAGER_LOGOUT_MODE_FORCE 2 struct _GsdSmartcardManager { GObject parent; guint start_idle_id; GsdSmartcardService *service; GList *smartcards_watch_tasks; GCancellable *cancellable; GsdSessionManager *session_manager; GsdScreenSaver *screen_saver; GSettings *settings; NSSInitContext *nss_context; }; #define CONF_SCHEMA "org.gnome.settings-daemon.peripherals.smartcard" #define KEY_REMOVE_ACTION "removal-action" static void gsd_smartcard_manager_class_init (GsdSmartcardManagerClass *klass); static void gsd_smartcard_manager_init (GsdSmartcardManager *self); static void gsd_smartcard_manager_finalize (GObject *object); static void lock_screen (GsdSmartcardManager *self); static void log_out (GsdSmartcardManager *self); static void on_smartcards_from_driver_watched (GsdSmartcardManager *self, GAsyncResult *result, GTask *task); G_DEFINE_TYPE (GsdSmartcardManager, gsd_smartcard_manager, G_TYPE_OBJECT) G_DEFINE_QUARK (gsd-smartcard-manager-error, gsd_smartcard_manager_error) G_LOCK_DEFINE_STATIC (gsd_smartcards_watch_tasks); typedef struct { SECMODModule *driver; guint idle_id; GError *error; } DriverRegistrationOperation; static gpointer manager_object = NULL; static void gsd_smartcard_manager_class_init (GsdSmartcardManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = gsd_smartcard_manager_finalize; gsd_smartcard_utils_register_error_domain (GSD_SMARTCARD_MANAGER_ERROR, GSD_TYPE_SMARTCARD_MANAGER_ERROR); } static void gsd_smartcard_manager_init (GsdSmartcardManager *self) { } static void load_nss (GsdSmartcardManager *self) { NSSInitContext *context = NULL; /* The first field in the NSSInitParameters structure * is the size of the structure. NSS requires this, so * that it can change the size of the structure in future * versions of NSS in a detectable way */ NSSInitParameters parameters = { sizeof (parameters), }; static const guint32 flags = NSS_INIT_READONLY | NSS_INIT_FORCEOPEN | NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD; g_debug ("attempting to load NSS database '%s'", GSD_SMARTCARD_MANAGER_NSS_DB); PR_Init (PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); context = NSS_InitContext (GSD_SMARTCARD_MANAGER_NSS_DB, "", "", SECMOD_DB, ¶meters, flags); if (context == NULL) { gsize error_message_size; char *error_message; error_message_size = PR_GetErrorTextLength (); if (error_message_size == 0) { g_debug ("NSS security system could not be initialized"); } else { error_message = g_alloca (error_message_size); PR_GetErrorText (error_message); g_debug ("NSS security system could not be initialized - %s", error_message); } self->nss_context = NULL; return; } g_debug ("NSS database '%s' loaded", GSD_SMARTCARD_MANAGER_NSS_DB); self->nss_context = context; } static void unload_nss (GsdSmartcardManager *self) { g_debug ("attempting to unload NSS security system with database '%s'", GSD_SMARTCARD_MANAGER_NSS_DB); if (self->nss_context != NULL) { g_clear_pointer (&self->nss_context, NSS_ShutdownContext); g_debug ("NSS database '%s' unloaded", GSD_SMARTCARD_MANAGER_NSS_DB); } else { g_debug ("NSS database '%s' already not loaded", GSD_SMARTCARD_MANAGER_NSS_DB); } } typedef struct { SECMODModule *driver; GHashTable *smartcards; int number_of_consecutive_errors; } WatchSmartcardsOperation; static void on_watch_cancelled (GCancellable *cancellable, WatchSmartcardsOperation *operation) { SECMOD_CancelWait (operation->driver); } static gboolean watch_one_event_from_driver (GsdSmartcardManager *self, WatchSmartcardsOperation *operation, GCancellable *cancellable, GError **error) { PK11SlotInfo *card = NULL, *old_card; CK_SLOT_ID slot_id; gulong handler_id; int old_slot_series = -1, slot_series; handler_id = g_cancellable_connect (cancellable, G_CALLBACK (on_watch_cancelled), operation, NULL); if (handler_id != 0) { /* Use the non-blocking version of the call as p11-kit, which * is used on both Fedora and Ubuntu, doesn't support the * blocking version of the call. */ card = SECMOD_WaitForAnyTokenEvent (operation->driver, CKF_DONT_BLOCK, PR_SecondsToInterval (1)); } g_cancellable_disconnect (cancellable, handler_id); if (g_cancellable_set_error_if_cancelled (cancellable, error)) { g_warning ("smartcard event function cancelled"); return FALSE; } if (card == NULL) { int error_code; error_code = PORT_GetError (); if (error_code == SEC_ERROR_NO_EVENT) { g_usleep (1 * G_USEC_PER_SEC); return TRUE; } operation->number_of_consecutive_errors++; if (operation->number_of_consecutive_errors > 10) { g_warning ("Got %d consecutive smartcard errors, so giving up.", operation->number_of_consecutive_errors); g_set_error (error, GSD_SMARTCARD_MANAGER_ERROR, GSD_SMARTCARD_MANAGER_ERROR_WITH_NSS, "encountered unexpected error while " "waiting for smartcard events (error %x)", error_code); return FALSE; } g_warning ("Got potentially spurious smartcard event error: %x.", error_code); g_usleep (1 * G_USEC_PER_SEC); return TRUE; } operation->number_of_consecutive_errors = 0; slot_id = PK11_GetSlotID (card); slot_series = PK11_GetSlotSeries (card); old_card = g_hash_table_lookup (operation->smartcards, GINT_TO_POINTER ((int) slot_id)); /* If there is a different card in the slot now than * there was before, then we need to emit a removed signal * for the old card */ if (old_card != NULL) { old_slot_series = PK11_GetSlotSeries (old_card); if (old_slot_series != slot_series) { /* Card registered with slot previously is * different than this card, so update its * exported state to track the implicit missed * removal */ gsd_smartcard_service_sync_token (self->service, old_card, cancellable); } g_hash_table_remove (operation->smartcards, GINT_TO_POINTER ((int) slot_id)); } if (PK11_IsPresent (card)) { g_debug ("Detected smartcard insertion event in slot %d", (int) slot_id); g_hash_table_replace (operation->smartcards, GINT_TO_POINTER ((int) slot_id), PK11_ReferenceSlot (card)); gsd_smartcard_service_sync_token (self->service, card, cancellable); } else if (old_card == NULL) { /* If the just removed smartcard is not known to us then * ignore the removal event. NSS sends a synthentic removal * event for slots that are empty at startup */ g_debug ("Detected slot %d is empty in reader", (int) slot_id); } else { g_debug ("Detected smartcard removal event in slot %d", (int) slot_id); /* If the just removed smartcard is known to us then * we need to update its exported state to reflect the * removal */ if (old_slot_series == slot_series) gsd_smartcard_service_sync_token (self->service, card, cancellable); } PK11_FreeSlot (card); return TRUE; } static void watch_smartcards_from_driver (GTask *task, GsdSmartcardManager *self, WatchSmartcardsOperation *operation, GCancellable *cancellable) { g_debug ("watching for smartcard events"); while (!g_cancellable_is_cancelled (cancellable)) { gboolean watch_succeeded; GError *error = NULL; watch_succeeded = watch_one_event_from_driver (self, operation, cancellable, &error); if (g_task_return_error_if_cancelled (task)) { break; } if (!watch_succeeded) { g_task_return_error (task, error); break; } } } static void destroy_watch_smartcards_operation (WatchSmartcardsOperation *operation) { SECMOD_DestroyModule (operation->driver); g_hash_table_unref (operation->smartcards); g_free (operation); } static void on_smartcards_watch_task_destroyed (GsdSmartcardManager *self, GTask *freed_task) { G_LOCK (gsd_smartcards_watch_tasks); self->smartcards_watch_tasks = g_list_remove (self->smartcards_watch_tasks, freed_task); G_UNLOCK (gsd_smartcards_watch_tasks); } static void sync_initial_tokens_from_driver (GsdSmartcardManager *self, SECMODModule *driver, GHashTable *smartcards, GCancellable *cancellable) { int i; for (i = 0; i < driver->slotCount; i++) { PK11SlotInfo *card; card = driver->slots[i]; if (PK11_IsPresent (card)) { CK_SLOT_ID slot_id; slot_id = PK11_GetSlotID (card); g_debug ("Detected smartcard in slot %d at start up", (int) slot_id); g_hash_table_replace (smartcards, GINT_TO_POINTER ((int) slot_id), PK11_ReferenceSlot (card)); gsd_smartcard_service_sync_token (self->service, card, cancellable); } } } static void watch_smartcards_from_driver_async (GsdSmartcardManager *self, SECMODModule *driver, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; WatchSmartcardsOperation *operation; operation = g_new0 (WatchSmartcardsOperation, 1); operation->driver = SECMOD_ReferenceModule (driver); operation->smartcards = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) PK11_FreeSlot); task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, operation, (GDestroyNotify) destroy_watch_smartcards_operation); G_LOCK (gsd_smartcards_watch_tasks); self->smartcards_watch_tasks = g_list_prepend (self->smartcards_watch_tasks, task); g_object_weak_ref (G_OBJECT (task), (GWeakNotify) on_smartcards_watch_task_destroyed, self); G_UNLOCK (gsd_smartcards_watch_tasks); sync_initial_tokens_from_driver (self, driver, operation->smartcards, cancellable); g_task_run_in_thread (task, (GTaskThreadFunc) watch_smartcards_from_driver); } static gboolean register_driver_finish (GsdSmartcardManager *self, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } static void on_driver_registered (GsdSmartcardManager *self, GAsyncResult *result, GTask *task) { GError *error = NULL; DriverRegistrationOperation *operation; operation = g_task_get_task_data (G_TASK (result)); if (!register_driver_finish (self, result, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } watch_smartcards_from_driver_async (self, operation->driver, self->cancellable, (GAsyncReadyCallback) on_smartcards_from_driver_watched, task); g_task_return_boolean (task, TRUE); g_object_unref (task); } static void on_smartcards_from_driver_watched (GsdSmartcardManager *self, GAsyncResult *result, GTask *task) { g_debug ("Done watching smartcards from driver"); } static void destroy_driver_registration_operation (DriverRegistrationOperation *operation) { SECMOD_DestroyModule (operation->driver); g_free (operation); } static gboolean on_task_thread_to_complete_driver_registration (GTask *task) { DriverRegistrationOperation *operation; operation = g_task_get_task_data (task); if (operation->error != NULL) g_task_return_error (task, operation->error); else g_task_return_boolean (task, TRUE); return G_SOURCE_REMOVE; } static gboolean on_main_thread_to_register_driver (GTask *task) { GsdSmartcardManager *self; DriverRegistrationOperation *operation; GSource *source; self = g_task_get_source_object (task); operation = g_task_get_task_data (task); gsd_smartcard_service_register_driver (self->service, operation->driver); source = g_idle_source_new (); g_task_attach_source (task, source, (GSourceFunc) on_task_thread_to_complete_driver_registration); g_source_unref (source); return G_SOURCE_REMOVE; } static void register_driver (GsdSmartcardManager *self, SECMODModule *driver, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; DriverRegistrationOperation *operation; task = g_task_new (self, cancellable, callback, user_data); operation = g_new0 (DriverRegistrationOperation, 1); operation->driver = SECMOD_ReferenceModule (driver); g_task_set_task_data (task, operation, (GDestroyNotify) destroy_driver_registration_operation); operation->idle_id = g_idle_add ((GSourceFunc) on_main_thread_to_register_driver, task); g_source_set_name_by_id (operation->idle_id, "[gnome-settings-daemon] on_main_thread_to_register_driver"); } static void activate_driver (GsdSmartcardManager *self, SECMODModule *driver, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; g_debug ("Activating driver '%s'", driver->commonName); task = g_task_new (self, cancellable, callback, user_data); register_driver (self, driver, cancellable, (GAsyncReadyCallback) on_driver_registered, task); } typedef struct { int pending_drivers_count; int activated_drivers_count; } ActivateAllDriversOperation; static gboolean activate_driver_async_finish (GsdSmartcardManager *self, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } static void try_to_complete_all_drivers_activation (GTask *task) { ActivateAllDriversOperation *operation; operation = g_task_get_task_data (task); if (operation->pending_drivers_count > 0) return; if (operation->activated_drivers_count > 0) g_task_return_boolean (task, TRUE); else g_task_return_new_error (task, GSD_SMARTCARD_MANAGER_ERROR, GSD_SMARTCARD_MANAGER_ERROR_NO_DRIVERS, "No smartcards exist to be activated."); g_object_unref (task); } static void on_driver_activated (GsdSmartcardManager *self, GAsyncResult *result, GTask *task) { GError *error = NULL; gboolean driver_activated; ActivateAllDriversOperation *operation; driver_activated = activate_driver_async_finish (self, result, &error); operation = g_task_get_task_data (task); if (driver_activated) operation->activated_drivers_count++; operation->pending_drivers_count--; try_to_complete_all_drivers_activation (task); } static void activate_all_drivers_async (GsdSmartcardManager *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; SECMODListLock *lock; SECMODModuleList *driver_list, *node; ActivateAllDriversOperation *operation; task = g_task_new (self, cancellable, callback, user_data); operation = g_new0 (ActivateAllDriversOperation, 1); g_task_set_task_data (task, operation, (GDestroyNotify) g_free); lock = SECMOD_GetDefaultModuleListLock (); g_assert (lock != NULL); SECMOD_GetReadLock (lock); driver_list = SECMOD_GetDefaultModuleList (); for (node = driver_list; node != NULL; node = node->next) { if (!node->module->loaded) continue; if (!SECMOD_HasRemovableSlots (node->module)) continue; if (node->module->dllName == NULL) continue; operation->pending_drivers_count++; activate_driver (self, node->module, cancellable, (GAsyncReadyCallback) on_driver_activated, task); } SECMOD_ReleaseReadLock (lock); try_to_complete_all_drivers_activation (task); } /* Will error with %GSD_SMARTCARD_MANAGER_ERROR_NO_DRIVERS if there were no * drivers to activate.. */ static gboolean activate_all_drivers_async_finish (GsdSmartcardManager *self, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } static void on_all_drivers_activated (GsdSmartcardManager *self, GAsyncResult *result, GTask *task) { GError *error = NULL; gboolean driver_activated; PK11SlotInfo *login_token; driver_activated = activate_all_drivers_async_finish (self, result, &error); if (!driver_activated) { g_task_return_error (task, error); return; } login_token = gsd_smartcard_manager_get_login_token (self); if (login_token || g_getenv ("PKCS11_LOGIN_TOKEN_NAME") != NULL) { /* The card used to log in was removed before login completed. * Do removal action immediately */ if (!login_token || !PK11_IsPresent (login_token)) gsd_smartcard_manager_do_remove_action (self); } g_task_return_boolean (task, TRUE); g_object_unref (task); } static void watch_smartcards (GTask *task, GsdSmartcardManager *self, gpointer data, GCancellable *cancellable) { GMainContext *context; GMainLoop *loop; g_debug ("Getting list of suitable drivers"); context = g_main_context_new (); g_main_context_push_thread_default (context); activate_all_drivers_async (self, cancellable, (GAsyncReadyCallback) on_all_drivers_activated, task); loop = g_main_loop_new (context, FALSE); g_main_loop_run (loop); g_main_loop_unref (loop); g_main_context_pop_thread_default (context); g_main_context_unref (context); } static void watch_smartcards_async (GsdSmartcardManager *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; task = g_task_new (self, cancellable, callback, user_data); g_task_run_in_thread (task, (GTaskThreadFunc) watch_smartcards); } static gboolean watch_smartcards_async_finish (GsdSmartcardManager *self, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } static void on_smartcards_watched (GsdSmartcardManager *self, GAsyncResult *result) { GError *error = NULL; if (!watch_smartcards_async_finish (self, result, &error)) { g_debug ("Error watching smartcards: %s", error->message); g_error_free (error); } } static void on_service_created (GObject *source_object, GAsyncResult *result, GsdSmartcardManager *self) { GsdSmartcardService *service; GError *error = NULL; service = gsd_smartcard_service_new_finish (result, &error); if (service == NULL) { g_warning("Couldn't create session bus service: %s", error->message); g_error_free (error); return; } self->service = service; watch_smartcards_async (self, self->cancellable, (GAsyncReadyCallback) on_smartcards_watched, NULL); } static gboolean gsd_smartcard_manager_idle_cb (GsdSmartcardManager *self) { gnome_settings_profile_start (NULL); self->cancellable = g_cancellable_new(); self->settings = g_settings_new (CONF_SCHEMA); load_nss (self); gsd_smartcard_service_new_async (self, self->cancellable, (GAsyncReadyCallback) on_service_created, self); gnome_settings_profile_end (NULL); self->start_idle_id = 0; return FALSE; } gboolean gsd_smartcard_manager_start (GsdSmartcardManager *self, GError **error) { gnome_settings_profile_start (NULL); self->start_idle_id = g_idle_add ((GSourceFunc) gsd_smartcard_manager_idle_cb, self); g_source_set_name_by_id (self->start_idle_id, "[gnome-settings-daemon] gsd_smartcard_manager_idle_cb"); gnome_settings_profile_end (NULL); return TRUE; } void gsd_smartcard_manager_stop (GsdSmartcardManager *self) { g_debug ("Stopping smartcard manager"); g_cancellable_cancel (self->cancellable); unload_nss (self); g_clear_object (&self->settings); g_clear_object (&self->cancellable); g_clear_object (&self->session_manager); g_clear_object (&self->screen_saver); } static void on_screen_locked (GsdScreenSaver *screen_saver, GAsyncResult *result, GsdSmartcardManager *self) { gboolean is_locked; GError *error = NULL; is_locked = gsd_screen_saver_call_lock_finish (screen_saver, result, &error); if (!is_locked) { g_warning ("Couldn't lock screen: %s", error->message); g_error_free (error); return; } } static void lock_screen (GsdSmartcardManager *self) { if (self->screen_saver == NULL) self->screen_saver = gnome_settings_bus_get_screen_saver_proxy (); gsd_screen_saver_call_lock (self->screen_saver, self->cancellable, (GAsyncReadyCallback) on_screen_locked, self); } static void on_logged_out (GsdSessionManager *session_manager, GAsyncResult *result, GsdSmartcardManager *self) { gboolean is_logged_out; GError *error = NULL; is_logged_out = gsd_session_manager_call_logout_finish (session_manager, result, &error); if (!is_logged_out) { g_warning ("Couldn't log out: %s", error->message); g_error_free (error); return; } } static void log_out (GsdSmartcardManager *self) { if (self->session_manager == NULL) self->session_manager = gnome_settings_bus_get_session_proxy (); gsd_session_manager_call_logout (self->session_manager, GSD_SESSION_MANAGER_LOGOUT_MODE_FORCE, self->cancellable, (GAsyncReadyCallback) on_logged_out, self); } void gsd_smartcard_manager_do_remove_action (GsdSmartcardManager *self) { char *remove_action; remove_action = g_settings_get_string (self->settings, KEY_REMOVE_ACTION); if (strcmp (remove_action, "lock-screen") == 0) lock_screen (self); else if (strcmp (remove_action, "force-logout") == 0) log_out (self); } static PK11SlotInfo * get_login_token_for_operation (GsdSmartcardManager *self, WatchSmartcardsOperation *operation) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, operation->smartcards); while (g_hash_table_iter_next (&iter, &key, &value)) { PK11SlotInfo *card_slot; const char *token_name; card_slot = (PK11SlotInfo *) value; token_name = PK11_GetTokenName (card_slot); if (g_strcmp0 (g_getenv ("PKCS11_LOGIN_TOKEN_NAME"), token_name) == 0) return card_slot; } return NULL; } PK11SlotInfo * gsd_smartcard_manager_get_login_token (GsdSmartcardManager *self) { PK11SlotInfo *card_slot = NULL; GList *node; G_LOCK (gsd_smartcards_watch_tasks); node = self->smartcards_watch_tasks; while (node != NULL) { GTask *task = node->data; WatchSmartcardsOperation *operation = g_task_get_task_data (task); card_slot = get_login_token_for_operation (self, operation); if (card_slot != NULL) break; node = node->next; } G_UNLOCK (gsd_smartcards_watch_tasks); return card_slot; } static GList * get_inserted_tokens_for_operation (GsdSmartcardManager *self, WatchSmartcardsOperation *operation) { GList *inserted_tokens = NULL; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, operation->smartcards); while (g_hash_table_iter_next (&iter, &key, &value)) { PK11SlotInfo *card_slot; card_slot = (PK11SlotInfo *) value; if (PK11_IsPresent (card_slot)) inserted_tokens = g_list_prepend (inserted_tokens, card_slot); } return inserted_tokens; } GList * gsd_smartcard_manager_get_inserted_tokens (GsdSmartcardManager *self, gsize *num_tokens) { GList *inserted_tokens = NULL, *node; G_LOCK (gsd_smartcards_watch_tasks); for (node = self->smartcards_watch_tasks; node != NULL; node = node->next) { GTask *task = node->data; WatchSmartcardsOperation *operation = g_task_get_task_data (task); GList *operation_inserted_tokens; operation_inserted_tokens = get_inserted_tokens_for_operation (self, operation); inserted_tokens = g_list_concat (inserted_tokens, operation_inserted_tokens); } G_UNLOCK (gsd_smartcards_watch_tasks); if (num_tokens != NULL) *num_tokens = g_list_length (inserted_tokens); return inserted_tokens; } static void gsd_smartcard_manager_finalize (GObject *object) { GsdSmartcardManager *self; g_return_if_fail (object != NULL); g_return_if_fail (GSD_IS_SMARTCARD_MANAGER (object)); self = GSD_SMARTCARD_MANAGER (object); g_return_if_fail (self != NULL); if (self->start_idle_id != 0) g_source_remove (self->start_idle_id); gsd_smartcard_manager_stop (self); G_OBJECT_CLASS (gsd_smartcard_manager_parent_class)->finalize (object); } GsdSmartcardManager * gsd_smartcard_manager_new (void) { if (manager_object != NULL) { g_object_ref (manager_object); } else { manager_object = g_object_new (GSD_TYPE_SMARTCARD_MANAGER, NULL); g_object_add_weak_pointer (manager_object, (gpointer *) &manager_object); } return GSD_SMARTCARD_MANAGER (manager_object); }