2009-06-16 19:43:12 +04:00
/*
* Copyright ( C ) 2009 Kay Sievers < kay . sievers @ vrfy . org >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation ; either version 2 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details :
*/
2010-08-04 13:53:25 +04:00
# include <acl/libacl.h>
# include <sys/stat.h>
2009-06-16 19:43:12 +04:00
# include <errno.h>
# include <getopt.h>
# include <glib.h>
2010-08-04 13:53:25 +04:00
# include <inttypes.h>
2009-06-16 19:43:12 +04:00
# include <libudev.h>
2010-08-04 13:53:25 +04:00
# include <stdbool.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
2009-06-16 19:43:12 +04:00
static int debug ;
2009-09-27 17:37:26 +04:00
enum {
ACTION_NONE = 0 ,
ACTION_REMOVE ,
ACTION_ADD ,
ACTION_CHANGE
} ;
2009-06-16 19:43:12 +04:00
static int set_facl ( const char * filename , uid_t uid , int add )
{
int get ;
acl_t acl ;
acl_entry_t entry = NULL ;
acl_entry_t e ;
acl_permset_t permset ;
int ret ;
2010-08-04 13:53:25 +04:00
/* don't touch ACLs for root */
if ( uid = = 0 )
return 0 ;
2009-06-16 19:43:12 +04:00
/* read current record */
acl = acl_get_file ( filename , ACL_TYPE_ACCESS ) ;
if ( ! acl )
return - 1 ;
/* locate ACL_USER entry for uid */
get = acl_get_entry ( acl , ACL_FIRST_ENTRY , & e ) ;
while ( get = = 1 ) {
acl_tag_t t ;
acl_get_tag_type ( e , & t ) ;
if ( t = = ACL_USER ) {
uid_t * u ;
u = ( uid_t * ) acl_get_qualifier ( e ) ;
if ( u = = NULL ) {
ret = - 1 ;
goto out ;
}
if ( * u = = uid ) {
entry = e ;
acl_free ( u ) ;
break ;
}
acl_free ( u ) ;
}
get = acl_get_entry ( acl , ACL_NEXT_ENTRY , & e ) ;
}
/* remove ACL_USER entry for uid */
if ( ! add ) {
if ( entry = = NULL ) {
ret = 0 ;
goto out ;
}
acl_delete_entry ( acl , entry ) ;
goto update ;
}
/* create ACL_USER entry for uid */
if ( entry = = NULL ) {
ret = acl_create_entry ( & acl , & entry ) ;
if ( ret ! = 0 )
goto out ;
acl_set_tag_type ( entry , ACL_USER ) ;
acl_set_qualifier ( entry , & uid ) ;
}
/* add permissions for uid */
acl_get_permset ( entry , & permset ) ;
acl_add_perm ( permset , ACL_READ | ACL_WRITE ) ;
update :
/* update record */
if ( debug )
printf ( " %c%u %s \n " , add ? ' + ' : ' - ' , uid , filename ) ;
acl_calc_mask ( & acl ) ;
ret = acl_set_file ( filename , ACL_TYPE_ACCESS , acl ) ;
if ( ret ! = 0 )
goto out ;
out :
acl_free ( acl ) ;
return ret ;
}
/* check if a given uid is listed */
static int uid_in_list ( GSList * list , uid_t uid )
{
GSList * l ;
for ( l = list ; l ! = NULL ; l = g_slist_next ( l ) )
if ( uid = = GPOINTER_TO_UINT ( l - > data ) )
return 1 ;
return 0 ;
}
/* return list of current uids of local active sessions */
static GSList * uids_with_local_active_session ( const char * own_id )
{
GSList * list = NULL ;
GKeyFile * keyfile ;
keyfile = g_key_file_new ( ) ;
if ( g_key_file_load_from_file ( keyfile , " /var/run/ConsoleKit/database " , 0 , NULL ) ) {
gchar * * groups ;
groups = g_key_file_get_groups ( keyfile , NULL ) ;
if ( groups ! = NULL ) {
int i ;
for ( i = 0 ; groups [ i ] ! = NULL ; i + + ) {
uid_t u ;
if ( ! g_str_has_prefix ( groups [ i ] , " Session " ) )
continue ;
if ( own_id ! = NULL & & g_str_has_suffix ( groups [ i ] , own_id ) )
continue ;
if ( ! g_key_file_get_boolean ( keyfile , groups [ i ] , " is_local " , NULL ) )
continue ;
if ( ! g_key_file_get_boolean ( keyfile , groups [ i ] , " is_active " , NULL ) )
continue ;
u = g_key_file_get_integer ( keyfile , groups [ i ] , " uid " , NULL ) ;
if ( u > 0 & & ! uid_in_list ( list , u ) )
list = g_slist_prepend ( list , GUINT_TO_POINTER ( u ) ) ;
}
g_strfreev ( groups ) ;
}
}
g_key_file_free ( keyfile ) ;
return list ;
}
/* ConsoleKit calls us with special variables */
2009-09-27 17:37:26 +04:00
static int consolekit_called ( const char * ck_action , uid_t * uid , uid_t * uid2 , const char * * remove_session_id , int * action )
2009-06-16 19:43:12 +04:00
{
2009-09-27 17:37:26 +04:00
int a = ACTION_NONE ;
uid_t u = 0 ;
uid_t u2 = 0 ;
2009-06-16 19:43:12 +04:00
const char * s ;
2009-09-27 17:37:26 +04:00
const char * s2 ;
const char * old_session = NULL ;
2009-06-16 19:43:12 +04:00
2009-09-27 17:37:26 +04:00
if ( ck_action = = NULL | | strcmp ( ck_action , " seat_active_session_changed " ) ! = 0 )
2009-06-16 19:43:12 +04:00
return - 1 ;
2009-09-27 17:37:26 +04:00
/* We can have one of: remove, add, change, no-change */
s = getenv ( " CK_SEAT_OLD_SESSION_ID " ) ;
s2 = getenv ( " CK_SEAT_SESSION_ID " ) ;
if ( s = = NULL & & s2 = = NULL ) {
2009-06-16 19:43:12 +04:00
return - 1 ;
2009-09-27 17:37:26 +04:00
} else if ( s2 = = NULL ) {
a = ACTION_REMOVE ;
} else if ( s = = NULL ) {
a = ACTION_ADD ;
} else {
a = ACTION_CHANGE ;
}
2009-06-16 19:43:12 +04:00
2009-09-27 17:37:26 +04:00
switch ( a ) {
case ACTION_ADD :
s = getenv ( " CK_SEAT_SESSION_USER_UID " ) ;
if ( s = = NULL )
return - 1 ;
u = strtoul ( s , NULL , 10 ) ;
s = getenv ( " CK_SEAT_SESSION_IS_LOCAL " ) ;
if ( s = = NULL )
return - 1 ;
if ( strcmp ( s , " true " ) ! = 0 )
return 0 ;
break ;
case ACTION_REMOVE :
s = getenv ( " CK_SEAT_OLD_SESSION_USER_UID " ) ;
if ( s = = NULL )
return - 1 ;
u = strtoul ( s , NULL , 10 ) ;
s = getenv ( " CK_SEAT_OLD_SESSION_IS_LOCAL " ) ;
if ( s = = NULL )
return - 1 ;
if ( strcmp ( s , " true " ) ! = 0 )
return 0 ;
old_session = getenv ( " CK_SEAT_OLD_SESSION_ID " ) ;
if ( old_session = = NULL )
return - 1 ;
break ;
case ACTION_CHANGE :
s = getenv ( " CK_SEAT_OLD_SESSION_USER_UID " ) ;
if ( s = = NULL )
return - 1 ;
u = strtoul ( s , NULL , 10 ) ;
s = getenv ( " CK_SEAT_SESSION_USER_UID " ) ;
if ( s = = NULL )
return - 1 ;
u2 = strtoul ( s , NULL , 10 ) ;
s = getenv ( " CK_SEAT_OLD_SESSION_IS_LOCAL " ) ;
s2 = getenv ( " CK_SEAT_SESSION_IS_LOCAL " ) ;
if ( s = = NULL | | s2 = = NULL )
return - 1 ;
/* don't process non-local session changes */
if ( strcmp ( s , " true " ) ! = 0 & & strcmp ( s2 , " true " ) ! = 0 )
return 0 ;
if ( strcmp ( s , " true " ) = = 0 & & strcmp ( s , " true " ) = = 0 ) {
/* process the change */
if ( u = = u2 ) {
/* special case: we noop if we are
* changing between local sessions for
* the same uid */
a = ACTION_NONE ;
}
old_session = getenv ( " CK_SEAT_OLD_SESSION_ID " ) ;
if ( old_session = = NULL )
return - 1 ;
} else if ( strcmp ( s , " true " ) = = 0 ) {
/* only process the removal */
a = ACTION_REMOVE ;
old_session = getenv ( " CK_SEAT_OLD_SESSION_ID " ) ;
if ( old_session = = NULL )
return - 1 ;
} else if ( strcmp ( s2 , " true " ) = = 0 ) {
/* only process the addition */
a = ACTION_ADD ;
u = u2 ;
}
break ;
}
2009-06-16 19:43:12 +04:00
2009-09-27 17:37:26 +04:00
* remove_session_id = old_session ;
2009-06-16 19:43:12 +04:00
* uid = u ;
2009-09-27 17:37:26 +04:00
* uid2 = u2 ;
* action = a ;
2009-06-16 19:43:12 +04:00
return 0 ;
}
/* add or remove a ACL for a given uid from all matching devices */
static void apply_acl_to_devices ( uid_t uid , int add )
{
struct udev * udev ;
struct udev_enumerate * enumerate ;
struct udev_list_entry * list_entry ;
/* iterate over all devices tagged with ACL_SET */
udev = udev_new ( ) ;
enumerate = udev_enumerate_new ( udev ) ;
2010-04-22 20:33:49 +04:00
udev_enumerate_add_match_tag ( enumerate , " udev-acl " ) ;
2009-06-16 19:43:12 +04:00
udev_enumerate_scan_devices ( enumerate ) ;
udev_list_entry_foreach ( list_entry , udev_enumerate_get_list_entry ( enumerate ) ) {
struct udev_device * device ;
const char * node ;
device = udev_device_new_from_syspath ( udev_enumerate_get_udev ( enumerate ) ,
udev_list_entry_get_name ( list_entry ) ) ;
if ( device = = NULL )
continue ;
node = udev_device_get_devnode ( device ) ;
if ( node = = NULL )
continue ;
set_facl ( node , uid , add ) ;
udev_device_unref ( device ) ;
}
udev_enumerate_unref ( enumerate ) ;
udev_unref ( udev ) ;
}
2009-09-27 17:37:26 +04:00
static void
remove_uid ( uid_t uid , const char * remove_session_id )
{
/*
* Remove ACL for given uid from all matching devices
* when there is currently no local active session .
*/
GSList * list ;
list = uids_with_local_active_session ( remove_session_id ) ;
if ( ! uid_in_list ( list , uid ) )
apply_acl_to_devices ( uid , 0 ) ;
g_slist_free ( list ) ;
}
2009-06-16 19:43:12 +04:00
int main ( int argc , char * argv [ ] )
{
static const struct option options [ ] = {
{ " action " , required_argument , NULL , ' a ' } ,
{ " device " , required_argument , NULL , ' D ' } ,
{ " user " , required_argument , NULL , ' u ' } ,
{ " debug " , no_argument , NULL , ' d ' } ,
{ " help " , no_argument , NULL , ' h ' } ,
{ }
} ;
2009-09-27 17:37:26 +04:00
int action = - 1 ;
2009-06-16 19:43:12 +04:00
const char * device = NULL ;
2010-08-04 13:53:25 +04:00
bool uid_given = false ;
2009-06-16 19:43:12 +04:00
uid_t uid = 0 ;
2009-09-27 17:37:26 +04:00
uid_t uid2 = 0 ;
const char * remove_session_id = NULL ;
2009-06-16 19:43:12 +04:00
int rc = 0 ;
/* valgrind is more important to us than a slice allocator */
g_slice_set_config ( G_SLICE_CONFIG_ALWAYS_MALLOC , 1 ) ;
while ( 1 ) {
int option ;
option = getopt_long ( argc , argv , " +a:D:u:dh " , options , NULL ) ;
if ( option = = - 1 )
break ;
switch ( option ) {
case ' a ' :
2010-04-12 18:52:41 +04:00
if ( strcmp ( optarg , " remove " ) = = 0 )
2009-09-27 17:37:26 +04:00
action = ACTION_REMOVE ;
2009-06-16 19:43:12 +04:00
else
2010-04-12 18:52:41 +04:00
action = ACTION_ADD ;
2009-06-16 19:43:12 +04:00
break ;
case ' D ' :
device = optarg ;
break ;
case ' u ' :
2010-08-04 13:53:25 +04:00
uid_given = true ;
2009-06-16 19:43:12 +04:00
uid = strtoul ( optarg , NULL , 10 ) ;
break ;
case ' d ' :
debug = 1 ;
break ;
case ' h ' :
printf ( " Usage: udev-acl --action=ACTION [--device=DEVICEFILE] [--user=UID] \n \n " ) ;
goto out ;
}
}
2010-08-04 13:53:25 +04:00
if ( action < 0 & & device = = NULL & & ! uid_given )
if ( ! consolekit_called ( argv [ optind ] , & uid , & uid2 , & remove_session_id , & action ) )
uid_given = true ;
2009-06-16 19:43:12 +04:00
2009-09-27 17:37:26 +04:00
if ( action < 0 ) {
2009-06-16 19:43:12 +04:00
fprintf ( stderr , " missing action \n \n " ) ;
rc = 2 ;
goto out ;
}
2010-08-04 13:53:25 +04:00
if ( device ! = NULL & & uid_given ) {
2009-06-16 19:43:12 +04:00
fprintf ( stderr , " only one option, --device=DEVICEFILE or --user=UID expected \n \n " ) ;
rc = 3 ;
goto out ;
}
2010-08-04 13:53:25 +04:00
if ( uid_given ) {
2009-09-27 17:37:26 +04:00
switch ( action ) {
case ACTION_ADD :
2009-06-16 19:43:12 +04:00
/* Add ACL for given uid to all matching devices. */
apply_acl_to_devices ( uid , 1 ) ;
2009-09-27 17:37:26 +04:00
break ;
case ACTION_REMOVE :
remove_uid ( uid , remove_session_id ) ;
break ;
case ACTION_CHANGE :
remove_uid ( uid , remove_session_id ) ;
apply_acl_to_devices ( uid2 , 1 ) ;
break ;
case ACTION_NONE :
goto out ;
break ;
default :
g_assert_not_reached ( ) ;
break ;
2009-06-16 19:43:12 +04:00
}
} else if ( device ! = NULL ) {
/*
* Add ACLs for all current session uids to a given device .
*
* Or remove ACLs for uids which do not have any current local
* active session . Remove is not really interesting , because in
* most cases the device node is removed anyway .
*/
GSList * list ;
GSList * l ;
list = uids_with_local_active_session ( NULL ) ;
for ( l = list ; l ! = NULL ; l = g_slist_next ( l ) ) {
uid_t u ;
u = GPOINTER_TO_UINT ( l - > data ) ;
2009-09-27 17:37:26 +04:00
if ( action = = ACTION_ADD | | ! uid_in_list ( list , u ) )
set_facl ( device , u , action = = ACTION_ADD ) ;
2009-06-16 19:43:12 +04:00
}
g_slist_free ( list ) ;
} else {
fprintf ( stderr , " --device=DEVICEFILE or --user=UID expected \n \n " ) ;
rc = 3 ;
}
out :
return rc ;
}