Home | History | Annotate | Download | only in dbus
      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 #define DBUS_USERDB_INCLUDES_PRIVATE 1
     24 #include "dbus-userdb.h"
     25 #include "dbus-hash.h"
     26 #include "dbus-test.h"
     27 #include "dbus-internals.h"
     28 #include "dbus-protocol.h"
     29 #include <string.h>
     30 
     31 /**
     32  * @addtogroup DBusInternalsUtils
     33  * @{
     34  */
     35 
     36 /**
     37  * Frees the given #DBusUserInfo's members with _dbus_user_info_free()
     38  * and also calls dbus_free() on the block itself
     39  *
     40  * @param info the info
     41  */
     42 void
     43 _dbus_user_info_free_allocated (DBusUserInfo *info)
     44 {
     45   if (info == NULL) /* hash table will pass NULL */
     46     return;
     47 
     48   _dbus_user_info_free (info);
     49   dbus_free (info);
     50 }
     51 
     52 /**
     53  * Frees the given #DBusGroupInfo's members with _dbus_group_info_free()
     54  * and also calls dbus_free() on the block itself
     55  *
     56  * @param info the info
     57  */
     58 void
     59 _dbus_group_info_free_allocated (DBusGroupInfo *info)
     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 /**
     69  * Checks if a given string is actually a number
     70  * and converts it if it is
     71  *
     72  * @param str the string to check
     73  * @param num the memory location of the unsigned long to fill in
     74  * @returns TRUE if str is a number and num is filled in
     75  */
     76 dbus_bool_t
     77 _dbus_is_a_number (const DBusString *str,
     78                    unsigned long    *num)
     79 {
     80   int end;
     81 
     82   if (_dbus_string_parse_int (str, 0, num, &end) &&
     83       end == _dbus_string_get_length (str))
     84     return TRUE;
     85   else
     86     return FALSE;
     87 }
     88 
     89 /**
     90  * Looks up a uid or username in the user database.  Only one of name
     91  * or UID can be provided. There are wrapper functions for this that
     92  * are better to use, this one does no locking or anything on the
     93  * database and otherwise sort of sucks.
     94  *
     95  * @param db the database
     96  * @param uid the user ID or #DBUS_UID_UNSET
     97  * @param username username or #NULL
     98  * @param error error to fill in
     99  * @returns the entry in the database
    100  */
    101 DBusUserInfo*
    102 _dbus_user_database_lookup (DBusUserDatabase *db,
    103                             dbus_uid_t        uid,
    104                             const DBusString *username,
    105                             DBusError        *error)
    106 {
    107   DBusUserInfo *info;
    108 
    109   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
    110   _dbus_assert (uid != DBUS_UID_UNSET || username != NULL);
    111 
    112   /* See if the username is really a number */
    113   if (uid == DBUS_UID_UNSET)
    114     {
    115       unsigned long n;
    116 
    117       if (_dbus_is_a_number (username, &n))
    118         uid = n;
    119     }
    120 
    121   if (uid != DBUS_UID_UNSET)
    122     info = _dbus_hash_table_lookup_ulong (db->users, uid);
    123   else
    124     info = _dbus_hash_table_lookup_string (db->users_by_name, _dbus_string_get_const_data (username));
    125 
    126   if (info)
    127     {
    128       _dbus_verbose ("Using cache for UID "DBUS_UID_FORMAT" information\n",
    129                      info->uid);
    130       return info;
    131     }
    132   else
    133     {
    134       if (uid != DBUS_UID_UNSET)
    135 	_dbus_verbose ("No cache for UID "DBUS_UID_FORMAT"\n",
    136 		       uid);
    137       else
    138 	_dbus_verbose ("No cache for user \"%s\"\n",
    139 		       _dbus_string_get_const_data (username));
    140 
    141       info = dbus_new0 (DBusUserInfo, 1);
    142       if (info == NULL)
    143         {
    144           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
    145           return NULL;
    146         }
    147 
    148       if (uid != DBUS_UID_UNSET)
    149         {
    150           if (!_dbus_user_info_fill_uid (info, uid, error))
    151             {
    152               _DBUS_ASSERT_ERROR_IS_SET (error);
    153               _dbus_user_info_free_allocated (info);
    154               return NULL;
    155             }
    156         }
    157       else
    158         {
    159           if (!_dbus_user_info_fill (info, username, error))
    160             {
    161               _DBUS_ASSERT_ERROR_IS_SET (error);
    162               _dbus_user_info_free_allocated (info);
    163               return NULL;
    164             }
    165         }
    166 
    167       /* be sure we don't use these after here */
    168       uid = DBUS_UID_UNSET;
    169       username = NULL;
    170 
    171       /* insert into hash */
    172       if (!_dbus_hash_table_insert_ulong (db->users, info->uid, info))
    173         {
    174           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
    175           _dbus_user_info_free_allocated (info);
    176           return NULL;
    177         }
    178 
    179       if (!_dbus_hash_table_insert_string (db->users_by_name,
    180                                            info->username,
    181                                            info))
    182         {
    183           _dbus_hash_table_remove_ulong (db->users, info->uid);
    184           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
    185           return NULL;
    186         }
    187 
    188       return info;
    189     }
    190 }
    191 
    192 _DBUS_DEFINE_GLOBAL_LOCK(system_users);
    193 static dbus_bool_t database_locked = FALSE;
    194 static DBusUserDatabase *system_db = NULL;
    195 static DBusString process_username;
    196 static DBusString process_homedir;
    197 
    198 static void
    199 shutdown_system_db (void *data)
    200 {
    201   _dbus_user_database_unref (system_db);
    202   system_db = NULL;
    203   _dbus_string_free (&process_username);
    204   _dbus_string_free (&process_homedir);
    205 }
    206 
    207 static dbus_bool_t
    208 init_system_db (void)
    209 {
    210   _dbus_assert (database_locked);
    211 
    212   if (system_db == NULL)
    213     {
    214       DBusError error;
    215       const DBusUserInfo *info;
    216 
    217       system_db = _dbus_user_database_new ();
    218       if (system_db == NULL)
    219         return FALSE;
    220 
    221       dbus_error_init (&error);
    222 
    223       if (!_dbus_user_database_get_uid (system_db,
    224                                         _dbus_getuid (),
    225                                         &info,
    226                                         &error))
    227         {
    228           _dbus_user_database_unref (system_db);
    229           system_db = NULL;
    230 
    231           if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
    232             {
    233               dbus_error_free (&error);
    234               return FALSE;
    235             }
    236           else
    237             {
    238               /* This really should not happen. */
    239               _dbus_warn ("Could not get password database information for UID of current process: %s\n",
    240                           error.message);
    241               dbus_error_free (&error);
    242               return FALSE;
    243             }
    244         }
    245 
    246       if (!_dbus_string_init (&process_username))
    247         {
    248           _dbus_user_database_unref (system_db);
    249           system_db = NULL;
    250           return FALSE;
    251         }
    252 
    253       if (!_dbus_string_init (&process_homedir))
    254         {
    255           _dbus_string_free (&process_username);
    256           _dbus_user_database_unref (system_db);
    257           system_db = NULL;
    258           return FALSE;
    259         }
    260 
    261       if (!_dbus_string_append (&process_username,
    262                                 info->username) ||
    263           !_dbus_string_append (&process_homedir,
    264                                 info->homedir) ||
    265           !_dbus_register_shutdown_func (shutdown_system_db, NULL))
    266         {
    267           _dbus_string_free (&process_username);
    268           _dbus_string_free (&process_homedir);
    269           _dbus_user_database_unref (system_db);
    270           system_db = NULL;
    271           return FALSE;
    272         }
    273     }
    274 
    275   return TRUE;
    276 }
    277 
    278 /**
    279  * Locks global system user database.
    280  */
    281 void
    282 _dbus_user_database_lock_system (void)
    283 {
    284   _DBUS_LOCK (system_users);
    285   database_locked = TRUE;
    286 }
    287 
    288 /**
    289  * Unlocks global system user database.
    290  */
    291 void
    292 _dbus_user_database_unlock_system (void)
    293 {
    294   database_locked = FALSE;
    295   _DBUS_UNLOCK (system_users);
    296 }
    297 
    298 /**
    299  * Gets the system global user database;
    300  * must be called with lock held (_dbus_user_database_lock_system()).
    301  *
    302  * @returns the database or #NULL if no memory
    303  */
    304 DBusUserDatabase*
    305 _dbus_user_database_get_system (void)
    306 {
    307   _dbus_assert (database_locked);
    308 
    309   init_system_db ();
    310 
    311   return system_db;
    312 }
    313 
    314 /**
    315  * Gets username of user owning current process.  The returned string
    316  * is valid until dbus_shutdown() is called.
    317  *
    318  * @param username place to store pointer to username
    319  * @returns #FALSE if no memory
    320  */
    321 dbus_bool_t
    322 _dbus_username_from_current_process (const DBusString **username)
    323 {
    324   _dbus_user_database_lock_system ();
    325   if (!init_system_db ())
    326     {
    327       _dbus_user_database_unlock_system ();
    328       return FALSE;
    329     }
    330   *username = &process_username;
    331   _dbus_user_database_unlock_system ();
    332 
    333   return TRUE;
    334 }
    335 
    336 /**
    337  * Gets homedir of user owning current process.  The returned string
    338  * is valid until dbus_shutdown() is called.
    339  *
    340  * @param homedir place to store pointer to homedir
    341  * @returns #FALSE if no memory
    342  */
    343 dbus_bool_t
    344 _dbus_homedir_from_current_process (const DBusString  **homedir)
    345 {
    346   _dbus_user_database_lock_system ();
    347   if (!init_system_db ())
    348     {
    349       _dbus_user_database_unlock_system ();
    350       return FALSE;
    351     }
    352   *homedir = &process_homedir;
    353   _dbus_user_database_unlock_system ();
    354 
    355   return TRUE;
    356 }
    357 
    358 /**
    359  * Gets the home directory for the given user.
    360  *
    361  * @param username the username
    362  * @param homedir string to append home directory to
    363  * @returns #TRUE if user existed and we appended their homedir
    364  */
    365 dbus_bool_t
    366 _dbus_homedir_from_username (const DBusString *username,
    367                              DBusString       *homedir)
    368 {
    369   DBusUserDatabase *db;
    370   const DBusUserInfo *info;
    371   _dbus_user_database_lock_system ();
    372 
    373   db = _dbus_user_database_get_system ();
    374   if (db == NULL)
    375     {
    376       _dbus_user_database_unlock_system ();
    377       return FALSE;
    378     }
    379 
    380   if (!_dbus_user_database_get_username (db, username,
    381                                          &info, NULL))
    382     {
    383       _dbus_user_database_unlock_system ();
    384       return FALSE;
    385     }
    386 
    387   if (!_dbus_string_append (homedir, info->homedir))
    388     {
    389       _dbus_user_database_unlock_system ();
    390       return FALSE;
    391     }
    392 
    393   _dbus_user_database_unlock_system ();
    394   return TRUE;
    395 }
    396 
    397 /**
    398  * Gets the credentials corresponding to the given username.
    399  *
    400  * @param username the username
    401  * @param credentials credentials to fill in
    402  * @returns #TRUE if the username existed and we got some credentials
    403  */
    404 dbus_bool_t
    405 _dbus_credentials_from_username (const DBusString *username,
    406                                  DBusCredentials  *credentials)
    407 {
    408   DBusUserDatabase *db;
    409   const DBusUserInfo *info;
    410   _dbus_user_database_lock_system ();
    411 
    412   db = _dbus_user_database_get_system ();
    413   if (db == NULL)
    414     {
    415       _dbus_user_database_unlock_system ();
    416       return FALSE;
    417     }
    418 
    419   if (!_dbus_user_database_get_username (db, username,
    420                                          &info, NULL))
    421     {
    422       _dbus_user_database_unlock_system ();
    423       return FALSE;
    424     }
    425 
    426   credentials->pid = DBUS_PID_UNSET;
    427   credentials->uid = info->uid;
    428   credentials->gid = info->primary_gid;
    429 
    430   _dbus_user_database_unlock_system ();
    431   return TRUE;
    432 }
    433 
    434 /**
    435  * Creates a new user database object used to look up and
    436  * cache user information.
    437  * @returns new database, or #NULL on out of memory
    438  */
    439 DBusUserDatabase*
    440 _dbus_user_database_new (void)
    441 {
    442   DBusUserDatabase *db;
    443 
    444   db = dbus_new0 (DBusUserDatabase, 1);
    445   if (db == NULL)
    446     return NULL;
    447 
    448   db->refcount = 1;
    449 
    450   db->users = _dbus_hash_table_new (DBUS_HASH_ULONG,
    451                                     NULL, (DBusFreeFunction) _dbus_user_info_free_allocated);
    452 
    453   if (db->users == NULL)
    454     goto failed;
    455 
    456   db->groups = _dbus_hash_table_new (DBUS_HASH_ULONG,
    457                                      NULL, (DBusFreeFunction) _dbus_group_info_free_allocated);
    458 
    459   if (db->groups == NULL)
    460     goto failed;
    461 
    462   db->users_by_name = _dbus_hash_table_new (DBUS_HASH_STRING,
    463                                             NULL, NULL);
    464   if (db->users_by_name == NULL)
    465     goto failed;
    466 
    467   db->groups_by_name = _dbus_hash_table_new (DBUS_HASH_STRING,
    468                                              NULL, NULL);
    469   if (db->groups_by_name == NULL)
    470     goto failed;
    471 
    472   return db;
    473 
    474  failed:
    475   _dbus_user_database_unref (db);
    476   return NULL;
    477 }
    478 
    479 /**
    480  * Flush all information out of the user database.
    481  */
    482 void
    483 _dbus_user_database_flush (DBusUserDatabase *db)
    484 {
    485   _dbus_hash_table_remove_all(db->users_by_name);
    486   _dbus_hash_table_remove_all(db->groups_by_name);
    487   _dbus_hash_table_remove_all(db->users);
    488   _dbus_hash_table_remove_all(db->groups);
    489 }
    490 
    491 #ifdef DBUS_BUILD_TESTS
    492 /**
    493  * Increments refcount of user database.
    494  * @param db the database
    495  * @returns the database
    496  */
    497 DBusUserDatabase *
    498 _dbus_user_database_ref (DBusUserDatabase  *db)
    499 {
    500   _dbus_assert (db->refcount > 0);
    501 
    502   db->refcount += 1;
    503 
    504   return db;
    505 }
    506 #endif /* DBUS_BUILD_TESTS */
    507 
    508 /**
    509  * Decrements refcount of user database.
    510  * @param db the database
    511  */
    512 void
    513 _dbus_user_database_unref (DBusUserDatabase  *db)
    514 {
    515   _dbus_assert (db->refcount > 0);
    516 
    517   db->refcount -= 1;
    518   if (db->refcount == 0)
    519     {
    520       if (db->users)
    521         _dbus_hash_table_unref (db->users);
    522 
    523       if (db->groups)
    524         _dbus_hash_table_unref (db->groups);
    525 
    526       if (db->users_by_name)
    527         _dbus_hash_table_unref (db->users_by_name);
    528 
    529       if (db->groups_by_name)
    530         _dbus_hash_table_unref (db->groups_by_name);
    531 
    532       dbus_free (db);
    533     }
    534 }
    535 
    536 /**
    537  * Gets the user information for the given UID,
    538  * returned user info should not be freed.
    539  *
    540  * @param db user database
    541  * @param uid the user ID
    542  * @param info return location for const ref to user info
    543  * @param error error location
    544  * @returns #FALSE if error is set
    545  */
    546 dbus_bool_t
    547 _dbus_user_database_get_uid (DBusUserDatabase    *db,
    548                              dbus_uid_t           uid,
    549                              const DBusUserInfo **info,
    550                              DBusError           *error)
    551 {
    552   *info = _dbus_user_database_lookup (db, uid, NULL, error);
    553   return *info != NULL;
    554 }
    555 
    556 /**
    557  * Gets the user information for the given username.
    558  *
    559  * @param db user database
    560  * @param username the user name
    561  * @param info return location for const ref to user info
    562  * @param error error location
    563  * @returns #FALSE if error is set
    564  */
    565 dbus_bool_t
    566 _dbus_user_database_get_username  (DBusUserDatabase     *db,
    567                                    const DBusString     *username,
    568                                    const DBusUserInfo  **info,
    569                                    DBusError            *error)
    570 {
    571   *info = _dbus_user_database_lookup (db, DBUS_UID_UNSET, username, error);
    572   return *info != NULL;
    573 }
    574 
    575 /** @} */
    576 
    577 /* Tests in dbus-userdb-util.c */
    578