/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2004-2005 James M. Cape . * Copyright (C) 2007-2008 William Jon McCann * Copyright (C) 2009 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _XOPEN_SOURCE #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "um-user.h" #include "um-account-type.h" #include "um-utils.h" #define UM_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_USER, UmUserClass)) #define UM_IS_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_USER)) #define UM_USER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), UM_TYPE_USER, UmUserClass)) #define MAX_FILE_SIZE 65536 typedef struct { uid_t uid; gchar *user_name; gchar *real_name; gint account_type; gint password_mode; gchar *password_hint; gchar *email; gchar *language; gchar *location; guint64 login_frequency; gchar *icon_file; gboolean locked; gboolean automatic_login; gboolean system_account; } UserProperties; static void collect_props (const gchar *key, const GValue *value, UserProperties *props) { gboolean handled = TRUE; if (strcmp (key, "Uid") == 0) { props->uid = g_value_get_uint64 (value); } else if (strcmp (key, "UserName") == 0) { props->user_name = g_value_dup_string (value); } else if (strcmp (key, "RealName") == 0) { props->real_name = g_value_dup_string (value); } else if (strcmp (key, "AccountType") == 0) { props->account_type = g_value_get_int (value); } else if (strcmp (key, "Email") == 0) { props->email = g_value_dup_string (value); } else if (strcmp (key, "Language") == 0) { props->language = g_value_dup_string (value); } else if (strcmp (key, "Location") == 0) { props->location = g_value_dup_string (value); } else if (strcmp (key, "LoginFrequency") == 0) { props->login_frequency = g_value_get_uint64 (value); } else if (strcmp (key, "IconFile") == 0) { props->icon_file = g_value_dup_string (value); } else if (strcmp (key, "Locked") == 0) { props->locked = g_value_get_boolean (value); } else if (strcmp (key, "AutomaticLogin") == 0) { props->automatic_login = g_value_get_boolean (value); } else if (strcmp (key, "SystemAccount") == 0) { props->system_account = g_value_get_boolean (value); } else if (strcmp (key, "PasswordMode") == 0) { props->password_mode = g_value_get_int (value); } else if (strcmp (key, "PasswordHint") == 0) { props->password_hint = g_value_dup_string (value); } else if (strcmp (key, "HomeDirectory") == 0) { /* ignore */ } else if (strcmp (key, "Shell") == 0) { /* ignore */ } else { handled = FALSE; } if (!handled) g_debug ("unhandled property %s", key); } static void user_properties_free (UserProperties *props) { g_free (props->user_name); g_free (props->real_name); g_free (props->password_hint); g_free (props->email); g_free (props->language); g_free (props->location); g_free (props->icon_file); g_free (props); } static UserProperties * user_properties_get (DBusGConnection *bus, const gchar *object_path) { UserProperties *props; GError *error; DBusGProxy *proxy; GHashTable *hash_table; props = g_new0 (UserProperties, 1); proxy = dbus_g_proxy_new_for_name (bus, "org.freedesktop.Accounts", object_path, "org.freedesktop.DBus.Properties"); error = NULL; if (!dbus_g_proxy_call (proxy, "GetAll", &error, G_TYPE_STRING, "org.freedesktop.Accounts.User", G_TYPE_INVALID, dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), &hash_table, G_TYPE_INVALID)) { g_debug ("Error calling GetAll() when retrieving properties for %s: %s", object_path, error->message); g_error_free (error); g_free (props); props = NULL; goto out; } g_hash_table_foreach (hash_table, (GHFunc) collect_props, props); g_hash_table_unref (hash_table); out: g_object_unref (proxy); return props; } struct _UmUser { GObject parent; DBusGConnection *bus; DBusGProxy *proxy; gchar *object_path; UserProperties *props; gchar *display_name; }; typedef struct _UmUserClass { GObjectClass parent_class; } UmUserClass; enum { CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static void um_user_finalize (GObject *object); G_DEFINE_TYPE (UmUser, um_user, G_TYPE_OBJECT) static void um_user_class_init (UmUserClass *class) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (class); gobject_class->finalize = um_user_finalize; signals[CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void um_user_init (UmUser *user) { } static void um_user_finalize (GObject *object) { UmUser *user; user = UM_USER (object); g_free (user->display_name); dbus_g_connection_unref (user->bus); g_free (user->object_path); if (user->proxy != NULL) g_object_unref (user->proxy); if (user->props != NULL) user_properties_free (user->props); (*G_OBJECT_CLASS (um_user_parent_class)->finalize) (object); } uid_t um_user_get_uid (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), -1); return user->props->uid; } const gchar * um_user_get_real_name (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->real_name; } const gchar * um_user_get_display_name (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); if (user->display_name) return user->display_name; if (user->props->real_name && *user->props->real_name != '\0') return user->props->real_name; return user->props->user_name; } const gchar * um_user_get_user_name (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->user_name; } gint um_user_get_account_type (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), UM_ACCOUNT_TYPE_STANDARD); return user->props->account_type; } gulong um_user_get_login_frequency (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), 0); return user->props->login_frequency; } gint um_user_collate (UmUser *user1, UmUser *user2) { const char *str1; const char *str2; gulong num1; gulong num2; g_return_val_if_fail (UM_IS_USER (user1), 0); g_return_val_if_fail (UM_IS_USER (user2), 0); num1 = user1->props->login_frequency; num2 = user2->props->login_frequency; if (num1 > num2) { return -1; } if (num1 < num2) { return 1; } /* if login frequency is equal try names */ if (user1->props->real_name != NULL) { str1 = user1->props->real_name; } else { str1 = user1->props->user_name; } if (user2->props->real_name != NULL) { str2 = user2->props->real_name; } else { str2 = user2->props->user_name; } if (str1 == NULL && str2 != NULL) { return -1; } if (str1 != NULL && str2 == NULL) { return 1; } if (str1 == NULL && str2 == NULL) { return 0; } return g_utf8_collate (str1, str2); } static gboolean check_user_file (const char *filename, gssize max_file_size) { struct stat fileinfo; if (max_file_size < 0) { max_file_size = G_MAXSIZE; } /* Exists/Readable? */ if (stat (filename, &fileinfo) < 0) { g_debug ("File does not exist"); return FALSE; } /* Is a regular file */ if (G_UNLIKELY (!S_ISREG (fileinfo.st_mode))) { g_debug ("File is not a regular file"); return FALSE; } /* Size is kosher? */ if (G_UNLIKELY (fileinfo.st_size > max_file_size)) { g_debug ("File is too large"); return FALSE; } return TRUE; } static cairo_surface_t * surface_from_pixbuf (GdkPixbuf *pixbuf) { cairo_surface_t *surface; cairo_t *cr; surface = cairo_image_surface_create (gdk_pixbuf_get_has_alpha (pixbuf) ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf)); cr = cairo_create (surface); gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); cairo_paint (cr); cairo_destroy (cr); return surface; } /** * go_cairo_convert_data_to_pixbuf: * @src: a pointer to pixel data in cairo format * @dst: a pointer to pixel data in pixbuf format * @width: image width * @height: image height * @rowstride: data rowstride * * Converts the pixel data stored in @src in CAIRO_FORMAT_ARGB32 cairo format * to GDK_COLORSPACE_RGB pixbuf format and move them * to @dst. If @src == @dst, pixel are converted in place. **/ static void go_cairo_convert_data_to_pixbuf (unsigned char *dst, unsigned char const *src, int width, int height, int rowstride) { int i,j; unsigned int t; unsigned char a, b, c; g_return_if_fail (dst != NULL); #define MULT(d,c,a,t) G_STMT_START { t = (a)? c * 255 / a: 0; d = t;} G_STMT_END if (src == dst || src == NULL) { for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { #if G_BYTE_ORDER == G_LITTLE_ENDIAN MULT(a, dst[2], dst[3], t); MULT(b, dst[1], dst[3], t); MULT(c, dst[0], dst[3], t); dst[0] = a; dst[1] = b; dst[2] = c; #else MULT(a, dst[1], dst[0], t); MULT(b, dst[2], dst[0], t); MULT(c, dst[3], dst[0], t); dst[3] = dst[0]; dst[0] = a; dst[1] = b; dst[2] = c; #endif dst += 4; } dst += rowstride - width * 4; } } else { for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { #if G_BYTE_ORDER == G_LITTLE_ENDIAN MULT(dst[0], src[2], src[3], t); MULT(dst[1], src[1], src[3], t); MULT(dst[2], src[0], src[3], t); dst[3] = src[3]; #else MULT(dst[0], src[1], src[0], t); MULT(dst[1], src[2], src[0], t); MULT(dst[2], src[3], src[0], t); dst[3] = src[0]; #endif src += 4; dst += 4; } src += rowstride - width * 4; dst += rowstride - width * 4; } } #undef MULT } static void cairo_to_pixbuf (guint8 *src_data, GdkPixbuf *dst_pixbuf) { unsigned char *src; unsigned char *dst; guint w; guint h; guint rowstride; w = gdk_pixbuf_get_width (dst_pixbuf); h = gdk_pixbuf_get_height (dst_pixbuf); rowstride = gdk_pixbuf_get_rowstride (dst_pixbuf); dst = gdk_pixbuf_get_pixels (dst_pixbuf); src = src_data; go_cairo_convert_data_to_pixbuf (dst, src, w, h, rowstride); } static GdkPixbuf * frame_pixbuf (GdkPixbuf *source) { GdkPixbuf *dest; cairo_t *cr; cairo_surface_t *surface; guint w; guint h; guint rowstride; int frame_width; double radius; guint8 *data; frame_width = 2; w = gdk_pixbuf_get_width (source) + frame_width * 2; h = gdk_pixbuf_get_height (source) + frame_width * 2; radius = w / 10; dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, w, h); rowstride = gdk_pixbuf_get_rowstride (dest); data = g_new0 (guint8, h * rowstride); surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, w, h, rowstride); cr = cairo_create (surface); cairo_surface_destroy (surface); /* set up image */ cairo_rectangle (cr, 0, 0, w, h); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); cairo_fill (cr); rounded_rectangle (cr, 1.0, 0.5, 0.5, radius, w - 1, h - 1); cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3); cairo_fill_preserve (cr); surface = surface_from_pixbuf (source); cairo_set_source_surface (cr, surface, frame_width, frame_width); cairo_fill (cr); cairo_surface_destroy (surface); cairo_to_pixbuf (data, dest); cairo_destroy (cr); g_free (data); return dest; } GdkPixbuf * um_user_render_icon (UmUser *user, gboolean with_frame, gint icon_size) { GdkPixbuf *pixbuf; GdkPixbuf *framed; gboolean res; GError *error; g_return_val_if_fail (UM_IS_USER (user), NULL); g_return_val_if_fail (icon_size > 12, NULL); pixbuf = NULL; if (user->props->icon_file) { res = check_user_file (user->props->icon_file, MAX_FILE_SIZE); if (res) { pixbuf = gdk_pixbuf_new_from_file_at_size (user->props->icon_file, icon_size, icon_size, NULL); } else { pixbuf = NULL; } } if (pixbuf != NULL) { goto out; } error = NULL; pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), "avatar-default", icon_size, GTK_ICON_LOOKUP_FORCE_SIZE, &error); if (error) { g_warning ("%s", error->message); g_error_free (error); } out: if (pixbuf != NULL && with_frame) { framed = frame_pixbuf (pixbuf); if (framed != NULL) { g_object_unref (pixbuf); pixbuf = framed; } } return pixbuf; } const gchar * um_user_get_email (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->email; } const gchar * um_user_get_language (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); if (*user->props->language == '\0') return NULL; return user->props->language; } const gchar * um_user_get_location (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->location; } gint um_user_get_password_mode (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), UM_PASSWORD_MODE_NONE); return user->props->password_mode; } const char * um_user_get_password_hint (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->password_hint; } const char * um_user_get_icon_file (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->icon_file; } gboolean um_user_get_locked (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), FALSE); return user->props->locked; } gboolean um_user_get_automatic_login (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), FALSE); return user->props->automatic_login; } gboolean um_user_is_system_account (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), FALSE); return user->props->system_account; } const gchar * um_user_get_object_path (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->object_path; } static gboolean update_info (UmUser *user) { UserProperties *props; props = user_properties_get (user->bus, user->object_path); if (props != NULL) { if (user->props != NULL) user_properties_free (user->props); user->props = props; return TRUE; } else { return FALSE; } } static void changed_handler (DBusGProxy *proxy, gpointer *data) { UmUser *user = UM_USER (data); if (update_info (user)) { if (user->display_name != NULL) { um_user_show_full_display_name (user); } g_signal_emit (user, signals[CHANGED], 0); } } UmUser * um_user_new_from_object_path (const gchar *object_path) { UmUser *user; GError *error; user = (UmUser *)g_object_new (UM_TYPE_USER, NULL); user->object_path = g_strdup (object_path); error = NULL; user->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); if (user->bus == NULL) { g_warning ("Couldn't connect to system bus: %s", error->message); goto error; } user->proxy = dbus_g_proxy_new_for_name (user->bus, "org.freedesktop.Accounts", user->object_path, "org.freedesktop.Accounts.User"); dbus_g_proxy_set_default_timeout (user->proxy, INT_MAX); dbus_g_proxy_add_signal (user->proxy, "Changed", G_TYPE_INVALID); dbus_g_proxy_connect_signal (user->proxy, "Changed", G_CALLBACK (changed_handler), user, NULL); if (!update_info (user)) goto error; return user; error: g_object_unref (user); return NULL; } void um_user_set_email (UmUser *user, const gchar *email) { GError *error = NULL; if (!dbus_g_proxy_call (user->proxy, "SetEmail", &error, G_TYPE_STRING, email, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetEmail call failed: %s", error->message); g_error_free (error); return; } } void um_user_set_language (UmUser *user, const gchar *language) { GError *error = NULL; if (!dbus_g_proxy_call (user->proxy, "SetLanguage", &error, G_TYPE_STRING, language, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetLanguage call failed: %s", error->message); g_error_free (error); return; } } void um_user_set_location (UmUser *user, const gchar *location) { GError *error = NULL; if (!dbus_g_proxy_call (user->proxy, "SetLocation", &error, G_TYPE_STRING, location, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetLocation call failed: %s", error->message); g_error_free (error); return; } } void um_user_set_user_name (UmUser *user, const gchar *user_name) { GError *error = NULL; if (!dbus_g_proxy_call (user->proxy, "SetUserName", &error, G_TYPE_STRING, user_name, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetUserName call failed: %s", error->message); g_error_free (error); return; } } void um_user_set_real_name (UmUser *user, const gchar *real_name) { GError *error = NULL; if (!dbus_g_proxy_call (user->proxy, "SetRealName", &error, G_TYPE_STRING, real_name, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetRealName call failed: %s", error->message); g_error_free (error); return; } } void um_user_set_icon_file (UmUser *user, const gchar *icon_file) { GError *error = NULL; if (!dbus_g_proxy_call (user->proxy, "SetIconFile", &error, G_TYPE_STRING, icon_file, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetIconFile call failed: %s", error->message); g_error_free (error); return; } } void um_user_set_icon_data (UmUser *user, GdkPixbuf *pixbuf) { gchar *path; gint fd; GOutputStream *stream; GError *error; path = g_build_filename (g_get_tmp_dir (), "usericonXXXXXX", NULL); fd = g_mkstemp (path); if (fd == -1) { g_warning ("failed to create temporary file for image data"); g_free (path); return; } stream = g_unix_output_stream_new (fd, TRUE); error = NULL; if (!gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, &error, NULL)) { g_warning ("failed to save image: %s", error->message); g_error_free (error); g_object_unref (stream); return; } g_object_unref (stream); um_user_set_icon_file (user, path); /* if we ever make the dbus call async, the g_remove call needs * to wait for its completion */ g_remove (path); g_free (path); } void um_user_set_account_type (UmUser *user, gint account_type) { GError *error = NULL; if (!dbus_g_proxy_call (user->proxy, "SetAccountType", &error, G_TYPE_INT, account_type, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetAccountType call failed: %s", error->message); g_error_free (error); return; } } static gchar salt_char (GRand *rand) { gchar salt[] = "ABCDEFGHIJKLMNOPQRSTUVXYZ" "abcdefghijklmnopqrstuvxyz" "./0123456789"; return salt[g_rand_int_range (rand, 0, G_N_ELEMENTS (salt))]; } static gchar * make_crypted (const gchar *plain) { GString *salt; gchar *result; GRand *rand; gint i; rand = g_rand_new (); salt = g_string_sized_new (21); /* SHA 256 */ g_string_append (salt, "$6$"); for (i = 0; i < 16; i++) { g_string_append_c (salt, salt_char (rand)); } g_string_append_c (salt, '$'); result = g_strdup (crypt (plain, salt->str)); g_string_free (salt, TRUE); g_rand_free (rand); return result; } void um_user_set_password (UmUser *user, gint password_mode, const gchar *password, const gchar *hint) { GError *error = NULL; gchar *crypted; if (password_mode == 0) { crypted = make_crypted (password); if (!dbus_g_proxy_call (user->proxy, "SetPassword", &error, G_TYPE_STRING, crypted, G_TYPE_STRING, hint, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetPassword call failed: %s", error->message); g_error_free (error); } memset (crypted, 0, strlen (crypted)); g_free (crypted); } else if (password_mode == 3 || password_mode == 4) { /* FIXME: this is a slightly odd side-effect: * you disable the account, and autologin flips * we should remove that once gdm knows to * ignore autologin for disabled accounts */ if (password_mode == 3 && um_user_get_automatic_login (user)) { um_user_set_automatic_login (user, FALSE); } if (!dbus_g_proxy_call (user->proxy, "SetLocked", &error, G_TYPE_BOOLEAN, (password_mode == 3), G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetLocked call failed: %s", error->message); g_error_free (error); } } else { if (!dbus_g_proxy_call (user->proxy, "SetPasswordMode", &error, G_TYPE_INT, password_mode, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetPasswordMode call failed: %s", error->message); g_error_free (error); } } } #ifdef HAVE_SYSTEMD #include gboolean um_user_is_logged_in (UmUser *user) { int n_sessions; n_sessions = sd_uid_get_sessions (um_user_get_uid (user), 0, NULL) > 0; return n_sessions > 0; } #else gboolean um_user_is_logged_in (UmUser *user) { DBusGProxy *proxy; GPtrArray *array; GError *error; gint n_sessions; proxy = dbus_g_proxy_new_for_name (user->bus, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager"); array = NULL; error = NULL; if (!dbus_g_proxy_call (proxy, "GetSessionsForUnixUser", &error, G_TYPE_UINT, um_user_get_uid (user), G_TYPE_INVALID, dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &array, G_TYPE_INVALID)) { g_warning ("GetSessionsForUnixUser failed: %s", error->message); g_error_free (error); return FALSE; } n_sessions = array->len; g_ptr_array_foreach (array, (GFunc)g_free, NULL); g_ptr_array_free (array, TRUE); g_object_unref (proxy); return n_sessions > 0; } #endif void um_user_set_automatic_login (UmUser *user, gboolean enabled) { GError *error = NULL; if (!dbus_g_proxy_call (user->proxy, "SetAutomaticLogin", &error, G_TYPE_BOOLEAN, enabled, G_TYPE_INVALID, G_TYPE_INVALID)) { g_warning ("SetAutomaticLogin call failed: %s", error->message); g_error_free (error); } } void um_user_show_full_display_name (UmUser *user) { char *uniq_name; g_return_if_fail (UM_IS_USER (user)); if (user->props->real_name != NULL) { uniq_name = g_strdup_printf ("%s (%s)", user->props->real_name, user->props->user_name); } else { uniq_name = NULL; } if (uniq_name && g_strcmp0 (uniq_name, user->display_name) != 0) { g_free (user->display_name); user->display_name = uniq_name; } else { g_free (uniq_name); } } void um_user_show_short_display_name (UmUser *user) { g_return_if_fail (UM_IS_USER (user)); if (user->display_name) { g_free (user->display_name); user->display_name = NULL; } }