blob: 319f8f31ff75c0d646dcfa89dd654a8dfdfb18e6 [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
Radek Krejci23bb4a42020-05-19 16:34:05 +0200144 arglist = Py_BuildValue("(sssO)", auth_name, instruction, prompt, ssh->clb_interactive_data ? ssh->clb_interactive_data : Py_None);
Radek Krejcib20999f2017-06-21 13:47:11 +0200145 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{
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200327 const char *host = nc_session_get_host(self->session);
328 if (host)
329 return PyUnicode_FromFormat("NETCONF Session %u to %s:%u (%lu references)", nc_session_get_id(self->session),
330 nc_session_get_host(self->session), nc_session_get_port(self->session),
331 ((PyObject*)(self))->ob_refcnt);
332 else
333 return PyUnicode_FromFormat("NETCONF Session %u to %s (%lu references)", nc_session_get_id(self->session),
334 nc_session_get_path(self->session), ((PyObject*)(self))->ob_refcnt);
Radek Krejcic61f0b42017-06-07 13:21:41 +0200335}
336
337/*
338 * tp_methods callbacks held by ncSessionMethods[]
339 */
340
341/*
342 * tp_getset callbacs held by ncSessionGetSetters[]
343 */
344#if 0
345static PyObject *
346ncSessionGetSTatus(ncSessionObject *self, void *closure)
347{
348 NC_STATUS s;
349
350 s = nc_session_get_status(self->session);
351 switch(s) {
352 case NC_STATUS_ERR:
353 /* exception */
354 return NULL;
355 }
356 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
357}
358#endif
359
360static PyObject *
361ncSessionGetId(ncSessionObject *self, void *closure)
362{
363 return PyUnicode_FromFormat("%u", nc_session_get_id(self->session));
364}
365
366static PyObject *
367ncSessionGetHost(ncSessionObject *self, void *closure)
368{
369 return PyUnicode_FromString(nc_session_get_host(self->session));
370}
371
372static PyObject *
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200373ncSessionGetPath(ncSessionObject *self, void *closure)
374{
375 return PyUnicode_FromString(nc_session_get_path(self->session));
376}
377
378static PyObject *
Radek Krejcic61f0b42017-06-07 13:21:41 +0200379ncSessionGetPort(ncSessionObject *self, void *closure)
380{
381 return PyUnicode_FromFormat("%u", nc_session_get_port(self->session));
382}
383
384static PyObject *
385ncSessionGetUser(ncSessionObject *self, void *closure)
386{
387 return PyUnicode_FromString(nc_session_get_username(self->session));
388}
389
390static PyObject *
391ncSessionGetTransport(ncSessionObject *self, void *closure)
392{
393 NC_TRANSPORT_IMPL ti = nc_session_get_ti(self->session);
394 switch (ti) {
Radek Krejcic2074282017-11-06 15:57:22 +0100395#ifdef NC_ENABLED_SSH
Radek Krejcic61f0b42017-06-07 13:21:41 +0200396 case NC_TI_LIBSSH:
397 return PyUnicode_FromString("SSH");
Radek Krejcic2074282017-11-06 15:57:22 +0100398#endif /* NC_ENABLED_SSH */
399#ifdef NC_ENABLEd_TLS
Radek Krejcic61f0b42017-06-07 13:21:41 +0200400 case NC_TI_OPENSSL:
401 return PyUnicode_FromString("TLS");
Radek Krejcic2074282017-11-06 15:57:22 +0100402#endif /* NC_ENABLED_TLS */
Radek Krejcic61f0b42017-06-07 13:21:41 +0200403 default:
404 return PyUnicode_FromString("unknown");
405 }
406}
407
408static PyObject *
409ncSessionGetCapabilities(ncSessionObject *self, void *closure)
410{
411 PyObject *list;
412 const char * const *cpblts;
413 ssize_t pos;
414
415 cpblts = nc_session_get_cpblts(self->session);
416 if (cpblts == NULL) {
417 return (NULL);
418 }
419
420 list = PyList_New(0);
421 for(pos = 0; cpblts[pos]; ++pos) {
422 PyList_Append(list, PyUnicode_FromString(cpblts[pos]));
423 }
424
425 return list;
426}
427
428static PyObject *
429ncSessionGetVersion(ncSessionObject *self, void *closure)
430{
431 if (nc_session_get_version(self->session)) {
432 return PyUnicode_FromString("1.1");
433 } else {
434 return PyUnicode_FromString("1.0");
435 }
436}
437
Radek Krejci9305fbb2018-01-25 13:19:46 +0100438static PyObject *
439ncSessionGetContext(ncSessionObject *self, void *closure)
440{
441 PyObject *context, *result, *module;
442
443
444 /* process the received data */
445 context = SWIG_NewPointerObj(self->ctx, SWIG_Python_TypeQuery("ly_ctx*"), 0);
446 if (!context) {
447 PyErr_SetString(libnetconf2Error, "Building Python object from context structure failed.");
448 goto error;
449 }
450
Radek Krejcieed7f872018-02-13 16:57:42 +0100451 module = PyImport_ImportModule("yang");
Radek Krejci9305fbb2018-01-25 13:19:46 +0100452 if (module == NULL) {
Radek Krejcieed7f872018-02-13 16:57:42 +0100453 PyErr_SetString(libnetconf2Error, "Could not import libyang python module");
Radek Krejci9305fbb2018-01-25 13:19:46 +0100454 goto error;
455 }
456
457 result = PyObject_CallMethod(module, "create_new_Context", "(O)", context, NULL);
458 Py_DECREF(module);
459 Py_DECREF(context);
460 if (!result) {
461 PyErr_SetString(libnetconf2Error, "Could not create Context object.");
462 goto error;
463 }
464
465 return result;
466
467error:
468 Py_XDECREF(context);
469 return NULL;
470}
471
Radek Krejcic61f0b42017-06-07 13:21:41 +0200472/*
473 * Callback structures
474 */
475
476static PyGetSetDef ncSessionGetSetters[] = {
477 {"id", (getter)ncSessionGetId, NULL, "NETCONF Session id.", NULL},
478 {"host", (getter)ncSessionGetHost, NULL, "Host where the NETCONF Session is connected.", NULL},
479 {"port", (getter)ncSessionGetPort, NULL, "Port number where the NETCONF Session is connected.", NULL},
480 {"user", (getter)ncSessionGetUser, NULL, "Username of the user connected with the NETCONF Session.", NULL},
481 {"transport", (getter)ncSessionGetTransport, NULL, "Transport protocol used for the NETCONF Session.", NULL},
482 {"version", (getter)ncSessionGetVersion, NULL, "NETCONF Protocol version used for the NETCONF Session.", NULL},
483 {"capabilities", (getter)ncSessionGetCapabilities, NULL, "Capabilities of the NETCONF Session.", NULL},
Radek Krejci9305fbb2018-01-25 13:19:46 +0100484 {"context", (getter)ncSessionGetContext, NULL, "libyang context of the NETCONF Session.", NULL},
Radek Krejcic61f0b42017-06-07 13:21:41 +0200485 {NULL} /* Sentinel */
486};
487
488static PyMemberDef ncSessionMembers[] = {
489 {NULL} /* Sentinel */
490};
491
492static PyMethodDef ncSessionMethods[] = {
Radek Krejcic2074282017-11-06 15:57:22 +0100493#ifdef NC_ENABLED_SSH
Radek Krejcic95d9ff2017-07-04 16:48:06 +0200494 {"newChannel", (PyCFunction)newChannel, METH_NOARGS,
495 "newChannel()\n--\n\n"
496 "Create another NETCONF session on existing SSH session using separated SSH channel\n\n"
497 ":returns: New netconf2.Session instance.\n"},
Radek Krejcic2074282017-11-06 15:57:22 +0100498#endif /* NC_ENABLED_SSH */
Radek Krejcie0854c02017-10-10 21:11:29 +0200499 /* RPCs */
500 {"rpcGet", (PyCFunction)ncRPCGet, METH_VARARGS | METH_KEYWORDS,
501 "Send NETCONF <get> operation on the Session.\n\n"
502 "ncRPCGet(subtree=None, xpath=None)\n"
503 ":returns: Reply from the server.\n"},
Radek Krejcib0bf24c2018-02-07 16:40:27 +0100504 {"rpcGetConfig", (PyCFunction)ncRPCGetConfig, METH_VARARGS | METH_KEYWORDS,
505 "Send NETCONF <get-config> operation on the Session.\n\n"
506 "ncRPCGetConfig(datastore, subtree=None, xpath=None)\n"
507 ":returns: Reply from the server.\n"},
508 {"rpcEditConfig", (PyCFunction)ncRPCEditConfig, METH_VARARGS | METH_KEYWORDS,
509 "Send NETCONF <edit-config> operation on the Session.\n\n"
510 "ncRPCEditConfig(datastore, data, defop=None, testopt=None, erropt=None)\n"
511 ":returns: None\n"},
Radek Krejcic61f0b42017-06-07 13:21:41 +0200512 {NULL} /* Sentinel */
513};
514
515PyDoc_STRVAR(sessionDoc,
Radek Krejcib03ebe42017-07-04 14:00:33 +0200516 "The NETCONF Session object.\n\n"
517 "Arguments: (host='localhost', port=830, transport=None)\n");
Radek Krejcic61f0b42017-06-07 13:21:41 +0200518
519PyTypeObject ncSessionType = {
520 PyVarObject_HEAD_INIT(NULL, 0)
521 "netconf2.Session", /* tp_name */
522 sizeof(ncSessionObject), /* tp_basicsize */
523 0, /* tp_itemsize */
524 (destructor)ncSessionFree, /* tp_dealloc */
525 0, /* tp_print */
526 0, /* tp_getattr */
527 0, /* tp_setattr */
528 0, /* tp_reserved */
529 (reprfunc)ncSessionStr, /* tp_repr */
530 0, /* tp_as_number */
531 0, /* tp_as_sequence */
532 0, /* tp_as_mapping */
533 0, /* tp_hash */
534 0, /* tp_call */
535 (reprfunc)ncSessionStr, /* tp_str */
536 0, /* tp_getattro */
537 0, /* tp_setattro */
538 0, /* tp_as_buffer */
539 Py_TPFLAGS_DEFAULT |
540 Py_TPFLAGS_BASETYPE, /* tp_flags */
541 sessionDoc, /* tp_doc */
542 0, /* tp_traverse */
543 0, /* tp_clear */
544 0, /* tp_richcompare */
545 0, /* tp_weaklistoffset */
546 0, /* tp_iter */
547 0, /* tp_iternext */
548 ncSessionMethods, /* tp_methods */
549 ncSessionMembers, /* tp_members */
550 ncSessionGetSetters, /* tp_getset */
551 0, /* tp_base */
552 0, /* tp_dict */
553 0, /* tp_descr_get */
554 0, /* tp_descr_set */
555 0, /* tp_dictoffset */
556 (initproc)ncSessionInit, /* tp_init */
557 0, /* tp_alloc */
558 ncSessionNew, /* tp_new */
559};
560