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

69 statements  

1#! /usr/bin/env python 

2""" 

3rca-sh - Remote Control Access Shell. 

4 

5Copyright (C) 2017-2018 Mathias Stelzer 

6 

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. 

11 

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. 

16 

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 

27 

28CONFIG_PATH = '/etc/rca-sh.ini' 

29 

30 

31def check(users, command, user=None): 

32 """ 

33 Check whether the user is allowed to run the command. 

34 

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() 

42 

43 syslog.syslog(syslog.LOG_INFO, 'Checking whether "{}" is allowed to run: {}'.format(user, command)) 

44 

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 

50 

51 for allowed_command in allowed_commands: 

52 

53 if allowed_command.startswith('/') and allowed_command.endswith('/'): 

54 pattern = allowed_command[1:-1] # remove slashes 

55 

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 

61 

62 if allowed_command == command: 

63 syslog.syslog(syslog.LOG_DEBUG, 'match: ' + allowed_command) 

64 return True 

65 

66 syslog.syslog(syslog.LOG_DEBUG, 'ignore: ' + allowed_command) 

67 

68 syslog.syslog(syslog.LOG_ERR, '"{}" is not allowed to run: {}'.format(user, command)) 

69 return False 

70 

71 

72def configure(): 

73 """Configure the app and return the config.""" 

74 config = configparser.ConfigParser() 

75 config.read(CONFIG_PATH) 

76 

77 _facility = config.get('config', 'log facility') 

78 facility = getattr(syslog, 'LOG_{}'.format(_facility.upper())) 

79 

80 debug = config.getboolean('config', 'debug') 

81 

82 if debug: 

83 syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG)) 

84 else: 

85 syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_ERR)) 

86 

87 syslog.closelog() 

88 syslog.openlog('rca-sh', syslog.LOG_PID, facility) 

89 

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 

96 

97 

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 

104 

105 

106def main(args=None): 

107 """Initialize and run entry point. 

108 

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:] 

113 

114 args = get_argument_parser().parse_args(args) 

115 

116 users = configure() 

117 

118 if not users: 

119 syslog.syslog(syslog.LOG_ERR, 'No users configured.') 

120 sys.exit(1) 

121 

122 if args.command is None: 

123 syslog.syslog(syslog.LOG_ERR, 'No command to execute.') 

124 sys.exit(1) 

125 

126 if not check(users, args.command, user=args.user): 

127 sys.exit(1) 

128 

129 if args.user: 

130 # test, don't run the command 

131 sys.exit(0) 

132 

133 subprocess.call(args.command, shell=True) 

134 sys.exit(0) 

135 

136 

137if __name__ == '__main__': 

138 main()