Add .dbus-keyrings
[platform/upstream/dbus.git] / dbus / dbus-userdb.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-userdb.c User database abstraction
3  * 
4  * Copyright (C) 2003  Red Hat, Inc.
5  *
6  * Licensed under the Academic Free License version 1.2
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23 #include "dbus-userdb.h"
24 #include "dbus-hash.h"
25 #include "dbus-test.h"
26 #include "dbus-internals.h"
27 #include <string.h>
28
29 struct DBusUserDatabase
30 {
31   int refcount;
32
33   DBusHashTable *users;
34   DBusHashTable *groups;
35   DBusHashTable *users_by_name;
36   DBusHashTable *groups_by_name;
37 };
38
39 static void
40 free_user_info (void *data)
41 {
42   DBusUserInfo *info = data;
43
44   if (info == NULL) /* hash table will pass NULL */
45     return;
46
47   _dbus_user_info_free (info);
48   dbus_free (info);
49 }
50
51 static void
52 free_group_info (void *data)
53 {
54   DBusGroupInfo *info = data;
55
56   if (info == NULL) /* hash table will pass NULL */
57     return;
58
59   _dbus_group_info_free (info);
60   dbus_free (info);
61 }
62
63 static DBusUserInfo*
64 _dbus_user_database_lookup (DBusUserDatabase *db,
65                             dbus_uid_t        uid,
66                             const DBusString *username,
67                             DBusError        *error)
68 {
69   DBusUserInfo *info;
70
71   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
72   _dbus_assert (uid != DBUS_UID_UNSET || username != NULL);
73   
74   if (uid != DBUS_UID_UNSET)
75     info = _dbus_hash_table_lookup_ulong (db->users, uid);
76   else
77     info = _dbus_hash_table_lookup_string (db->users_by_name, _dbus_string_get_const_data (username));
78   
79   if (info)
80     {
81       _dbus_verbose ("Using cache for UID "DBUS_UID_FORMAT" information\n",
82                      uid);
83       return info;
84     }
85   else
86     {
87       _dbus_verbose ("No cache for UID "DBUS_UID_FORMAT"\n",
88                      uid);
89       
90       info = dbus_new0 (DBusUserInfo, 1);
91       if (info == NULL)
92         {
93           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
94           return NULL;
95         }
96
97       if (uid != DBUS_UID_UNSET)
98         {
99           if (!_dbus_user_info_fill_uid (info, uid, error))
100             {
101               _DBUS_ASSERT_ERROR_IS_SET (error);
102               free_user_info (info);
103               return NULL;
104             }
105         }
106       else
107         {
108           if (!_dbus_user_info_fill (info, username, error))
109             {
110               _DBUS_ASSERT_ERROR_IS_SET (error);
111               free_user_info (info);
112               return NULL;
113             }
114         }
115
116       /* be sure we don't use these after here */
117       uid = DBUS_UID_UNSET;
118       username = NULL;
119
120       /* insert into hash */
121       if (!_dbus_hash_table_insert_ulong (db->users, info->uid, info))
122         {
123           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
124           free_user_info (info);
125           return NULL;
126         }
127
128       if (!_dbus_hash_table_insert_string (db->users_by_name,
129                                            info->username,
130                                            info))
131         {
132           _dbus_hash_table_remove_ulong (db->users, info->uid);
133           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
134           return NULL;
135         }
136       
137       return info;
138     }
139 }
140
141 static DBusGroupInfo*
142 _dbus_user_database_lookup_group (DBusUserDatabase *db,
143                                   dbus_gid_t        gid,
144                                   const DBusString *groupname,
145                                   DBusError        *error)
146 {
147   DBusGroupInfo *info;
148
149   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
150
151   if (gid != DBUS_GID_UNSET)
152     info = _dbus_hash_table_lookup_ulong (db->groups, gid);
153   else
154     info = _dbus_hash_table_lookup_string (db->groups_by_name,
155                                            _dbus_string_get_const_data (groupname));
156   if (info)
157     {
158       _dbus_verbose ("Using cache for GID "DBUS_GID_FORMAT" information\n",
159                      gid);
160       return info;
161     }
162   else
163     {
164       _dbus_verbose ("No cache for GID "DBUS_GID_FORMAT"\n",
165                      gid);
166       
167       info = dbus_new0 (DBusGroupInfo, 1);
168       if (info == NULL)
169         {
170           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
171           return NULL;
172         }
173
174       if (!_dbus_group_info_fill_gid (info, gid, error))
175         {
176           _DBUS_ASSERT_ERROR_IS_SET (error);
177           free_group_info (info);
178           return NULL;
179         }
180
181       if (!_dbus_hash_table_insert_ulong (db->groups, info->gid, info))
182         {
183           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
184           free_group_info (info);
185           return NULL;
186         }
187
188
189       if (!_dbus_hash_table_insert_string (db->groups_by_name,
190                                            info->groupname,
191                                            info))
192         {
193           _dbus_hash_table_remove_ulong (db->groups, info->gid);
194           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
195           return NULL;
196         }
197       
198       return info;
199     }
200 }
201
202 _DBUS_DEFINE_GLOBAL_LOCK(system_users);
203 static dbus_bool_t database_locked = FALSE;
204 static DBusUserDatabase *system_db = NULL;
205 static DBusString process_username;
206 static DBusString process_homedir;
207       
208 static void
209 shutdown_system_db (void *data)
210 {
211   _dbus_user_database_unref (system_db);
212   system_db = NULL;
213   _dbus_string_free (&process_username);
214   _dbus_string_free (&process_homedir);
215 }
216
217 static dbus_bool_t
218 init_system_db (void)
219 {
220   _dbus_assert (database_locked);
221     
222   if (system_db == NULL)
223     {
224       DBusError error;
225       const DBusUserInfo *info;
226       
227       system_db = _dbus_user_database_new ();
228       if (system_db == NULL)
229         return FALSE;
230
231       dbus_error_init (&error);
232
233       if (!_dbus_user_database_get_uid (system_db,
234                                         _dbus_getuid (),
235                                         &info,
236                                         &error))
237         {
238           _dbus_user_database_unref (system_db);
239           system_db = NULL;
240           
241           if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
242             {
243               dbus_error_free (&error);
244               return FALSE;
245             }
246           else
247             {
248               /* This really should not happen. */
249               _dbus_warn ("Could not get password database information for UID of current process: %s\n",
250                           error.message);
251               dbus_error_free (&error);
252               return FALSE;
253             }
254         }
255
256       if (!_dbus_string_init (&process_username))
257         {
258           _dbus_user_database_unref (system_db);
259           system_db = NULL;
260           return FALSE;
261         }
262
263       if (!_dbus_string_init (&process_homedir))
264         {
265           _dbus_string_free (&process_username);
266           _dbus_user_database_unref (system_db);
267           system_db = NULL;
268           return FALSE;
269         }
270
271       if (!_dbus_string_append (&process_username,
272                                 info->username) ||
273           !_dbus_string_append (&process_homedir,
274                                 info->homedir) ||
275           !_dbus_register_shutdown_func (shutdown_system_db, NULL))
276         {
277           _dbus_string_free (&process_username);
278           _dbus_string_free (&process_homedir);
279           _dbus_user_database_unref (system_db);
280           system_db = NULL;
281           return FALSE;
282         }
283     }
284
285   return TRUE;
286 }
287
288 /**
289  * @addtogroup DBusInternalsUtils
290  * @{
291  */
292
293 /**
294  * Locks global system user database.
295  */
296 void
297 _dbus_user_database_lock_system (void)
298 {
299   _DBUS_LOCK (system_users);
300   database_locked = TRUE;
301 }
302
303 /**
304  * Unlocks global system user database.
305  */
306 void
307 _dbus_user_database_unlock_system (void)
308 {
309   database_locked = FALSE;
310   _DBUS_UNLOCK (system_users);
311 }
312
313 /**
314  * Gets the system global user database;
315  * must be called with lock held (_dbus_user_database_lock_system()).
316  *
317  * @returns the database or #NULL if no memory
318  */
319 DBusUserDatabase*
320 _dbus_user_database_get_system (void)
321 {
322   _dbus_assert (database_locked);
323
324   init_system_db ();
325   
326   return system_db;
327 }
328
329 /**
330  * Gets username of user owning current process.  The returned string
331  * is valid until dbus_shutdown() is called.
332  *
333  * @param username place to store pointer to username
334  * @returns #FALSE if no memory
335  */
336 dbus_bool_t
337 _dbus_username_from_current_process (const DBusString **username)
338 {
339   _dbus_user_database_lock_system ();
340   if (!init_system_db ())
341     {
342       _dbus_user_database_unlock_system ();
343       return FALSE;
344     }
345   *username = &process_username;
346   _dbus_user_database_unlock_system ();  
347
348   return TRUE;
349 }
350
351 /**
352  * Gets homedir of user owning current process.  The returned string
353  * is valid until dbus_shutdown() is called.
354  *
355  * @param homedir place to store pointer to homedir
356  * @returns #FALSE if no memory
357  */
358 dbus_bool_t
359 _dbus_homedir_from_current_process (const DBusString  **homedir)
360 {
361   _dbus_user_database_lock_system ();
362   if (!init_system_db ())
363     {
364       _dbus_user_database_unlock_system ();
365       return FALSE;
366     }
367   *homedir = &process_homedir;
368   _dbus_user_database_unlock_system ();
369
370   return TRUE;
371 }
372
373 /**
374  * Gets user ID given username
375  *
376  * @param username the username
377  * @param uid return location for UID
378  * @returns #TRUE if username existed and we got the UID
379  */
380 dbus_bool_t
381 _dbus_get_user_id (const DBusString  *username,
382                    dbus_uid_t        *uid)
383 {
384   DBusCredentials creds;
385
386   if (!_dbus_credentials_from_username (username, &creds))
387     return FALSE;
388
389   if (creds.uid == DBUS_UID_UNSET)
390     return FALSE;
391
392   *uid = creds.uid;
393
394   return TRUE;
395 }
396
397 /**
398  * Gets group ID given groupname
399  *
400  * @param groupname the groupname
401  * @param gid return location for GID
402  * @returns #TRUE if group name existed and we got the GID
403  */
404 dbus_bool_t
405 _dbus_get_group_id (const DBusString  *groupname,
406                     dbus_gid_t        *gid)
407 {
408   DBusUserDatabase *db;
409   const DBusGroupInfo *info;
410   _dbus_user_database_lock_system ();
411
412   db = _dbus_user_database_get_system ();
413   if (db == NULL)
414     {
415       _dbus_user_database_unlock_system ();
416       return FALSE;
417     }
418
419   if (!_dbus_user_database_get_groupname (db, groupname,
420                                           &info, NULL))
421     {
422       _dbus_user_database_unlock_system ();
423       return FALSE;
424     }
425
426   *gid = info->gid;
427   
428   _dbus_user_database_unlock_system ();
429   return TRUE;
430 }
431
432 /**
433  * Gets the home directory for the given user.
434  *
435  * @param username the username
436  * @param homedir string to append home directory to
437  * @returns #TRUE if user existed and we appended their homedir
438  */
439 dbus_bool_t
440 _dbus_homedir_from_username (const DBusString *username,
441                              DBusString       *homedir)
442 {
443   DBusUserDatabase *db;
444   const DBusUserInfo *info;
445   _dbus_user_database_lock_system ();
446
447   db = _dbus_user_database_get_system ();
448   if (db == NULL)
449     {
450       _dbus_user_database_unlock_system ();
451       return FALSE;
452     }
453
454   if (!_dbus_user_database_get_username (db, username,
455                                          &info, NULL))
456     {
457       _dbus_user_database_unlock_system ();
458       return FALSE;
459     }
460
461   if (!_dbus_string_append (homedir, info->homedir))
462     {
463       _dbus_user_database_unlock_system ();
464       return FALSE;
465     }
466   
467   _dbus_user_database_unlock_system ();
468   return TRUE;
469 }
470
471 /**
472  * Gets a UID from a UID string.
473  *
474  * @param uid_str the UID in string form
475  * @param uid UID to fill in
476  * @returns #TRUE if successfully filled in UID
477  */
478 dbus_bool_t
479 _dbus_uid_from_string (const DBusString      *uid_str,
480                        dbus_uid_t            *uid)
481 {
482   int end;
483   long val;
484   
485   if (_dbus_string_get_length (uid_str) == 0)
486     {
487       _dbus_verbose ("UID string was zero length\n");
488       return FALSE;
489     }
490
491   val = -1;
492   end = 0;
493   if (!_dbus_string_parse_int (uid_str, 0, &val,
494                                &end))
495     {
496       _dbus_verbose ("could not parse string as a UID\n");
497       return FALSE;
498     }
499   
500   if (end != _dbus_string_get_length (uid_str))
501     {
502       _dbus_verbose ("string contained trailing stuff after UID\n");
503       return FALSE;
504     }
505
506   *uid = val;
507
508   return TRUE;
509 }
510
511 /**
512  * Gets the credentials corresponding to the given username.
513  *
514  * @param username the username
515  * @param credentials credentials to fill in
516  * @returns #TRUE if the username existed and we got some credentials
517  */
518 dbus_bool_t
519 _dbus_credentials_from_username (const DBusString *username,
520                                  DBusCredentials  *credentials)
521 {
522   DBusUserDatabase *db;
523   const DBusUserInfo *info;
524   _dbus_user_database_lock_system ();
525
526   db = _dbus_user_database_get_system ();
527   if (db == NULL)
528     {
529       _dbus_user_database_unlock_system ();
530       return FALSE;
531     }
532
533   if (!_dbus_user_database_get_username (db, username,
534                                          &info, NULL))
535     {
536       _dbus_user_database_unlock_system ();
537       return FALSE;
538     }
539
540   credentials->pid = DBUS_PID_UNSET;
541   credentials->uid = info->uid;
542   credentials->gid = info->primary_gid;
543   
544   _dbus_user_database_unlock_system ();
545   return TRUE;
546 }
547
548 /**
549  * Gets the credentials corresponding to the given UID.
550  *
551  * @param uid the UID
552  * @param credentials credentials to fill in
553  * @returns #TRUE if the UID existed and we got some credentials
554  */
555 dbus_bool_t
556 _dbus_credentials_from_uid (dbus_uid_t        uid,
557                             DBusCredentials  *credentials)
558 {
559   DBusUserDatabase *db;
560   const DBusUserInfo *info;
561   _dbus_user_database_lock_system ();
562
563   db = _dbus_user_database_get_system ();
564   if (db == NULL)
565     {
566       _dbus_user_database_unlock_system ();
567       return FALSE;
568     }
569
570   if (!_dbus_user_database_get_uid (db, uid,
571                                     &info, NULL))
572     {
573       _dbus_user_database_unlock_system ();
574       return FALSE;
575     }
576
577   _dbus_assert (info->uid == uid);
578   
579   credentials->pid = DBUS_PID_UNSET;
580   credentials->uid = info->uid;
581   credentials->gid = info->primary_gid;
582   
583   _dbus_user_database_unlock_system ();
584   return TRUE;
585 }
586
587 /**
588  * Creates a new user database object used to look up and
589  * cache user information.
590  * @returns new database, or #NULL on out of memory
591  */
592 DBusUserDatabase*
593 _dbus_user_database_new (void)
594 {
595   DBusUserDatabase *db;
596   
597   db = dbus_new0 (DBusUserDatabase, 1);
598   if (db == NULL)
599     return NULL;
600
601   db->refcount = 1;
602
603   db->users = _dbus_hash_table_new (DBUS_HASH_ULONG,
604                                     NULL, free_user_info);
605   
606   if (db->users == NULL)
607     goto failed;
608
609   db->groups = _dbus_hash_table_new (DBUS_HASH_ULONG,
610                                      NULL, free_group_info);
611   
612   if (db->groups == NULL)
613     goto failed;
614
615   db->users_by_name = _dbus_hash_table_new (DBUS_HASH_STRING,
616                                             NULL, NULL);
617   if (db->users_by_name == NULL)
618     goto failed;
619   
620   db->groups_by_name = _dbus_hash_table_new (DBUS_HASH_STRING,
621                                              NULL, NULL);
622   if (db->groups_by_name == NULL)
623     goto failed;
624   
625   return db;
626   
627  failed:
628   _dbus_user_database_unref (db);
629   return NULL;
630 }
631
632 /**
633  * Increments refcount of user database.
634  * @param db the database
635  */
636 void
637 _dbus_user_database_ref (DBusUserDatabase  *db)
638 {
639   _dbus_assert (db->refcount > 0);
640
641   db->refcount += 1;
642 }
643
644 /**
645  * Decrements refcount of user database.
646  * @param db the database
647  */
648 void
649 _dbus_user_database_unref (DBusUserDatabase  *db)
650 {
651   _dbus_assert (db->refcount > 0);
652
653   db->refcount -= 1;
654   if (db->refcount == 0)
655     {
656       if (db->users)
657         _dbus_hash_table_unref (db->users);
658
659       if (db->groups)
660         _dbus_hash_table_unref (db->groups);
661
662       if (db->users_by_name)
663         _dbus_hash_table_unref (db->users_by_name);
664
665       if (db->groups_by_name)
666         _dbus_hash_table_unref (db->groups_by_name);
667       
668       dbus_free (db);
669     }
670 }
671
672 /**
673  * Gets all groups for a particular user. Returns #FALSE
674  * if no memory, or user isn't known, but always initializes
675  * group_ids to a NULL array. Sets error to the reason
676  * for returning #FALSE.
677  *
678  * @param db the user database object
679  * @param uid the user ID
680  * @param group_ids return location for array of group IDs
681  * @param n_group_ids return location for length of returned array
682  * @param error return location for error
683  * @returns #TRUE on success
684  */
685 dbus_bool_t
686 _dbus_user_database_get_groups (DBusUserDatabase  *db,
687                                 dbus_uid_t         uid,
688                                 dbus_gid_t       **group_ids,
689                                 int               *n_group_ids,
690                                 DBusError         *error)
691 {
692   DBusUserInfo *info;
693   
694   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
695
696   *group_ids = NULL;
697   *n_group_ids = 0;
698   
699   info = _dbus_user_database_lookup (db, uid, NULL, error);
700   if (info == NULL)
701     {
702       _DBUS_ASSERT_ERROR_IS_SET (error);
703       return FALSE;
704     }
705
706   if (info->n_group_ids > 0)
707     {
708       *group_ids = dbus_new (dbus_gid_t, info->n_group_ids);
709       if (*group_ids == NULL)
710         {
711           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
712           return FALSE;
713         }
714
715       *n_group_ids = info->n_group_ids;
716
717       memcpy (*group_ids, info->group_ids, info->n_group_ids * sizeof (dbus_gid_t));
718     }
719
720   return TRUE;
721 }
722
723 /**
724  * Gets the user information for the given UID,
725  * returned user info should not be freed. 
726  *
727  * @param db user database
728  * @param uid the user ID
729  * @param info return location for const ref to user info
730  * @param error error location
731  * @returns #FALSE if error is set
732  */
733 dbus_bool_t
734 _dbus_user_database_get_uid (DBusUserDatabase    *db,
735                              dbus_uid_t           uid,
736                              const DBusUserInfo **info,
737                              DBusError           *error)
738 {
739   *info = _dbus_user_database_lookup (db, uid, NULL, error);
740   return *info != NULL;
741 }
742
743 /**
744  * Gets the user information for the given GID,
745  * returned group info should not be freed. 
746  *
747  * @param db user database
748  * @param gid the group ID
749  * @param info return location for const ref to group info
750  * @param error error location
751  * @returns #FALSE if error is set
752  */
753 dbus_bool_t
754 _dbus_user_database_get_gid (DBusUserDatabase     *db,
755                              dbus_gid_t            gid,
756                              const DBusGroupInfo **info,
757                              DBusError            *error)
758 {
759   *info = _dbus_user_database_lookup_group (db, gid, NULL, error);
760   return *info != NULL;
761 }
762
763 /**
764  * Gets the user information for the given username.
765  *
766  * @param db user database
767  * @param username the user name
768  * @param info return location for const ref to user info
769  * @param error error location
770  * @returns #FALSE if error is set
771  */
772 dbus_bool_t
773 _dbus_user_database_get_username  (DBusUserDatabase     *db,
774                                    const DBusString     *username,
775                                    const DBusUserInfo  **info,
776                                    DBusError            *error)
777 {
778   *info = _dbus_user_database_lookup (db, DBUS_UID_UNSET, username, error);
779   return *info != NULL;
780 }
781
782 /**
783  * Gets the user information for the given group name,
784  * returned group info should not be freed. 
785  *
786  * @param db user database
787  * @param groupname the group name
788  * @param info return location for const ref to group info
789  * @param error error location
790  * @returns #FALSE if error is set
791  */
792 dbus_bool_t
793 _dbus_user_database_get_groupname (DBusUserDatabase     *db,
794                                    const DBusString     *groupname,
795                                    const DBusGroupInfo **info,
796                                    DBusError            *error)
797 {
798   *info = _dbus_user_database_lookup_group (db, DBUS_GID_UNSET, groupname, error);
799   return *info != NULL;
800 }
801
802 /** @} */
803
804 #ifdef DBUS_BUILD_TESTS
805 #include <stdio.h>
806
807 /**
808  * Unit test for dbus-userdb.c.
809  * 
810  * @returns #TRUE on success.
811  */
812 dbus_bool_t
813 _dbus_userdb_test (const char *test_data_dir)
814 {
815   const DBusString *username;
816   const DBusString *homedir;
817
818   if (!_dbus_username_from_current_process (&username))
819     _dbus_assert_not_reached ("didn't get username");
820
821   if (!_dbus_homedir_from_current_process (&homedir))
822     _dbus_assert_not_reached ("didn't get homedir");  
823
824   printf ("    Current user: %s homedir: %s\n",
825           _dbus_string_get_const_data (username),
826           _dbus_string_get_const_data (homedir));
827   
828   return TRUE;
829 }
830 #endif /* DBUS_BUILD_TESTS */