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