1import sudo 2import signal 3from os import path 4 5 6class ReasonLoggerIOPlugin(sudo.Plugin): 7 """ 8 An example sudo plugin demonstrating how to use the sudo conversation API. 9 10 From the python plugin, you can ask something from the user using the 11 "sudo.conv" function. It expects one or more "sudo.ConvMessage" instances 12 which specifies how the interaction has to look like. 13 14 sudo.ConvMessage has the following fields (see help(sudo.ConvMessage)): 15 msg_type: int Specifies the type of the conversation. 16 See sudo.CONV.* constants below. 17 timeout: int The maximum amount of time for the conversation 18 in seconds. After the timeout exceeds, the "sudo.conv" 19 function will raise sudo.ConversationInterrupted 20 exception. 21 msg: str The message to display for the user. 22 23 To specify the conversion type you can use the following constants: 24 sudo.CONV.PROMPT_ECHO_OFF 25 sudo.CONV.PROMPT_ECHO_ON 26 sudo.CONV.ERROR_MSG 27 sudo.CONV.INFO_MSG 28 sudo.CONV.PROMPT_MASK 29 sudo.CONV.PROMPT_ECHO_OK 30 sudo.CONV.PREFER_TTY 31 """ 32 33 def open(self, argv, command_info): 34 try: 35 conv_timeout = 120 # in seconds 36 sudo.log_info("Please provide your reason " 37 "for executing {}".format(argv)) 38 39 # We ask two questions, the second is not visible on screen, 40 # so the user can hide a hidden message in case of criminals are 41 # forcing him for running the command. 42 # You can either specify the arguments in strict order (timeout 43 # being optional), or use named arguments. 44 message1 = sudo.ConvMessage(sudo.CONV.PROMPT_ECHO_ON, 45 "Reason: ", 46 conv_timeout) 47 message2 = sudo.ConvMessage(msg="Secret reason: ", 48 timeout=conv_timeout, 49 msg_type=sudo.CONV.PROMPT_MASK) 50 reply1, reply2 = sudo.conv(message1, message2, 51 on_suspend=self.on_conversation_suspend, 52 on_resume=self.on_conversation_resume) 53 54 with open(self._log_file_path(), "a") as file: 55 print("Executed", ' '.join(argv), file=file) 56 print("Reason:", reply1, file=file) 57 print("Hidden reason:", reply2, file=file) 58 59 except sudo.ConversationInterrupted: 60 sudo.log_error("You did not answer in time") 61 return sudo.RC.REJECT 62 63 def on_conversation_suspend(self, signum): 64 # This is just an example of how to do something on conversation 65 # suspend. You can skip specifying 'on_suspend' argument if there 66 # is no need 67 sudo.log_info("conversation suspend: signal", 68 self._signal_name(signum)) 69 70 def on_conversation_resume(self, signum): 71 # This is just an example of how to do something on conversation 72 # resume. You can skip specifying 'on_resume' argument if there 73 # is no need 74 sudo.log_info("conversation resume: signal was", 75 self._signal_name(signum)) 76 77 # helper functions: 78 if hasattr(signal, "Signals"): 79 @classmethod 80 def _signal_name(cls, signum: int): 81 try: 82 return signal.Signals(signum).name 83 except Exception: 84 return "{}".format(signum) 85 else: 86 @classmethod 87 def _signal_name(cls, signum: int): 88 for n, v in sorted(signal.__dict__.items()): 89 if v != signum: 90 continue 91 if n.startswith("SIG") and not n.startswith("SIG_"): 92 return n 93 return "{}".format(signum) 94 95 def _log_file_path(self): 96 options_dict = sudo.options_as_dict(self.plugin_options) 97 log_path = options_dict.get("LogPath", "/tmp") 98 return path.join(log_path, "sudo_reasons.txt") 99