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