blob: 3b2545b38d459ea00b22e5715f48c57dfc769de6 [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;
162
163}
164
165char *
Radek Krejcic61f0b42017-06-07 13:21:41 +0200166auth_privkey_passphrase_clb(const char *privkey_path, void *priv)
167{
168 /* password is provided as priv when setting up the callback */
169 return strdup((char *)priv);
170}
171
172static void
173ncSessionFree(ncSessionObject *self)
174{
175 PyObject *err_type, *err_value, *err_traceback;
176
177 /* save the current exception state */
178 PyErr_Fetch(&err_type, &err_value, &err_traceback);
179
180 nc_session_free(self->session, NULL);
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200181
182 (*self->ctx_counter)--;
183 if (!(*self->ctx_counter)) {
184 ly_ctx_destroy(self->ctx, NULL);
185 free(self->ctx_counter);
186 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200187
188 /* restore the saved exception state */
189 PyErr_Restore(err_type, err_value, err_traceback);
190
191 Py_TYPE(self)->tp_free((PyObject*)self);
192}
193
194static PyObject *
195ncSessionNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
196{
197 ncSessionObject *self;
198
199 self = (ncSessionObject *)type->tp_alloc(type, 0);
200 if (self != NULL) {
201 /* NULL initiation */
202 self->session = NULL;
Radek Krejci41e901a2017-09-21 13:02:07 +0200203 self->ctx = NULL;
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200204 self->ctx_counter = calloc(1, sizeof *self->ctx_counter);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200205 }
206
207 return (PyObject *)self;
208}
209
210static int
211ncSessionInit(ncSessionObject *self, PyObject *args, PyObject *kwds)
212{
213 const char *host = NULL;
214 PyObject *transport = NULL;
215 unsigned short port = 0;
Radek Krejcic2074282017-11-06 15:57:22 +0100216 struct nc_session *session = NULL;
Radek Krejcic61f0b42017-06-07 13:21:41 +0200217
218 char *kwlist[] = {"host", "port", "transport", NULL};
219
220 /* Get input parameters */
221 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zHO", kwlist, &host, &port, &transport)) {
222 return -1;
223 }
224
225 /* connect */
Radek Krejcic2074282017-11-06 15:57:22 +0100226#ifdef NC_ENABLED_TLS
Radek Krejcic61f0b42017-06-07 13:21:41 +0200227 if (transport && PyObject_TypeCheck(transport, &ncTLSType)) {
Radek Krejci41e901a2017-09-21 13:02:07 +0200228 session = nc_connect_tls(host, port, NULL);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200229 } else {
Radek Krejcic2074282017-11-06 15:57:22 +0100230#else /* !NC_ENABLED_TLS */
231 {
232#endif
233#ifdef NC_ENABLED_SSH
Radek Krejcic61f0b42017-06-07 13:21:41 +0200234 if (transport) {
235 /* set SSH parameters */
236 if (((ncSSHObject*)transport)->username) {
237 nc_client_ssh_set_username(PyUnicode_AsUTF8(((ncSSHObject*)transport)->username));
238 }
Radek Krejcifa3731e2017-11-08 12:49:46 +0100239
240 nc_client_ssh_set_auth_hostkey_check_clb(&auth_hostkey_check_pyclb, (void *)transport);
241
Radek Krejcic61f0b42017-06-07 13:21:41 +0200242 if (((ncSSHObject*)transport)->password) {
243 nc_client_ssh_set_auth_password_clb(&auth_password_clb,
244 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
245 nc_client_ssh_set_auth_interactive_clb(&auth_interactive_clb,
246 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
247 nc_client_ssh_set_auth_privkey_passphrase_clb(&auth_privkey_passphrase_clb,
248 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
Radek Krejcib20999f2017-06-21 13:47:11 +0200249 } else {
250 if (((ncSSHObject *)transport)->clb_password) {
251 nc_client_ssh_set_auth_password_clb(&auth_password_pyclb, (void *)transport);
252 }
253 if (((ncSSHObject *)transport)->clb_interactive) {
254 nc_client_ssh_set_auth_interactive_clb(&auth_interactive_pyclb, (void *)transport);
255 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200256 }
257 }
258
259 /* create connection */
Radek Krejci41e901a2017-09-21 13:02:07 +0200260 session = nc_connect_ssh(host, port, NULL);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200261 /* cleanup */
262 if (transport) {
263 if (((ncSSHObject*)transport)->username) {
264 nc_client_ssh_set_username(NULL);
265 }
266 if (((ncSSHObject*)transport)->password) {
267 nc_client_ssh_set_auth_password_clb(NULL, NULL);
268 nc_client_ssh_set_auth_interactive_clb(NULL, NULL);
269 nc_client_ssh_set_auth_privkey_passphrase_clb(NULL, NULL);
270 }
271 }
Radek Krejcic2074282017-11-06 15:57:22 +0100272#endif /* NC_ENABLED_SSH */
Radek Krejcic61f0b42017-06-07 13:21:41 +0200273 }
274
275 /* check the result */
276 if (!session) {
277 return -1;
278 }
279
Radek Krejci41e901a2017-09-21 13:02:07 +0200280 /* get the internally created context for this session */
281 self->ctx = nc_session_get_ctx(session);
282
Radek Krejcic61f0b42017-06-07 13:21:41 +0200283 /* replace the previous (if any) data in the session object */
284 nc_session_free(self->session, NULL);
285 self->session = session;
286
287 return 0;
288}
289
Radek Krejcic2074282017-11-06 15:57:22 +0100290#ifdef NC_ENABLED_SSH
291
Radek Krejcic61f0b42017-06-07 13:21:41 +0200292static PyObject *
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200293newChannel(PyObject *self)
294{
295 ncSessionObject *new;
296
297 if (nc_session_get_ti(((ncSessionObject *)self)->session) != NC_TI_LIBSSH) {
298 PyErr_SetString(PyExc_TypeError, "The session must be on SSH.");
299 return NULL;
300 }
301
302 new = (ncSessionObject *)self->ob_type->tp_alloc(self->ob_type, 0);
303 if (!new) {
304 return NULL;
305 }
306
307 new->ctx = ((ncSessionObject *)self)->ctx;
308 new->session = nc_connect_ssh_channel(((ncSessionObject *)self)->session, new->ctx);
309 if (!new->session) {
310 Py_DECREF(new);
311 return NULL;
312 }
313
314 new->ctx_counter = ((ncSessionObject *)self)->ctx_counter;
315 (*new->ctx_counter)++;
316 return (PyObject*)new;
317}
318
Radek Krejcic2074282017-11-06 15:57:22 +0100319#endif /* NC_ENABLED_SSH */
320
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200321static PyObject *
Radek Krejcic61f0b42017-06-07 13:21:41 +0200322ncSessionStr(ncSessionObject *self)
323{
324 return PyUnicode_FromFormat("NETCONF Session %u to %s:%u (%lu references)", nc_session_get_id(self->session),
325 nc_session_get_host(self->session), nc_session_get_port(self->session),
326 ((PyObject*)(self))->ob_refcnt);
327}
328
329/*
330 * tp_methods callbacks held by ncSessionMethods[]
331 */
332
333/*
334 * tp_getset callbacs held by ncSessionGetSetters[]
335 */
336#if 0
337static PyObject *
338ncSessionGetSTatus(ncSessionObject *self, void *closure)
339{
340 NC_STATUS s;
341
342 s = nc_session_get_status(self->session);
343 switch(s) {
344 case NC_STATUS_ERR:
345 /* exception */
346 return NULL;
347 }
348 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
349}
350#endif
351
352static PyObject *
353ncSessionGetId(ncSessionObject *self, void *closure)
354{
355 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
356}
357
358static PyObject *
359ncSessionGetHost(ncSessionObject *self, void *closure)
360{
361 return PyUnicode_FromString(nc_session_get_host(self->session));
362}
363
364static PyObject *
365ncSessionGetPort(ncSessionObject *self, void *closure)
366{
367 return PyUnicode_FromFormat("%u", nc_session_get_port(self->session));
368}
369
370static PyObject *
371ncSessionGetUser(ncSessionObject *self, void *closure)
372{
373 return PyUnicode_FromString(nc_session_get_username(self->session));
374}
375
376static PyObject *
377ncSessionGetTransport(ncSessionObject *self, void *closure)
378{
379 NC_TRANSPORT_IMPL ti = nc_session_get_ti(self->session);
380 switch (ti) {
Radek Krejcic2074282017-11-06 15:57:22 +0100381#ifdef NC_ENABLED_SSH
Radek Krejcic61f0b42017-06-07 13:21:41 +0200382 case NC_TI_LIBSSH:
383 return PyUnicode_FromString("SSH");
Radek Krejcic2074282017-11-06 15:57:22 +0100384#endif /* NC_ENABLED_SSH */
385#ifdef NC_ENABLEd_TLS
Radek Krejcic61f0b42017-06-07 13:21:41 +0200386 case NC_TI_OPENSSL:
387 return PyUnicode_FromString("TLS");
Radek Krejcic2074282017-11-06 15:57:22 +0100388#endif /* NC_ENABLED_TLS */
Radek Krejcic61f0b42017-06-07 13:21:41 +0200389 default:
390 return PyUnicode_FromString("unknown");
391 }
392}
393
394static PyObject *
395ncSessionGetCapabilities(ncSessionObject *self, void *closure)
396{
397 PyObject *list;
398 const char * const *cpblts;
399 ssize_t pos;
400
401 cpblts = nc_session_get_cpblts(self->session);
402 if (cpblts == NULL) {
403 return (NULL);
404 }
405
406 list = PyList_New(0);
407 for(pos = 0; cpblts[pos]; ++pos) {
408 PyList_Append(list, PyUnicode_FromString(cpblts[pos]));
409 }
410
411 return list;
412}
413
414static PyObject *
415ncSessionGetVersion(ncSessionObject *self, void *closure)
416{
417 if (nc_session_get_version(self->session)) {
418 return PyUnicode_FromString("1.1");
419 } else {
420 return PyUnicode_FromString("1.0");
421 }
422}
423
Radek Krejci9305fbb2018-01-25 13:19:46 +0100424static PyObject *
425ncSessionGetContext(ncSessionObject *self, void *closure)
426{
427 PyObject *context, *result, *module;
428
429
430 /* process the received data */
431 context = SWIG_NewPointerObj(self->ctx, SWIG_Python_TypeQuery("ly_ctx*"), 0);
432 if (!context) {
433 PyErr_SetString(libnetconf2Error, "Building Python object from context structure failed.");
434 goto error;
435 }
436
Radek Krejcieed7f872018-02-13 16:57:42 +0100437 module = PyImport_ImportModule("yang");
Radek Krejci9305fbb2018-01-25 13:19:46 +0100438 if (module == NULL) {
Radek Krejcieed7f872018-02-13 16:57:42 +0100439 PyErr_SetString(libnetconf2Error, "Could not import libyang python module");
Radek Krejci9305fbb2018-01-25 13:19:46 +0100440 goto error;
441 }
442
443 result = PyObject_CallMethod(module, "create_new_Context", "(O)", context, NULL);
444 Py_DECREF(module);
445 Py_DECREF(context);
446 if (!result) {
447 PyErr_SetString(libnetconf2Error, "Could not create Context object.");
448 goto error;
449 }
450
451 return result;
452
453error:
454 Py_XDECREF(context);
455 return NULL;
456}
457
Radek Krejcic61f0b42017-06-07 13:21:41 +0200458/*
459 * Callback structures
460 */
461
462static PyGetSetDef ncSessionGetSetters[] = {
463 {"id", (getter)ncSessionGetId, NULL, "NETCONF Session id.", NULL},
464 {"host", (getter)ncSessionGetHost, NULL, "Host where the NETCONF Session is connected.", NULL},
465 {"port", (getter)ncSessionGetPort, NULL, "Port number where the NETCONF Session is connected.", NULL},
466 {"user", (getter)ncSessionGetUser, NULL, "Username of the user connected with the NETCONF Session.", NULL},
467 {"transport", (getter)ncSessionGetTransport, NULL, "Transport protocol used for the NETCONF Session.", NULL},
468 {"version", (getter)ncSessionGetVersion, NULL, "NETCONF Protocol version used for the NETCONF Session.", NULL},
469 {"capabilities", (getter)ncSessionGetCapabilities, NULL, "Capabilities of the NETCONF Session.", NULL},
Radek Krejci9305fbb2018-01-25 13:19:46 +0100470 {"context", (getter)ncSessionGetContext, NULL, "libyang context of the NETCONF Session.", NULL},
Radek Krejcic61f0b42017-06-07 13:21:41 +0200471 {NULL} /* Sentinel */
472};
473
474static PyMemberDef ncSessionMembers[] = {
475 {NULL} /* Sentinel */
476};
477
478static PyMethodDef ncSessionMethods[] = {
Radek Krejcic2074282017-11-06 15:57:22 +0100479#ifdef NC_ENABLED_SSH
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200480 {"newChannel", (PyCFunction)newChannel, METH_NOARGS,
481 "newChannel()\n--\n\n"
482 "Create another NETCONF session on existing SSH session using separated SSH channel\n\n"
483 ":returns: New netconf2.Session instance.\n"},
Radek Krejcic2074282017-11-06 15:57:22 +0100484#endif /* NC_ENABLED_SSH */
Radek Krejcie0854c02017-10-10 21:11:29 +0200485 /* RPCs */
486 {"rpcGet", (PyCFunction)ncRPCGet, METH_VARARGS | METH_KEYWORDS,
487 "Send NETCONF <get> operation on the Session.\n\n"
488 "ncRPCGet(subtree=None, xpath=None)\n"
489 ":returns: Reply from the server.\n"},
Radek Krejcib0bf24c2018-02-07 16:40:27 +0100490 {"rpcGetConfig", (PyCFunction)ncRPCGetConfig, METH_VARARGS | METH_KEYWORDS,
491 "Send NETCONF <get-config> operation on the Session.\n\n"
492 "ncRPCGetConfig(datastore, subtree=None, xpath=None)\n"
493 ":returns: Reply from the server.\n"},
494 {"rpcEditConfig", (PyCFunction)ncRPCEditConfig, METH_VARARGS | METH_KEYWORDS,
495 "Send NETCONF <edit-config> operation on the Session.\n\n"
496 "ncRPCEditConfig(datastore, data, defop=None, testopt=None, erropt=None)\n"
497 ":returns: None\n"},
Radek Krejcic61f0b42017-06-07 13:21:41 +0200498 {NULL} /* Sentinel */
499};
500
501PyDoc_STRVAR(sessionDoc,
Radek Krejcib03ebe42017-07-04 14:00:33 +0200502 "The NETCONF Session object.\n\n"
503 "Arguments: (host='localhost', port=830, transport=None)\n");
Radek Krejcic61f0b42017-06-07 13:21:41 +0200504
505PyTypeObject ncSessionType = {
506 PyVarObject_HEAD_INIT(NULL, 0)
507 "netconf2.Session", /* tp_name */
508 sizeof(ncSessionObject), /* tp_basicsize */
509 0, /* tp_itemsize */
510 (destructor)ncSessionFree, /* tp_dealloc */
511 0, /* tp_print */
512 0, /* tp_getattr */
513 0, /* tp_setattr */
514 0, /* tp_reserved */
515 (reprfunc)ncSessionStr, /* tp_repr */
516 0, /* tp_as_number */
517 0, /* tp_as_sequence */
518 0, /* tp_as_mapping */
519 0, /* tp_hash */
520 0, /* tp_call */
521 (reprfunc)ncSessionStr, /* tp_str */
522 0, /* tp_getattro */
523 0, /* tp_setattro */
524 0, /* tp_as_buffer */
525 Py_TPFLAGS_DEFAULT |
526 Py_TPFLAGS_BASETYPE, /* tp_flags */
527 sessionDoc, /* tp_doc */
528 0, /* tp_traverse */
529 0, /* tp_clear */
530 0, /* tp_richcompare */
531 0, /* tp_weaklistoffset */
532 0, /* tp_iter */
533 0, /* tp_iternext */
534 ncSessionMethods, /* tp_methods */
535 ncSessionMembers, /* tp_members */
536 ncSessionGetSetters, /* tp_getset */
537 0, /* tp_base */
538 0, /* tp_dict */
539 0, /* tp_descr_get */
540 0, /* tp_descr_set */
541 0, /* tp_dictoffset */
542 (initproc)ncSessionInit, /* tp_init */
543 0, /* tp_alloc */
544 ncSessionNew, /* tp_new */
545};
546