Coverage for src/rca_sh.py: 100.00%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#! /usr/bin/env python
2"""
3rca-sh - Remote Control Access Shell.
5Copyright (C) 2017-2018 Mathias Stelzer
7This program is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
17You should have received a copy of the GNU General Public License
18along with this program. If not, see <http://www.gnu.org/licenses/>.
19"""
20import argparse
21import configparser
22import getpass
23import re
24import subprocess
25import sys
26import syslog
28CONFIG_PATH = '/etc/rca-sh.ini'
31def check(users, command, user=None):
32 """
33 Check whether the user is allowed to run the command.
35 :param users: User configuration
36 :param command: The command the user is trying to run.
37 :param user: The user who is trying to run the command. If not given,
38 the current user will be used.
39 """
40 if user is None:
41 user = getpass.getuser()
43 syslog.syslog(syslog.LOG_INFO, 'Checking whether "{}" is allowed to run: {}'.format(user, command))
45 try:
46 allowed_commands = users[user]
47 except KeyError:
48 syslog.syslog(syslog.LOG_INFO, 'User "{}" is not configured.'.format(user))
49 return False
51 for allowed_command in allowed_commands:
53 if allowed_command.startswith('/') and allowed_command.endswith('/'):
54 pattern = allowed_command[1:-1] # remove slashes
56 if re.match(pattern, command) is not None:
57 syslog.syslog(syslog.LOG_DEBUG, 'regex match: ' + pattern)
58 return True
59 syslog.syslog(syslog.LOG_DEBUG, 'regex ignore: ' + pattern)
60 continue
62 if allowed_command == command:
63 syslog.syslog(syslog.LOG_DEBUG, 'match: ' + allowed_command)
64 return True
66 syslog.syslog(syslog.LOG_DEBUG, 'ignore: ' + allowed_command)
68 syslog.syslog(syslog.LOG_ERR, '"{}" is not allowed to run: {}'.format(user, command))
69 return False
72def configure():
73 """Configure the app and return the config."""
74 config = configparser.ConfigParser()
75 config.read(CONFIG_PATH)
77 _facility = config.get('config', 'log facility')
78 facility = getattr(syslog, 'LOG_{}'.format(_facility.upper()))
80 debug = config.getboolean('config', 'debug')
82 if debug:
83 syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
84 else:
85 syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_ERR))
87 syslog.closelog()
88 syslog.openlog('rca-sh', syslog.LOG_PID, facility)
90 user_commands = {}
91 for user in config.options('commands'):
92 value = config.get('commands', user)
93 lines = value.splitlines()
94 user_commands[user] = [line for line in lines if line]
95 return user_commands
98def get_argument_parser():
99 """Build and return the commandline parser."""
100 parser = argparse.ArgumentParser()
101 parser.add_argument('-c', '--command')
102 parser.add_argument('-u', '--user', help="Test this user")
103 return parser
106def main(args=None):
107 """Initialize and run entry point.
109 Parse arguments, read the config, run the given command if allowed and exit.
110 """
111 if args is None: # nocov
112 args = sys.argv[1:]
114 args = get_argument_parser().parse_args(args)
116 users = configure()
118 if not users:
119 syslog.syslog(syslog.LOG_ERR, 'No users configured.')
120 sys.exit(1)
122 if args.command is None:
123 syslog.syslog(syslog.LOG_ERR, 'No command to execute.')
124 sys.exit(1)
126 if not check(users, args.command, user=args.user):
127 sys.exit(1)
129 if args.user:
130 # test, don't run the command
131 sys.exit(0)
133 subprocess.call(args.command, shell=True)
134 sys.exit(0)
137if __name__ == '__main__':
138 main()