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