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