1 /*
2 * SPDX-License-Identifier: ISC
3 *
4 * Copyright (c) 2019-2020 Robert Manner <robert.manner@oneidentity.com>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21 * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22 */
23
24 #include "python_plugin_common.h"
25
26 struct IOPluginContext
27 {
28 struct PluginContext base_ctx;
29 struct io_plugin *io_plugin;
30 };
31
32 #define BASE_CTX(io_ctx) (&(io_ctx->base_ctx))
33
34 #define PY_IO_PLUGIN_VERSION SUDO_API_MKVERSION(1, 0)
35
36 #define CALLBACK_PLUGINFUNC(func_name) io_ctx->io_plugin->func_name
37
38 // This also verifies compile time that the name matches the sudo plugin API.
39 #define CALLBACK_PYNAME(func_name) ((void)CALLBACK_PLUGINFUNC(func_name), #func_name)
40
41 #define MARK_CALLBACK_OPTIONAL(function_name) \
42 do { \
43 python_plugin_mark_callback_optional(plugin_ctx, CALLBACK_PYNAME(function_name), \
44 (void **)&CALLBACK_PLUGINFUNC(function_name)); \
45 } while(0)
46
47
48 static int
_call_plugin_open(struct IOPluginContext * io_ctx,int argc,char * const argv[],char * const command_info[])49 _call_plugin_open(struct IOPluginContext *io_ctx, int argc, char * const argv[], char * const command_info[])
50 {
51 debug_decl(_call_plugin_open, PYTHON_DEBUG_CALLBACKS);
52 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
53 plugin_ctx->call_close = 1;
54
55 if (!PyObject_HasAttrString(plugin_ctx->py_instance, CALLBACK_PYNAME(open))) {
56 debug_return_int(SUDO_RC_OK);
57 }
58
59 int rc = SUDO_RC_ERROR;
60 PyObject *py_argv = py_str_array_to_tuple_with_count(argc, argv);
61 PyObject *py_command_info = py_str_array_to_tuple(command_info);
62
63 if (py_argv != NULL && py_command_info != NULL) {
64 rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(open),
65 Py_BuildValue("(OO)", py_argv, py_command_info));
66 } else {
67 rc = SUDO_RC_ERROR;
68 }
69
70 if (rc != SUDO_RC_OK)
71 plugin_ctx->call_close = 0;
72
73 Py_XDECREF(py_argv);
74 Py_XDECREF(py_command_info);
75 debug_return_int(rc);
76 }
77
78 int
python_plugin_io_open(struct IOPluginContext * io_ctx,unsigned int version,sudo_conv_t conversation,sudo_printf_t sudo_printf,char * const settings[],char * const user_info[],char * const command_info[],int argc,char * const argv[],char * const user_env[],char * const plugin_options[],const char ** errstr)79 python_plugin_io_open(struct IOPluginContext *io_ctx,
80 unsigned int version, sudo_conv_t conversation,
81 sudo_printf_t sudo_printf, char * const settings[],
82 char * const user_info[], char * const command_info[],
83 int argc, char * const argv[], char * const user_env[],
84 char * const plugin_options[], const char **errstr)
85 {
86 debug_decl(python_plugin_io_open, PYTHON_DEBUG_CALLBACKS);
87
88 if (version < SUDO_API_MKVERSION(1, 2)) {
89 sudo_printf(SUDO_CONV_ERROR_MSG,
90 "Error: Python IO plugin requires at least plugin API version 1.2\n");
91 debug_return_int(SUDO_RC_ERROR);
92 }
93
94 int rc = python_plugin_register_logging(conversation, sudo_printf, settings);
95 if (rc != SUDO_RC_OK)
96 debug_return_int(rc);
97
98 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
99 rc = python_plugin_init(plugin_ctx, plugin_options, version);
100
101 if (rc != SUDO_RC_OK)
102 debug_return_int(rc);
103
104 rc = python_plugin_construct(plugin_ctx, PY_IO_PLUGIN_VERSION,
105 settings, user_info, user_env, plugin_options);
106 CALLBACK_SET_ERROR(plugin_ctx, errstr);
107 if (rc != SUDO_RC_OK) {
108 debug_return_int(rc);
109 }
110
111 // skip plugin callbacks which are not mandatory
112 MARK_CALLBACK_OPTIONAL(log_ttyin);
113 MARK_CALLBACK_OPTIONAL(log_ttyout);
114 MARK_CALLBACK_OPTIONAL(log_stdin);
115 MARK_CALLBACK_OPTIONAL(log_stdout);
116 MARK_CALLBACK_OPTIONAL(log_stderr);
117 MARK_CALLBACK_OPTIONAL(change_winsize);
118 MARK_CALLBACK_OPTIONAL(log_suspend);
119 // open and close are mandatory
120
121 if (argc > 0) // we only call open if there is request for running sg
122 rc = _call_plugin_open(io_ctx, argc, argv, command_info);
123
124 CALLBACK_SET_ERROR(plugin_ctx, errstr);
125 debug_return_int(rc);
126 }
127
128 void
python_plugin_io_close(struct IOPluginContext * io_ctx,int exit_status,int error)129 python_plugin_io_close(struct IOPluginContext *io_ctx, int exit_status, int error)
130 {
131 debug_decl(python_plugin_io_close, PYTHON_DEBUG_CALLBACKS);
132 python_plugin_close(BASE_CTX(io_ctx), CALLBACK_PYNAME(close),
133 Py_BuildValue("(ii)", error == 0 ? exit_status : -1, error));
134 debug_return;
135 }
136
137 int
python_plugin_io_show_version(struct IOPluginContext * io_ctx,int verbose)138 python_plugin_io_show_version(struct IOPluginContext *io_ctx, int verbose)
139 {
140 debug_decl(python_plugin_io_show_version, PYTHON_DEBUG_CALLBACKS);
141
142 PyThreadState_Swap(BASE_CTX(io_ctx)->py_interpreter);
143
144 debug_return_int(python_plugin_show_version(BASE_CTX(io_ctx), CALLBACK_PYNAME(show_version),
145 verbose, PY_IO_PLUGIN_VERSION, "io"));
146 }
147
148 int
python_plugin_io_log_ttyin(struct IOPluginContext * io_ctx,const char * buf,unsigned int len,const char ** errstr)149 python_plugin_io_log_ttyin(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
150 {
151 debug_decl(python_plugin_io_log_ttyin, PYTHON_DEBUG_CALLBACKS);
152 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
153 PyThreadState_Swap(plugin_ctx->py_interpreter);
154 int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_ttyin),
155 Py_BuildValue("(s#)", buf, len));
156 CALLBACK_SET_ERROR(plugin_ctx, errstr);
157 debug_return_int(rc);
158 }
159
160 int
python_plugin_io_log_ttyout(struct IOPluginContext * io_ctx,const char * buf,unsigned int len,const char ** errstr)161 python_plugin_io_log_ttyout(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
162 {
163 debug_decl(python_plugin_io_log_ttyout, PYTHON_DEBUG_CALLBACKS);
164 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
165 PyThreadState_Swap(plugin_ctx->py_interpreter);
166 int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_ttyout),
167 Py_BuildValue("(s#)", buf, len));
168 CALLBACK_SET_ERROR(plugin_ctx, errstr);
169 debug_return_int(rc);
170 }
171
172 int
python_plugin_io_log_stdin(struct IOPluginContext * io_ctx,const char * buf,unsigned int len,const char ** errstr)173 python_plugin_io_log_stdin(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
174 {
175 debug_decl(python_plugin_io_log_stdin, PYTHON_DEBUG_CALLBACKS);
176 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
177 PyThreadState_Swap(plugin_ctx->py_interpreter);
178 int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_stdin),
179 Py_BuildValue("(s#)", buf, len));
180 CALLBACK_SET_ERROR(plugin_ctx, errstr);
181 debug_return_int(rc);
182 }
183
184 int
python_plugin_io_log_stdout(struct IOPluginContext * io_ctx,const char * buf,unsigned int len,const char ** errstr)185 python_plugin_io_log_stdout(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
186 {
187 debug_decl(python_plugin_io_log_stdout, PYTHON_DEBUG_CALLBACKS);
188 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
189 PyThreadState_Swap(plugin_ctx->py_interpreter);
190 int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_stdout),
191 Py_BuildValue("(s#)", buf, len));
192 CALLBACK_SET_ERROR(plugin_ctx, errstr);
193 debug_return_int(rc);
194 }
195
196 int
python_plugin_io_log_stderr(struct IOPluginContext * io_ctx,const char * buf,unsigned int len,const char ** errstr)197 python_plugin_io_log_stderr(struct IOPluginContext *io_ctx, const char *buf, unsigned int len, const char **errstr)
198 {
199 debug_decl(python_plugin_io_log_stderr, PYTHON_DEBUG_CALLBACKS);
200 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
201 PyThreadState_Swap(plugin_ctx->py_interpreter);
202 int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_stderr),
203 Py_BuildValue("(s#)", buf, len));
204 CALLBACK_SET_ERROR(plugin_ctx, errstr);
205 debug_return_int(rc);
206 }
207
208 int
python_plugin_io_change_winsize(struct IOPluginContext * io_ctx,unsigned int line,unsigned int cols,const char ** errstr)209 python_plugin_io_change_winsize(struct IOPluginContext *io_ctx, unsigned int line, unsigned int cols, const char **errstr)
210 {
211 debug_decl(python_plugin_io_change_winsize, PYTHON_DEBUG_CALLBACKS);
212 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
213 PyThreadState_Swap(plugin_ctx->py_interpreter);
214 int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(change_winsize),
215 Py_BuildValue("(ii)", line, cols));
216 CALLBACK_SET_ERROR(plugin_ctx, errstr);
217 debug_return_int(rc);
218 }
219
220 int
python_plugin_io_log_suspend(struct IOPluginContext * io_ctx,int signo,const char ** errstr)221 python_plugin_io_log_suspend(struct IOPluginContext *io_ctx, int signo, const char **errstr)
222 {
223 debug_decl(python_plugin_io_log_suspend, PYTHON_DEBUG_CALLBACKS);
224 struct PluginContext *plugin_ctx = BASE_CTX(io_ctx);
225 PyThreadState_Swap(plugin_ctx->py_interpreter);
226 int rc = python_plugin_api_rc_call(plugin_ctx, CALLBACK_PYNAME(log_suspend),
227 Py_BuildValue("(i)", signo));
228 CALLBACK_SET_ERROR(plugin_ctx, errstr);
229 debug_return_int(rc);
230 }
231
232 // generate symbols for loading multiple io plugins:
233 sudo_dso_public struct io_plugin python_io;
234 #define IO_SYMBOL_NAME(symbol) symbol
235 #include "python_plugin_io_multi.inc"
236 #define IO_SYMBOL_NAME(symbol) symbol##1
237 #include "python_plugin_io_multi.inc"
238 #define IO_SYMBOL_NAME(symbol) symbol##2
239 #include "python_plugin_io_multi.inc"
240 #define IO_SYMBOL_NAME(symbol) symbol##3
241 #include "python_plugin_io_multi.inc"
242 #define IO_SYMBOL_NAME(symbol) symbol##4
243 #include "python_plugin_io_multi.inc"
244 #define IO_SYMBOL_NAME(symbol) symbol##5
245 #include "python_plugin_io_multi.inc"
246 #define IO_SYMBOL_NAME(symbol) symbol##6
247 #include "python_plugin_io_multi.inc"
248 #define IO_SYMBOL_NAME(symbol) symbol##7
249 #include "python_plugin_io_multi.inc"
250
251 static struct io_plugin *extra_io_plugins[] = {
252 &python_io1,
253 &python_io2,
254 &python_io3,
255 &python_io4,
256 &python_io5,
257 &python_io6,
258 &python_io7
259 };
260
261 sudo_dso_public struct io_plugin *
python_io_clone(void)262 python_io_clone(void)
263 {
264 static size_t counter = 0;
265 struct io_plugin *next_plugin = NULL;
266
267 size_t max = sizeof(extra_io_plugins) / sizeof(*extra_io_plugins);
268 if (counter < max) {
269 next_plugin = extra_io_plugins[counter];
270 ++counter;
271 } else if (counter == max) {
272 ++counter;
273 py_sudo_log(SUDO_CONV_ERROR_MSG, "sudo: loading more than %d sudo python IO plugins is not supported\n", counter);
274 }
275
276 return next_plugin;
277 }
278