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 */ 44void 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 */ 60void 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 */ 75void 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 */ 88void 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 */ 102dbus_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 */ 127DBusUserInfo* 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 222static dbus_bool_t database_locked = FALSE; 223static DBusUserDatabase *system_db = NULL; 224static DBusString process_username; 225static DBusString process_homedir; 226 227static void 228shutdown_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 237static dbus_bool_t 238init_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 */ 309void 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 */ 319void 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 */ 332DBusUserDatabase* 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 */ 345void 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 */ 363dbus_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 */ 385dbus_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 */ 407dbus_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 */ 446dbus_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 */ 492dbus_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 */ 530DBusUserDatabase* 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 */ 573void 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 */ 588DBusUserDatabase * 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 */ 603void 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 */ 637dbus_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 */ 656dbus_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