blob: c4c77db948bccea9e08b12b12603830a556855f2 [file] [log] [blame]
Radek Krejcic61f0b42017-06-07 13:21:41 +02001/**
2 * @file session.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief NETCONF session management in Python3 bindings for libnetconf2 (client-side)
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>
Radek Krejcifa3731e2017-11-08 12:49:46 +010021#include <libssh/libssh.h>
Radek Krejcic61f0b42017-06-07 13:21:41 +020022#include <libyang/libyang.h>
Radek Krejci9305fbb2018-01-25 13:19:46 +010023#include <libyang/swigpyrun.h>
Radek Krejcic61f0b42017-06-07 13:21:41 +020024
Radek Krejcib20999f2017-06-21 13:47:11 +020025#include "../src/config.h"
Radek Krejcic61f0b42017-06-07 13:21:41 +020026#include "netconf.h"
Radek Krejcie0854c02017-10-10 21:11:29 +020027#include "session.h"
28#include "rpc.h"
Radek Krejcic61f0b42017-06-07 13:21:41 +020029
Radek Krejci9305fbb2018-01-25 13:19:46 +010030extern PyObject *libnetconf2Error;
31
Radek Krejcifa3731e2017-11-08 12:49:46 +010032int
33auth_hostkey_check_pyclb(const char *hostname, ssh_session session, void *priv)
34{
35 PyObject *arglist, *result;
36 ncSSHObject *ssh = (ncSSHObject*)priv;
37 int ret = EXIT_FAILURE, rc, state;
38 unsigned char *hash_sha1 = NULL;
39 char *hexa;
40 const char *keytype = NULL;
41 ssh_key srv_pubkey;
42 size_t hlen;
43
44 state = ssh_is_server_known(session);
45 if (state == SSH_SERVER_KNOWN_OK) {
46 /* known host */
47 return EXIT_SUCCESS;
48 } else if (!ssh->clb_hostcheck) {
49 /* no callback, hostkey check failed */
50 return EXIT_FAILURE;
51 }
52
53 /* use the callback set by Python application */
54 rc = ssh_get_publickey(session, &srv_pubkey);
55 if (rc < 0) {
56 PyErr_SetString(PyExc_RuntimeError, "Unable to get server public key.");
57 return -1;
58 }
59
60 keytype = ssh_key_type_to_char(ssh_key_type(srv_pubkey));
61 rc = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash_sha1, &hlen);
62 ssh_key_free(srv_pubkey);
63 if (rc < 0) {
64 PyErr_SetString(PyExc_RuntimeError, "Failed to calculate SHA1 hash of the server public key.");
65 return -1;
66 }
67
68 hexa = ssh_get_hexa(hash_sha1, hlen);
69 arglist = Py_BuildValue("(sissO)", hostname, state, keytype, hexa, ssh->clb_hostcheck_data ? ssh->clb_hostcheck_data : Py_None);
70 if (!arglist) {
71 PyErr_Print();
72 ssh_string_free_char(hexa);
73 ssh_clean_pubkey_hash(&hash_sha1);
74 return -1;
75 }
76 result = PyObject_CallObject(ssh->clb_hostcheck, arglist);
77 Py_DECREF(arglist);
78 ssh_string_free_char(hexa);
79 ssh_clean_pubkey_hash(&hash_sha1);
80
81 if (result) {
82 if (!PyBool_Check(result)) {
83 PyErr_SetString(PyExc_TypeError, "Invalid hostkey check callback result.");
84 } else if (result == Py_True) {
85 ret = EXIT_SUCCESS;
86 } else if (result != Py_False) {
87 PyErr_SetString(PyExc_TypeError, "Invalid hostkey check callback result.");
88 }
89 Py_DECREF(result);
90 }
91
92 return ret;
93}
94
Radek Krejcic61f0b42017-06-07 13:21:41 +020095char *
96auth_password_clb(const char *UNUSED(username), const char *UNUSED(hostname), void *priv)
97{
98 /* password is provided as priv when setting up the callback */
99 return strdup((char *)priv);
100}
Radek Krejcib20999f2017-06-21 13:47:11 +0200101
102char *
103auth_password_pyclb(const char *username, const char *hostname, void *priv)
104{
105 PyObject *arglist, *result;
106 ncSSHObject *ssh = (ncSSHObject*)priv;
107 char *password = NULL;
108
109 arglist = Py_BuildValue("(ssO)", username, hostname, ssh->clb_password_data ? ssh->clb_password_data : Py_None);
110 if (!arglist) {
111 PyErr_Print();
112 return NULL;
113 }
114 result = PyObject_CallObject(ssh->clb_password, arglist);
115 Py_DECREF(arglist);
116
117 if (result) {
118 if (!PyUnicode_Check(result)) {
119 PyErr_SetString(PyExc_TypeError, "Invalid password authentication callback result.");
120 } else {
121 password = strdup(PyUnicode_AsUTF8(result));
122 Py_DECREF(result);
123 }
124 }
125
126 return password;
127}
128
Radek Krejcic61f0b42017-06-07 13:21:41 +0200129char *
130auth_interactive_clb(const char *UNUSED(auth_name), const char *UNUSED(instruction), const char *UNUSED(prompt),
131 int UNUSED(echo), void *priv)
132{
133 /* password is provided as priv when setting up the callback */
134 return strdup((char *)priv);
135}
136
137char *
Radek Krejcib20999f2017-06-21 13:47:11 +0200138auth_interactive_pyclb(const char *auth_name, const char *instruction, const char *prompt, int UNUSED(echo), void *priv)
139{
140 PyObject *arglist, *result;
141 ncSSHObject *ssh = (ncSSHObject*)priv;
142 char *password = NULL;
143
144 arglist = Py_BuildValue("(sssO)", auth_name, instruction, prompt, ssh->clb_password_data ? ssh->clb_password_data : Py_None);
145 if (!arglist) {
146 PyErr_Print();
147 return NULL;
148 }
149 result = PyObject_CallObject(ssh->clb_interactive, arglist);
150 Py_DECREF(arglist);
151
152 if (result) {
153 if (!PyUnicode_Check(result)) {
154 PyErr_SetString(PyExc_TypeError, "Invalid password authentication callback result.");
155 } else {
156 password = strdup(PyUnicode_AsUTF8(result));
157 Py_DECREF(result);
158 }
159 }
160
161 return password;
Radek Krejcib20999f2017-06-21 13:47:11 +0200162}
163
164char *
Radek Krejcic61f0b42017-06-07 13:21:41 +0200165auth_privkey_passphrase_clb(const char *privkey_path, void *priv)
166{
167 /* password is provided as priv when setting up the callback */
168 return strdup((char *)priv);
169}
170
171static void
172ncSessionFree(ncSessionObject *self)
173{
174 PyObject *err_type, *err_value, *err_traceback;
175
176 /* save the current exception state */
177 PyErr_Fetch(&err_type, &err_value, &err_traceback);
178
179 nc_session_free(self->session, NULL);
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200180
181 (*self->ctx_counter)--;
182 if (!(*self->ctx_counter)) {
183 ly_ctx_destroy(self->ctx, NULL);
184 free(self->ctx_counter);
185 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200186
187 /* restore the saved exception state */
188 PyErr_Restore(err_type, err_value, err_traceback);
189
190 Py_TYPE(self)->tp_free((PyObject*)self);
191}
192
193static PyObject *
194ncSessionNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
195{
196 ncSessionObject *self;
197
198 self = (ncSessionObject *)type->tp_alloc(type, 0);
199 if (self != NULL) {
200 /* NULL initiation */
201 self->session = NULL;
Radek Krejci41e901a2017-09-21 13:02:07 +0200202 self->ctx = NULL;
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200203 self->ctx_counter = calloc(1, sizeof *self->ctx_counter);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200204 }
205
206 return (PyObject *)self;
207}
208
209static int
210ncSessionInit(ncSessionObject *self, PyObject *args, PyObject *kwds)
211{
212 const char *host = NULL;
213 PyObject *transport = NULL;
214 unsigned short port = 0;
Radek Krejcic2074282017-11-06 15:57:22 +0100215 struct nc_session *session = NULL;
Radek Krejcic61f0b42017-06-07 13:21:41 +0200216
217 char *kwlist[] = {"host", "port", "transport", NULL};
218
219 /* Get input parameters */
220 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zHO", kwlist, &host, &port, &transport)) {
221 return -1;
222 }
223
224 /* connect */
Radek Krejcic2074282017-11-06 15:57:22 +0100225#ifdef NC_ENABLED_TLS
Radek Krejcic61f0b42017-06-07 13:21:41 +0200226 if (transport && PyObject_TypeCheck(transport, &ncTLSType)) {
Radek Krejci41e901a2017-09-21 13:02:07 +0200227 session = nc_connect_tls(host, port, NULL);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200228 } else {
Radek Krejcic2074282017-11-06 15:57:22 +0100229#else /* !NC_ENABLED_TLS */
230 {
231#endif
232#ifdef NC_ENABLED_SSH
Radek Krejcic61f0b42017-06-07 13:21:41 +0200233 if (transport) {
234 /* set SSH parameters */
235 if (((ncSSHObject*)transport)->username) {
236 nc_client_ssh_set_username(PyUnicode_AsUTF8(((ncSSHObject*)transport)->username));
237 }
Radek Krejcifa3731e2017-11-08 12:49:46 +0100238
239 nc_client_ssh_set_auth_hostkey_check_clb(&auth_hostkey_check_pyclb, (void *)transport);
240
Radek Krejcic61f0b42017-06-07 13:21:41 +0200241 if (((ncSSHObject*)transport)->password) {
242 nc_client_ssh_set_auth_password_clb(&auth_password_clb,
243 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
244 nc_client_ssh_set_auth_interactive_clb(&auth_interactive_clb,
245 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
246 nc_client_ssh_set_auth_privkey_passphrase_clb(&auth_privkey_passphrase_clb,
247 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
Radek Krejcib20999f2017-06-21 13:47:11 +0200248 } else {
249 if (((ncSSHObject *)transport)->clb_password) {
250 nc_client_ssh_set_auth_password_clb(&auth_password_pyclb, (void *)transport);
251 }
252 if (((ncSSHObject *)transport)->clb_interactive) {
253 nc_client_ssh_set_auth_interactive_clb(&auth_interactive_pyclb, (void *)transport);
254 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200255 }
256 }
257
258 /* create connection */
Radek Krejci41e901a2017-09-21 13:02:07 +0200259 session = nc_connect_ssh(host, port, NULL);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200260 /* cleanup */
261 if (transport) {
262 if (((ncSSHObject*)transport)->username) {
263 nc_client_ssh_set_username(NULL);
264 }
265 if (((ncSSHObject*)transport)->password) {
266 nc_client_ssh_set_auth_password_clb(NULL, NULL);
267 nc_client_ssh_set_auth_interactive_clb(NULL, NULL);
268 nc_client_ssh_set_auth_privkey_passphrase_clb(NULL, NULL);
269 }
270 }
Radek Krejcic2074282017-11-06 15:57:22 +0100271#endif /* NC_ENABLED_SSH */
Radek Krejcic61f0b42017-06-07 13:21:41 +0200272 }
273
274 /* check the result */
275 if (!session) {
276 return -1;
277 }
278
Radek Krejci50a37dd2018-08-29 14:49:46 +0200279 if (PyErr_Occurred()) {
280 PyErr_PrintEx(0);
281 }
282
Radek Krejci41e901a2017-09-21 13:02:07 +0200283 /* get the internally created context for this session */
284 self->ctx = nc_session_get_ctx(session);
285
Radek Krejcic61f0b42017-06-07 13:21:41 +0200286 /* replace the previous (if any) data in the session object */
287 nc_session_free(self->session, NULL);
288 self->session = session;
289
290 return 0;
291}
292
Radek Krejcic2074282017-11-06 15:57:22 +0100293#ifdef NC_ENABLED_SSH
294
Radek Krejcic61f0b42017-06-07 13:21:41 +0200295static PyObject *
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200296newChannel(PyObject *self)
297{
298 ncSessionObject *new;
299
300 if (nc_session_get_ti(((ncSessionObject *)self)->session) != NC_TI_LIBSSH) {
301 PyErr_SetString(PyExc_TypeError, "The session must be on SSH.");
302 return NULL;
303 }
304
305 new = (ncSessionObject *)self->ob_type->tp_alloc(self->ob_type, 0);
306 if (!new) {
307 return NULL;
308 }
309
310 new->ctx = ((ncSessionObject *)self)->ctx;
311 new->session = nc_connect_ssh_channel(((ncSessionObject *)self)->session, new->ctx);
312 if (!new->session) {
313 Py_DECREF(new);
314 return NULL;
315 }
316
317 new->ctx_counter = ((ncSessionObject *)self)->ctx_counter;
318 (*new->ctx_counter)++;
319 return (PyObject*)new;
320}
321
Radek Krejcic2074282017-11-06 15:57:22 +0100322#endif /* NC_ENABLED_SSH */
323
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200324static PyObject *
Radek Krejcic61f0b42017-06-07 13:21:41 +0200325ncSessionStr(ncSessionObject *self)
326{
327 return PyUnicode_FromFormat("NETCONF Session %u to %s:%u (%lu references)", nc_session_get_id(self->session),
328 nc_session_get_host(self->session), nc_session_get_port(self->session),
329 ((PyObject*)(self))->ob_refcnt);
330}
331
332/*
333 * tp_methods callbacks held by ncSessionMethods[]
334 */
335
336/*
337 * tp_getset callbacs held by ncSessionGetSetters[]
338 */
339#if 0
340static PyObject *
341ncSessionGetSTatus(ncSessionObject *self, void *closure)
342{
343 NC_STATUS s;
344
345 s = nc_session_get_status(self->session);
346 switch(s) {
347 case NC_STATUS_ERR:
348 /* exception */
349 return NULL;
350 }
351 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
352}
353#endif
354
355static PyObject *
356ncSessionGetId(ncSessionObject *self, void *closure)
357{
358 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
359}
360
361static PyObject *
362ncSessionGetHost(ncSessionObject *self, void *closure)
363{
364 return PyUnicode_FromString(nc_session_get_host(self->session));
365}
366
367static PyObject *
368ncSessionGetPort(ncSessionObject *self, void *closure)
369{
370 return PyUnicode_FromFormat("%u", nc_session_get_port(self->session));
371}
372
373static PyObject *
374ncSessionGetUser(ncSessionObject *self, void *closure)
375{
376 return PyUnicode_FromString(nc_session_get_username(self->session));
377}
378
379static PyObject *
380ncSessionGetTransport(ncSessionObject *self, void *closure)
381{
382 NC_TRANSPORT_IMPL ti = nc_session_get_ti(self->session);
383 switch (ti) {
Radek Krejcic2074282017-11-06 15:57:22 +0100384#ifdef NC_ENABLED_SSH
Radek Krejcic61f0b42017-06-07 13:21:41 +0200385 case NC_TI_LIBSSH:
386 return PyUnicode_FromString("SSH");
Radek Krejcic2074282017-11-06 15:57:22 +0100387#endif /* NC_ENABLED_SSH */
388#ifdef NC_ENABLEd_TLS
Radek Krejcic61f0b42017-06-07 13:21:41 +0200389 case NC_TI_OPENSSL:
390 return PyUnicode_FromString("TLS");
Radek Krejcic2074282017-11-06 15:57:22 +0100391#endif /* NC_ENABLED_TLS */
Radek Krejcic61f0b42017-06-07 13:21:41 +0200392 default:
393 return PyUnicode_FromString("unknown");
394 }
395}
396
397static PyObject *
398ncSessionGetCapabilities(ncSessionObject *self, void *closure)
399{
400 PyObject *list;
401 const char * const *cpblts;
402 ssize_t pos;
403
404 cpblts = nc_session_get_cpblts(self->session);
405 if (cpblts == NULL) {
406 return (NULL);
407 }
408
409 list = PyList_New(0);
410 for(pos = 0; cpblts[pos]; ++pos) {
411 PyList_Append(list, PyUnicode_FromString(cpblts[pos]));
412 }
413
414 return list;
415}
416
417static PyObject *
418ncSessionGetVersion(ncSessionObject *self, void *closure)
419{
420 if (nc_session_get_version(self->session)) {
421 return PyUnicode_FromString("1.1");
422 } else {
423 return PyUnicode_FromString("1.0");
424 }
425}
426
Radek Krejci9305fbb2018-01-25 13:19:46 +0100427static PyObject *
428ncSessionGetContext(ncSessionObject *self, void *closure)
429{
430 PyObject *context, *result, *module;
431
432
433 /* process the received data */
434 context = SWIG_NewPointerObj(self->ctx, SWIG_Python_TypeQuery("ly_ctx*"), 0);
435 if (!context) {
436 PyErr_SetString(libnetconf2Error, "Building Python object from context structure failed.");
437 goto error;
438 }
439
Radek Krejcieed7f872018-02-13 16:57:42 +0100440 module = PyImport_ImportModule("yang");
Radek Krejci9305fbb2018-01-25 13:19:46 +0100441 if (module == NULL) {
Radek Krejcieed7f872018-02-13 16:57:42 +0100442 PyErr_SetString(libnetconf2Error, "Could not import libyang python module");
Radek Krejci9305fbb2018-01-25 13:19:46 +0100443 goto error;
444 }
445
446 result = PyObject_CallMethod(module, "create_new_Context", "(O)", context, NULL);
447 Py_DECREF(module);
448 Py_DECREF(context);
449 if (!result) {
450 PyErr_SetString(libnetconf2Error, "Could not create Context object.");
451 goto error;
452 }
453
454 return result;
455
456error:
457 Py_XDECREF(context);
458 return NULL;
459}
460
Radek Krejcic61f0b42017-06-07 13:21:41 +0200461/*
462 * Callback structures
463 */
464
465static PyGetSetDef ncSessionGetSetters[] = {
466 {"id", (getter)ncSessionGetId, NULL, "NETCONF Session id.", NULL},
467 {"host", (getter)ncSessionGetHost, NULL, "Host where the NETCONF Session is connected.", NULL},
468 {"port", (getter)ncSessionGetPort, NULL, "Port number where the NETCONF Session is connected.", NULL},
469 {"user", (getter)ncSessionGetUser, NULL, "Username of the user connected with the NETCONF Session.", NULL},
470 {"transport", (getter)ncSessionGetTransport, NULL, "Transport protocol used for the NETCONF Session.", NULL},
471 {"version", (getter)ncSessionGetVersion, NULL, "NETCONF Protocol version used for the NETCONF Session.", NULL},
472 {"capabilities", (getter)ncSessionGetCapabilities, NULL, "Capabilities of the NETCONF Session.", NULL},
Radek Krejci9305fbb2018-01-25 13:19:46 +0100473 {"context", (getter)ncSessionGetContext, NULL, "libyang context of the NETCONF Session.", NULL},
Radek Krejcic61f0b42017-06-07 13:21:41 +0200474 {NULL} /* Sentinel */
475};
476
477static PyMemberDef ncSessionMembers[] = {
478 {NULL} /* Sentinel */
479};
480
481static PyMethodDef ncSessionMethods[] = {
Radek Krejcic2074282017-11-06 15:57:22 +0100482#ifdef NC_ENABLED_SSH
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200483 {"newChannel", (PyCFunction)newChannel, METH_NOARGS,
484 "newChannel()\n--\n\n"
485 "Create another NETCONF session on existing SSH session using separated SSH channel\n\n"
486 ":returns: New netconf2.Session instance.\n"},
Radek Krejcic2074282017-11-06 15:57:22 +0100487#endif /* NC_ENABLED_SSH */
Radek Krejcie0854c02017-10-10 21:11:29 +0200488 /* RPCs */
489 {"rpcGet", (PyCFunction)ncRPCGet, METH_VARARGS | METH_KEYWORDS,
490 "Send NETCONF <get> operation on the Session.\n\n"
491 "ncRPCGet(subtree=None, xpath=None)\n"
492 ":returns: Reply from the server.\n"},
Radek Krejcib0bf24c2018-02-07 16:40:27 +0100493 {"rpcGetConfig", (PyCFunction)ncRPCGetConfig, METH_VARARGS | METH_KEYWORDS,
494 "Send NETCONF <get-config> operation on the Session.\n\n"
495 "ncRPCGetConfig(datastore, subtree=None, xpath=None)\n"
496 ":returns: Reply from the server.\n"},
497 {"rpcEditConfig", (PyCFunction)ncRPCEditConfig, METH_VARARGS | METH_KEYWORDS,
498 "Send NETCONF <edit-config> operation on the Session.\n\n"
499 "ncRPCEditConfig(datastore, data, defop=None, testopt=None, erropt=None)\n"
500 ":returns: None\n"},
Radek Krejcic61f0b42017-06-07 13:21:41 +0200501 {NULL} /* Sentinel */
502};
503
504PyDoc_STRVAR(sessionDoc,
Radek Krejcib03ebe42017-07-04 14:00:33 +0200505 "The NETCONF Session object.\n\n"
506 "Arguments: (host='localhost', port=830, transport=None)\n");
Radek Krejcic61f0b42017-06-07 13:21:41 +0200507
508PyTypeObject ncSessionType = {
509 PyVarObject_HEAD_INIT(NULL, 0)
510 "netconf2.Session", /* tp_name */
511 sizeof(ncSessionObject), /* tp_basicsize */
512 0, /* tp_itemsize */
513 (destructor)ncSessionFree, /* tp_dealloc */
514 0, /* tp_print */
515 0, /* tp_getattr */
516 0, /* tp_setattr */
517 0, /* tp_reserved */
518 (reprfunc)ncSessionStr, /* tp_repr */
519 0, /* tp_as_number */
520 0, /* tp_as_sequence */
521 0, /* tp_as_mapping */
522 0, /* tp_hash */
523 0, /* tp_call */
524 (reprfunc)ncSessionStr, /* tp_str */
525 0, /* tp_getattro */
526 0, /* tp_setattro */
527 0, /* tp_as_buffer */
528 Py_TPFLAGS_DEFAULT |
529 Py_TPFLAGS_BASETYPE, /* tp_flags */
530 sessionDoc, /* tp_doc */
531 0, /* tp_traverse */
532 0, /* tp_clear */
533 0, /* tp_richcompare */
534 0, /* tp_weaklistoffset */
535 0, /* tp_iter */
536 0, /* tp_iternext */
537 ncSessionMethods, /* tp_methods */
538 ncSessionMembers, /* tp_members */
539 ncSessionGetSetters, /* tp_getset */
540 0, /* tp_base */
541 0, /* tp_dict */
542 0, /* tp_descr_get */
543 0, /* tp_descr_set */
544 0, /* tp_dictoffset */
545 (initproc)ncSessionInit, /* tp_init */
546 0, /* tp_alloc */
547 ncSessionNew, /* tp_new */
548};
549