blob: bd6e7df47a7bf549420874211ec8eb47e40511d9 [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>
21#include <libyang/libyang.h>
22
Radek Krejcib20999f2017-06-21 13:47:11 +020023#include "../src/config.h"
Radek Krejcic61f0b42017-06-07 13:21:41 +020024#include "netconf.h"
Radek Krejcie0854c02017-10-10 21:11:29 +020025#include "session.h"
26#include "rpc.h"
Radek Krejcic61f0b42017-06-07 13:21:41 +020027
28char *
29auth_password_clb(const char *UNUSED(username), const char *UNUSED(hostname), void *priv)
30{
31 /* password is provided as priv when setting up the callback */
32 return strdup((char *)priv);
33}
Radek Krejcib20999f2017-06-21 13:47:11 +020034
35char *
36auth_password_pyclb(const char *username, const char *hostname, void *priv)
37{
38 PyObject *arglist, *result;
39 ncSSHObject *ssh = (ncSSHObject*)priv;
40 char *password = NULL;
41
42 arglist = Py_BuildValue("(ssO)", username, hostname, ssh->clb_password_data ? ssh->clb_password_data : Py_None);
43 if (!arglist) {
44 PyErr_Print();
45 return NULL;
46 }
47 result = PyObject_CallObject(ssh->clb_password, arglist);
48 Py_DECREF(arglist);
49
50 if (result) {
51 if (!PyUnicode_Check(result)) {
52 PyErr_SetString(PyExc_TypeError, "Invalid password authentication callback result.");
53 } else {
54 password = strdup(PyUnicode_AsUTF8(result));
55 Py_DECREF(result);
56 }
57 }
58
59 return password;
60}
61
Radek Krejcic61f0b42017-06-07 13:21:41 +020062char *
63auth_interactive_clb(const char *UNUSED(auth_name), const char *UNUSED(instruction), const char *UNUSED(prompt),
64 int UNUSED(echo), void *priv)
65{
66 /* password is provided as priv when setting up the callback */
67 return strdup((char *)priv);
68}
69
70char *
Radek Krejcib20999f2017-06-21 13:47:11 +020071auth_interactive_pyclb(const char *auth_name, const char *instruction, const char *prompt, int UNUSED(echo), void *priv)
72{
73 PyObject *arglist, *result;
74 ncSSHObject *ssh = (ncSSHObject*)priv;
75 char *password = NULL;
76
77 arglist = Py_BuildValue("(sssO)", auth_name, instruction, prompt, ssh->clb_password_data ? ssh->clb_password_data : Py_None);
78 if (!arglist) {
79 PyErr_Print();
80 return NULL;
81 }
82 result = PyObject_CallObject(ssh->clb_interactive, arglist);
83 Py_DECREF(arglist);
84
85 if (result) {
86 if (!PyUnicode_Check(result)) {
87 PyErr_SetString(PyExc_TypeError, "Invalid password authentication callback result.");
88 } else {
89 password = strdup(PyUnicode_AsUTF8(result));
90 Py_DECREF(result);
91 }
92 }
93
94 return password;
95
96}
97
98char *
Radek Krejcic61f0b42017-06-07 13:21:41 +020099auth_privkey_passphrase_clb(const char *privkey_path, void *priv)
100{
101 /* password is provided as priv when setting up the callback */
102 return strdup((char *)priv);
103}
104
105static void
106ncSessionFree(ncSessionObject *self)
107{
108 PyObject *err_type, *err_value, *err_traceback;
109
110 /* save the current exception state */
111 PyErr_Fetch(&err_type, &err_value, &err_traceback);
112
113 nc_session_free(self->session, NULL);
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200114
115 (*self->ctx_counter)--;
116 if (!(*self->ctx_counter)) {
117 ly_ctx_destroy(self->ctx, NULL);
118 free(self->ctx_counter);
119 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200120
121 /* restore the saved exception state */
122 PyErr_Restore(err_type, err_value, err_traceback);
123
124 Py_TYPE(self)->tp_free((PyObject*)self);
125}
126
127static PyObject *
128ncSessionNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
129{
130 ncSessionObject *self;
131
132 self = (ncSessionObject *)type->tp_alloc(type, 0);
133 if (self != NULL) {
134 /* NULL initiation */
135 self->session = NULL;
Radek Krejci41e901a2017-09-21 13:02:07 +0200136 self->ctx = NULL;
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200137 self->ctx_counter = calloc(1, sizeof *self->ctx_counter);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200138 }
139
140 return (PyObject *)self;
141}
142
143static int
144ncSessionInit(ncSessionObject *self, PyObject *args, PyObject *kwds)
145{
146 const char *host = NULL;
147 PyObject *transport = NULL;
148 unsigned short port = 0;
149 struct nc_session *session;
150
151 char *kwlist[] = {"host", "port", "transport", NULL};
152
153 /* Get input parameters */
154 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zHO", kwlist, &host, &port, &transport)) {
155 return -1;
156 }
157
158 /* connect */
159 if (transport && PyObject_TypeCheck(transport, &ncTLSType)) {
Radek Krejci41e901a2017-09-21 13:02:07 +0200160 session = nc_connect_tls(host, port, NULL);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200161 } else {
162 if (transport) {
163 /* set SSH parameters */
164 if (((ncSSHObject*)transport)->username) {
165 nc_client_ssh_set_username(PyUnicode_AsUTF8(((ncSSHObject*)transport)->username));
166 }
167 if (((ncSSHObject*)transport)->password) {
168 nc_client_ssh_set_auth_password_clb(&auth_password_clb,
169 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
170 nc_client_ssh_set_auth_interactive_clb(&auth_interactive_clb,
171 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
172 nc_client_ssh_set_auth_privkey_passphrase_clb(&auth_privkey_passphrase_clb,
173 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
Radek Krejcib20999f2017-06-21 13:47:11 +0200174 } else {
175 if (((ncSSHObject *)transport)->clb_password) {
176 nc_client_ssh_set_auth_password_clb(&auth_password_pyclb, (void *)transport);
177 }
178 if (((ncSSHObject *)transport)->clb_interactive) {
179 nc_client_ssh_set_auth_interactive_clb(&auth_interactive_pyclb, (void *)transport);
180 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200181 }
182 }
183
184 /* create connection */
Radek Krejci41e901a2017-09-21 13:02:07 +0200185 session = nc_connect_ssh(host, port, NULL);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200186 /* cleanup */
187 if (transport) {
188 if (((ncSSHObject*)transport)->username) {
189 nc_client_ssh_set_username(NULL);
190 }
191 if (((ncSSHObject*)transport)->password) {
192 nc_client_ssh_set_auth_password_clb(NULL, NULL);
193 nc_client_ssh_set_auth_interactive_clb(NULL, NULL);
194 nc_client_ssh_set_auth_privkey_passphrase_clb(NULL, NULL);
195 }
196 }
197 }
198
199 /* check the result */
200 if (!session) {
201 return -1;
202 }
203
Radek Krejci41e901a2017-09-21 13:02:07 +0200204 /* get the internally created context for this session */
205 self->ctx = nc_session_get_ctx(session);
206
Radek Krejcic61f0b42017-06-07 13:21:41 +0200207 /* replace the previous (if any) data in the session object */
208 nc_session_free(self->session, NULL);
209 self->session = session;
210
211 return 0;
212}
213
214static PyObject *
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200215newChannel(PyObject *self)
216{
217 ncSessionObject *new;
218
219 if (nc_session_get_ti(((ncSessionObject *)self)->session) != NC_TI_LIBSSH) {
220 PyErr_SetString(PyExc_TypeError, "The session must be on SSH.");
221 return NULL;
222 }
223
224 new = (ncSessionObject *)self->ob_type->tp_alloc(self->ob_type, 0);
225 if (!new) {
226 return NULL;
227 }
228
229 new->ctx = ((ncSessionObject *)self)->ctx;
230 new->session = nc_connect_ssh_channel(((ncSessionObject *)self)->session, new->ctx);
231 if (!new->session) {
232 Py_DECREF(new);
233 return NULL;
234 }
235
236 new->ctx_counter = ((ncSessionObject *)self)->ctx_counter;
237 (*new->ctx_counter)++;
238 return (PyObject*)new;
239}
240
241static PyObject *
Radek Krejcic61f0b42017-06-07 13:21:41 +0200242ncSessionStr(ncSessionObject *self)
243{
244 return PyUnicode_FromFormat("NETCONF Session %u to %s:%u (%lu references)", nc_session_get_id(self->session),
245 nc_session_get_host(self->session), nc_session_get_port(self->session),
246 ((PyObject*)(self))->ob_refcnt);
247}
248
249/*
250 * tp_methods callbacks held by ncSessionMethods[]
251 */
252
253/*
254 * tp_getset callbacs held by ncSessionGetSetters[]
255 */
256#if 0
257static PyObject *
258ncSessionGetSTatus(ncSessionObject *self, void *closure)
259{
260 NC_STATUS s;
261
262 s = nc_session_get_status(self->session);
263 switch(s) {
264 case NC_STATUS_ERR:
265 /* exception */
266 return NULL;
267 }
268 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
269}
270#endif
271
272static PyObject *
273ncSessionGetId(ncSessionObject *self, void *closure)
274{
275 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
276}
277
278static PyObject *
279ncSessionGetHost(ncSessionObject *self, void *closure)
280{
281 return PyUnicode_FromString(nc_session_get_host(self->session));
282}
283
284static PyObject *
285ncSessionGetPort(ncSessionObject *self, void *closure)
286{
287 return PyUnicode_FromFormat("%u", nc_session_get_port(self->session));
288}
289
290static PyObject *
291ncSessionGetUser(ncSessionObject *self, void *closure)
292{
293 return PyUnicode_FromString(nc_session_get_username(self->session));
294}
295
296static PyObject *
297ncSessionGetTransport(ncSessionObject *self, void *closure)
298{
299 NC_TRANSPORT_IMPL ti = nc_session_get_ti(self->session);
300 switch (ti) {
301 case NC_TI_LIBSSH:
302 return PyUnicode_FromString("SSH");
303 case NC_TI_OPENSSL:
304 return PyUnicode_FromString("TLS");
305 default:
306 return PyUnicode_FromString("unknown");
307 }
308}
309
310static PyObject *
311ncSessionGetCapabilities(ncSessionObject *self, void *closure)
312{
313 PyObject *list;
314 const char * const *cpblts;
315 ssize_t pos;
316
317 cpblts = nc_session_get_cpblts(self->session);
318 if (cpblts == NULL) {
319 return (NULL);
320 }
321
322 list = PyList_New(0);
323 for(pos = 0; cpblts[pos]; ++pos) {
324 PyList_Append(list, PyUnicode_FromString(cpblts[pos]));
325 }
326
327 return list;
328}
329
330static PyObject *
331ncSessionGetVersion(ncSessionObject *self, void *closure)
332{
333 if (nc_session_get_version(self->session)) {
334 return PyUnicode_FromString("1.1");
335 } else {
336 return PyUnicode_FromString("1.0");
337 }
338}
339
340/*
341 * Callback structures
342 */
343
344static PyGetSetDef ncSessionGetSetters[] = {
345 {"id", (getter)ncSessionGetId, NULL, "NETCONF Session id.", NULL},
346 {"host", (getter)ncSessionGetHost, NULL, "Host where the NETCONF Session is connected.", NULL},
347 {"port", (getter)ncSessionGetPort, NULL, "Port number where the NETCONF Session is connected.", NULL},
348 {"user", (getter)ncSessionGetUser, NULL, "Username of the user connected with the NETCONF Session.", NULL},
349 {"transport", (getter)ncSessionGetTransport, NULL, "Transport protocol used for the NETCONF Session.", NULL},
350 {"version", (getter)ncSessionGetVersion, NULL, "NETCONF Protocol version used for the NETCONF Session.", NULL},
351 {"capabilities", (getter)ncSessionGetCapabilities, NULL, "Capabilities of the NETCONF Session.", NULL},
352 {NULL} /* Sentinel */
353};
354
355static PyMemberDef ncSessionMembers[] = {
356 {NULL} /* Sentinel */
357};
358
359static PyMethodDef ncSessionMethods[] = {
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200360 {"newChannel", (PyCFunction)newChannel, METH_NOARGS,
361 "newChannel()\n--\n\n"
362 "Create another NETCONF session on existing SSH session using separated SSH channel\n\n"
363 ":returns: New netconf2.Session instance.\n"},
Radek Krejcie0854c02017-10-10 21:11:29 +0200364 /* RPCs */
365 {"rpcGet", (PyCFunction)ncRPCGet, METH_VARARGS | METH_KEYWORDS,
366 "Send NETCONF <get> operation on the Session.\n\n"
367 "ncRPCGet(subtree=None, xpath=None)\n"
368 ":returns: Reply from the server.\n"},
369 {"rpcGetConfig", (PyCFunction)ncRPCGetConfig, METH_VARARGS | METH_KEYWORDS,
370 "Send NETCONF <get-config> operation on the Session.\n\n"
371 "ncRPCGetConfig(datastore, subtree=None, xpath=None)\n"
372 ":returns: Reply from the server.\n"},
Radek Krejcic61f0b42017-06-07 13:21:41 +0200373 {NULL} /* Sentinel */
374};
375
376PyDoc_STRVAR(sessionDoc,
Radek Krejcib03ebe42017-07-04 14:00:33 +0200377 "The NETCONF Session object.\n\n"
378 "Arguments: (host='localhost', port=830, transport=None)\n");
Radek Krejcic61f0b42017-06-07 13:21:41 +0200379
380PyTypeObject ncSessionType = {
381 PyVarObject_HEAD_INIT(NULL, 0)
382 "netconf2.Session", /* tp_name */
383 sizeof(ncSessionObject), /* tp_basicsize */
384 0, /* tp_itemsize */
385 (destructor)ncSessionFree, /* tp_dealloc */
386 0, /* tp_print */
387 0, /* tp_getattr */
388 0, /* tp_setattr */
389 0, /* tp_reserved */
390 (reprfunc)ncSessionStr, /* tp_repr */
391 0, /* tp_as_number */
392 0, /* tp_as_sequence */
393 0, /* tp_as_mapping */
394 0, /* tp_hash */
395 0, /* tp_call */
396 (reprfunc)ncSessionStr, /* tp_str */
397 0, /* tp_getattro */
398 0, /* tp_setattro */
399 0, /* tp_as_buffer */
400 Py_TPFLAGS_DEFAULT |
401 Py_TPFLAGS_BASETYPE, /* tp_flags */
402 sessionDoc, /* tp_doc */
403 0, /* tp_traverse */
404 0, /* tp_clear */
405 0, /* tp_richcompare */
406 0, /* tp_weaklistoffset */
407 0, /* tp_iter */
408 0, /* tp_iternext */
409 ncSessionMethods, /* tp_methods */
410 ncSessionMembers, /* tp_members */
411 ncSessionGetSetters, /* tp_getset */
412 0, /* tp_base */
413 0, /* tp_dict */
414 0, /* tp_descr_get */
415 0, /* tp_descr_set */
416 0, /* tp_dictoffset */
417 (initproc)ncSessionInit, /* tp_init */
418 0, /* tp_alloc */
419 ncSessionNew, /* tp_new */
420};
421