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