blob: 474cee0d10b88867d1958f42c3bbc49a28fc8c44 [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;
29 struct nc_session *session;
30} ncSessionObject;
31
32char *
33auth_password_clb(const char *UNUSED(username), const char *UNUSED(hostname), void *priv)
34{
35 /* password is provided as priv when setting up the callback */
36 return strdup((char *)priv);
37}
Radek Krejcib20999f2017-06-21 13:47:11 +020038
39char *
40auth_password_pyclb(const char *username, const char *hostname, void *priv)
41{
42 PyObject *arglist, *result;
43 ncSSHObject *ssh = (ncSSHObject*)priv;
44 char *password = NULL;
45
46 arglist = Py_BuildValue("(ssO)", username, hostname, ssh->clb_password_data ? ssh->clb_password_data : Py_None);
47 if (!arglist) {
48 PyErr_Print();
49 return NULL;
50 }
51 result = PyObject_CallObject(ssh->clb_password, arglist);
52 Py_DECREF(arglist);
53
54 if (result) {
55 if (!PyUnicode_Check(result)) {
56 PyErr_SetString(PyExc_TypeError, "Invalid password authentication callback result.");
57 } else {
58 password = strdup(PyUnicode_AsUTF8(result));
59 Py_DECREF(result);
60 }
61 }
62
63 return password;
64}
65
Radek Krejcic61f0b42017-06-07 13:21:41 +020066char *
67auth_interactive_clb(const char *UNUSED(auth_name), const char *UNUSED(instruction), const char *UNUSED(prompt),
68 int UNUSED(echo), void *priv)
69{
70 /* password is provided as priv when setting up the callback */
71 return strdup((char *)priv);
72}
73
74char *
Radek Krejcib20999f2017-06-21 13:47:11 +020075auth_interactive_pyclb(const char *auth_name, const char *instruction, const char *prompt, int UNUSED(echo), void *priv)
76{
77 PyObject *arglist, *result;
78 ncSSHObject *ssh = (ncSSHObject*)priv;
79 char *password = NULL;
80
81 arglist = Py_BuildValue("(sssO)", auth_name, instruction, prompt, ssh->clb_password_data ? ssh->clb_password_data : Py_None);
82 if (!arglist) {
83 PyErr_Print();
84 return NULL;
85 }
86 result = PyObject_CallObject(ssh->clb_interactive, arglist);
87 Py_DECREF(arglist);
88
89 if (result) {
90 if (!PyUnicode_Check(result)) {
91 PyErr_SetString(PyExc_TypeError, "Invalid password authentication callback result.");
92 } else {
93 password = strdup(PyUnicode_AsUTF8(result));
94 Py_DECREF(result);
95 }
96 }
97
98 return password;
99
100}
101
102char *
Radek Krejcic61f0b42017-06-07 13:21:41 +0200103auth_privkey_passphrase_clb(const char *privkey_path, void *priv)
104{
105 /* password is provided as priv when setting up the callback */
106 return strdup((char *)priv);
107}
108
109static void
110ncSessionFree(ncSessionObject *self)
111{
112 PyObject *err_type, *err_value, *err_traceback;
113
114 /* save the current exception state */
115 PyErr_Fetch(&err_type, &err_value, &err_traceback);
116
117 nc_session_free(self->session, NULL);
118 ly_ctx_destroy(self->ctx, NULL);
119
120 /* restore the saved exception state */
121 PyErr_Restore(err_type, err_value, err_traceback);
122
123 Py_TYPE(self)->tp_free((PyObject*)self);
124}
125
126static PyObject *
127ncSessionNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
128{
129 ncSessionObject *self;
130
131 self = (ncSessionObject *)type->tp_alloc(type, 0);
132 if (self != NULL) {
133 /* NULL initiation */
134 self->session = NULL;
135
136 /* prepare libyang context or use the one already present in the session */
Radek Krejci82441b02017-06-21 13:40:40 +0200137 self->ctx = ly_ctx_new(SCHEMAS_DIR);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200138 if (!self->ctx) {
139 Py_DECREF(self);
140 return NULL;
141 }
142 }
143
144 return (PyObject *)self;
145}
146
147static int
148ncSessionInit(ncSessionObject *self, PyObject *args, PyObject *kwds)
149{
150 const char *host = NULL;
151 PyObject *transport = NULL;
152 unsigned short port = 0;
153 struct nc_session *session;
154
155 char *kwlist[] = {"host", "port", "transport", NULL};
156
157 /* Get input parameters */
158 if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zHO", kwlist, &host, &port, &transport)) {
159 return -1;
160 }
161
162 /* connect */
163 if (transport && PyObject_TypeCheck(transport, &ncTLSType)) {
164 session = nc_connect_tls(host, port, self->ctx);
165 } else {
166 if (transport) {
167 /* set SSH parameters */
168 if (((ncSSHObject*)transport)->username) {
169 nc_client_ssh_set_username(PyUnicode_AsUTF8(((ncSSHObject*)transport)->username));
170 }
171 if (((ncSSHObject*)transport)->password) {
172 nc_client_ssh_set_auth_password_clb(&auth_password_clb,
173 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
174 nc_client_ssh_set_auth_interactive_clb(&auth_interactive_clb,
175 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
176 nc_client_ssh_set_auth_privkey_passphrase_clb(&auth_privkey_passphrase_clb,
177 (void *)PyUnicode_AsUTF8(((ncSSHObject*)transport)->password));
Radek Krejcib20999f2017-06-21 13:47:11 +0200178 } else {
179 if (((ncSSHObject *)transport)->clb_password) {
180 nc_client_ssh_set_auth_password_clb(&auth_password_pyclb, (void *)transport);
181 }
182 if (((ncSSHObject *)transport)->clb_interactive) {
183 nc_client_ssh_set_auth_interactive_clb(&auth_interactive_pyclb, (void *)transport);
184 }
Radek Krejcic61f0b42017-06-07 13:21:41 +0200185 }
186 }
187
188 /* create connection */
189 session = nc_connect_ssh(host, port, self->ctx);
190
191 /* 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
209 /* replace the previous (if any) data in the session object */
210 nc_session_free(self->session, NULL);
211 self->session = session;
212
213 return 0;
214}
215
216static PyObject *
217ncSessionStr(ncSessionObject *self)
218{
219 return PyUnicode_FromFormat("NETCONF Session %u to %s:%u (%lu references)", nc_session_get_id(self->session),
220 nc_session_get_host(self->session), nc_session_get_port(self->session),
221 ((PyObject*)(self))->ob_refcnt);
222}
223
224/*
225 * tp_methods callbacks held by ncSessionMethods[]
226 */
227
228/*
229 * tp_getset callbacs held by ncSessionGetSetters[]
230 */
231#if 0
232static PyObject *
233ncSessionGetSTatus(ncSessionObject *self, void *closure)
234{
235 NC_STATUS s;
236
237 s = nc_session_get_status(self->session);
238 switch(s) {
239 case NC_STATUS_ERR:
240 /* exception */
241 return NULL;
242 }
243 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
244}
245#endif
246
247static PyObject *
248ncSessionGetId(ncSessionObject *self, void *closure)
249{
250 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
251}
252
253static PyObject *
254ncSessionGetHost(ncSessionObject *self, void *closure)
255{
256 return PyUnicode_FromString(nc_session_get_host(self->session));
257}
258
259static PyObject *
260ncSessionGetPort(ncSessionObject *self, void *closure)
261{
262 return PyUnicode_FromFormat("%u", nc_session_get_port(self->session));
263}
264
265static PyObject *
266ncSessionGetUser(ncSessionObject *self, void *closure)
267{
268 return PyUnicode_FromString(nc_session_get_username(self->session));
269}
270
271static PyObject *
272ncSessionGetTransport(ncSessionObject *self, void *closure)
273{
274 NC_TRANSPORT_IMPL ti = nc_session_get_ti(self->session);
275 switch (ti) {
276 case NC_TI_LIBSSH:
277 return PyUnicode_FromString("SSH");
278 case NC_TI_OPENSSL:
279 return PyUnicode_FromString("TLS");
280 default:
281 return PyUnicode_FromString("unknown");
282 }
283}
284
285static PyObject *
286ncSessionGetCapabilities(ncSessionObject *self, void *closure)
287{
288 PyObject *list;
289 const char * const *cpblts;
290 ssize_t pos;
291
292 cpblts = nc_session_get_cpblts(self->session);
293 if (cpblts == NULL) {
294 return (NULL);
295 }
296
297 list = PyList_New(0);
298 for(pos = 0; cpblts[pos]; ++pos) {
299 PyList_Append(list, PyUnicode_FromString(cpblts[pos]));
300 }
301
302 return list;
303}
304
305static PyObject *
306ncSessionGetVersion(ncSessionObject *self, void *closure)
307{
308 if (nc_session_get_version(self->session)) {
309 return PyUnicode_FromString("1.1");
310 } else {
311 return PyUnicode_FromString("1.0");
312 }
313}
314
315/*
316 * Callback structures
317 */
318
319static PyGetSetDef ncSessionGetSetters[] = {
320 {"id", (getter)ncSessionGetId, NULL, "NETCONF Session id.", NULL},
321 {"host", (getter)ncSessionGetHost, NULL, "Host where the NETCONF Session is connected.", NULL},
322 {"port", (getter)ncSessionGetPort, NULL, "Port number where the NETCONF Session is connected.", NULL},
323 {"user", (getter)ncSessionGetUser, NULL, "Username of the user connected with the NETCONF Session.", NULL},
324 {"transport", (getter)ncSessionGetTransport, NULL, "Transport protocol used for the NETCONF Session.", NULL},
325 {"version", (getter)ncSessionGetVersion, NULL, "NETCONF Protocol version used for the NETCONF Session.", NULL},
326 {"capabilities", (getter)ncSessionGetCapabilities, NULL, "Capabilities of the NETCONF Session.", NULL},
327 {NULL} /* Sentinel */
328};
329
330static PyMemberDef ncSessionMembers[] = {
331 {NULL} /* Sentinel */
332};
333
334static PyMethodDef ncSessionMethods[] = {
335 {NULL} /* Sentinel */
336};
337
338PyDoc_STRVAR(sessionDoc,
Radek Krejcib03ebe42017-07-04 14:00:33 +0200339 "The NETCONF Session object.\n\n"
340 "Arguments: (host='localhost', port=830, transport=None)\n");
Radek Krejcic61f0b42017-06-07 13:21:41 +0200341
342PyTypeObject ncSessionType = {
343 PyVarObject_HEAD_INIT(NULL, 0)
344 "netconf2.Session", /* tp_name */
345 sizeof(ncSessionObject), /* tp_basicsize */
346 0, /* tp_itemsize */
347 (destructor)ncSessionFree, /* tp_dealloc */
348 0, /* tp_print */
349 0, /* tp_getattr */
350 0, /* tp_setattr */
351 0, /* tp_reserved */
352 (reprfunc)ncSessionStr, /* tp_repr */
353 0, /* tp_as_number */
354 0, /* tp_as_sequence */
355 0, /* tp_as_mapping */
356 0, /* tp_hash */
357 0, /* tp_call */
358 (reprfunc)ncSessionStr, /* tp_str */
359 0, /* tp_getattro */
360 0, /* tp_setattro */
361 0, /* tp_as_buffer */
362 Py_TPFLAGS_DEFAULT |
363 Py_TPFLAGS_BASETYPE, /* tp_flags */
364 sessionDoc, /* tp_doc */
365 0, /* tp_traverse */
366 0, /* tp_clear */
367 0, /* tp_richcompare */
368 0, /* tp_weaklistoffset */
369 0, /* tp_iter */
370 0, /* tp_iternext */
371 ncSessionMethods, /* tp_methods */
372 ncSessionMembers, /* tp_members */
373 ncSessionGetSetters, /* tp_getset */
374 0, /* tp_base */
375 0, /* tp_dict */
376 0, /* tp_descr_get */
377 0, /* tp_descr_set */
378 0, /* tp_dictoffset */
379 (initproc)ncSessionInit, /* tp_init */
380 0, /* tp_alloc */
381 ncSessionNew, /* tp_new */
382};
383