1/* $Id: miniupnpcmodule.c,v 1.25 2014/09/06 08:08:25 nanard Exp $*/
2/* Project : miniupnp
3 * Author : Thomas BERNARD
4 * website : http://miniupnp.tuxfamily.org/
5 * copyright (c) 2007-2014 Thomas Bernard
6 * This software is subjet to the conditions detailed in the
7 * provided LICENCE file. */
8#include <Python.h>
9#define MINIUPNP_STATICLIB
10#include "structmember.h"
11#include "miniupnpc.h"
12#include "upnpcommands.h"
13#include "upnperrors.h"
14
15/* for compatibility with Python < 2.4 */
16#ifndef Py_RETURN_NONE
17#define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
18#endif
19
20#ifndef Py_RETURN_TRUE
21#define Py_RETURN_TRUE return Py_INCREF(Py_True), Py_True
22#endif
23
24#ifndef Py_RETURN_FALSE
25#define Py_RETURN_FALSE return Py_INCREF(Py_False), Py_False
26#endif
27
28/* for compatibility with Python < 3.0 */
29#ifndef PyVarObject_HEAD_INIT
30#define PyVarObject_HEAD_INIT(type, size) \
31    PyObject_HEAD_INIT(type) size,
32#endif
33
34#ifndef Py_TYPE
35#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
36#endif
37
38typedef struct {
39    PyObject_HEAD
40    /* Type-specific fields go here. */
41	struct UPNPDev * devlist;
42	struct UPNPUrls urls;
43	struct IGDdatas data;
44	unsigned int discoverdelay;	/* value passed to upnpDiscover() */
45	char lanaddr[40];	/* our ip address on the LAN */
46	char * multicastif;
47	char * minissdpdsocket;
48} UPnPObject;
49
50static PyMemberDef UPnP_members[] = {
51	{"lanaddr", T_STRING_INPLACE, offsetof(UPnPObject, lanaddr),
52	 READONLY, "ip address on the LAN"
53	},
54	{"discoverdelay", T_UINT, offsetof(UPnPObject, discoverdelay),
55	 0/*READWRITE*/, "value in ms used to wait for SSDP responses"
56	},
57	/* T_STRING is allways readonly :( */
58	{"multicastif", T_STRING, offsetof(UPnPObject, multicastif),
59	 0, "IP of the network interface to be used for multicast operations"
60	},
61	{"minissdpdsocket", T_STRING, offsetof(UPnPObject, multicastif),
62	 0, "path of the MiniSSDPd unix socket"
63	},
64	{NULL}
65};
66
67static void
68UPnPObject_dealloc(UPnPObject *self)
69{
70	freeUPNPDevlist(self->devlist);
71	FreeUPNPUrls(&self->urls);
72	Py_TYPE(self)->tp_free((PyObject*)self);
73}
74
75static PyObject *
76UPnP_discover(UPnPObject *self)
77{
78	struct UPNPDev * dev;
79	int i;
80	PyObject *res = NULL;
81	if(self->devlist)
82	{
83		freeUPNPDevlist(self->devlist);
84		self->devlist = 0;
85	}
86	Py_BEGIN_ALLOW_THREADS
87	self->devlist = upnpDiscover((int)self->discoverdelay/*timeout in ms*/,
88	                             0/* multicast if*/,
89	                             0/*minissdpd socket*/,
90								 0/*sameport flag*/,
91	                             0/*ip v6*/,
92	                             0/*error */);
93	Py_END_ALLOW_THREADS
94	/* Py_RETURN_NONE ??? */
95	for(dev = self->devlist, i = 0; dev; dev = dev->pNext)
96		i++;
97	res = Py_BuildValue("i", i);
98	return res;
99}
100
101static PyObject *
102UPnP_selectigd(UPnPObject *self)
103{
104	int r;
105Py_BEGIN_ALLOW_THREADS
106	r = UPNP_GetValidIGD(self->devlist, &self->urls, &self->data,
107	                     self->lanaddr, sizeof(self->lanaddr));
108Py_END_ALLOW_THREADS
109	if(r)
110	{
111		return Py_BuildValue("s", self->urls.controlURL);
112	}
113	else
114	{
115		/* TODO: have our own exception type ! */
116		PyErr_SetString(PyExc_Exception, "No UPnP device discovered");
117		return NULL;
118	}
119}
120
121static PyObject *
122UPnP_totalbytesent(UPnPObject *self)
123{
124	UNSIGNED_INTEGER i;
125Py_BEGIN_ALLOW_THREADS
126	i = UPNP_GetTotalBytesSent(self->urls.controlURL_CIF,
127	                           self->data.CIF.servicetype);
128Py_END_ALLOW_THREADS
129	return Py_BuildValue("I", i);
130}
131
132static PyObject *
133UPnP_totalbytereceived(UPnPObject *self)
134{
135	UNSIGNED_INTEGER i;
136Py_BEGIN_ALLOW_THREADS
137	i = UPNP_GetTotalBytesReceived(self->urls.controlURL_CIF,
138		                           self->data.CIF.servicetype);
139Py_END_ALLOW_THREADS
140	return Py_BuildValue("I", i);
141}
142
143static PyObject *
144UPnP_totalpacketsent(UPnPObject *self)
145{
146	UNSIGNED_INTEGER i;
147Py_BEGIN_ALLOW_THREADS
148	i = UPNP_GetTotalPacketsSent(self->urls.controlURL_CIF,
149		                         self->data.CIF.servicetype);
150Py_END_ALLOW_THREADS
151	return Py_BuildValue("I", i);
152}
153
154static PyObject *
155UPnP_totalpacketreceived(UPnPObject *self)
156{
157	UNSIGNED_INTEGER i;
158Py_BEGIN_ALLOW_THREADS
159	i = UPNP_GetTotalPacketsReceived(self->urls.controlURL_CIF,
160		                          self->data.CIF.servicetype);
161Py_END_ALLOW_THREADS
162	return Py_BuildValue("I", i);
163}
164
165static PyObject *
166UPnP_statusinfo(UPnPObject *self)
167{
168	char status[64];
169	char lastconnerror[64];
170	unsigned int uptime = 0;
171	int r;
172	status[0] = '\0';
173	lastconnerror[0] = '\0';
174Py_BEGIN_ALLOW_THREADS
175	r = UPNP_GetStatusInfo(self->urls.controlURL, self->data.first.servicetype,
176	                   status, &uptime, lastconnerror);
177Py_END_ALLOW_THREADS
178	if(r==UPNPCOMMAND_SUCCESS) {
179		return Py_BuildValue("(s,I,s)", status, uptime, lastconnerror);
180	} else {
181		/* TODO: have our own exception type ! */
182		PyErr_SetString(PyExc_Exception, strupnperror(r));
183		return NULL;
184	}
185}
186
187static PyObject *
188UPnP_connectiontype(UPnPObject *self)
189{
190	char connectionType[64];
191	int r;
192	connectionType[0] = '\0';
193Py_BEGIN_ALLOW_THREADS
194	r = UPNP_GetConnectionTypeInfo(self->urls.controlURL,
195	                               self->data.first.servicetype,
196	                               connectionType);
197Py_END_ALLOW_THREADS
198	if(r==UPNPCOMMAND_SUCCESS) {
199		return Py_BuildValue("s", connectionType);
200	} else {
201		/* TODO: have our own exception type ! */
202		PyErr_SetString(PyExc_Exception, strupnperror(r));
203		return NULL;
204	}
205}
206
207static PyObject *
208UPnP_externalipaddress(UPnPObject *self)
209{
210	char externalIPAddress[40];
211	int r;
212	externalIPAddress[0] = '\0';
213Py_BEGIN_ALLOW_THREADS
214	r = UPNP_GetExternalIPAddress(self->urls.controlURL,
215	                              self->data.first.servicetype,
216	                              externalIPAddress);
217Py_END_ALLOW_THREADS
218	if(r==UPNPCOMMAND_SUCCESS) {
219		return Py_BuildValue("s", externalIPAddress);
220	} else {
221		/* TODO: have our own exception type ! */
222		PyErr_SetString(PyExc_Exception, strupnperror(r));
223		return NULL;
224	}
225}
226
227/* AddPortMapping(externalPort, protocol, internalHost, internalPort, desc,
228 *                remoteHost)
229 * protocol is 'UDP' or 'TCP' */
230static PyObject *
231UPnP_addportmapping(UPnPObject *self, PyObject *args)
232{
233	char extPort[6];
234	unsigned short ePort;
235	char inPort[6];
236	unsigned short iPort;
237	const char * proto;
238	const char * host;
239	const char * desc;
240	const char * remoteHost;
241	const char * leaseDuration = "0";
242	int r;
243	if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto,
244	                                     &host, &iPort, &desc, &remoteHost))
245        return NULL;
246Py_BEGIN_ALLOW_THREADS
247	sprintf(extPort, "%hu", ePort);
248	sprintf(inPort, "%hu", iPort);
249	r = UPNP_AddPortMapping(self->urls.controlURL, self->data.first.servicetype,
250	                        extPort, inPort, host, desc, proto,
251	                        remoteHost, leaseDuration);
252Py_END_ALLOW_THREADS
253	if(r==UPNPCOMMAND_SUCCESS)
254	{
255		Py_RETURN_TRUE;
256	}
257	else
258	{
259		// TODO: RAISE an Exception. See upnpcommands.h for errors codes.
260		// upnperrors.c
261		//Py_RETURN_FALSE;
262		/* TODO: have our own exception type ! */
263		PyErr_SetString(PyExc_Exception, strupnperror(r));
264		return NULL;
265	}
266}
267
268/* AddAnyPortMapping(externalPort, protocol, internalHost, internalPort, desc,
269 *                   remoteHost)
270 * protocol is 'UDP' or 'TCP' */
271static PyObject *
272UPnP_addanyportmapping(UPnPObject *self, PyObject *args)
273{
274	char extPort[6];
275	unsigned short ePort;
276	char inPort[6];
277	unsigned short iPort;
278	char reservedPort[6];
279	const char * proto;
280	const char * host;
281	const char * desc;
282	const char * remoteHost;
283	const char * leaseDuration = "0";
284	int r;
285	if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto, &host, &iPort, &desc, &remoteHost))
286        return NULL;
287Py_BEGIN_ALLOW_THREADS
288	sprintf(extPort, "%hu", ePort);
289	sprintf(inPort, "%hu", iPort);
290	r = UPNP_AddAnyPortMapping(self->urls.controlURL, self->data.first.servicetype,
291	                           extPort, inPort, host, desc, proto,
292	                           remoteHost, leaseDuration, reservedPort);
293Py_END_ALLOW_THREADS
294	if(r==UPNPCOMMAND_SUCCESS) {
295		return Py_BuildValue("i", atoi(reservedPort));
296	} else {
297		/* TODO: have our own exception type ! */
298		PyErr_SetString(PyExc_Exception, strupnperror(r));
299		return NULL;
300	}
301}
302
303
304/* DeletePortMapping(extPort, proto, removeHost='')
305 * proto = 'UDP', 'TCP' */
306static PyObject *
307UPnP_deleteportmapping(UPnPObject *self, PyObject *args)
308{
309	char extPort[6];
310	unsigned short ePort;
311	const char * proto;
312	const char * remoteHost = "";
313	int r;
314	if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost))
315		return NULL;
316Py_BEGIN_ALLOW_THREADS
317	sprintf(extPort, "%hu", ePort);
318	r = UPNP_DeletePortMapping(self->urls.controlURL, self->data.first.servicetype,
319	                           extPort, proto, remoteHost);
320Py_END_ALLOW_THREADS
321	if(r==UPNPCOMMAND_SUCCESS) {
322		Py_RETURN_TRUE;
323	} else {
324		/* TODO: have our own exception type ! */
325		PyErr_SetString(PyExc_Exception, strupnperror(r));
326		return NULL;
327	}
328}
329
330/* DeletePortMappingRange(extPort, proto, removeHost='')
331 * proto = 'UDP', 'TCP' */
332static PyObject *
333UPnP_deleteportmappingrange(UPnPObject *self, PyObject *args)
334{
335	char extPortStart[6];
336	unsigned short ePortStart;
337	char extPortEnd[6];
338	unsigned short ePortEnd;
339	const char * proto;
340	unsigned char manage;
341	char manageStr[1];
342	int r;
343	if(!PyArg_ParseTuple(args, "HHsb", &ePortStart, &ePortEnd, &proto, &manage))
344		return NULL;
345Py_BEGIN_ALLOW_THREADS
346	sprintf(extPortStart, "%hu", ePortStart);
347	sprintf(extPortEnd, "%hu", ePortEnd);
348	sprintf(manageStr, "%hhu", manage);
349	r = UPNP_DeletePortMappingRange(self->urls.controlURL, self->data.first.servicetype,
350					extPortStart, extPortEnd, proto, manageStr);
351Py_END_ALLOW_THREADS
352	if(r==UPNPCOMMAND_SUCCESS) {
353		Py_RETURN_TRUE;
354	} else {
355		/* TODO: have our own exception type ! */
356		PyErr_SetString(PyExc_Exception, strupnperror(r));
357		return NULL;
358	}
359}
360
361static PyObject *
362UPnP_getportmappingnumberofentries(UPnPObject *self)
363{
364	unsigned int n = 0;
365	int r;
366Py_BEGIN_ALLOW_THREADS
367	r = UPNP_GetPortMappingNumberOfEntries(self->urls.controlURL,
368	                                   self->data.first.servicetype,
369									   &n);
370Py_END_ALLOW_THREADS
371	if(r==UPNPCOMMAND_SUCCESS) {
372		return Py_BuildValue("I", n);
373	} else {
374		/* TODO: have our own exception type ! */
375		PyErr_SetString(PyExc_Exception, strupnperror(r));
376		return NULL;
377	}
378}
379
380/* GetSpecificPortMapping(ePort, proto, remoteHost='')
381 * proto = 'UDP' or 'TCP' */
382static PyObject *
383UPnP_getspecificportmapping(UPnPObject *self, PyObject *args)
384{
385	char extPort[6];
386	unsigned short ePort;
387	const char * proto;
388	const char * remoteHost = "";
389	char intClient[40];
390	char intPort[6];
391	unsigned short iPort;
392	char desc[80];
393	char enabled[4];
394	char leaseDuration[16];
395	if(!PyArg_ParseTuple(args, "Hs|z", &ePort, &proto, &remoteHost))
396		return NULL;
397	extPort[0] = '\0'; intClient[0] = '\0'; intPort[0] = '\0';
398	desc[0] = '\0'; enabled[0] = '\0'; leaseDuration[0] = '\0';
399Py_BEGIN_ALLOW_THREADS
400	sprintf(extPort, "%hu", ePort);
401	UPNP_GetSpecificPortMappingEntry(self->urls.controlURL,
402	                                 self->data.first.servicetype,
403									 extPort, proto, remoteHost,
404									 intClient, intPort,
405	                                 desc, enabled, leaseDuration);
406Py_END_ALLOW_THREADS
407	if(intClient[0])
408	{
409		iPort = (unsigned short)atoi(intPort);
410		return Py_BuildValue("(s,H,s,O,i)",
411		                     intClient, iPort, desc,
412		                     PyBool_FromLong(atoi(enabled)),
413		                     atoi(leaseDuration));
414	}
415	else
416	{
417		Py_RETURN_NONE;
418	}
419}
420
421/* GetGenericPortMapping(index) */
422static PyObject *
423UPnP_getgenericportmapping(UPnPObject *self, PyObject *args)
424{
425	int i, r;
426	char index[8];
427	char intClient[40];
428	char intPort[6];
429	unsigned short iPort;
430	char extPort[6];
431	unsigned short ePort;
432	char protocol[4];
433	char desc[80];
434	char enabled[6];
435	char rHost[64];
436	char duration[16];	/* lease duration */
437	unsigned int dur;
438	if(!PyArg_ParseTuple(args, "i", &i))
439		return NULL;
440Py_BEGIN_ALLOW_THREADS
441	snprintf(index, sizeof(index), "%d", i);
442	rHost[0] = '\0'; enabled[0] = '\0';
443	duration[0] = '\0'; desc[0] = '\0';
444	extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0';
445	r = UPNP_GetGenericPortMappingEntry(self->urls.controlURL,
446	                                    self->data.first.servicetype,
447										index,
448										extPort, intClient, intPort,
449										protocol, desc, enabled, rHost,
450										duration);
451Py_END_ALLOW_THREADS
452	if(r==UPNPCOMMAND_SUCCESS)
453	{
454		ePort = (unsigned short)atoi(extPort);
455		iPort = (unsigned short)atoi(intPort);
456		dur = (unsigned int)strtoul(duration, 0, 0);
457		return Py_BuildValue("(H,s,(s,H),s,s,s,I)",
458		                     ePort, protocol, intClient, iPort,
459		                     desc, enabled, rHost, dur);
460	}
461	else
462	{
463		Py_RETURN_NONE;
464	}
465}
466
467/* miniupnpc.UPnP object Method Table */
468static PyMethodDef UPnP_methods[] = {
469    {"discover", (PyCFunction)UPnP_discover, METH_NOARGS,
470     "discover UPnP IGD devices on the network"
471    },
472	{"selectigd", (PyCFunction)UPnP_selectigd, METH_NOARGS,
473	 "select a valid UPnP IGD among discovered devices"
474	},
475	{"totalbytesent", (PyCFunction)UPnP_totalbytesent, METH_NOARGS,
476	 "return the total number of bytes sent by UPnP IGD"
477	},
478	{"totalbytereceived", (PyCFunction)UPnP_totalbytereceived, METH_NOARGS,
479	 "return the total number of bytes received by UPnP IGD"
480	},
481	{"totalpacketsent", (PyCFunction)UPnP_totalpacketsent, METH_NOARGS,
482	 "return the total number of packets sent by UPnP IGD"
483	},
484	{"totalpacketreceived", (PyCFunction)UPnP_totalpacketreceived, METH_NOARGS,
485	 "return the total number of packets received by UPnP IGD"
486	},
487	{"statusinfo", (PyCFunction)UPnP_statusinfo, METH_NOARGS,
488	 "return status and uptime"
489	},
490	{"connectiontype", (PyCFunction)UPnP_connectiontype, METH_NOARGS,
491	 "return IGD WAN connection type"
492	},
493	{"externalipaddress", (PyCFunction)UPnP_externalipaddress, METH_NOARGS,
494	 "return external IP address"
495	},
496	{"addportmapping", (PyCFunction)UPnP_addportmapping, METH_VARARGS,
497	 "add a port mapping"
498	},
499	{"addanyportmapping", (PyCFunction)UPnP_addanyportmapping, METH_VARARGS,
500	 "add a port mapping, IGD to select alternative if necessary"
501	},
502	{"deleteportmapping", (PyCFunction)UPnP_deleteportmapping, METH_VARARGS,
503	 "delete a port mapping"
504	},
505	{"deleteportmappingrange", (PyCFunction)UPnP_deleteportmappingrange, METH_VARARGS,
506	 "delete a range of port mappings"
507	},
508	{"getportmappingnumberofentries", (PyCFunction)UPnP_getportmappingnumberofentries, METH_NOARGS,
509	 "-- non standard --"
510	},
511	{"getspecificportmapping", (PyCFunction)UPnP_getspecificportmapping, METH_VARARGS,
512	 "get details about a specific port mapping entry"
513	},
514	{"getgenericportmapping", (PyCFunction)UPnP_getgenericportmapping, METH_VARARGS,
515	 "get all details about the port mapping at index"
516	},
517    {NULL}  /* Sentinel */
518};
519
520static PyTypeObject UPnPType = {
521    PyVarObject_HEAD_INIT(NULL,
522    0)                         /*ob_size*/
523    "miniupnpc.UPnP",          /*tp_name*/
524    sizeof(UPnPObject),        /*tp_basicsize*/
525    0,                         /*tp_itemsize*/
526    (destructor)UPnPObject_dealloc,/*tp_dealloc*/
527    0,                         /*tp_print*/
528    0,                         /*tp_getattr*/
529    0,                         /*tp_setattr*/
530    0,                         /*tp_compare*/
531    0,                         /*tp_repr*/
532    0,                         /*tp_as_number*/
533    0,                         /*tp_as_sequence*/
534    0,                         /*tp_as_mapping*/
535    0,                         /*tp_hash */
536    0,                         /*tp_call*/
537    0,                         /*tp_str*/
538    0,                         /*tp_getattro*/
539    0,                         /*tp_setattro*/
540    0,                         /*tp_as_buffer*/
541    Py_TPFLAGS_DEFAULT,        /*tp_flags*/
542    "UPnP objects",            /* tp_doc */
543    0,		                   /* tp_traverse */
544    0,		                   /* tp_clear */
545    0,		                   /* tp_richcompare */
546    0,		                   /* tp_weaklistoffset */
547    0,		                   /* tp_iter */
548    0,		                   /* tp_iternext */
549    UPnP_methods,              /* tp_methods */
550    UPnP_members,              /* tp_members */
551    0,                         /* tp_getset */
552    0,                         /* tp_base */
553    0,                         /* tp_dict */
554    0,                         /* tp_descr_get */
555    0,                         /* tp_descr_set */
556    0,                         /* tp_dictoffset */
557    0,/*(initproc)UPnP_init,*/      /* tp_init */
558    0,                         /* tp_alloc */
559#ifndef _WIN32
560    PyType_GenericNew,/*UPnP_new,*/      /* tp_new */
561#else
562    0,
563#endif
564};
565
566/* module methods */
567static PyMethodDef miniupnpc_methods[] = {
568    {NULL}  /* Sentinel */
569};
570
571#if PY_MAJOR_VERSION >= 3
572static struct PyModuleDef moduledef = {
573    PyModuleDef_HEAD_INIT,
574    "miniupnpc",     /* m_name */
575    "miniupnpc module.",  /* m_doc */
576    -1,                  /* m_size */
577    miniupnpc_methods,    /* m_methods */
578    NULL,                /* m_reload */
579    NULL,                /* m_traverse */
580    NULL,                /* m_clear */
581    NULL,                /* m_free */
582};
583#endif
584
585#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
586#define PyMODINIT_FUNC void
587#endif
588
589PyMODINIT_FUNC
590#if PY_MAJOR_VERSION >= 3
591PyInit_miniupnpc(void)
592#else
593initminiupnpc(void)
594#endif
595{
596    PyObject* m;
597
598#ifdef _WIN32
599    UPnPType.tp_new = PyType_GenericNew;
600#endif
601    if (PyType_Ready(&UPnPType) < 0)
602#if PY_MAJOR_VERSION >= 3
603        return 0;
604#else
605        return;
606#endif
607
608#if PY_MAJOR_VERSION >= 3
609    m = PyModule_Create(&moduledef);
610#else
611    m = Py_InitModule3("miniupnpc", miniupnpc_methods,
612                       "miniupnpc module.");
613#endif
614
615    Py_INCREF(&UPnPType);
616    PyModule_AddObject(m, "UPnP", (PyObject *)&UPnPType);
617
618#if PY_MAJOR_VERSION >= 3
619    return m;
620#endif
621}
622
623