Espace d'Asher256

Script pour GNU/Linux : lancer un logiciel X (graphique) avec un utilisateur n’ayant pas le droit





Il vous est probablement déjà arrivé, sous GNU/Linux, de devoir lancer un programme graphique, comme Firefox, ou un logiciel suspect que vous venez de télécharger, avec un autre utilisateur, tout en restant sous votre session X actuelle, sans ouvrir une nouvelle session.

Plusieurs méthodes existent pour cela. Par exemple, se connecter en ssh sur localhost avec l’option -X ou -Y, ou utiliser xhost. Cependant, la première méthode est un peu lente (notamment pour lancer un jeu vidéo). Quant à la seconde, elle pose des problèmes de sécurité.

Une solution intéressante consiste à exporter votre clé avec xauth, puis à l’importer dans l’utilisateur concerné. Comme cette manipulation nécessite plusieurs commandes manuelles, j’ai développé le programme sendxauth.py, qui automatise cette opération via une commande simple à retenir.

Voici un exemple pour clarifier les choses :

Si mon explication précédente n’était pas claire, laissez-moi vous donner un cas concret.

Supposons que vous devez absolument tester un programme douteux. Comme il pourrait modifier ou voler des informations sensibles dans le répertoire de votre utilisateur principal (par exemple, vos fichiers de configuration de navigateur contenant potentiellement des mots de passe, ou vos courriels personnels archivés), il est préférable de lancer ce programme avec un utilisateur restreint. Plutôt que d’exécuter ce logiciel avec votre compte principal, vous utilisez un utilisateur très limité, nommé « cobaye », qui ne possède aucun droit sur le système, si ce n’est sur ses propres fichiers.

Le problème survient si vous vous connectez directement avec cet utilisateur limité :

su -l cobaye

et que vous lancez un programme graphique, comme xterm (toujours en tant que « cobaye ») :

xterm

Vous obtiendrez l’erreur suivante :

xterm Xt error: Can't open display:
xterm:  DISPLAY is not set

Cela est normal, car l’utilisateur n’a pas les droits d’accès à votre display (pour des raisons de sécurité).

Pour lui accorder ces droits, vous devez utiliser sendxauth.py (le script de ce tutoriel, présenté plus bas), qui automatise cette opération :

sendxauth.py cobaye

Ensuite, lorsque vous vous reconnecterez en tant que « cobaye » :

su -l cobaye

et que vous aurez défini les deux variables d’environnement recommandées par sendxauth.py :

export DISPLAY=":0.0"
export XAUTHORITY="/home/cobaye/.Xauthority"

xterm se lancera sans problème, sous l’utilisateur « cobaye ».

Le script: sendxauth.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) Asher256
#
# Website : http://blog.asher256.com
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with This program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
"""Send xauth key to different users.

"""

import sys
import os
import re
import pwd
from getopt import gnu_getopt, GetoptError

VERSION = '0.1'

SOURCE_USER = ''
DESTINATION_USER = ''
DISPLAY = ':0.0'
VERBOSE = False
DEV_NULL = ' >/dev/null 2>&1'

def vprint(string):
"""Print 'string' if --verbose is defined.

"""
if VERBOSE != False:
print string

def check_environment():
"""Check if all required environment variables are available.

"""
display = os.getenv('DISPLAY')
if display == None:
print 'DISPLAY environment variable is not declared.'
sys.exit(1)
else:
globals()["DISPLAY"] = display

def user_exists(user):
"""Return True if the user exists.

"""
try:
pwd.getpwnam(user)
except KeyError:
return False
else:
return True

def handle_arguments():
"""Handle options in the arguments (argv).

"""
try:
args = sys.argv[1:]
optlist = gnu_getopt(args, 'vh', ['help', 'verbose'])
except GetoptError:
print 'Error when parsing arguments.'
print "--help for more informations."
sys.exit(1)

if len(sys.argv) < 2:
print 'What\'s the destination user ?'
print "--help for more informations."
sys.exit(1)

for user in optlist[1]:
if not user_exists(user):
print "The user '%s' doesn't exists." % user
sys.exit(1)
else:
globals()["DESTINATION_USER"] = user

break

for option, value in optlist[0]:
print option
if option in ['-v', '--verbose']:
globals()['VERBOSE'] = True
globals()['DEV_NULL'] = ''
elif option in ['-h', '--help']:
print __doc__[0:-2]
print
print 'Usage: %s [OPTIONS] destination_user <source_user>' \
% os.path.basename(sys.argv[0])
print
print "OPTIONS :"
print " -h, --help Show this help"
print " -v, --verbose Verbose mode"
print
sys.exit(0)

def commands_required(*cmd_list):
"""This function tests if all programs in
the arguments are available in the environment
variable 'PATH'.

"""
path = os.getenv('PATH')
if path != None:
path_list = path.split(os.pathsep)
else:
print "The environment variable PATH is not defined."
sys.exit(1)

for command in cmd_list:
error = True
for path in path_list:
command_path = os.path.join(path, command)
if os.access(command_path, os.X_OK):
error = False
break

if error:
print 'The command \'%s\' is not found.' % command
sys.exit(1)

def su_command_generator(command, user=''):
"""Convert a command to : su -c 'command' user and return that.

"""
command = re.sub(r"(['\\])", r"\\\1", command)
command = 'su -c \'' + command + '\''
if user != '':
command += ' ' + user
return command

def send_xauth(destination_user, source_user=''):
"""Send the authentication to the destination user

If source_user is '', the source user is the actual user (automatically
detected).

"""
if source_user != '':
sys.stdout.write(source_user + '\'s ')
print 'xauth key will be sent to', destination_user + '...'
print

auth_tmpfile = '/tmp/sendxauth' + str(os.getpid())

try:
# extract
command = 'xauth extract ' + auth_tmpfile + ' ' + DISPLAY
if source_user != '':
sys.stdout.write('You must enter ' + source_user + ' password : ')
command = su_command_generator(command)

vprint(command)
result = os.system(command + DEV_NULL)
if source_user != '':
print
if result != 0:
print 'Errors when extracting xauth key.'
sys.exit(1)

# chmod auth key
os.chmod(auth_tmpfile, 0777)

# merge
sys.stderr.write('You must enter ' + destination_user + ' password : ')
destination_home = pwd.getpwnam(destination_user)[5]
authfile = os.path.join(destination_home, '.Xauthority')

# masquer les ' et \ dans authfile
command = su_command_generator('xauth merge ' + auth_tmpfile,
destination_user)
command = 'XAUTHORITY=\'' + authfile + '\' ' + command
vprint(command)
result = os.system(command + DEV_NULL)
if DEV_NULL != '':
print

if result != 0:
print 'Error when merging xauth key by ' + destination_user + '.'
sys.exit(1)
else:
print "xauthority is sent to %s !" % destination_user
print
print "You maybe must declare these shell variables before " + \
"running a graphical program :"
print "export DISPLAY=\"%s\"" % DISPLAY
print "export XAUTHORITY=\"%s/.Xauthority\"" % \
pwd.getpwnam(destination_user)[5]
finally:
try:
os.remove(auth_tmpfile)
except OSError:
print 'Warning: Cannot remove ' + auth_tmpfile + '...'
else:
vprint('Notice: ' + auth_tmpfile + ' deleted ;)')

if __name__ == '__main__':
try:
commands_required('xauth', 'su')
check_environment()
handle_arguments()
send_xauth(DESTINATION_USER, SOURCE_USER)
except KeyboardInterrupt:
print "Interrupted."

# vim:ai:et:sw=4:ts=4:sts=4:tw=78:fenc=utf-8