diff options
author | Marius Vollmer <mvollmer@redhat.com> | 2013-10-22 21:31:10 +0300 |
---|---|---|
committer | Marius Vollmer <mvollmer@redhat.com> | 2013-10-22 21:31:25 +0300 |
commit | d4869d33b609f87146531ca284fce3aa680d15e3 (patch) | |
tree | c01f8ef1304d9848fe12693f5bcf78199edb3da0 | |
parent | 7098d4999d035b4bc47f714711c7e6f0c9aabc01 (diff) | |
download | accountsservice-d4869d33b609f87146531ca284fce3aa680d15e3.tar.gz |
Daemon: Support for local groups.
-rw-r--r-- | data/org.freedesktop.Accounts.Group.xml | 149 | ||||
-rw-r--r-- | data/org.freedesktop.Accounts.User.xml | 50 | ||||
-rw-r--r-- | data/org.freedesktop.Accounts.xml | 134 | ||||
-rw-r--r-- | src/Makefile.am | 7 | ||||
-rw-r--r-- | src/daemon.c | 631 | ||||
-rw-r--r-- | src/daemon.h | 10 | ||||
-rw-r--r-- | src/group.c | 483 | ||||
-rw-r--r-- | src/group.h | 55 | ||||
-rw-r--r-- | src/types.h | 1 | ||||
-rw-r--r-- | src/user.c | 63 | ||||
-rw-r--r-- | src/user.h | 6 | ||||
-rw-r--r-- | src/util.c | 32 | ||||
-rw-r--r-- | src/util.h | 3 |
13 files changed, 1586 insertions, 38 deletions
diff --git a/data/org.freedesktop.Accounts.Group.xml b/data/org.freedesktop.Accounts.Group.xml new file mode 100644 index 0000000..50b871f --- /dev/null +++ b/data/org.freedesktop.Accounts.Group.xml @@ -0,0 +1,149 @@ +<!DOCTYPE node PUBLIC +"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" > +<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <interface name="org.freedesktop.Accounts.Group"> + + <method name="SetGroupName"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="name" direction="in" type="s"> + <doc:doc> + <doc:summary> + The new groupname. + </doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para> + Sets the groups groupname. Note that it is usually not allowed + to have multiple groups with the same groupname. + </doc:para> + </doc:description> + <doc:permission> + The caller needs the following PolicyKit authorizations: + <doc:list> + <doc:item> + <doc:term>org.freedesktop.accounts.user-administration</doc:term> + </doc:item> + </doc:list> + </doc:permission> + <doc:errors> + <doc:error name="org.freedesktop.Accounts.Error.PermissionDenied">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="org.freedesktop.Accounts.Error.Failed">if the operation failed</doc:error> + </doc:errors> + </doc:doc> + </method> + + <method name="AddUser"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="user" direction="in" type="o"> + <doc:doc> + <doc:summary> + The object path of the user to add to the group + </doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para> + Adds a user to the group as a direct member. + </doc:para> + </doc:description> + <doc:permission> + The caller needs the following PolicyKit authorizations: + <doc:list> + <doc:item> + <doc:term>org.freedesktop.accounts.user-administration</doc:term> + </doc:item> + </doc:list> + </doc:permission> + <doc:errors> + <doc:error name="org.freedesktop.Accounts.Error.PermissionDenied">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="org.freedesktop.Accounts.Error.Failed">if the operation failed</doc:error> + </doc:errors> + </doc:doc> + </method> + + <method name="RemoveUser"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="user" direction="in" type="o"> + <doc:doc> + <doc:summary> + The object path of the user to remove from the group. + </doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para> + Removes a user from the direct members of a group. + </doc:para> + </doc:description> + <doc:permission> + The caller needs the following PolicyKit authorizations: + <doc:list> + <doc:item> + <doc:term>org.freedesktop.accounts.user-administration</doc:term> + </doc:item> + </doc:list> + </doc:permission> + <doc:errors> + <doc:error name="org.freedesktop.Accounts.Error.PermissionDenied">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="org.freedesktop.Accounts.Error.Failed">if the operation failed</doc:error> + </doc:errors> + </doc:doc> + </method> + + <property name="Gid" type="t" access="read"> + <doc:doc> + <doc:description> + <doc:para> + The gid of the group. + </doc:para> + </doc:description> + </doc:doc> + </property> + + <property name="GroupName" type="s" access="read"> + <doc:doc> + <doc:description> + <doc:para> + The groupname of the group. + </doc:para> + </doc:description> + </doc:doc> + </property> + + <property name="LocalGroup" type="b" access="read"> + <doc:doc> + <doc:description> + <doc:para> + Whether the group is a local group or not. + </doc:para> + </doc:description> + </doc:doc> + </property> + + <property name="Users" type="ao" access="read"> + <doc:doc> + <doc:description> + <doc:para> + The users that are direct members of this group. + </doc:para> + </doc:description> + </doc:doc> + </property> + + <signal name="Changed"> + <doc:doc> + <doc:description> + <doc:para> + Emitted when the group has changed. + </doc:para> + </doc:description> + </doc:doc> + </signal> + + </interface> +</node> diff --git a/data/org.freedesktop.Accounts.User.xml b/data/org.freedesktop.Accounts.User.xml index 88198d8..6e7871f 100644 --- a/data/org.freedesktop.Accounts.User.xml +++ b/data/org.freedesktop.Accounts.User.xml @@ -533,6 +533,42 @@ </doc:doc> </method> + <method name="FindGroups"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="indirect" direction="in" type="b"> + <doc:doc> + <doc:summary> + Whether to also look for groups that this user is only an + indirect member of. + </doc:summary> + </doc:doc> + </arg> + <arg name="groups" direction="out" type="ao"> + <doc:doc> + <doc:summary> + The groups that include this user. + </doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para> + Finds all groups that this user is a member of. This will + search all groups, not only the cached ones, and might thus + be slow. + </doc:para> + <doc:para> + A user is a direct member of a group when that group + explicitly lists the user. A user is an indirect member of + a group G if it is a member of a group that is listed by G. + </doc:para> + </doc:description> + <doc:errors> + <doc:error name="org.freedesktop.Accounts.Error.Failed">if the operation failed</doc:error> + </doc:errors> + </doc:doc> + </method> + <property name="Uid" type="t" access="read"> <doc:doc> <doc:description> @@ -766,6 +802,20 @@ </doc:doc> </property> + <property name="CachedGroups" type="ao" access="read"> + <doc:doc> + <doc:description> + <doc:para> + The cached groups that the user is a member of (directly or + indirectly). This is not an exhaustive list of all groups + that the user is a member of; the user might be a member of + groups that are not currently cached. To find all such + groups, use the FindGroups method. + </doc:para> + </doc:description> + </doc:doc> + </property> + <signal name="Changed"> <doc:doc> <doc:description> diff --git a/data/org.freedesktop.Accounts.xml b/data/org.freedesktop.Accounts.xml index 692540a..5ea60c6 100644 --- a/data/org.freedesktop.Accounts.xml +++ b/data/org.freedesktop.Accounts.xml @@ -214,6 +214,140 @@ </doc:doc> </signal> + <method name="ListCachedGroups"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="users" direction="out" type="ao"> + <doc:doc><doc:summary>Object paths of cached groups</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Lists the groups that are currently known to the system. + Groups that are defined locally are always known, but + remotely defined groups might only become known after they + have been explicitly mentioned in a call to + FindGroupByName, or similar operations. + </doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="FindGroupById"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="id" direction="in" type="x"> + <doc:doc><doc:summary>The gid to look up</doc:summary></doc:doc> + </arg> + <arg name="group" direction="out" type="o"> + <doc:doc><doc:summary>Object path of group</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Finds a group by gid. + </doc:para> + </doc:description> + <doc:errors> + <doc:error name="org.freedesktop.Accounts.Error.Failed">if no group with the given gid exists</doc:error> + </doc:errors> + </doc:doc> + </method> + + <method name="FindGroupByName"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="name" direction="in" type="s"> + <doc:doc><doc:summary>The groupname to look up</doc:summary></doc:doc> + </arg> + <arg name="group" direction="out" type="o"> + <doc:doc><doc:summary>Object path of group</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Finds a group by its groupname. + </doc:para> + </doc:description> + <doc:errors> + <doc:error name="org.freedesktop.Accounts.Error.Failed">if no group with the given groupname exists</doc:error> + </doc:errors> + </doc:doc> + </method> + + <method name="CreateGroup"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="name" direction="in" type="s"> + <doc:doc><doc:summary>The groupname for the new group</doc:summary></doc:doc> + </arg> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="group" direction="out" type="o"> + <doc:doc><doc:summary>Object path of the new group</doc:summary></doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para> + Creates a new group. + </doc:para> + </doc:description> + <doc:permission> + The caller needs the org.freedesktop.accounts.user-administration PolicyKit authorization. + </doc:permission> + <doc:errors> + <doc:error name="org.freedesktop.Accounts.Error.PermissionDenied">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="org.freedesktop.Accounts.Error.Failed">if the operation failed</doc:error> + </doc:errors> + </doc:doc> + </method> + + <method name="DeleteGroup"> + <annotation name="org.freedesktop.DBus.GLib.Async" value=""/> + <arg name="id" direction="in" type="x"> + <doc:doc><doc:summary>The gid to delete</doc:summary></doc:doc> + </arg> + + <doc:doc> + <doc:description> + <doc:para> + Deletes a user account. + </doc:para> + </doc:description> + <doc:permission> + The caller needs the org.freedesktop.accounts.user-administration PolicyKit authorization. + </doc:permission> + <doc:errors> + <doc:error name="org.freedesktop.Accounts.Error.PermissionDenied">if the caller lacks the appropriate PolicyKit authorization</doc:error> + <doc:error name="org.freedesktop.Accounts.Error.Failed">if the operation failed</doc:error> + </doc:errors> + </doc:doc> + </method> + + <signal name="GroupAdded"> + <arg name="user" type="o"> + <doc:doc><doc:summary>Object path of the group that was added.</doc:summary></doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para> + Emitted when a group becomes known to the local system. + </doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="GroupDeleted"> + <arg name="user" type="o"> + <doc:doc><doc:summary>Object path of the group that was deleted.</doc:summary></doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para> + Emitted when a group is no longer known to the local system. + </doc:para> + </doc:description> + </doc:doc> + </signal> + <property name="DaemonVersion" type="s" access="read"> <doc:doc> <doc:description> diff --git a/src/Makefile.am b/src/Makefile.am index 6940f2d..b9a4472 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,6 +18,8 @@ libaccounts_generated_la_SOURCES = \ accounts-generated.h \ accounts-user-generated.c \ accounts-user-generated.h \ + accounts-group-generated.c \ + accounts-group-generated.h \ $(NULL) BUILT_SOURCES += $(libaccounts_generated_la_SOURCES) @@ -27,6 +29,9 @@ accounts-generated.c accounts-generated.h: $(top_srcdir)/data/org.freedesktop.Ac accounts-user-generated.c accounts-user-generated.h: $(top_srcdir)/data/org.freedesktop.Accounts.User.xml Makefile gdbus-codegen --generate-c-code accounts-user-generated --c-namespace Accounts --interface-prefix=org.freedesktop.Accounts. $(top_srcdir)/data/org.freedesktop.Accounts.User.xml +accounts-group-generated.c accounts-group-generated.h: $(top_srcdir)/data/org.freedesktop.Accounts.Group.xml Makefile + gdbus-codegen --generate-c-code accounts-group-generated --c-namespace Accounts --interface-prefix=org.freedesktop.Accounts. $(top_srcdir)/data/org.freedesktop.Accounts.Group.xml + libexec_PROGRAMS = accounts-daemon accounts_daemon_SOURCES = \ @@ -39,6 +44,8 @@ accounts_daemon_SOURCES = \ user-classify.c \ user.h \ user.c \ + group.h \ + group.c \ util.h \ util.c \ main.c diff --git a/src/daemon.c b/src/daemon.c index 44e3ecc..9285c94 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -23,7 +23,6 @@ #include "config.h" #include <stdlib.h> -#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> @@ -65,6 +64,9 @@ struct DaemonPrivate { GDBusProxy *bus_proxy; GHashTable *users; + GHashTable *groups; + + GHashTable *objpath_to_users; User *autologin; @@ -83,7 +85,8 @@ struct DaemonPrivate { GHashTable *extension_ifaces; }; -typedef struct passwd * (* EntryGeneratorFunc) (GHashTable *, gpointer *); +typedef struct passwd * (* UserEntryGeneratorFunc) (GHashTable *, gpointer *); +typedef struct group * (* GroupEntryGeneratorFunc) (GHashTable *, gpointer *); static void daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface); @@ -97,7 +100,9 @@ static const GDBusErrorEntry accounts_error_entries[] = { ERROR_USER_EXISTS, "org.freedesktop.Accounts.Error.UserExists" }, { ERROR_USER_DOES_NOT_EXIST, "org.freedesktop.Accounts.Error.UserDoesNotExist" }, { ERROR_PERMISSION_DENIED, "org.freedesktop.Accounts.Error.PermissionDenied" }, - { ERROR_NOT_SUPPORTED, "org.freedesktop.Accounts.Error.NotSupported" } + { ERROR_NOT_SUPPORTED, "org.freedesktop.Accounts.Error.NotSupported" }, + { ERROR_GROUP_EXISTS, "org.freedesktop.Accounts.Error.GroupExists" }, + { ERROR_GROUP_DOES_NOT_EXIST, "org.freedesktop.Accounts.Error.GroupDoesNotExist" } }; GQuark @@ -128,6 +133,7 @@ error_get_type (void) ENUM_ENTRY (ERROR_USER_DOES_NOT_EXIST, "UserDoesntExist"), ENUM_ENTRY (ERROR_PERMISSION_DENIED, "PermissionDenied"), ENUM_ENTRY (ERROR_NOT_SUPPORTED, "NotSupported"), + ENUM_ENTRY (ERROR_GROUP_EXISTS, "GroupExists"), { 0, 0, 0 } }; g_assert (NUM_ERRORS == G_N_ELEMENTS (values) - 1); @@ -136,6 +142,68 @@ error_get_type (void) return etype; } + +static gboolean +daemon_local_group_is_excluded (Daemon *daemon, struct group *grent) +{ + // We ignore groups that are the primary group of a same-named + // user. + + struct passwd *pwent = getpwnam (grent->gr_name); + if (pwent && pwent->pw_gid == grent->gr_gid) + return TRUE; + + return FALSE; +} + +static void +register_user (Daemon *daemon, + User *user) +{ + user_register (user); + g_hash_table_insert (daemon->priv->objpath_to_users, (gchar *)user_get_object_path (user), user); +} + +static void +unregister_user (Daemon *daemon, + User *user) +{ + g_hash_table_remove (daemon->priv->objpath_to_users, user_get_object_path (user)); + user_unregister (user); +} + +User * +daemon_local_get_user (Daemon *daemon, + const gchar *object_path) +{ + return g_hash_table_lookup (daemon->priv->objpath_to_users, object_path); +} + +static void +register_group (Daemon *daemon, + Group *group) +{ + GError *error = NULL; + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (group), + daemon->priv->bus_connection, + group_get_object_path (group), + &error)) { + if (error != NULL) { + g_critical ("error exporting group object: %s", error->message); + g_error_free (error); + } + return; + } +} + +static void +unregister_group (Daemon *daemon, + Group *group) +{ + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (group)); +} + #ifdef HAVE_UTMPX_H typedef struct { @@ -400,10 +468,39 @@ entry_generator_cachedir (GHashTable *users, return NULL; } +static struct group * +entry_generator_fgetgrent (GHashTable *users, + gpointer *state) +{ + struct group *grent; + FILE *fp; + + /* First iteration */ + if (*state == NULL) { + *state = fp = fopen (PATH_GROUP, "r"); + if (fp == NULL) { + g_warning ("Unable to open %s: %s", PATH_GROUP, g_strerror (errno)); + return NULL; + } + } + + /* Every iteration */ + fp = *state; + grent = fgetgrent (fp); + if (grent != NULL) { + return grent; + } + + /* Last iteration */ + fclose (fp); + *state = NULL; + return NULL; +} + static void -load_entries (Daemon *daemon, - GHashTable *users, - EntryGeneratorFunc entry_generator) +load_user_entries (Daemon *daemon, + GHashTable *users, + UserEntryGeneratorFunc entry_generator) { gpointer generator_state = NULL; struct passwd *pwent; @@ -446,6 +543,53 @@ load_entries (Daemon *daemon, g_assert (generator_state == NULL); } +static void +load_group_entries (Daemon *daemon, + GHashTable *users, + GHashTable *groups, + GroupEntryGeneratorFunc entry_generator) +{ + gpointer generator_state = NULL; + struct group *grent; + Group *group = NULL; + + g_assert (entry_generator != NULL); + + for (;;) { + grent = entry_generator (groups, &generator_state); + if (grent == NULL) + break; + + /* Skip excluded groups... */ + if (daemon_local_group_is_excluded (daemon, grent)) { + g_debug ("skipping group: %s", grent->gr_name); + continue; + } + + /* ignore duplicate entries */ + if (g_hash_table_lookup (groups, grent->gr_name)) { + continue; + } + + group = g_hash_table_lookup (daemon->priv->groups, grent->gr_name); + if (group == NULL) { + group = group_new (daemon, grent->gr_gid); + } else { + g_object_ref (group); + } + + /* freeze & update group */ + g_object_freeze_notify (G_OBJECT (group)); + group_update_from_grent (group, grent, users); + + g_hash_table_insert (groups, g_strdup (group_get_group_name (group)), group); + g_debug ("loaded group: %s", group_get_group_name (group)); + } + + /* Generator should have cleaned up */ + g_assert (generator_state == NULL); +} + static GHashTable * create_users_hash_table (void) { @@ -455,18 +599,67 @@ create_users_hash_table (void) g_object_unref); } +static GHashTable * +create_groups_hash_table (void) +{ + return g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); +} + static void -reload_users (Daemon *daemon) +update_user_cached_groups (GHashTable *users, GHashTable *groups) +{ + GHashTable *objpath_to_user; + GHashTableIter iter; + gpointer name; + User *user; + Group *group; + + objpath_to_user = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_iter_init (&iter, users); + while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) { + g_hash_table_insert (objpath_to_user, (gchar *)user_get_object_path (user), user); + user_reset_cached_groups (user); + } + + g_hash_table_iter_init (&iter, groups); + while (g_hash_table_iter_next (&iter, &name, (gpointer *)&group)) { + GStrv members = group_get_users (group); + int i; + for (i = 0; members[i]; i++) { + User *user = g_hash_table_lookup (objpath_to_user, members[i]); + if (user) + user_add_cached_group (user, group); + } + } + + g_hash_table_iter_init (&iter, users); + while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) { + user_finish_cached_groups (user); + } + + g_hash_table_destroy (objpath_to_user); +} + +static void +reload_users_and_groups (Daemon *daemon) { GHashTable *users; GHashTable *old_users; + GHashTable *groups; + GHashTable *old_groups; GHashTable *local; GHashTableIter iter; gpointer name; User *user; + Group *group; - /* Track the users that we saw during our (re)load */ + /* Track the users and groups that we saw during our (re)load */ users = create_users_hash_table (); + groups = create_groups_hash_table (); /* * NOTE: As we load data from all the sources, notifies are @@ -475,7 +668,7 @@ reload_users (Daemon *daemon) */ /* Load the local users into our hash table */ - load_entries (daemon, users, entry_generator_fgetpwent); + load_user_entries (daemon, users, entry_generator_fgetpwent); local = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_iter_init (&iter, users); while (g_hash_table_iter_next (&iter, &name, NULL)) @@ -483,9 +676,9 @@ reload_users (Daemon *daemon) /* Now add/update users from other sources, possibly non-local */ #ifdef HAVE_UTMPX_H - load_entries (daemon, users, entry_generator_wtmp); + load_user_entries (daemon, users, entry_generator_wtmp); #endif - load_entries (daemon, users, entry_generator_cachedir); + load_user_entries (daemon, users, entry_generator_cachedir); /* Mark which users are local, which are not */ g_hash_table_iter_init (&iter, users); @@ -494,6 +687,12 @@ reload_users (Daemon *daemon) g_hash_table_destroy (local); + /* Now the groups. + */ + load_group_entries (daemon, users, groups, entry_generator_fgetgrent); + + update_user_cached_groups (users, groups); + /* Swap out the users */ old_users = daemon->priv->users; daemon->priv->users = users; @@ -502,7 +701,7 @@ reload_users (Daemon *daemon) g_hash_table_iter_init (&iter, old_users); while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) { if (!g_hash_table_lookup (users, name)) { - user_unregister (user); + unregister_user (daemon, user); accounts_accounts_emit_user_deleted (ACCOUNTS_ACCOUNTS (daemon), user_get_object_path (user)); } @@ -512,7 +711,7 @@ reload_users (Daemon *daemon) g_hash_table_iter_init (&iter, users); while (g_hash_table_iter_next (&iter, &name, (gpointer *)&user)) { if (!g_hash_table_lookup (old_users, name)) { - user_register (user); + register_user (daemon, user); accounts_accounts_emit_user_added (ACCOUNTS_ACCOUNTS (daemon), user_get_object_path (user)); } @@ -520,12 +719,40 @@ reload_users (Daemon *daemon) } g_hash_table_destroy (old_users); + + /* Swap out the groups. + */ + old_groups = daemon->priv->groups; + daemon->priv->groups = groups; + + /* Remove all the old groups */ + g_hash_table_iter_init (&iter, old_groups); + while (g_hash_table_iter_next (&iter, &name, (gpointer *)&group)) { + if (!g_hash_table_lookup (groups, name)) { + unregister_group (daemon, group); + accounts_accounts_emit_group_deleted (ACCOUNTS_ACCOUNTS (daemon), + group_get_object_path (group)); + } + } + + /* Register all the new groups */ + g_hash_table_iter_init (&iter, groups); + while (g_hash_table_iter_next (&iter, &name, (gpointer *)&group)) { + if (!g_hash_table_lookup (old_groups, name)) { + register_group (daemon, group); + accounts_accounts_emit_group_added (ACCOUNTS_ACCOUNTS (daemon), + group_get_object_path (group)); + } + g_object_thaw_notify (G_OBJECT (group)); + } + + g_hash_table_destroy (old_groups); } static gboolean -reload_users_timeout (Daemon *daemon) +reload_users_and_groups_timeout (Daemon *daemon) { - reload_users (daemon); + reload_users_and_groups (daemon); daemon->priv->reload_id = 0; return FALSE; @@ -582,7 +809,7 @@ reload_autologin_timeout (Daemon *daemon) } static void -queue_reload_users_soon (Daemon *daemon) +queue_reload_users_and_groups_soon (Daemon *daemon) { if (daemon->priv->reload_id > 0) { return; @@ -591,17 +818,17 @@ queue_reload_users_soon (Daemon *daemon) /* we wait half a second or so in case /etc/passwd and * /etc/shadow are changed at the same time, or repeatedly. */ - daemon->priv->reload_id = g_timeout_add (500, (GSourceFunc)reload_users_timeout, daemon); + daemon->priv->reload_id = g_timeout_add (500, (GSourceFunc)reload_users_and_groups_timeout, daemon); } static void -queue_reload_users (Daemon *daemon) +queue_reload_users_and_groups (Daemon *daemon) { if (daemon->priv->reload_id > 0) { return; } - daemon->priv->reload_id = g_idle_add ((GSourceFunc)reload_users_timeout, daemon); + daemon->priv->reload_id = g_idle_add ((GSourceFunc)reload_users_and_groups_timeout, daemon); } static void @@ -615,18 +842,18 @@ queue_reload_autologin (Daemon *daemon) } static void -on_users_monitor_changed (GFileMonitor *monitor, - GFile *file, - GFile *other_file, - GFileMonitorEvent event_type, - Daemon *daemon) +on_users_and_groups_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + Daemon *daemon) { if (event_type != G_FILE_MONITOR_EVENT_CHANGED && event_type != G_FILE_MONITOR_EVENT_CREATED) { return; } - queue_reload_users_soon (daemon); + queue_reload_users_and_groups_soon (daemon); } static void @@ -686,28 +913,30 @@ daemon_init (Daemon *daemon) daemon->priv->extension_ifaces = daemon_read_extension_ifaces (); daemon->priv->users = create_users_hash_table (); + daemon->priv->groups = create_groups_hash_table (); + daemon->priv->objpath_to_users = g_hash_table_new (g_str_hash, g_str_equal); daemon->priv->passwd_monitor = setup_monitor (daemon, PATH_PASSWD, - on_users_monitor_changed); + on_users_and_groups_monitor_changed); daemon->priv->shadow_monitor = setup_monitor (daemon, PATH_SHADOW, - on_users_monitor_changed); + on_users_and_groups_monitor_changed); daemon->priv->group_monitor = setup_monitor (daemon, PATH_GROUP, - on_users_monitor_changed); + on_users_and_groups_monitor_changed); #ifdef HAVE_UTMPX_H daemon->priv->wtmp_monitor = setup_monitor (daemon, PATH_WTMP, - on_users_monitor_changed); + on_users_and_groups_monitor_changed); #endif daemon->priv->gdm_monitor = setup_monitor (daemon, PATH_GDM_CUSTOM, on_gdm_monitor_changed); - queue_reload_users (daemon); + queue_reload_users_and_groups (daemon); queue_reload_autologin (daemon); } @@ -727,6 +956,8 @@ daemon_finalize (GObject *object) g_object_unref (daemon->priv->bus_connection); g_hash_table_destroy (daemon->priv->users); + g_hash_table_destroy (daemon->priv->groups); + g_hash_table_destroy (daemon->priv->objpath_to_users); g_hash_table_unref (daemon->priv->extension_ifaces); @@ -818,7 +1049,7 @@ add_new_user_for_pwent (Daemon *daemon, user = user_new (daemon, pwent->pw_uid); user_update_from_pwent (user, pwent); - user_register (user); + register_user (daemon, user); g_hash_table_insert (daemon->priv->users, g_strdup (user_get_user_name (user)), @@ -881,6 +1112,76 @@ daemon_local_find_user_by_name (Daemon *daemon, return user; } +static Group * +add_new_group_for_grent (Daemon *daemon, + struct group *grent) +{ + Group *group; + + group = group_new (daemon, grent->gr_gid); + group_update_from_grent (group, grent, daemon->priv->users); + register_group (daemon, group); + + g_hash_table_insert (daemon->priv->groups, + g_strdup (group_get_group_name (group)), + group); + + update_user_cached_groups (daemon->priv->users, daemon->priv->groups); + + accounts_accounts_emit_group_added (ACCOUNTS_ACCOUNTS (daemon), group_get_object_path (group)); + + return group; +} + +static Group * +find_group_by_grent (Daemon *daemon, + struct group *grent) +{ + Group *group; + + if (daemon_local_group_is_excluded (daemon, grent)) { + g_debug ("rejecting excluded group %s/%d", grent->gr_name, grent->gr_gid); + return NULL; + } + + group = g_hash_table_lookup (daemon->priv->groups, grent->gr_name); + + if (group == NULL) + group = add_new_group_for_grent (daemon, grent); + + return group; +} + +Group * +daemon_local_find_group_by_id (Daemon *daemon, + gid_t gid) +{ + struct group *grent; + + grent = getgrgid (gid); + if (grent == NULL) { + g_debug ("unable to lookup gid %d", (int)gid); + return NULL; + } + + return find_group_by_grent (daemon, grent); +} + +Group * +daemon_local_find_group_by_name (Daemon *daemon, + const gchar *name) +{ + struct group *grent; + + grent = getgrnam (name); + if (grent == NULL) { + g_debug ("unable to lookup name %s: %s", name, g_strerror (errno)); + return NULL; + } + + return find_group_by_grent (daemon, grent); +} + User * daemon_local_get_automatic_login_user (Daemon *daemon) { @@ -1000,6 +1301,79 @@ daemon_list_cached_users (AccountsAccounts *accounts, return TRUE; } +typedef struct { + Daemon *daemon; + GDBusMethodInvocation *context; +} ListGroupData; + + +static ListGroupData * +list_group_data_new (Daemon *daemon, + GDBusMethodInvocation *context) +{ + ListGroupData *data; + + data = g_new0 (ListGroupData, 1); + + data->daemon = g_object_ref (daemon); + data->context = context; + + return data; +} + +static void +list_group_data_free (ListGroupData *data) +{ + g_object_unref (data->daemon); + g_free (data); +} + +static gboolean +finish_list_cached_groups (gpointer user_data) +{ + ListGroupData *data = user_data; + GPtrArray *object_paths; + GHashTableIter iter; + const gchar *name; + Group *group; + + object_paths = g_ptr_array_new (); + + g_hash_table_iter_init (&iter, data->daemon->priv->groups); + while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&group)) { + g_ptr_array_add (object_paths, (gpointer) group_get_object_path (group)); + } + g_ptr_array_add (object_paths, NULL); + + accounts_accounts_complete_list_cached_groups (NULL, data->context, (const gchar * const *) object_paths->pdata); + + g_ptr_array_free (object_paths, TRUE); + + list_group_data_free (data); + + return FALSE; +} + +static gboolean +daemon_list_cached_groups (AccountsAccounts *accounts, + GDBusMethodInvocation *context) +{ + Daemon *daemon = (Daemon*)accounts; + ListGroupData *data; + + data = list_group_data_new (daemon, context); + + if (daemon->priv->reload_id > 0) { + /* reload in progress, wait a bit */ + g_idle_add (finish_list_cached_groups, data); + } + else { + finish_list_cached_groups (data); + } + + return TRUE; +} + static const gchar * daemon_get_daemon_version (AccountsAccounts *object) { @@ -1031,7 +1405,7 @@ typedef struct { } CreateUserData; static void -create_data_free (gpointer data) +create_user_data_free (gpointer data) { CreateUserData *cd = data; @@ -1119,7 +1493,7 @@ daemon_create_user (AccountsAccounts *accounts, daemon_create_user_authorized_cb, context, data, - (GDestroyNotify)create_data_free); + (GDestroyNotify)create_user_data_free); return TRUE; } @@ -1208,7 +1582,7 @@ daemon_uncache_user_authorized_cb (Daemon *daemon, accounts_accounts_complete_uncache_user (NULL, context); - queue_reload_users (daemon); + queue_reload_users_and_groups (daemon); } static gboolean @@ -1334,6 +1708,186 @@ daemon_delete_user (AccountsAccounts *accounts, return TRUE; } +/** Groups */ + +static gboolean +daemon_find_group_by_id (AccountsAccounts *accounts, + GDBusMethodInvocation *context, + gint64 gid) +{ + Daemon *daemon = (Daemon*)accounts; + Group *group; + + group = daemon_local_find_group_by_id (daemon, gid); + + if (group) { + accounts_accounts_complete_find_group_by_id (NULL, context, group_get_object_path (group)); + } + else { + throw_error (context, ERROR_FAILED, "Failed to look up group with gid %d.", (int)gid); + } + + return TRUE; +} + +static gboolean +daemon_find_group_by_name (AccountsAccounts *accounts, + GDBusMethodInvocation *context, + const gchar *name) +{ + Daemon *daemon = (Daemon*)accounts; + Group *group; + + group = daemon_local_find_group_by_name (daemon, name); + + if (group) { + accounts_accounts_complete_find_group_by_name (NULL, context, group_get_object_path (group)); + } + else { + throw_error (context, ERROR_FAILED, "Failed to look up group with name %s.", name); + } + + return TRUE; +} + +typedef struct { + gchar *group_name; +} CreateGroupData; + +static void +create_group_data_free (gpointer data) +{ + CreateGroupData *cd = data; + + g_free (cd->group_name); + g_free (cd); +} + +static void +daemon_create_group_authorized_cb (Daemon *daemon, + User *dummy, + GDBusMethodInvocation *context, + gpointer data) + +{ + CreateGroupData *cd = data; + Group *group; + GError *error; + const gchar *argv[4]; + + if (getgrnam (cd->group_name) != NULL) { + throw_error (context, ERROR_GROUP_EXISTS, "A group with name '%s' already exists", cd->group_name); + return; + } + + sys_log (context, "create group '%s'", cd->group_name); + + argv[0] = "/usr/sbin/groupadd"; + argv[1] = "--"; + argv[2] = cd->group_name; + argv[3] = NULL; + + error = NULL; + if (!spawn_with_login_uid (context, argv, &error)) { + throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message); + g_error_free (error); + return; + } + + group = daemon_local_find_group_by_name (daemon, cd->group_name); + + accounts_accounts_complete_create_group (NULL, context, group_get_object_path (group)); +} + +static gboolean +daemon_create_group (AccountsAccounts *accounts, + GDBusMethodInvocation *context, + const gchar *group_name) +{ + Daemon *daemon = (Daemon*)accounts; + CreateGroupData *data; + + data = g_new0 (CreateGroupData, 1); + data->group_name = g_strdup (group_name); + + daemon_local_check_auth (daemon, + NULL, + "org.freedesktop.accounts.user-administration", + TRUE, + daemon_create_group_authorized_cb, + context, + data, + (GDestroyNotify)create_group_data_free); + + return TRUE; +} + +typedef struct { + gint64 gid; +} DeleteGroupData; + +static void +daemon_delete_group_authorized_cb (Daemon *daemon, + User *dummy, + GDBusMethodInvocation *context, + gpointer data) + +{ + DeleteGroupData *gd = data; + GError *error; + struct group *grent; + const gchar *argv[4]; + + grent = getgrgid (gd->gid); + + if (grent == NULL) { + throw_error (context, ERROR_GROUP_DOES_NOT_EXIST, "No group with gid %d found", gd->gid); + return; + } + + sys_log (context, "delete group '%s' (%d)", grent->gr_name, gd->gid); + + argv[0] = "/usr/sbin/groupdel"; + argv[1] = "--"; + argv[2] = grent->gr_name; + argv[3] = NULL; + + error = NULL; + if (!spawn_with_login_uid (context, argv, &error)) { + throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message); + g_error_free (error); + return; + } + + accounts_accounts_complete_delete_group (NULL, context); +} + + +static gboolean +daemon_delete_group (AccountsAccounts *accounts, + GDBusMethodInvocation *context, + gint64 gid) +{ + Daemon *daemon = (Daemon*)accounts; + DeleteGroupData *data; + + data = g_new0 (DeleteGroupData, 1); + data->gid = gid; + + daemon_local_check_auth (daemon, + NULL, + "org.freedesktop.accounts.user-administration", + TRUE, + daemon_delete_group_authorized_cb, + context, + data, + (GDestroyNotify)g_free); + + return TRUE; +} + +/** Auth helpers */ + typedef struct { Daemon *daemon; User *user; @@ -1616,7 +2170,14 @@ daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface) iface->handle_find_user_by_id = daemon_find_user_by_id; iface->handle_find_user_by_name = daemon_find_user_by_name; iface->handle_list_cached_users = daemon_list_cached_users; - iface->get_daemon_version = daemon_get_daemon_version; iface->handle_cache_user = daemon_cache_user; iface->handle_uncache_user = daemon_uncache_user; + + iface->handle_list_cached_groups = daemon_list_cached_groups; + iface->handle_create_group = daemon_create_group; + iface->handle_delete_group = daemon_delete_group; + iface->handle_find_group_by_id = daemon_find_group_by_id; + iface->handle_find_group_by_name = daemon_find_group_by_name; + + iface->get_daemon_version = daemon_get_daemon_version; } diff --git a/src/daemon.h b/src/daemon.h index b7e072e..8bb98a5 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -28,6 +28,7 @@ #include "types.h" #include "user.h" +#include "group.h" #include "accounts-generated.h" G_BEGIN_DECLS @@ -57,6 +58,8 @@ typedef enum { ERROR_USER_DOES_NOT_EXIST, ERROR_PERMISSION_DENIED, ERROR_NOT_SUPPORTED, + ERROR_GROUP_EXISTS, + ERROR_GROUP_DOES_NOT_EXIST, NUM_ERRORS } Error; @@ -75,6 +78,11 @@ User *daemon_local_find_user_by_id (Daemon *daemon, uid_t uid); User *daemon_local_find_user_by_name (Daemon *daemon, const gchar *name); +Group *daemon_local_find_group_by_id (Daemon *daemon, + gid_t gid); +Group *daemon_local_find_group_by_name (Daemon *daemon, + const gchar *name); + User *daemon_local_get_automatic_login_user (Daemon *daemon); typedef void (*AuthorizedCallback) (Daemon *daemon, @@ -99,6 +107,8 @@ gboolean daemon_local_set_automatic_login (Daemon *daemon, GHashTable * daemon_read_extension_ifaces (void); GHashTable * daemon_get_extension_ifaces (Daemon *daemon); +User *daemon_local_get_user (Daemon *daemon, const gchar *object_path); + G_END_DECLS #endif /* __DAEMON_H__ */ diff --git a/src/group.c b/src/group.c new file mode 100644 index 0000000..3d71a34 --- /dev/null +++ b/src/group.c @@ -0,0 +1,483 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>. + * Copyright (C) 2007-2008 William Jon McCann <mccann@jhu.edu> + * Copyright (C) 2009-2010 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 _BSD_SOURCE + +#include "config.h" + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include <grp.h> +#ifdef HAVE_SHADOW_H +#include <shadow.h> +#endif + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib-object.h> +#include <glib/gstdio.h> +#include <gio/gio.h> +#include <gio/gunixinputstream.h> +#include <polkit/polkit.h> + +#include "daemon.h" +#include "group.h" +#include "accounts-group-generated.h" +#include "util.h" + +enum { + PROP_0, + PROP_GID, + PROP_GROUP_NAME, + PROP_LOCAL_GROUP, + PROP_USERS, +}; + +struct Group { + AccountsGroupSkeleton parent; + + gchar *object_path; + + Daemon *daemon; + + gid_t gid; + gchar *group_name; + gboolean local_group; + GStrv users; +}; + +typedef struct GroupClass +{ + AccountsGroupSkeletonClass parent_class; +} GroupClass; + +static void group_accounts_group_iface_init (AccountsGroupIface *iface); + +G_DEFINE_TYPE_WITH_CODE (Group, group, ACCOUNTS_TYPE_GROUP_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_GROUP, group_accounts_group_iface_init)); + +static GStrv +find_user_paths (Group *group, GHashTable *users, gchar **members) +{ + int i, j, n; + GStrv paths; + + n = 0; + for (i = 0; members[i]; i++) { + if (g_hash_table_lookup (users, members[i])) + n++; + } + + paths = g_new0 (gchar *, n+1); + for (i = 0, j = 0; members[i] && j < n; i++) { + User *user = g_hash_table_lookup (users, members[i]); + if (user) + paths[j++] = g_strdup (user_get_object_path (user)); + } + + strv_sort (paths); + + return paths; +} + +void +group_update_from_grent (Group *group, + struct group *grent, + GHashTable *users) +{ + gboolean changed = FALSE; + GStrv new_members; + + g_object_freeze_notify (G_OBJECT (group)); + + if (grent->gr_gid != group->gid) { + group->gid = grent->gr_gid; + changed = TRUE; + g_object_notify (G_OBJECT (group), "gid"); + } + + if (g_strcmp0 (group->group_name, grent->gr_name) != 0) { + g_free (group->group_name); + group->group_name = g_strdup (grent->gr_name); + changed = TRUE; + g_object_notify (G_OBJECT (group), "group-name"); + } + + new_members = find_user_paths (group, users, grent->gr_mem); + if (!strv_equal (group->users, new_members)) { + g_strfreev (group->users); + group->users = new_members; + changed = TRUE; + g_object_notify (G_OBJECT (group), "users"); + } else + g_strfreev (new_members); + + g_object_thaw_notify (G_OBJECT (group)); + + if (changed) + accounts_group_emit_changed (ACCOUNTS_GROUP (group)); +} + +void +group_changed (Group *group) +{ + accounts_group_emit_changed (ACCOUNTS_GROUP (group)); +} + +static gchar * +compute_object_path (Group *group) +{ + gchar *object_path; + + object_path = g_strdup_printf ("/org/freedesktop/Accounts/Group%ld", + (long) group->gid); + return object_path; +} + +Group * +group_new (Daemon *daemon, + gid_t gid) +{ + Group *group; + + group = g_object_new (TYPE_GROUP, NULL); + group->daemon = daemon; + group->gid = gid; + group->local_group = TRUE; + + group->object_path = compute_object_path (group); + + return group; +} + +const gchar * +group_get_object_path (Group *group) +{ + return group->object_path; +} + +gid_t +group_get_gid (Group *group) +{ + return group->gid; +} + +const gchar * +group_get_group_name (Group *group) +{ + return group->group_name; +} + +gboolean +group_get_local_group (Group *group) +{ + return group->local_group; +} + +GStrv +group_get_users (Group *group) +{ + return group->users; +} + +static void +throw_error (GDBusMethodInvocation *context, + gint error_code, + const gchar *format, + ...) +{ + va_list args; + gchar *message; + + va_start (args, format); + message = g_strdup_vprintf (format, args); + va_end (args); + + g_dbus_method_invocation_return_error (context, ERROR, error_code, "%s", message); + + g_free (message); +} + +static void +group_finalize (GObject *object) +{ + Group *group; + + group = GROUP (object); + + g_free (group->object_path); + g_free (group->group_name); + + if (G_OBJECT_CLASS (group_parent_class)->finalize) + (*G_OBJECT_CLASS (group_parent_class)->finalize) (object); +} + +static void +group_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + Group *group = GROUP (object); + + switch (param_id) { + case PROP_GID: + group->gid = g_value_get_uint64 (value); + break; + case PROP_GROUP_NAME: + group->group_name = g_value_dup_string (value); + break; + case PROP_LOCAL_GROUP: + group->local_group = g_value_get_boolean (value); + break; + case PROP_USERS: + group->users = g_value_dup_boxed (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +group_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + Group *group = GROUP (object); + + switch (param_id) { + case PROP_GID: + g_value_set_uint64 (value, group_get_gid (group)); + break; + case PROP_GROUP_NAME: + g_value_set_string (value, group_get_group_name (group)); + break; + case PROP_LOCAL_GROUP: + g_value_set_boolean (value, group_get_local_group (group)); + break; + case PROP_USERS: + g_value_set_boxed (value, group_get_users (group)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +group_class_init (GroupClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (class); + + gobject_class->get_property = group_get_property; + gobject_class->set_property = group_set_property; + gobject_class->finalize = group_finalize; + + accounts_group_override_properties (gobject_class, 1); +} + +typedef struct { + Group *group; + gchar *new_group_name; +} SetGroupNameData; + +static void +free_set_group_data (SetGroupNameData *data) +{ + g_object_unref (data->group); + g_free (data->new_group_name); + g_free (data); +} + +static void +group_set_group_name_authorized_cb (Daemon *daemon, + User *dummy, + GDBusMethodInvocation *context, + gpointer user_data) + +{ + SetGroupNameData *data = user_data; + GError *error; + const gchar *argv[6]; + + sys_log (context, "changing name of group '%s' to '%s'", + group_get_group_name (data->group), + data->new_group_name); + + argv[0] = "/usr/sbin/groupmod"; + argv[1] = "-n"; + argv[2] = data->new_group_name; + argv[3] = "--"; + argv[4] = group_get_group_name (data->group); + argv[5] = NULL; + + error = NULL; + if (!spawn_with_login_uid (context, argv, &error)) { + throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message); + g_error_free (error); + return; + } + + accounts_group_complete_set_group_name (NULL, context); +} + +static gboolean +group_set_group_name (AccountsGroup *group, + GDBusMethodInvocation *context, + const gchar *new_group_name) +{ + SetGroupNameData *data; + + data = g_new0 (SetGroupNameData, 1); + data->group = g_object_ref (GROUP (group)); + data->new_group_name = g_strdup (new_group_name); + + daemon_local_check_auth (data->group->daemon, + NULL, + "org.freedesktop.accounts.user-administration", + TRUE, + group_set_group_name_authorized_cb, + context, + data, + (GDestroyNotify)free_set_group_data); + + return TRUE; + +} + +typedef struct { + Group *group; + User *user; + gboolean add; +} AddRemoveUserData; + +static void +free_add_remove_user_data (AddRemoveUserData *data) +{ + g_object_unref (data->group); + g_object_unref (data->user); + g_free (data); +} + +static void +group_add_remove_user_authorized_cb (Daemon *daemon, + User *dummy, + GDBusMethodInvocation *context, + gpointer user_data) + +{ + AddRemoveUserData *data = user_data; + GError *error; + const gchar *argv[6]; + + sys_log (context, "%s user '%s' %s group '%s'", + data->add? "add" : "remove", + user_get_user_name (data->user), + data->add? "to" : "from", + group_get_group_name (data->group)); + + argv[0] = "/usr/sbin/groupmems"; + argv[1] = "-g"; + argv[2] = group_get_group_name (data->group); + argv[3] = data->add? "-a" : "-d"; + argv[4] = user_get_user_name (data->user); + argv[5] = NULL; + + error = NULL; + if (!spawn_with_login_uid (context, argv, &error)) { + throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message); + g_error_free (error); + return; + } + + if (data->add) + accounts_group_complete_add_user (NULL, context); + else + accounts_group_complete_remove_user (NULL, context); +} + +static gboolean +group_add_remove_user (AccountsGroup *group, + GDBusMethodInvocation *context, + const gchar *user_path, + gboolean add) +{ + AddRemoveUserData *data; + User *user = daemon_local_get_user (GROUP(group)->daemon, user_path); + + if (user == NULL) { + throw_error (context, ERROR_FAILED, "object '%s' does not exist", user_path); + return TRUE; + } + + data = g_new0 (AddRemoveUserData, 1); + data->group = g_object_ref (GROUP (group)); + data->user = g_object_ref (user); + data->add = add; + + daemon_local_check_auth (data->group->daemon, + NULL, + "org.freedesktop.accounts.user-administration", + TRUE, + group_add_remove_user_authorized_cb, + context, + data, + (GDestroyNotify)free_add_remove_user_data); + + return TRUE; +} + +static gboolean +group_add_user (AccountsGroup *group, + GDBusMethodInvocation *context, + const gchar *user_path) +{ + return group_add_remove_user (group, context, user_path, TRUE); +} + +static gboolean +group_remove_user (AccountsGroup *group, + GDBusMethodInvocation *context, + const gchar *user_path) +{ + return group_add_remove_user (group, context, user_path, FALSE); +} + +static void +group_accounts_group_iface_init (AccountsGroupIface *iface) +{ + iface->handle_set_group_name = group_set_group_name; + iface->handle_add_user = group_add_user; + iface->handle_remove_user = group_remove_user; +} + +static void +group_init (Group *group) +{ + group->object_path = NULL; + group->group_name = NULL; +} diff --git a/src/group.h b/src/group.h new file mode 100644 index 0000000..0d76301 --- /dev/null +++ b/src/group.h @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2009-2010 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 3 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 + */ + +#ifndef __GROUP__ +#define __GROUP__ + +#include <sys/types.h> +#include <grp.h> + +#include <glib.h> +#include <gio/gio.h> + +#include "types.h" + +G_BEGIN_DECLS + +#define TYPE_GROUP (group_get_type ()) +#define GROUP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_GROUP, Group)) +#define IS_GROUP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TYPE_GROUP)) + +GType group_get_type (void) G_GNUC_CONST; +Group * group_new (Daemon *daemon, + gid_t gid); + +void group_update_from_grent (Group *group, + struct group *grent, + GHashTable *users); + +void group_changed (Group *group); + +const gchar * group_get_object_path (Group *group); +gid_t group_get_gid (Group *group); +const gchar * group_get_group_name (Group *group); +gboolean group_get_local_group (Group *group); +GStrv group_get_users (Group *group); + +G_END_DECLS + +#endif diff --git a/src/types.h b/src/types.h index ff19943..77ee755 100644 --- a/src/types.h +++ b/src/types.h @@ -24,5 +24,6 @@ typedef struct Daemon Daemon; typedef struct User User; +typedef struct Group Group; #endif @@ -70,6 +70,7 @@ enum { PROP_AUTOMATIC_LOGIN, PROP_SYSTEM_ACCOUNT, PROP_LOCAL_ACCOUNT, + PROP_CACHED_GROUPS }; struct User { @@ -104,9 +105,12 @@ struct User { gboolean automatic_login; gboolean system_account; gboolean local_account; + GStrv cached_groups; guint *extension_ids; guint n_extension_ids; + + GStrv cached_groups_stage; }; typedef struct UserClass @@ -393,6 +397,39 @@ user_update_system_account_property (User *user, g_object_notify (G_OBJECT (user), "system-account"); } +void +user_reset_cached_groups (User *user) +{ + user->cached_groups_stage = g_malloc_n (1, sizeof(gchar*)); + user->cached_groups_stage[0] = NULL; +} + +void +user_add_cached_group (User *user, + Group *group) +{ + int n = g_strv_length (user->cached_groups_stage); + n += 1; + user->cached_groups_stage = g_realloc_n (user->cached_groups_stage, + n+1, sizeof(gchar*)); + user->cached_groups_stage[n-1] = g_strdup (group_get_object_path (group)); + user->cached_groups_stage[n] = NULL; +} + +void +user_finish_cached_groups (User *user) +{ + strv_sort (user->cached_groups_stage); + if (!strv_equal (user->cached_groups, user->cached_groups_stage)) { + g_strfreev (user->cached_groups); + user->cached_groups = user->cached_groups_stage; + g_object_notify (G_OBJECT (user), "cached-groups"); + accounts_user_emit_changed (ACCOUNTS_USER (user)); + } else + g_strfreev (user->cached_groups_stage); + user->cached_groups_stage = NULL; +} + static void user_save_to_keyfile (User *user, GKeyFile *keyfile) @@ -752,8 +789,6 @@ user_register (User *user) return; } - user->object_path = compute_object_path (user); - if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (user), user->system_bus_connection, user->object_path, @@ -811,6 +846,8 @@ user_new (Daemon *daemon, user->daemon = daemon; user->uid = uid; + user->object_path = compute_object_path (user); + return user; } @@ -844,6 +881,12 @@ user_get_uid (User *user) return user->uid; } +uid_t +user_get_gid (User *user) +{ + return user->gid; +} + const gchar * user_get_shell(User *user) { @@ -1170,6 +1213,17 @@ user_set_x_session (AccountsUser *auser, return TRUE; } +static gboolean +user_find_groups (AccountsUser *auser, + GDBusMethodInvocation *context, + gboolean indirect) +{ + User *user = (User*)auser; + + accounts_user_complete_find_groups (auser, context, (const gchar *const *)user->cached_groups); + return TRUE; +} + static void user_change_location_authorized_cb (Daemon *daemon, User *user, @@ -2248,6 +2302,9 @@ user_get_property (GObject *object, case PROP_LOCAL_ACCOUNT: g_value_set_boolean (value, user->local_account); break; + case PROP_CACHED_GROUPS: + g_value_set_boxed (value, user->cached_groups); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -2285,6 +2342,7 @@ user_accounts_user_iface_init (AccountsUserIface *iface) iface->handle_set_shell = user_set_shell; iface->handle_set_user_name = user_set_user_name; iface->handle_set_xsession = user_set_x_session; + iface->handle_find_groups = user_find_groups; iface->get_uid = user_real_get_uid; iface->get_user_name = user_real_get_user_name; iface->get_real_name = user_real_get_real_name; @@ -2309,7 +2367,6 @@ user_accounts_user_iface_init (AccountsUserIface *iface) static void user_init (User *user) { - user->system_bus_connection = NULL; user->object_path = NULL; user->user_name = NULL; user->real_name = NULL; @@ -64,6 +64,11 @@ void user_update_system_account_property (User *user, void user_register (User *user); void user_unregister (User *user); +void user_reset_cached_groups (User *user); +void user_add_cached_group (User *user, + Group *group); +void user_finish_cached_groups (User *user); + void user_changed (User *user); void user_save (User *user); @@ -73,6 +78,7 @@ gboolean user_get_system_account (User *user); gboolean user_get_local_account (User *user); const gchar * user_get_object_path (User *user); uid_t user_get_uid (User *user); +gid_t user_get_gid (User *user); const gchar * user_get_shell (User *user); G_END_DECLS @@ -21,6 +21,7 @@ #include "config.h" +#include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> @@ -313,3 +314,34 @@ get_caller_uid (GDBusMethodInvocation *context, return TRUE; } + +gboolean +strv_equal (GStrv a, GStrv b) +{ + int i; + + if (a == NULL && b == NULL) + return TRUE; + + if (a == NULL || b == NULL) + return FALSE; + + for (i = 0; a[i] && b[i]; i++) + if (strcmp (a[i], b[i]) != 0) + return FALSE; + + return a[i] == NULL && b[i] == NULL; +} + +static int +cmpstringp(const void *p1, const void *p2) +{ + return strcmp(* (char * const *) p1, * (char * const *) p2); +} + +void +strv_sort (GStrv a) +{ + int n = g_strv_length (a); + qsort (a, n, sizeof (gchar *), cmpstringp); +} @@ -40,6 +40,9 @@ gint get_user_groups (const gchar *username, gid_t group, gid_t **groups); +void strv_sort (GStrv strv); +gboolean strv_equal (GStrv a, GStrv b); + G_END_DECLS #endif /* __UTIL_H__ */ |