2003-09-29 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 /**
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  */
639 void
640 _dbus_user_database_ref (DBusUserDatabase  *db)
641 {
642   _dbus_assert (db->refcount > 0);
643
644   db->refcount += 1;
645 }
646
647 /**
648  * Decrements refcount of user database.
649  * @param db the database
650  */
651 void
652 _dbus_user_database_unref (DBusUserDatabase  *db)
653 {
654   _dbus_assert (db->refcount > 0);
655
656   db->refcount -= 1;
657   if (db->refcount == 0)
658     {
659       if (db->users)
660         _dbus_hash_table_unref (db->users);
661
662       if (db->groups)
663         _dbus_hash_table_unref (db->groups);
664
665       if (db->users_by_name)
666         _dbus_hash_table_unref (db->users_by_name);
667
668       if (db->groups_by_name)
669         _dbus_hash_table_unref (db->groups_by_name);
670       
671       dbus_free (db);
672     }
673 }
674
675 /**
676  * Gets all groups for a particular user. Returns #FALSE
677  * if no memory, or user isn't known, but always initializes
678  * group_ids to a NULL array. Sets error to the reason
679  * for returning #FALSE.
680  *
681  * @param db the user database object
682  * @param uid the user ID
683  * @param group_ids return location for array of group IDs
684  * @param n_group_ids return location for length of returned array
685  * @param error return location for error
686  * @returns #TRUE on success
687  */
688 dbus_bool_t
689 _dbus_user_database_get_groups (DBusUserDatabase  *db,
690                                 dbus_uid_t         uid,
691                                 dbus_gid_t       **group_ids,
692                                 int               *n_group_ids,
693                                 DBusError         *error)
694 {
695   DBusUserInfo *info;
696   
697   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
698
699   *group_ids = NULL;
700   *n_group_ids = 0;
701   
702   info = _dbus_user_database_lookup (db, uid, NULL, error);
703   if (info == NULL)
704     {
705       _DBUS_ASSERT_ERROR_IS_SET (error);
706       return FALSE;
707     }
708
709   if (info->n_group_ids > 0)
710     {
711       *group_ids = dbus_new (dbus_gid_t, info->n_group_ids);
712       if (*group_ids == NULL)
713         {
714           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
715           return FALSE;
716         }
717
718       *n_group_ids = info->n_group_ids;
719
720       memcpy (*group_ids, info->group_ids, info->n_group_ids * sizeof (dbus_gid_t));
721     }
722
723   return TRUE;
724 }
725
726 /**
727  * Gets the user information for the given UID,
728  * returned user info should not be freed. 
729  *
730  * @param db user database
731  * @param uid the user ID
732  * @param info return location for const ref to user info
733  * @param error error location
734  * @returns #FALSE if error is set
735  */
736 dbus_bool_t
737 _dbus_user_database_get_uid (DBusUserDatabase    *db,
738                              dbus_uid_t           uid,
739                              const DBusUserInfo **info,
740                              DBusError           *error)
741 {
742   *info = _dbus_user_database_lookup (db, uid, NULL, error);
743   return *info != NULL;
744 }
745
746 /**
747  * Gets the user information for the given GID,
748  * returned group info should not be freed. 
749  *
750  * @param db user database
751  * @param gid the group ID
752  * @param info return location for const ref to group info
753  * @param error error location
754  * @returns #FALSE if error is set
755  */
756 dbus_bool_t
757 _dbus_user_database_get_gid (DBusUserDatabase     *db,
758                              dbus_gid_t            gid,
759                              const DBusGroupInfo **info,
760                              DBusError            *error)
761 {
762   *info = _dbus_user_database_lookup_group (db, gid, NULL, error);
763   return *info != NULL;
764 }
765
766 /**
767  * Gets the user information for the given username.
768  *
769  * @param db user database
770  * @param username the user name
771  * @param info return location for const ref to user info
772  * @param error error location
773  * @returns #FALSE if error is set
774  */
775 dbus_bool_t
776 _dbus_user_database_get_username  (DBusUserDatabase     *db,
777                                    const DBusString     *username,
778                                    const DBusUserInfo  **info,
779                                    DBusError            *error)
780 {
781   *info = _dbus_user_database_lookup (db, DBUS_UID_UNSET, username, error);
782   return *info != NULL;
783 }
784
785 /**
786  * Gets the user information for the given group name,
787  * returned group info should not be freed. 
788  *
789  * @param db user database
790  * @param groupname the group name
791  * @param info return location for const ref to group info
792  * @param error error location
793  * @returns #FALSE if error is set
794  */
795 dbus_bool_t
796 _dbus_user_database_get_groupname (DBusUserDatabase     *db,
797                                    const DBusString     *groupname,
798                                    const DBusGroupInfo **info,
799                                    DBusError            *error)
800 {
801   *info = _dbus_user_database_lookup_group (db, DBUS_GID_UNSET, groupname, error);
802   return *info != NULL;
803 }
804
805 /** @} */
806
807 #ifdef DBUS_BUILD_TESTS
808 #include <stdio.h>
809
810 /**
811  * Unit test for dbus-userdb.c.
812  * 
813  * @returns #TRUE on success.
814  */
815 dbus_bool_t
816 _dbus_userdb_test (const char *test_data_dir)
817 {
818   const DBusString *username;
819   const DBusString *homedir;
820
821   if (!_dbus_username_from_current_process (&username))
822     _dbus_assert_not_reached ("didn't get username");
823
824   if (!_dbus_homedir_from_current_process (&homedir))
825     _dbus_assert_not_reached ("didn't get homedir");  
826
827   printf ("    Current user: %s homedir: %s\n",
828           _dbus_string_get_const_data (username),
829           _dbus_string_get_const_data (homedir));
830   
831   return TRUE;
832 }
833 #endif /* DBUS_BUILD_TESTS */