Console user security policy
[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, 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 #include "dbus-userdb.h"
24 #include "dbus-hash.h"
25 #include "dbus-test.h"
26 #include "dbus-internals.h"
27 #include "dbus-protocol.h"
28 #include <string.h>
29
30 /**
31  * Internals of DBusUserDatabase
32  */
33 struct DBusUserDatabase
34 {
35   int refcount; /**< Reference count */
36
37   DBusHashTable *users; /**< Users in the database by UID */
38   DBusHashTable *groups; /**< Groups in the database by GID */
39   DBusHashTable *users_by_name; /**< Users in the database by name */
40   DBusHashTable *groups_by_name; /**< Groups in the database by name */
41
42 };
43
44 static void
45 free_user_info (void *data)
46 {
47   DBusUserInfo *info = data;
48
49   if (info == NULL) /* hash table will pass NULL */
50     return;
51
52   _dbus_user_info_free (info);
53   dbus_free (info);
54 }
55
56 static void
57 free_group_info (void *data)
58 {
59   DBusGroupInfo *info = data;
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 static DBusUserInfo*
69 _dbus_user_database_lookup (DBusUserDatabase *db,
70                             dbus_uid_t        uid,
71                             const DBusString *username,
72                             DBusError        *error)
73 {
74   DBusUserInfo *info;
75
76   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
77   _dbus_assert (uid != DBUS_UID_UNSET || username != NULL);
78   
79   if (uid != DBUS_UID_UNSET)
80     info = _dbus_hash_table_lookup_ulong (db->users, uid);
81   else
82     info = _dbus_hash_table_lookup_string (db->users_by_name, _dbus_string_get_const_data (username));
83   
84   if (info)
85     {
86       _dbus_verbose ("Using cache for UID "DBUS_UID_FORMAT" information\n",
87                      uid);
88       return info;
89     }
90   else
91     {
92       _dbus_verbose ("No cache for UID "DBUS_UID_FORMAT"\n",
93                      uid);
94       
95       info = dbus_new0 (DBusUserInfo, 1);
96       if (info == NULL)
97         {
98           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
99           return NULL;
100         }
101
102       if (uid != DBUS_UID_UNSET)
103         {
104           if (!_dbus_user_info_fill_uid (info, uid, error))
105             {
106               _DBUS_ASSERT_ERROR_IS_SET (error);
107               free_user_info (info);
108               return NULL;
109             }
110         }
111       else
112         {
113           if (!_dbus_user_info_fill (info, username, error))
114             {
115               _DBUS_ASSERT_ERROR_IS_SET (error);
116               free_user_info (info);
117               return NULL;
118             }
119         }
120
121       /* be sure we don't use these after here */
122       uid = DBUS_UID_UNSET;
123       username = NULL;
124
125       /* insert into hash */
126       if (!_dbus_hash_table_insert_ulong (db->users, info->uid, info))
127         {
128           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
129           free_user_info (info);
130           return NULL;
131         }
132
133       if (!_dbus_hash_table_insert_string (db->users_by_name,
134                                            info->username,
135                                            info))
136         {
137           _dbus_hash_table_remove_ulong (db->users, info->uid);
138           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
139           return NULL;
140         }
141       
142       return info;
143     }
144 }
145
146 static DBusGroupInfo*
147 _dbus_user_database_lookup_group (DBusUserDatabase *db,
148                                   dbus_gid_t        gid,
149                                   const DBusString *groupname,
150                                   DBusError        *error)
151 {
152   DBusGroupInfo *info;
153
154   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
155
156   if (gid != DBUS_GID_UNSET)
157     info = _dbus_hash_table_lookup_ulong (db->groups, gid);
158   else
159     info = _dbus_hash_table_lookup_string (db->groups_by_name,
160                                            _dbus_string_get_const_data (groupname));
161   if (info)
162     {
163       _dbus_verbose ("Using cache for GID "DBUS_GID_FORMAT" information\n",
164                      gid);
165       return info;
166     }
167   else
168     {
169       _dbus_verbose ("No cache for GID "DBUS_GID_FORMAT"\n",
170                      gid);
171       
172       info = dbus_new0 (DBusGroupInfo, 1);
173       if (info == NULL)
174         {
175           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
176           return NULL;
177         }
178
179       if (!_dbus_group_info_fill_gid (info, gid, error))
180         {
181           _DBUS_ASSERT_ERROR_IS_SET (error);
182           free_group_info (info);
183           return NULL;
184         }
185
186       if (!_dbus_hash_table_insert_ulong (db->groups, info->gid, info))
187         {
188           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
189           free_group_info (info);
190           return NULL;
191         }
192
193
194       if (!_dbus_hash_table_insert_string (db->groups_by_name,
195                                            info->groupname,
196                                            info))
197         {
198           _dbus_hash_table_remove_ulong (db->groups, info->gid);
199           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
200           return NULL;
201         }
202       
203       return info;
204     }
205 }
206
207 _DBUS_DEFINE_GLOBAL_LOCK(system_users);
208 static dbus_bool_t database_locked = FALSE;
209 static DBusUserDatabase *system_db = NULL;
210 static DBusString process_username;
211 static DBusString process_homedir;
212       
213 static void
214 shutdown_system_db (void *data)
215 {
216   _dbus_user_database_unref (system_db);
217   system_db = NULL;
218   _dbus_string_free (&process_username);
219   _dbus_string_free (&process_homedir);
220 }
221
222 static dbus_bool_t
223 init_system_db (void)
224 {
225   _dbus_assert (database_locked);
226     
227   if (system_db == NULL)
228     {
229       DBusError error;
230       const DBusUserInfo *info;
231       
232       system_db = _dbus_user_database_new ();
233       if (system_db == NULL)
234         return FALSE;
235
236       dbus_error_init (&error);
237
238       if (!_dbus_user_database_get_uid (system_db,
239                                         _dbus_getuid (),
240                                         &info,
241                                         &error))
242         {
243           _dbus_user_database_unref (system_db);
244           system_db = NULL;
245           
246           if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
247             {
248               dbus_error_free (&error);
249               return FALSE;
250             }
251           else
252             {
253               /* This really should not happen. */
254               _dbus_warn ("Could not get password database information for UID of current process: %s\n",
255                           error.message);
256               dbus_error_free (&error);
257               return FALSE;
258             }
259         }
260
261       if (!_dbus_string_init (&process_username))
262         {
263           _dbus_user_database_unref (system_db);
264           system_db = NULL;
265           return FALSE;
266         }
267
268       if (!_dbus_string_init (&process_homedir))
269         {
270           _dbus_string_free (&process_username);
271           _dbus_user_database_unref (system_db);
272           system_db = NULL;
273           return FALSE;
274         }
275
276       if (!_dbus_string_append (&process_username,
277                                 info->username) ||
278           !_dbus_string_append (&process_homedir,
279                                 info->homedir) ||
280           !_dbus_register_shutdown_func (shutdown_system_db, NULL))
281         {
282           _dbus_string_free (&process_username);
283           _dbus_string_free (&process_homedir);
284           _dbus_user_database_unref (system_db);
285           system_db = NULL;
286           return FALSE;
287         }
288     }
289
290   return TRUE;
291 }
292
293 /**
294  * @addtogroup DBusInternalsUtils
295  * @{
296  */
297
298 /**
299  * Locks global system user database.
300  */
301 void
302 _dbus_user_database_lock_system (void)
303 {
304   _DBUS_LOCK (system_users);
305   database_locked = TRUE;
306 }
307
308 /**
309  * Unlocks global system user database.
310  */
311 void
312 _dbus_user_database_unlock_system (void)
313 {
314   database_locked = FALSE;
315   _DBUS_UNLOCK (system_users);
316 }
317
318 /**
319  * Gets the system global user database;
320  * must be called with lock held (_dbus_user_database_lock_system()).
321  *
322  * @returns the database or #NULL if no memory
323  */
324 DBusUserDatabase*
325 _dbus_user_database_get_system (void)
326 {
327   _dbus_assert (database_locked);
328
329   init_system_db ();
330   
331   return system_db;
332 }
333
334 /**
335  * Gets username of user owning current process.  The returned string
336  * is valid until dbus_shutdown() is called.
337  *
338  * @param username place to store pointer to username
339  * @returns #FALSE if no memory
340  */
341 dbus_bool_t
342 _dbus_username_from_current_process (const DBusString **username)
343 {
344   _dbus_user_database_lock_system ();
345   if (!init_system_db ())
346     {
347       _dbus_user_database_unlock_system ();
348       return FALSE;
349     }
350   *username = &process_username;
351   _dbus_user_database_unlock_system ();  
352
353   return TRUE;
354 }
355
356 /**
357  * Gets homedir of user owning current process.  The returned string
358  * is valid until dbus_shutdown() is called.
359  *
360  * @param homedir place to store pointer to homedir
361  * @returns #FALSE if no memory
362  */
363 dbus_bool_t
364 _dbus_homedir_from_current_process (const DBusString  **homedir)
365 {
366   _dbus_user_database_lock_system ();
367   if (!init_system_db ())
368     {
369       _dbus_user_database_unlock_system ();
370       return FALSE;
371     }
372   *homedir = &process_homedir;
373   _dbus_user_database_unlock_system ();
374
375   return TRUE;
376 }
377
378 /**
379  * Gets user ID given username
380  *
381  * @param username the username
382  * @param uid return location for UID
383  * @returns #TRUE if username existed and we got the UID
384  */
385 dbus_bool_t
386 _dbus_get_user_id (const DBusString  *username,
387                    dbus_uid_t        *uid)
388 {
389   DBusCredentials creds;
390
391   if (!_dbus_credentials_from_username (username, &creds))
392     return FALSE;
393
394   if (creds.uid == DBUS_UID_UNSET)
395     return FALSE;
396
397   *uid = creds.uid;
398
399   return TRUE;
400 }
401
402 /**
403  * Checks to see if the UID sent in is the console user
404  *
405  * @param uid UID of person to check 
406  * @param error return location for errors
407  * @returns #TRUE if the UID is the same as the console user and there are no errors
408  */
409 dbus_bool_t
410 _dbus_is_console_user (dbus_uid_t uid,
411                        DBusError *error)
412 {
413
414   DBusUserDatabase *db;
415   const DBusUserInfo *info;
416   DBusString *console_file;
417   dbus_bool_t result = FALSE; 
418
419   _dbus_user_database_lock_system ();
420
421   db = _dbus_user_database_get_system ();
422   if (db == NULL)
423     {
424       dbus_set_error (error, DBUS_ERROR_FAILED, "Could not get system database.");
425       _dbus_user_database_unlock_system ();
426       return FALSE;
427     }
428
429   info = _dbus_user_database_lookup (db, uid, NULL, error);
430
431   if (info == NULL)
432     {
433       _dbus_user_database_unlock_system ();
434        return FALSE;
435     }
436
437   result = _dbus_user_at_console (info->username, error);
438
439   _dbus_user_database_unlock_system ();
440
441   return result;
442 }
443
444 /**
445  * Gets group ID given groupname
446  *
447  * @param groupname the groupname
448  * @param gid return location for GID
449  * @returns #TRUE if group name existed and we got the GID
450  */
451 dbus_bool_t
452 _dbus_get_group_id (const DBusString  *groupname,
453                     dbus_gid_t        *gid)
454 {
455   DBusUserDatabase *db;
456   const DBusGroupInfo *info;
457   _dbus_user_database_lock_system ();
458
459   db = _dbus_user_database_get_system ();
460   if (db == NULL)
461     {
462       _dbus_user_database_unlock_system ();
463       return FALSE;
464     }
465
466   if (!_dbus_user_database_get_groupname (db, groupname,
467                                           &info, NULL))
468     {
469       _dbus_user_database_unlock_system ();
470       return FALSE;
471     }
472
473   *gid = info->gid;
474   
475   _dbus_user_database_unlock_system ();
476   return TRUE;
477 }
478
479 /**
480  * Gets the home directory for the given user.
481  *
482  * @param username the username
483  * @param homedir string to append home directory to
484  * @returns #TRUE if user existed and we appended their homedir
485  */
486 dbus_bool_t
487 _dbus_homedir_from_username (const DBusString *username,
488                              DBusString       *homedir)
489 {
490   DBusUserDatabase *db;
491   const DBusUserInfo *info;
492   _dbus_user_database_lock_system ();
493
494   db = _dbus_user_database_get_system ();
495   if (db == NULL)
496     {
497       _dbus_user_database_unlock_system ();
498       return FALSE;
499     }
500
501   if (!_dbus_user_database_get_username (db, username,
502                                          &info, NULL))
503     {
504       _dbus_user_database_unlock_system ();
505       return FALSE;
506     }
507
508   if (!_dbus_string_append (homedir, info->homedir))
509     {
510       _dbus_user_database_unlock_system ();
511       return FALSE;
512     }
513   
514   _dbus_user_database_unlock_system ();
515   return TRUE;
516 }
517
518 /**
519  * Gets a UID from a UID string.
520  *
521  * @param uid_str the UID in string form
522  * @param uid UID to fill in
523  * @returns #TRUE if successfully filled in UID
524  */
525 dbus_bool_t
526 _dbus_uid_from_string (const DBusString      *uid_str,
527                        dbus_uid_t            *uid)
528 {
529   int end;
530   long val;
531   
532   if (_dbus_string_get_length (uid_str) == 0)
533     {
534       _dbus_verbose ("UID string was zero length\n");
535       return FALSE;
536     }
537
538   val = -1;
539   end = 0;
540   if (!_dbus_string_parse_int (uid_str, 0, &val,
541                                &end))
542     {
543       _dbus_verbose ("could not parse string as a UID\n");
544       return FALSE;
545     }
546   
547   if (end != _dbus_string_get_length (uid_str))
548     {
549       _dbus_verbose ("string contained trailing stuff after UID\n");
550       return FALSE;
551     }
552
553   *uid = val;
554
555   return TRUE;
556 }
557
558 /**
559  * Gets the credentials corresponding to the given username.
560  *
561  * @param username the username
562  * @param credentials credentials to fill in
563  * @returns #TRUE if the username existed and we got some credentials
564  */
565 dbus_bool_t
566 _dbus_credentials_from_username (const DBusString *username,
567                                  DBusCredentials  *credentials)
568 {
569   DBusUserDatabase *db;
570   const DBusUserInfo *info;
571   _dbus_user_database_lock_system ();
572
573   db = _dbus_user_database_get_system ();
574   if (db == NULL)
575     {
576       _dbus_user_database_unlock_system ();
577       return FALSE;
578     }
579
580   if (!_dbus_user_database_get_username (db, username,
581                                          &info, NULL))
582     {
583       _dbus_user_database_unlock_system ();
584       return FALSE;
585     }
586
587   credentials->pid = DBUS_PID_UNSET;
588   credentials->uid = info->uid;
589   credentials->gid = info->primary_gid;
590   
591   _dbus_user_database_unlock_system ();
592   return TRUE;
593 }
594
595 /**
596  * Gets the credentials corresponding to the given UID.
597  *
598  * @param uid the UID
599  * @param credentials credentials to fill in
600  * @returns #TRUE if the UID existed and we got some credentials
601  */
602 dbus_bool_t
603 _dbus_credentials_from_uid (dbus_uid_t        uid,
604                             DBusCredentials  *credentials)
605 {
606   DBusUserDatabase *db;
607   const DBusUserInfo *info;
608   _dbus_user_database_lock_system ();
609
610   db = _dbus_user_database_get_system ();
611   if (db == NULL)
612     {
613       _dbus_user_database_unlock_system ();
614       return FALSE;
615     }
616
617   if (!_dbus_user_database_get_uid (db, uid,
618                                     &info, NULL))
619     {
620       _dbus_user_database_unlock_system ();
621       return FALSE;
622     }
623
624   _dbus_assert (info->uid == uid);
625   
626   credentials->pid = DBUS_PID_UNSET;
627   credentials->uid = info->uid;
628   credentials->gid = info->primary_gid;
629   
630   _dbus_user_database_unlock_system ();
631   return TRUE;
632 }
633
634 /**
635  * Creates a new user database object used to look up and
636  * cache user information.
637  * @returns new database, or #NULL on out of memory
638  */
639 DBusUserDatabase*
640 _dbus_user_database_new (void)
641 {
642   DBusUserDatabase *db;
643   
644   db = dbus_new0 (DBusUserDatabase, 1);
645   if (db == NULL)
646     return NULL;
647
648   db->refcount = 1;
649
650   db->users = _dbus_hash_table_new (DBUS_HASH_ULONG,
651                                     NULL, free_user_info);
652   
653   if (db->users == NULL)
654     goto failed;
655
656   db->groups = _dbus_hash_table_new (DBUS_HASH_ULONG,
657                                      NULL, free_group_info);
658   
659   if (db->groups == NULL)
660     goto failed;
661
662   db->users_by_name = _dbus_hash_table_new (DBUS_HASH_STRING,
663                                             NULL, NULL);
664   if (db->users_by_name == NULL)
665     goto failed;
666   
667   db->groups_by_name = _dbus_hash_table_new (DBUS_HASH_STRING,
668                                              NULL, NULL);
669   if (db->groups_by_name == NULL)
670     goto failed;
671   
672   return db;
673   
674  failed:
675   _dbus_user_database_unref (db);
676   return NULL;
677 }
678
679 /**
680  * Increments refcount of user database.
681  * @param db the database
682  * @returns the database
683  */
684 DBusUserDatabase *
685 _dbus_user_database_ref (DBusUserDatabase  *db)
686 {
687   _dbus_assert (db->refcount > 0);
688
689   db->refcount += 1;
690
691   return db;
692 }
693
694 /**
695  * Decrements refcount of user database.
696  * @param db the database
697  */
698 void
699 _dbus_user_database_unref (DBusUserDatabase  *db)
700 {
701   _dbus_assert (db->refcount > 0);
702
703   db->refcount -= 1;
704   if (db->refcount == 0)
705     {
706       if (db->users)
707         _dbus_hash_table_unref (db->users);
708
709       if (db->groups)
710         _dbus_hash_table_unref (db->groups);
711
712       if (db->users_by_name)
713         _dbus_hash_table_unref (db->users_by_name);
714
715       if (db->groups_by_name)
716         _dbus_hash_table_unref (db->groups_by_name);
717       
718       dbus_free (db);
719     }
720 }
721
722 /**
723  * Gets all groups for a particular user. Returns #FALSE
724  * if no memory, or user isn't known, but always initializes
725  * group_ids to a NULL array. Sets error to the reason
726  * for returning #FALSE.
727  *
728  * @param db the user database object
729  * @param uid the user ID
730  * @param group_ids return location for array of group IDs
731  * @param n_group_ids return location for length of returned array
732  * @param error return location for error
733  * @returns #TRUE on success
734  */
735 dbus_bool_t
736 _dbus_user_database_get_groups (DBusUserDatabase  *db,
737                                 dbus_uid_t         uid,
738                                 dbus_gid_t       **group_ids,
739                                 int               *n_group_ids,
740                                 DBusError         *error)
741 {
742   DBusUserInfo *info;
743   
744   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
745
746   *group_ids = NULL;
747   *n_group_ids = 0;
748   
749   info = _dbus_user_database_lookup (db, uid, NULL, error);
750   if (info == NULL)
751     {
752       _DBUS_ASSERT_ERROR_IS_SET (error);
753       return FALSE;
754     }
755
756   if (info->n_group_ids > 0)
757     {
758       *group_ids = dbus_new (dbus_gid_t, info->n_group_ids);
759       if (*group_ids == NULL)
760         {
761           dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
762           return FALSE;
763         }
764
765       *n_group_ids = info->n_group_ids;
766
767       memcpy (*group_ids, info->group_ids, info->n_group_ids * sizeof (dbus_gid_t));
768     }
769
770   return TRUE;
771 }
772
773 /**
774  * Gets the user information for the given UID,
775  * returned user info should not be freed. 
776  *
777  * @param db user database
778  * @param uid the user ID
779  * @param info return location for const ref to user info
780  * @param error error location
781  * @returns #FALSE if error is set
782  */
783 dbus_bool_t
784 _dbus_user_database_get_uid (DBusUserDatabase    *db,
785                              dbus_uid_t           uid,
786                              const DBusUserInfo **info,
787                              DBusError           *error)
788 {
789   *info = _dbus_user_database_lookup (db, uid, NULL, error);
790   return *info != NULL;
791 }
792
793 /**
794  * Gets the user information for the given GID,
795  * returned group info should not be freed. 
796  *
797  * @param db user database
798  * @param gid the group ID
799  * @param info return location for const ref to group info
800  * @param error error location
801  * @returns #FALSE if error is set
802  */
803 dbus_bool_t
804 _dbus_user_database_get_gid (DBusUserDatabase     *db,
805                              dbus_gid_t            gid,
806                              const DBusGroupInfo **info,
807                              DBusError            *error)
808 {
809   *info = _dbus_user_database_lookup_group (db, gid, NULL, error);
810   return *info != NULL;
811 }
812
813 /**
814  * Gets the user information for the given username.
815  *
816  * @param db user database
817  * @param username the user name
818  * @param info return location for const ref to user info
819  * @param error error location
820  * @returns #FALSE if error is set
821  */
822 dbus_bool_t
823 _dbus_user_database_get_username  (DBusUserDatabase     *db,
824                                    const DBusString     *username,
825                                    const DBusUserInfo  **info,
826                                    DBusError            *error)
827 {
828   *info = _dbus_user_database_lookup (db, DBUS_UID_UNSET, username, error);
829   return *info != NULL;
830 }
831
832 /**
833  * Gets the user information for the given group name,
834  * returned group info should not be freed. 
835  *
836  * @param db user database
837  * @param groupname the group name
838  * @param info return location for const ref to group info
839  * @param error error location
840  * @returns #FALSE if error is set
841  */
842 dbus_bool_t
843 _dbus_user_database_get_groupname (DBusUserDatabase     *db,
844                                    const DBusString     *groupname,
845                                    const DBusGroupInfo **info,
846                                    DBusError            *error)
847 {
848   *info = _dbus_user_database_lookup_group (db, DBUS_GID_UNSET, groupname, error);
849   return *info != NULL;
850 }
851
852 /** @} */
853
854 #ifdef DBUS_BUILD_TESTS
855 #include <stdio.h>
856
857 /**
858  * Unit test for dbus-userdb.c.
859  * 
860  * @returns #TRUE on success.
861  */
862 dbus_bool_t
863 _dbus_userdb_test (const char *test_data_dir)
864 {
865   const DBusString *username;
866   const DBusString *homedir;
867
868   if (!_dbus_username_from_current_process (&username))
869     _dbus_assert_not_reached ("didn't get username");
870
871   if (!_dbus_homedir_from_current_process (&homedir))
872     _dbus_assert_not_reached ("didn't get homedir");  
873
874   printf ("    Current user: %s homedir: %s\n",
875           _dbus_string_get_const_data (username),
876           _dbus_string_get_const_data (homedir));
877   
878   return TRUE;
879 }
880 #endif /* DBUS_BUILD_TESTS */