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