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