Merge branch 'dbus-1.6'
[platform/upstream/dbus.git] / dbus / dbus-userdb-util.c
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* dbus-userdb-util.c Would be in dbus-userdb.c, but not used in libdbus
3  * 
4  * Copyright (C) 2003, 2004, 2005  Red Hat, Inc.
5  *
6  * Licensed under the Academic Free License version 2.1
7  * 
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  */
23 #include <config.h>
24 #include <unistd.h>
25 #define DBUS_USERDB_INCLUDES_PRIVATE 1
26 #include "dbus-userdb.h"
27 #include "dbus-test.h"
28 #include "dbus-internals.h"
29 #include "dbus-protocol.h"
30 #include <string.h>
31
32 #if HAVE_SYSTEMD
33 #include <systemd/sd-login.h>
34 #endif
35
36 /**
37  * @addtogroup DBusInternalsUtils
38  * @{
39  */
40
41 /**
42  * Checks to see if the UID sent in is the console user
43  *
44  * @param uid UID of person to check 
45  * @param error return location for errors
46  * @returns #TRUE if the UID is the same as the console user and there are no errors
47  */
48 dbus_bool_t
49 _dbus_is_console_user (dbus_uid_t uid,
50                        DBusError *error)
51 {
52
53   DBusUserDatabase *db;
54   const DBusUserInfo *info;
55   dbus_bool_t result = FALSE;
56
57 #ifdef HAVE_SYSTEMD
58   /* check if we have logind */
59   if (access ("/run/systemd/seats/", F_OK) >= 0)
60     {
61       int r;
62
63       /* Check whether this user is logged in on at least one physical
64          seat */
65       r = sd_uid_get_seats (uid, 0, NULL);
66       if (r < 0)
67         {
68           dbus_set_error (error, _dbus_error_from_errno (-r),
69                           "Failed to determine seats of user \"" DBUS_UID_FORMAT "\": %s",
70                           uid,
71                           _dbus_strerror (-r));
72           return FALSE;
73         }
74
75       return (r > 0);
76     }
77 #endif
78
79 #ifdef HAVE_CONSOLE_OWNER_FILE
80
81   DBusString f;
82   DBusStat st;
83
84   if (!_dbus_string_init (&f))
85     {
86       _DBUS_SET_OOM (error);
87       return FALSE;
88     }
89
90   if (!_dbus_string_append(&f, DBUS_CONSOLE_OWNER_FILE))
91     {
92       _dbus_string_free(&f);
93       _DBUS_SET_OOM (error);
94       return FALSE;
95     }
96
97   if (_dbus_stat(&f, &st, NULL) && (st.uid == uid))
98     {
99       _dbus_string_free(&f);
100       return TRUE;
101     }
102
103   _dbus_string_free(&f);
104
105 #endif /* HAVE_CONSOLE_OWNER_FILE */
106
107   _dbus_user_database_lock_system ();
108
109   db = _dbus_user_database_get_system ();
110   if (db == NULL)
111     {
112       dbus_set_error (error, DBUS_ERROR_FAILED, "Could not get system database.");
113       _dbus_user_database_unlock_system ();
114       return FALSE;
115     }
116
117   /* TPTD: this should be cache-safe, we've locked the DB and
118     _dbus_user_at_console doesn't pass it on. */
119   info = _dbus_user_database_lookup (db, uid, NULL, error);
120
121   if (info == NULL)
122     {
123       _dbus_user_database_unlock_system ();
124        return FALSE;
125     }
126
127   result = _dbus_user_at_console (info->username, error);
128
129   _dbus_user_database_unlock_system ();
130
131   return result;
132 }
133
134 /**
135  * Gets user ID given username
136  *
137  * @param username the username
138  * @param uid return location for UID
139  * @returns #TRUE if username existed and we got the UID
140  */
141 dbus_bool_t
142 _dbus_get_user_id (const DBusString  *username,
143                    dbus_uid_t        *uid)
144 {
145   return _dbus_get_user_id_and_primary_group (username, uid, NULL);
146 }
147
148 /**
149  * Gets group ID given groupname
150  *
151  * @param groupname the groupname
152  * @param gid return location for GID
153  * @returns #TRUE if group name existed and we got the GID
154  */
155 dbus_bool_t
156 _dbus_get_group_id (const DBusString  *groupname,
157                     dbus_gid_t        *gid)
158 {
159   DBusUserDatabase *db;
160   const DBusGroupInfo *info;
161   _dbus_user_database_lock_system ();
162
163   db = _dbus_user_database_get_system ();
164   if (db == NULL)
165     {
166       _dbus_user_database_unlock_system ();
167       return FALSE;
168     }
169
170   if (!_dbus_user_database_get_groupname (db, groupname,
171                                           &info, NULL))
172     {
173       _dbus_user_database_unlock_system ();
174       return FALSE;
175     }
176
177   *gid = info->gid;
178   
179   _dbus_user_database_unlock_system ();
180   return TRUE;
181 }
182
183 /**
184  * Gets user ID and primary group given username
185  *
186  * @param username the username
187  * @param uid_p return location for UID
188  * @param gid_p return location for GID
189  * @returns #TRUE if username existed and we got the UID and GID
190  */
191 dbus_bool_t
192 _dbus_get_user_id_and_primary_group (const DBusString  *username,
193                                      dbus_uid_t        *uid_p,
194                                      dbus_gid_t        *gid_p)
195 {
196   DBusUserDatabase *db;
197   const DBusUserInfo *info;
198   _dbus_user_database_lock_system ();
199
200   db = _dbus_user_database_get_system ();
201   if (db == NULL)
202     {
203       _dbus_user_database_unlock_system ();
204       return FALSE;
205     }
206
207   if (!_dbus_user_database_get_username (db, username,
208                                          &info, NULL))
209     {
210       _dbus_user_database_unlock_system ();
211       return FALSE;
212     }
213
214   if (uid_p)
215     *uid_p = info->uid;
216   if (gid_p)
217     *gid_p = info->primary_gid;
218   
219   _dbus_user_database_unlock_system ();
220   return TRUE;
221 }
222
223 /**
224  * Looks up a gid or group name in the user database.  Only one of
225  * name or GID can be provided. There are wrapper functions for this
226  * that are better to use, this one does no locking or anything on the
227  * database and otherwise sort of sucks.
228  *
229  * @param db the database
230  * @param gid the group ID or #DBUS_GID_UNSET
231  * @param groupname group name or #NULL 
232  * @param error error to fill in
233  * @returns the entry in the database
234  */
235 DBusGroupInfo*
236 _dbus_user_database_lookup_group (DBusUserDatabase *db,
237                                   dbus_gid_t        gid,
238                                   const DBusString *groupname,
239                                   DBusError        *error)
240 {
241   DBusGroupInfo *info;
242
243   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
244
245    /* See if the group is really a number */
246    if (gid == DBUS_UID_UNSET)
247     {
248       unsigned long n;
249
250       if (_dbus_is_a_number (groupname, &n))
251         gid = n;
252     }
253
254 #ifdef DBUS_ENABLE_USERDB_CACHE
255   if (gid != DBUS_GID_UNSET)
256     info = _dbus_hash_table_lookup_uintptr (db->groups, gid);
257   else
258     info = _dbus_hash_table_lookup_string (db->groups_by_name,
259                                            _dbus_string_get_const_data (groupname));
260   if (info)
261     {
262       _dbus_verbose ("Using cache for GID "DBUS_GID_FORMAT" information\n",
263                      info->gid);
264       return info;
265     }
266   else
267 #else
268   if (1)
269 #endif
270     {
271       if (gid != DBUS_GID_UNSET)
272         _dbus_verbose ("No cache for GID "DBUS_GID_FORMAT"\n",
273                        gid);
274       else
275         _dbus_verbose ("No cache for groupname \"%s\"\n",
276                        _dbus_string_get_const_data (groupname));
277       
278       info = dbus_new0 (DBusGroupInfo, 1);
279       if (info == NULL)
280         {
281           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
282           return NULL;
283         }
284
285       if (gid != DBUS_GID_UNSET)
286         {
287           if (!_dbus_group_info_fill_gid (info, gid, error))
288             {
289               _DBUS_ASSERT_ERROR_IS_SET (error);
290               _dbus_group_info_free_allocated (info);
291               return NULL;
292             }
293         }
294       else
295         {
296           if (!_dbus_group_info_fill (info, groupname, error))
297             {
298               _DBUS_ASSERT_ERROR_IS_SET (error);
299               _dbus_group_info_free_allocated (info);
300               return NULL;
301             }
302         }
303
304       /* don't use these past here */
305       gid = DBUS_GID_UNSET;
306       groupname = NULL;
307
308       if (!_dbus_hash_table_insert_uintptr (db->groups, info->gid, info))
309         {
310           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
311           _dbus_group_info_free_allocated (info);
312           return NULL;
313         }
314
315
316       if (!_dbus_hash_table_insert_string (db->groups_by_name,
317                                            info->groupname,
318                                            info))
319         {
320           _dbus_hash_table_remove_uintptr (db->groups, info->gid);
321           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
322           return NULL;
323         }
324       
325       return info;
326     }
327 }
328
329
330 /**
331  * Gets the user information for the given group name,
332  * returned group info should not be freed. 
333  *
334  * @param db user database
335  * @param groupname the group name
336  * @param info return location for const ref to group info
337  * @param error error location
338  * @returns #FALSE if error is set
339  */
340 dbus_bool_t
341 _dbus_user_database_get_groupname (DBusUserDatabase     *db,
342                                    const DBusString     *groupname,
343                                    const DBusGroupInfo **info,
344                                    DBusError            *error)
345 {
346   *info = _dbus_user_database_lookup_group (db, DBUS_GID_UNSET, groupname, error);
347   return *info != NULL;
348 }
349
350 /**
351  * Gets the user information for the given GID,
352  * returned group info should not be freed. 
353  *
354  * @param db user database
355  * @param gid the group ID
356  * @param info return location for const ref to group info
357  * @param error error location
358  * @returns #FALSE if error is set
359  */
360 dbus_bool_t
361 _dbus_user_database_get_gid (DBusUserDatabase     *db,
362                              dbus_gid_t            gid,
363                              const DBusGroupInfo **info,
364                              DBusError            *error)
365 {
366   *info = _dbus_user_database_lookup_group (db, gid, NULL, error);
367   return *info != NULL;
368 }
369
370
371 /**
372  * Gets all groups  corresponding to the given UID. Returns #FALSE
373  * if no memory, or user isn't known, but always initializes
374  * group_ids to a NULL array. 
375  *
376  * @param uid the UID
377  * @param group_ids return location for array of group IDs
378  * @param n_group_ids return location for length of returned array
379  * @returns #TRUE if the UID existed and we got some credentials
380  */
381 dbus_bool_t
382 _dbus_groups_from_uid (dbus_uid_t         uid,
383                        dbus_gid_t       **group_ids,
384                        int               *n_group_ids)
385 {
386   DBusUserDatabase *db;
387   const DBusUserInfo *info;
388   *group_ids = NULL;
389   *n_group_ids = 0;
390
391   _dbus_user_database_lock_system ();
392
393   db = _dbus_user_database_get_system ();
394   if (db == NULL)
395     {
396       _dbus_user_database_unlock_system ();
397       return FALSE;
398     }
399
400   if (!_dbus_user_database_get_uid (db, uid,
401                                     &info, NULL))
402     {
403       _dbus_user_database_unlock_system ();
404       return FALSE;
405     }
406
407   _dbus_assert (info->uid == uid);
408   
409   if (info->n_group_ids > 0)
410     {
411       *group_ids = dbus_new (dbus_gid_t, info->n_group_ids);
412       if (*group_ids == NULL)
413         {
414           _dbus_user_database_unlock_system ();
415           return FALSE;
416         }
417
418       *n_group_ids = info->n_group_ids;
419
420       memcpy (*group_ids, info->group_ids, info->n_group_ids * sizeof (dbus_gid_t));
421     }
422
423   _dbus_user_database_unlock_system ();
424   return TRUE;
425 }
426 /** @} */
427
428 #ifdef DBUS_BUILD_TESTS
429 #include <stdio.h>
430
431 /**
432  * Unit test for dbus-userdb.c.
433  * 
434  * @returns #TRUE on success.
435  */
436 dbus_bool_t
437 _dbus_userdb_test (const char *test_data_dir)
438 {
439   const DBusString *username;
440   const DBusString *homedir;
441   dbus_uid_t uid;
442   unsigned long *group_ids;
443   int n_group_ids, i;
444   DBusError error;
445
446   if (!_dbus_username_from_current_process (&username))
447     _dbus_assert_not_reached ("didn't get username");
448
449   if (!_dbus_homedir_from_current_process (&homedir))
450     _dbus_assert_not_reached ("didn't get homedir");  
451
452   if (!_dbus_get_user_id (username, &uid))
453     _dbus_assert_not_reached ("didn't get uid");
454
455   if (!_dbus_groups_from_uid (uid, &group_ids, &n_group_ids))
456     _dbus_assert_not_reached ("didn't get groups");
457
458   printf ("    Current user: %s homedir: %s gids:",
459           _dbus_string_get_const_data (username),
460           _dbus_string_get_const_data (homedir));
461
462   for (i=0; i<n_group_ids; i++)
463       printf(" %ld", group_ids[i]);
464
465   printf ("\n");
466
467   dbus_error_init (&error);
468   printf ("Is Console user: %i\n",
469           _dbus_is_console_user (uid, &error));
470   printf ("Invocation was OK: %s\n", error.message ? error.message : "yes");
471   dbus_error_free (&error);
472   printf ("Is Console user 4711: %i\n",
473           _dbus_is_console_user (4711, &error));
474   printf ("Invocation was OK: %s\n", error.message ? error.message : "yes");
475   dbus_error_free (&error);
476
477   dbus_free (group_ids);
478
479   return TRUE;
480 }
481 #endif /* DBUS_BUILD_TESTS */