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