blob: f990967530cb1fccc684cc0c031e377e4a084568 [file] [log] [blame]
Radek Krejcic61f0b42017-06-07 13:21:41 +02001/**
2 * @file ssh.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief SSH parameters management
5 *
6 * Copyright (c) 2017 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14
15/* Python API header */
16#include <Python.h>
17#include <structmember.h>
18
19/* standard headers */
20#include <string.h>
21
22#include "netconf.h"
23
24static void
25ncSSHFree(ncSSHObject *self)
26{
27 Py_XDECREF(self->username); /* PyUnicode */
28 Py_XDECREF(self->password); /* PyUnicode */
29 Py_XDECREF(self->pubkeys); /* PyList */
30 Py_XDECREF(self->privkeys); /* PyList */
31
Radek Krejcifa3731e2017-11-08 12:49:46 +010032 Py_XDECREF(self->clb_hostcheck);
33 Py_XDECREF(self->clb_hostcheck_data);
Radek Krejcib20999f2017-06-21 13:47:11 +020034 Py_XDECREF(self->clb_password);
35 Py_XDECREF(self->clb_password_data);
36 Py_XDECREF(self->clb_interactive);
37 Py_XDECREF(self->clb_interactive_data);
38
Radek Krejcic61f0b42017-06-07 13:21:41 +020039 Py_TYPE(self)->tp_free((PyObject*)self);
40}
41
42static int
43ncSSHInit(ncSSHObject *self, PyObject *args, PyObject *kwds)
44{
45 PyObject *user = NULL, *password = NULL;
46 PyObject *pubkey_path = NULL, *pubkey = NULL;
47 PyObject *privkey_path = NULL, *privkey = NULL;
48
49 char *kwlist[] = {"username", "password", "pubkey", "pubkey_file", "privkey", "privkey_file", NULL};
50
51 /* Get input parameters */
52 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUUUUU", kwlist,
53 &user, &password,
54 &pubkey, &pubkey_path,
55 &privkey, &privkey_path)) {
56 return -1;
57 }
58
59 if ((pubkey || pubkey_path) != (privkey || privkey_path)) {
60 PyErr_SetString(PyExc_TypeError, "Both public and private keys must be set.");
61 goto error;
62 }
63
64 /* username and password */
Radek Krejcib20999f2017-06-21 13:47:11 +020065 Py_XDECREF(self->username);
66 Py_XDECREF(self->password);
67
Radek Krejcic61f0b42017-06-07 13:21:41 +020068 if (user) {
69 Py_XDECREF(self->username);
70 Py_INCREF(user);
71 self->username = user;
Radek Krejcib20999f2017-06-21 13:47:11 +020072 } else {
73 self->username = NULL;
Radek Krejcic61f0b42017-06-07 13:21:41 +020074 }
75 if (password) {
76 Py_XDECREF(self->password);
77 Py_INCREF(password);
78 self->password = password;
Radek Krejcib20999f2017-06-21 13:47:11 +020079 } else {
80 self->password = NULL;
Radek Krejcic61f0b42017-06-07 13:21:41 +020081 }
82
83 /* keys */
84 Py_XDECREF(self->pubkeys);
85 Py_XDECREF(self->privkeys);
86
87 if (pubkey || pubkey_path) {
88 self->pubkeys = PyList_New(0);
89 self->privkeys = PyList_New(0);
90
91 if (!self->pubkeys || self->privkeys) {
92 PyErr_SetString(PyExc_MemoryError, "Unable to allocate memory for keys list.");
93 goto error;
94 }
95 } else {
96 self->pubkeys = NULL;
97 self->privkeys = NULL;
98 }
99
100 if (pubkey) {
101 PyList_Append(self->pubkeys, pubkey);
102 }
103 if (pubkey_path) {
104 /* process keys in the file */
105 }
106
107 if (privkey) {
108 PyList_Append(self->privkeys, privkey);
109 }
110 if (privkey_path) {
111 /* process keys in the file */
112 }
113
114 /* check that the keys pairs together */
115
Radek Krejcib20999f2017-06-21 13:47:11 +0200116 /* forget callbacks */
Radek Krejcifa3731e2017-11-08 12:49:46 +0100117 Py_CLEAR(self->clb_hostcheck);
118 Py_CLEAR(self->clb_hostcheck_data);
Radek Krejcib20999f2017-06-21 13:47:11 +0200119 Py_CLEAR(self->clb_password);
120 Py_CLEAR(self->clb_password_data);
121 Py_CLEAR(self->clb_interactive);
122 Py_CLEAR(self->clb_interactive_data);
123
Radek Krejcic61f0b42017-06-07 13:21:41 +0200124 return 0;
125
126error:
127
128 Py_CLEAR(self->username);
129 Py_CLEAR(self->password);
130 Py_CLEAR(self->pubkeys);
131 Py_CLEAR(self->privkeys);
Radek Krejcifa3731e2017-11-08 12:49:46 +0100132 Py_CLEAR(self->clb_hostcheck);
133 Py_CLEAR(self->clb_hostcheck_data);
Radek Krejcib20999f2017-06-21 13:47:11 +0200134 Py_CLEAR(self->clb_password);
135 Py_CLEAR(self->clb_password_data);
136 Py_CLEAR(self->clb_interactive);
137 Py_CLEAR(self->clb_interactive_data);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200138
139 return -1;
140}
141
142static PyObject *
143ncSSHStr(ncSSHObject *self)
144{
145 if (self->privkeys) {
146 if (self->username && self->password) {
147 return PyUnicode_FromFormat("SSH Settings with %d keys and password for user %U",
148 PyList_Size(self->privkeys), self->username);
149 } else if (self->password) {
150 return PyUnicode_FromFormat("SSH Settings with %d keys and password for default user.",
151 PyList_Size(self->privkeys));
152 } else {
153 return PyUnicode_FromFormat("SSH Settings with %d keys", PyList_Size(self->privkeys));
154 }
155 } else if (self->password) {
156 if (self->username) {
157 return PyUnicode_FromFormat("SSH Settings with password authentication for user %U.", self->username);
158 } else {
159 return PyUnicode_FromString("SSH Settings with password authentication for default user.");
160 }
161 } else if (self->username) {
162 return PyUnicode_FromFormat("SSH Settings for user %U.", self->username);
163 } else {
164 return PyUnicode_FromString("Default SSH Settings.");
165 }
166}
167
168/*
169 * tp_methods callbacks held by ncSessionMethods[]
170 */
171
172/*
173 * tp_getset callbacs held by ncSessionGetSetters[]
174 */
175static int
176ncSSHSetUser(ncSSHObject *self, PyObject *value, void *closure)
177{
178 if (!value) {
179 Py_XDECREF(self->username);
180 } else if (!PyUnicode_Check(value)) {
181 PyErr_SetString(PyExc_TypeError, "The attribute value must be a string.");
182 return -1;
183 } else {
184 Py_XDECREF(self->username);
185 Py_INCREF(value);
186 }
187 self->username = value;
188
189 return 0;
190}
191
192static PyObject *
193ncSSHGetUser(ncSSHObject *self, void *closure)
194{
195 Py_XINCREF(self->username);
196 return self->username;
197}
198
199static int
200ncSSHSetPassword(ncSSHObject *self, PyObject *value, void *closure)
201{
202 if (!value) {
203 Py_XDECREF(self->password);
204 } else if (!PyUnicode_Check(value)) {
Radek Krejcib20999f2017-06-21 13:47:11 +0200205 PyErr_SetString(PyExc_TypeError, "The value must be a string.");
Radek Krejcic61f0b42017-06-07 13:21:41 +0200206 return -1;
207 } else {
208 Py_XDECREF(self->password);
209 Py_INCREF(value);
210 }
211 self->password = value;
212
213 return 0;
214}
215
Radek Krejcib20999f2017-06-21 13:47:11 +0200216static PyObject *
Radek Krejcifa3731e2017-11-08 12:49:46 +0100217ncSSHSetAuthHostkeyCheckClb(ncSSHObject *self, PyObject *args, PyObject *keywords)
218{
219 PyObject *clb, *data = NULL;
220 static char *kwlist[] = {"func", "priv", NULL};
221
222 if (!PyArg_ParseTupleAndKeywords(args, keywords, "O|O:ncSSHSetAuthHostkeyCheckClb", kwlist, &clb, &data)) {
223 return NULL;
224 }
225
226 if (!clb) {
227 Py_XDECREF(self->clb_hostcheck);
228 Py_XDECREF(self->clb_hostcheck_data);
229 data = NULL;
230 } else if (!PyCallable_Check(clb)) {
231 PyErr_SetString(PyExc_TypeError, "The callback must be a function.");
232 return NULL;
233 } else {
234 Py_XDECREF(self->clb_hostcheck);
235 Py_XDECREF(self->clb_hostcheck_data);
236
237 Py_INCREF(clb);
238 if (data) {
239 Py_INCREF(data);
240 }
241 }
242 self->clb_hostcheck = clb;
243 self->clb_hostcheck_data = data;
244
245 Py_RETURN_NONE;
246}
247
248
249static PyObject *
Radek Krejcib20999f2017-06-21 13:47:11 +0200250ncSSHSetAuthPasswordClb(ncSSHObject *self, PyObject *args, PyObject *keywords)
251{
Radek Krejcifa3731e2017-11-08 12:49:46 +0100252 PyObject *clb, *data = NULL;
Radek Krejcib20999f2017-06-21 13:47:11 +0200253 static char *kwlist[] = {"func", "priv", NULL};
254
255 if (!PyArg_ParseTupleAndKeywords(args, keywords, "O|O:ncSSHSetAuthPasswordClb", kwlist, &clb, &data)) {
256 return NULL;
257 }
258
259 if (!clb) {
260 Py_XDECREF(self->clb_password);
261 Py_XDECREF(self->clb_password_data);
262 data = NULL;
263 } else if (!PyCallable_Check(clb)) {
264 PyErr_SetString(PyExc_TypeError, "The callback must be a function.");
265 return NULL;
266 } else {
267 Py_XDECREF(self->clb_password);
268 Py_XDECREF(self->clb_password_data);
269
270 Py_INCREF(clb);
271 if (data) {
272 Py_INCREF(data);
273 }
274 }
275 self->clb_password = clb;
276 self->clb_password_data = data;
277
278 Py_RETURN_NONE;
279}
280
281static PyObject *
282ncSSHSetAuthInteractiveClb(ncSSHObject *self, PyObject *args, PyObject *keywords)
283{
Radek Krejcifa3731e2017-11-08 12:49:46 +0100284 PyObject *clb, *data = NULL;
Radek Krejcib20999f2017-06-21 13:47:11 +0200285 static char *kwlist[] = {"func", "priv", NULL};
286
287 if (!PyArg_ParseTupleAndKeywords(args, keywords, "O|O:ncSSHSetAuthInteractiveClb", kwlist, &clb, &data)) {
288 return NULL;
289 }
290
291 if (!clb) {
292 Py_XDECREF(self->clb_interactive);
293 Py_XDECREF(self->clb_interactive_data);
294 data = NULL;
295 } else if (!PyCallable_Check(clb)) {
296 PyErr_SetString(PyExc_TypeError, "The callback must be a function.");
297 return NULL;
298 } else {
299 Py_XDECREF(self->clb_interactive);
300 Py_XDECREF(self->clb_interactive_data);
301
302 Py_INCREF(clb);
303 if (data) {
304 Py_INCREF(data);
305 }
306 }
307 self->clb_interactive = clb;
308 self->clb_interactive_data = data;
309
310 Py_RETURN_NONE;
311}
312
Radek Krejcic61f0b42017-06-07 13:21:41 +0200313/*
314 * Callback structures
315 */
316
317static PyGetSetDef ncSSHGetSetters[] = {
318 {"username", (getter)ncSSHGetUser, (setter)ncSSHSetUser, "SSH username.", NULL},
319 {"password", NULL, (setter)ncSSHSetPassword, "SSH password (or key passphrase).", NULL},
320 {NULL} /* Sentinel */
321};
322
Radek Krejcib20999f2017-06-21 13:47:11 +0200323static PyMethodDef ncSSHMethods[] = {
Radek Krejcifa3731e2017-11-08 12:49:46 +0100324 {"setAuthHostkeyCheckClb", (PyCFunction)ncSSHSetAuthHostkeyCheckClb, METH_VARARGS | METH_KEYWORDS,
325 "SSH Hostkey (fingerprint) check callback.\n\n"
326 "setAuthHostkeyCheckClb(func, priv=None)\n"
327 "with func(str hostname, int state, str keytype, str hexa, priv)\n"
328 "state is SERVER_ERROR (-1), SERVER_NOT_KNOWN (0), SERVER_CHANGED (2), SERVER_FOUND_OTHER (3), SERVER_FILE_NOT_FOUND (4)\n"
329 "callback returns True in case of valid hostkey.\n"},
330 {"setAuthPasswordClb", (PyCFunction)ncSSHSetAuthPasswordClb, METH_VARARGS | METH_KEYWORDS,
331 "SSH password authentication callback.\n\n"
332 "setAuthPasswordClb(func, priv=None)\n"},
333 {"setAuthInteractiveClb", (PyCFunction)ncSSHSetAuthInteractiveClb, METH_VARARGS | METH_KEYWORDS,
334 "setAuthInteractiveClb(func, priv=None)\n--\n\n"
335 "SSH keyboard-interactive authentication callback.\n\n"},
336 {NULL, NULL, 0, NULL}
Radek Krejcib20999f2017-06-21 13:47:11 +0200337};
Radek Krejcic61f0b42017-06-07 13:21:41 +0200338
339PyDoc_STRVAR(ncSSHDoc,
Radek Krejcib03ebe42017-07-04 14:00:33 +0200340 "Settings for SSH authentication.\n\n"
341 "Arguments: (user=None, password=None)\n");
Radek Krejcic61f0b42017-06-07 13:21:41 +0200342
343PyTypeObject ncSSHType = {
344 PyVarObject_HEAD_INIT(NULL, 0)
345 "netconf2.SSH", /* tp_name */
346 sizeof(ncSSHObject), /* tp_basicsize */
347 0, /* tp_itemsize */
348 (destructor)ncSSHFree, /* tp_dealloc */
349 0, /* tp_print */
350 0, /* tp_getattr */
351 0, /* tp_setattr */
352 0, /* tp_reserved */
353 (reprfunc)ncSSHStr, /* tp_repr */
354 0, /* tp_as_number */
355 0, /* tp_as_sequence */
356 0, /* tp_as_mapping */
357 0, /* tp_hash */
358 0, /* tp_call */
359 (reprfunc)ncSSHStr, /* tp_str */
360 0, /* tp_getattro */
361 0, /* tp_setattro */
362 0, /* tp_as_buffer */
363 Py_TPFLAGS_DEFAULT |
364 Py_TPFLAGS_BASETYPE, /* tp_flags */
365 ncSSHDoc, /* tp_doc */
366 0, /* tp_traverse */
367 0, /* tp_clear */
368 0, /* tp_richcompare */
369 0, /* tp_weaklistoffset */
370 0, /* tp_iter */
371 0, /* tp_iternext */
Radek Krejcib20999f2017-06-21 13:47:11 +0200372 ncSSHMethods, /* tp_methods */
Radek Krejcic61f0b42017-06-07 13:21:41 +0200373 0, /* tp_members */
374 ncSSHGetSetters, /* tp_getset */
375 0, /* tp_base */
376 0, /* tp_dict */
377 0, /* tp_descr_get */
378 0, /* tp_descr_set */
379 0, /* tp_dictoffset */
380 (initproc)ncSSHInit, /* tp_init */
381 0, /* tp_alloc */
382 0, /* tp_new */
383};
384