summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarius Vollmer <mvollmer@redhat.com>2013-10-22 21:31:10 +0300
committerMarius Vollmer <mvollmer@redhat.com>2013-10-22 21:31:25 +0300
commitd4869d33b609f87146531ca284fce3aa680d15e3 (patch)
treec01f8ef1304d9848fe12693f5bcf78199edb3da0
parent7098d4999d035b4bc47f714711c7e6f0c9aabc01 (diff)
downloadaccountsservice-d4869d33b609f87146531ca284fce3aa680d15e3.tar.gz
Daemon: Support for local groups.
-rw-r--r--data/org.freedesktop.Accounts.Group.xml149
-rw-r--r--data/org.freedesktop.Accounts.User.xml50
-rw-r--r--data/org.freedesktop.Accounts.xml134
-rw-r--r--src/Makefile.am7
-rw-r--r--src/daemon.c631
-rw-r--r--src/daemon.h10
-rw-r--r--src/group.c483
-rw-r--r--src/group.h55
-rw-r--r--src/types.h1
-rw-r--r--src/user.c63
-rw-r--r--src/user.h6
-rw-r--r--src/util.c32
-rw-r--r--src/util.h3
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
diff --git a/src/user.c b/src/user.c
index a88aaef..b2ae421 100644
--- a/src/user.c
+++ b/src/user.c
@@ -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;
diff --git a/src/user.h b/src/user.h
index 0848b50..37d9f15 100644
--- a/src/user.h
+++ b/src/user.h
@@ -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
diff --git a/src/util.c b/src/util.c
index 84e7b78..4605623 100644
--- a/src/util.c
+++ b/src/util.c
@@ -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);
+}
diff --git a/src/util.h b/src/util.h
index 41ba545..2b97089 100644
--- a/src/util.h
+++ b/src/util.h
@@ -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__ */