summarylogtreecommitdiffstats
path: root/run-as.py
blob: 3e132f8032a86fe9d98b7f522345ec5f295b61df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env python3
"""Run applications an another user"""

#    run-as
#
#    ----------------------------------------------------------------------
#    Copyright © 2023  Pellegrino Prevete
#
#    All rights reserved
#    ----------------------------------------------------------------------
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as published by
#    the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <https://www.gnu.org/licenses/>.
#

from argparse import ArgumentParser
from os import environ, stat
from pwd import getpwall, getpwuid, getpwnam
from shutil import which
import subprocess
from sys import stderr

description = "Run applications as another user"
version = 0.1

def sh(command):
    result = subprocess.run(command,
                            stdout=subprocess.PIPE,
                            text=True)
    return result

def no_access_msg():
    print("WARNING: you are trying to run an user script")

def not_found_msg(obj_type, user):
    print(f"ERROR: {obj_type} '{user}' not found.")

def resolve_user(user, uid=False):
    getuser = getpwnam
    if uid:
        getuser = getpwuid
    try:
        return getuser(user)
    except KeyError as no_user:
        return False

def resolve_command(command):
    if not which(command):
       try:
           stat(command)
       except PermissionError as no_access:
           no_access_msg()
       except FileNotFoundError as not_found:
           not_found_msg("command", command)
           return
       return command
    return which(command)
 

def resolve_systemd_homed(user):
    homectl_cmd = ['homectl',
                   'list']
    homectl = sh(homectl_cmd)
    homectl_out = homectl.stdout
    for line in homectl_out.split("\n")[1:-3]:
        _user = line.split(" ")[0]
        if user == _user:
            return True

def xhost(uid):
    user = getpwuid(uid).pw_name
    xhost_cmd = ['xhost',
                 f"+si:localuser:{user}"]
    xhost = sh(xhost_cmd)
    xhost_out = xhost.stdout

def run_as(uid, command, command_args, X=False):
    machinectl_cmd = ['machinectl',
                      'shell',
                      f"--uid={uid}"]
    call_cmd = []
    if X:
        display = environ['DISPLAY']
        machinectl_cmd.append(f"--setenv=DISPLAY={display}")
        call_cmd.append(which("enable-graphical-services"))
        xhost(uid)

    machinectl_cmd.append(".host")
    call_cmd.extend([command, *command_args])
    machinectl_cmd.extend(call_cmd)

    machinectl = sh(machinectl_cmd)
    machinectl_out = machinectl.stdout

    return machinectl_out

def get_args():
    parser = ArgumentParser(description=description)
    version = {'args': ['-V', '--version'],
               'kwargs': {'dest': 'version',
                          'action': 'store_true',
                          'default': False,
                          'help': 'print version'}}

    X = {'args': ['-X', '--graphical'],
         'kwargs': {'dest': 'X',
                    'action': 'store_true',
                    'default': False,
                    'help': 'run a graphical application'}}
    uid = {'args': ['-U', '--uid'],
           'kwargs': {'dest': 'uid',
                      'action': 'store_true',
                      'default': False,
                      'help': 'pass user as an uid'}}

    user = {'args': ['user'],
            'kwargs': {'nargs': "?",
                       'action': 'store',
                       'help': "username or UID you want to run as"}}

    command = {'args': ['command'],
               'kwargs': {'nargs': "?",
                          'action': 'store',
                          'help': "command you want to run"}}

    command_args = {'args': ['command_args'],
                    'kwargs': {'nargs': '*',
                               'action': 'store',
                               'help': "arguments to pass to the command"}}

    args = [version,
            X,
            uid,
            user,
            command,
            command_args]

    for arg in args:
        parser.add_argument(*arg['args'],
                            **arg['kwargs'])

    return parser, parser.parse_args()


def main():
    parser, args = get_args()

    if args.version:
        print(version)
        exit()

    if args.user:
        user = resolve_user(args.user, uid=args.uid)
        if not user:
            not_found_msg("user", args.user)
            exit()
        uid = user.pw_uid

    if args.command:
        command = resolve_command(args.command)
        if not command:
           not_found_msg("command", args.command)
           exit()

    if not (args.user and args.command):
        parser.print_help(stderr)
        exit()

    out = run_as(uid,
                 command,
                 args.command_args,
                 X=args.X)
    
    print(out)


if __name__ == "__main__":
    main()