Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/python/example_io_plugin.py
1532 views
1
import sudo
2
3
from os import path
4
import errno
5
import signal
6
import sys
7
import json
8
9
10
VERSION = 1.0
11
12
13
class SudoIOPlugin(sudo.Plugin):
14
"""Example sudo input/output plugin
15
16
Demonstrates how to use the sudo IO plugin API. All functions are added as
17
an example on their syntax, but note that all of them are optional.
18
19
On detailed description of the functions refer to sudo_plugin manual (man
20
sudo_plugin).
21
22
Most functions can express error or reject through their "int" return value
23
as documented in the manual. The sudo module also has constants for these:
24
sudo.RC.ACCEPT / sudo.RC.OK 1
25
sudo.RC.REJECT 0
26
sudo.RC.ERROR -1
27
sudo.RC.USAGE_ERROR -2
28
29
If the plugin encounters an error, instead of just returning sudo.RC.ERROR
30
result code it can also add a message describing the problem.
31
This can be done by raising the special exception:
32
raise sudo.PluginError("Message")
33
This added message will be used by the audit plugins.
34
35
If the function returns "None" (for example does not call return), it will
36
be considered sudo.RC.OK. If an exception other than sudo.PluginError is
37
raised, its backtrace will be shown to the user and the plugin function
38
returns sudo.RC.ERROR. If that is not acceptable, catch it.
39
"""
40
41
# -- Plugin API functions --
42
43
def __init__(self, version: str,
44
plugin_options: tuple, **kwargs):
45
"""The constructor of the IO plugin.
46
47
Other variables you can currently use as arguments are:
48
user_env: tuple
49
settings: tuple
50
user_info: tuple
51
52
For their detailed description, see the open() call of the C plugin API
53
in the sudo manual ("man sudo").
54
"""
55
if not version.startswith("1."):
56
raise sudo.SudoException(
57
"This plugin is not compatible with python plugin"
58
"API version {}".format(version))
59
60
# convert tuple of "key=value"s to dict
61
plugin_options = sudo.options_as_dict(plugin_options)
62
63
log_path = plugin_options.get("LogPath", "/tmp")
64
self._open_log_file(path.join(log_path, "sudo.log"))
65
self._log("", "-- Plugin STARTED --")
66
67
def __del__(self):
68
if hasattr(self, "_log_file"):
69
self._log("", "-- Plugin DESTROYED --")
70
self._log_file.close()
71
72
def open(self, argv: tuple,
73
command_info: tuple) -> int:
74
"""Receives the command the user wishes to run.
75
76
This function works the same as open() call of the C IO plugin API (see
77
sudo manual), except that:
78
- It only gets called before the user would execute some command (and
79
not for a version query for example).
80
- Other arguments of the C open() call are received through the
81
constructor.
82
"""
83
self._log("EXEC", " ".join(argv))
84
self._log("EXEC info", json.dumps(command_info, indent=4))
85
86
return sudo.RC.ACCEPT
87
88
def log_ttyout(self, buf: str) -> int:
89
return self._log("TTY OUT", buf.strip())
90
91
def log_ttyin(self, buf: str) -> int:
92
return self._log("TTY IN", buf.strip())
93
94
def log_stdin(self, buf: str) -> int:
95
return self._log("STD IN", buf.strip())
96
97
def log_stdout(self, buf: str) -> int:
98
return self._log("STD OUT", buf.strip())
99
100
def log_stderr(self, buf: str) -> int:
101
return self._log("STD ERR", buf.strip())
102
103
def change_winsize(self, line: int, cols: int) -> int:
104
self._log("WINSIZE", "{}x{}".format(line, cols))
105
106
def log_suspend(self, signo: int) -> int:
107
signal_description = self._signal_name(signo)
108
109
self._log("SUSPEND", signal_description)
110
111
def show_version(self, is_verbose: int) -> int:
112
sudo.log_info("Python Example IO Plugin version: {}".format(VERSION))
113
if is_verbose:
114
sudo.log_info("Python interpreter version:", sys.version)
115
116
def close(self, exit_status: int, error: int) -> None:
117
"""Called when a command execution finished.
118
119
Works the same as close() from C API (see sudo_plugin manual), except
120
that it only gets called if there was a command execution trial (open()
121
returned with sudo.RC.ACCEPT).
122
"""
123
if error == 0:
124
self._log("CLOSE", "Command returned {}".format(exit_status))
125
else:
126
error_name = errno.errorcode.get(error, "???")
127
self._log("CLOSE", "Failed to execute, execve returned {} ({})"
128
.format(error, error_name))
129
130
# -- Helper functions --
131
132
def _open_log_file(self, log_path):
133
sudo.log_info("Example sudo python plugin will log to", log_path)
134
self._log_file = open(log_path, "a")
135
136
def _log(self, type, message):
137
print(type, message, file=self._log_file)
138
return sudo.RC.ACCEPT
139
140
if hasattr(signal, "Signals"):
141
def _signal_name(cls, signo: int):
142
try:
143
return signal.Signals(signo).name
144
except ValueError:
145
return "signal {}".format(signo)
146
else:
147
def _signal_name(cls, signo: int):
148
for n, v in sorted(signal.__dict__.items()):
149
if v != signo:
150
continue;
151
if n.startswith("SIG") and not n.startswith("SIG_"):
152
return n
153
return "signal {}".format(signo)
154
155