1 /*
2  * Copyright (C) 2002-2003 Fhg Fokus
3  *
4  * This file is part of SEMS, a free SIP media server.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 
21 #include "PySemsAudio.h"
22 #include "PySems.h"
23 
24 #include "AmConfigReader.h"
25 #include "AmConfig.h"
26 #include "log.h"
27 #include "AmApi.h"
28 #include "AmUtils.h"
29 #include "AmPlugIn.h"
30 
31 #include "PySemsDialog.h"
32 #include "PySemsB2BDialog.h"
33 #include "PySemsB2ABDialog.h"
34 #include "PySemsUtils.h"
35 
36 #include <sip.h>
37 #include "sip/sipAPIpy_sems_lib.h"
38 
39 // earlier than 4.7.6, include headers:
40 #if SIP_VERSION < 0x040706
41 #include "sip/sippy_sems_libPySemsDialog.h"
42 #include "sip/sippy_sems_libPySemsB2BDialog.h"
43 #include "sip/sippy_sems_libPySemsB2ABDialog.h"
44 #endif
45 
46 #if SIP_VERSION < 0x040901
47 #define SIP_USE_OLD_CLASS_CONVERSION 1
48 #endif
49 
50 #include <unistd.h>
51 #include <pthread.h>
52 #include <regex.h>
53 #include <dirent.h>
54 
55 #include <set>
56 using std::set;
57 
58 
59 #define PYFILE_REGEX "(.+)\\.(py|pyc|pyo)$"
60 
61 
62 EXPORT_SESSION_FACTORY(PySemsFactory,MOD_NAME);
63 
64 PyMODINIT_FUNC initpy_sems_lib();
65 
66 /**
67  * \brief gets python global interpreter lock (GIL)
68  *
69  * structure to acquire the python global interpreter lock (GIL)
70  * while the structure is allocated.
71  */
72 struct PythonGIL
73 {
74   PyGILState_STATE gst;
75 
PythonGILPythonGIL76   PythonGIL() { gst = PyGILState_Ensure(); }
~PythonGILPythonGIL77   ~PythonGIL(){ PyGILState_Release(gst);   }
78 };
79 
80 
81 // This must be the first declaration of every
82 // function using Python C-API.
83 // But this is not necessary in function which
84 // will get called from Python
85 #define PYLOCK PythonGIL _py_gil
86 
87 extern "C" {
88 
py_sems_log(PyObject *,PyObject * args)89   static PyObject* py_sems_log(PyObject*, PyObject* args)
90   {
91     int level;
92     char *msg;
93 
94     if(!PyArg_ParseTuple(args,"is",&level,&msg))
95       return NULL;
96 
97     _LOG(level, "%s", msg);
98 
99     Py_INCREF(Py_None);
100     return Py_None;
101   }
102 
py_sems_getHeader(PyObject *,PyObject * args)103   static PyObject* py_sems_getHeader(PyObject*, PyObject* args)
104   {
105     char* headers;
106     char* header_name;
107     if(!PyArg_ParseTuple(args,"ss",&headers,&header_name))
108       return NULL;
109 
110     string res = getHeader(headers,header_name, true);
111     return PyString_FromString(res.c_str());
112   }
113 
114 
115   static PyMethodDef py_sems_methods[] = {
116     {"log", (PyCFunction)py_sems_log, METH_VARARGS,"Log a message using Sems' logging system"},
117     {"getHeader", (PyCFunction)py_sems_getHeader, METH_VARARGS,"Python getHeader wrapper"},
118     {NULL}  /* Sentinel */
119   };
120 }
121 
PySemsFactory(const string & _app_name)122 PySemsFactory::PySemsFactory(const string& _app_name)
123   : AmSessionFactory(_app_name)
124 {
125 }
126 
127 // void PySemsFactory::setScriptPath(const string& path)
128 // {
129 //     string python_path = script_path = path;
130 
131 //     if(python_path.length()){
132 // 	add_env_path("PYTHONPATH", python_path);
133 //     }
134 
135 //     add_env_path("PYTHONPATH",AmConfig::PlugInPath);
136 // }
137 
import_object(PyObject * m,char * name,PyTypeObject * type)138 void PySemsFactory::import_object(PyObject* m, char* name, PyTypeObject* type)
139 {
140   if (PyType_Ready(type) < 0){
141     ERROR("PyType_Ready failed !\n");
142     return;
143   }
144   Py_INCREF(type);
145   PyModule_AddObject(m, name, (PyObject *)type);
146 }
147 
import_py_sems_builtins()148 void PySemsFactory::import_py_sems_builtins()
149 {
150   // py_sems module - start
151   PyImport_AddModule("py_sems");
152   py_sems_module = Py_InitModule("py_sems",py_sems_methods);
153 
154   // PySemsAudioFile
155   import_object(py_sems_module,"PySemsAudioFile",&PySemsAudioFileType);
156 
157   PyModule_AddIntConstant(py_sems_module, "AUDIO_READ",AUDIO_READ);
158   PyModule_AddIntConstant(py_sems_module, "AUDIO_WRITE",AUDIO_WRITE);
159   // py_sems module - end
160 
161   // add log level for the log module
162   PyModule_AddIntConstant(py_sems_module, "SEMS_LOG_LEVEL",log_level);
163 
164   import_module("py_sems_log");
165   initpy_sems_lib();
166 }
167 
set_sys_path(const string & script_path)168 void PySemsFactory::set_sys_path(const string& script_path)
169 {
170   PyObject* py_mod = import_module("sys");
171   if(!py_mod)	return;
172 
173   PyObject* sys_path_str = PyString_FromString("path");
174   PyObject* sys_path = PyObject_GetAttr(py_mod,sys_path_str);
175   Py_DECREF(sys_path_str);
176 
177   if(!sys_path){
178     PyErr_Print();
179     Py_DECREF(py_mod);
180     return;
181   }
182 
183   if(!PyList_Insert(sys_path,0,PyString_FromString(script_path.c_str()))){
184     PyErr_Print();
185   }
186 }
187 
import_module(const char * modname)188 PyObject* PySemsFactory::import_module(const char* modname)
189 {
190   PyObject* py_mod_name = PyString_FromString(modname);
191   PyObject* py_mod = PyImport_Import(py_mod_name);
192   Py_DECREF(py_mod_name);
193 
194   if(!py_mod){
195     PyErr_Print();
196     ERROR("PySemsFactory: could not find python module '%s'.\n",modname);
197     ERROR("PySemsFactory: please check your installation.\n");
198     return NULL;
199   }
200 
201   return py_mod;
202 }
203 
init_python_interpreter(const string & script_path)204 void PySemsFactory::init_python_interpreter(const string& script_path)
205 {
206   if(!Py_IsInitialized()){
207 
208     add_env_path("PYTHONPATH",AmConfig::PlugInPath);
209     Py_Initialize();
210   }
211 
212   PyEval_InitThreads();
213   set_sys_path(script_path);
214   import_py_sems_builtins();
215   PyEval_ReleaseLock();
216 }
217 
newDlg(const string & name)218 AmSession* PySemsFactory::newDlg(const string& name)
219 {
220   PYLOCK;
221 
222   map<string,PySemsScriptDesc>::iterator mod_it = mod_reg.find(name);
223   if(mod_it == mod_reg.end()){
224     ERROR("Unknown script name '%s'\n", name.c_str());
225     throw AmSession::Exception(500,"Unknown Application");
226   }
227 
228   PySemsScriptDesc& mod_desc = mod_it->second;
229 
230   PyObject* dlg_inst = PyObject_Call(mod_desc.dlg_class,PyTuple_New(0),NULL);
231   if(!dlg_inst){
232 
233     PyErr_Print();
234     ERROR("PySemsFactory: while loading \"%s\": could not create instance\n",
235 	  name.c_str());
236     throw AmSession::Exception(500,"Internal error in PY_SEMS plug-in.");
237 
238     return NULL;
239   }
240 
241   int err=0;
242 
243   AmSession* sess = NULL;
244   PySemsDialogBase* dlg_base  = NULL;
245 
246   switch(mod_desc.dt) {
247   case PySemsScriptDesc::None: {
248     ERROR("wrong script type: None.\n");
249   }; break;
250   case PySemsScriptDesc::Dialog: {
251     PySemsDialog* dlg = (PySemsDialog*)
252 #ifdef SIP_USE_OLD_CLASS_CONVERSION
253       sipForceConvertTo_PySemsDialog(dlg_inst,&err);
254 #else
255       sipForceConvertToType(dlg_inst, sipType_PySemsDialog, NULL, SIP_NO_CONVERTORS, NULL, &err);
256 #endif
257     sess = dlg;
258     dlg_base = dlg;
259   }; break;
260 
261   case PySemsScriptDesc::B2BDialog: {
262     PySemsB2BDialog* b2b_dlg = (PySemsB2BDialog*)
263 #ifdef SIP_USE_OLD_CLASS_CONVERSION
264       sipForceConvertTo_PySemsB2BDialog(dlg_inst,&err);
265 #else
266       sipForceConvertToType(dlg_inst, sipType_PySemsB2BDialog, NULL, SIP_NO_CONVERTORS, NULL, &err);
267 #endif
268     sess = b2b_dlg;
269     dlg_base = b2b_dlg;
270   }; break;
271 
272   case PySemsScriptDesc::B2ABDialog: {
273     PySemsB2ABDialog* b2ab_dlg = (PySemsB2ABDialog*)
274 #ifdef SIP_USE_OLD_CLASS_CONVERSION
275       sipForceConvertTo_PySemsB2ABDialog(dlg_inst,&err);
276 #else
277       sipForceConvertToType(dlg_inst, sipType_PySemsB2ABDialog, NULL, SIP_NO_CONVERTORS, NULL, &err);
278 #endif
279     sess = b2ab_dlg;
280     dlg_base = b2ab_dlg;
281 
282   }; break;
283   }
284 
285   if (err || !dlg_base) {
286     // no luck
287     PyErr_Print();
288     ERROR("PySemsFactory: while loading \"%s\": could not retrieve a PySems*Dialog ptr.\n",
289 	  name.c_str());
290     throw AmSession::Exception(500,"Internal error in PY_SEMS plug-in.");
291     Py_DECREF(dlg_inst);
292     return NULL;
293   }
294 
295 
296   // take the ownership over dlg
297   sipTransferTo(dlg_inst,dlg_inst);
298   Py_DECREF(dlg_inst);
299   dlg_base->setPyPtrs(NULL,dlg_inst);
300   return sess;
301 }
302 
loadScript(const string & path)303 bool PySemsFactory::loadScript(const string& path)
304 {
305   PYLOCK;
306 
307   PyObject *modName,*mod,*dict, *dlg_class, *config=NULL;
308   PySemsScriptDesc::DialogType dt = PySemsScriptDesc::None;
309 
310 
311   modName = PyString_FromString(path.c_str());
312   mod     = PyImport_Import(modName);
313 
314   AmConfigReader cfg;
315   string cfg_file = add2path(AmConfig::ModConfigPath,1,(path + ".conf").c_str());
316 
317   Py_DECREF(modName);
318 
319   if(!mod){
320     PyErr_Print();
321     WARN("PySemsFactory: Failed to load \"%s\"\n", path.c_str());
322 
323     dict = PyImport_GetModuleDict();
324     Py_INCREF(dict);
325     PyDict_DelItemString(dict,path.c_str());
326     Py_DECREF(dict);
327 
328     return false;
329   }
330 
331   dict = PyModule_GetDict(mod);
332   dlg_class = PyDict_GetItemString(dict, "PySemsScript");
333 
334   if(!dlg_class){
335 
336     PyErr_Print();
337     WARN("PySemsFactory: class PySemsDialog not found in \"%s\"\n", path.c_str());
338     goto error1;
339   }
340 
341   Py_INCREF(dlg_class);
342 
343   if(PyObject_IsSubclass(dlg_class,(PyObject *)sipClass_PySemsDialog)) {
344     dt = PySemsScriptDesc::Dialog;
345     DBG("Loaded a Dialog Script.\n");
346   } else if (PyObject_IsSubclass(dlg_class,(PyObject *)sipClass_PySemsB2BDialog)) {
347     DBG("Loaded a B2BDialog Script.\n");
348     dt = PySemsScriptDesc::B2BDialog;
349   } else if (PyObject_IsSubclass(dlg_class,(PyObject *)sipClass_PySemsB2ABDialog)) {
350     DBG("Loaded a B2ABDialog Script.\n");
351     dt = PySemsScriptDesc::B2ABDialog;
352   } else {
353     WARN("PySemsFactory: in \"%s\": PySemsScript is not a "
354 	 "subtype of PySemsDialog\n", path.c_str());
355 
356     goto error2;
357   }
358 
359   if(cfg.loadFile(cfg_file)){
360     ERROR("could not load config file at %s\n",cfg_file.c_str());
361     goto error2;
362   }
363 
364   config = PyDict_New();
365   if(!config){
366     ERROR("could not allocate new dict for config\n");
367     goto error2;
368   }
369 
370   for(map<string,string>::const_iterator it = cfg.begin();
371       it != cfg.end(); it++){
372 
373     PyDict_SetItem(config,
374 		   PyString_FromString(it->first.c_str()),
375 		   PyString_FromString(it->second.c_str()));
376   }
377 
378   PyObject_SetAttrString(mod,"config",config);
379 
380   mod_reg.insert(std::make_pair(path,
381 			        PySemsScriptDesc(mod,dlg_class, dt)));
382 
383   return true;
384 
385  error2:
386   Py_DECREF(dlg_class);
387  error1:
388   Py_DECREF(mod);
389 
390   return false;
391 }
392 
393 /**
394  * Loads python script path and default script file from configuration file
395  */
onLoad()396 int PySemsFactory::onLoad()
397 {
398   AmConfigReader cfg;
399 
400   if(cfg.loadFile(add2path(AmConfig::ModConfigPath,1,MOD_NAME ".conf")))
401     return -1;
402 
403   // get application specific global parameters
404   configureModule(cfg);
405 
406   string script_path = cfg.getParameter("script_path");
407   init_python_interpreter(script_path);
408 
409 #ifdef PY_SEMS_WITH_TTS
410   DBG("** PY_SEMS Text-To-Speech enabled\n");
411 #else
412   DBG("** PY_SEMS Text-To-Speech disabled\n");
413 #endif
414 
415   DBG("** PY_SEMS script path: \'%s\'\n", script_path.c_str());
416 
417   regex_t reg;
418   if(regcomp(&reg,PYFILE_REGEX,REG_EXTENDED)){
419     ERROR("while compiling regular expression\n");
420     return -1;
421   }
422 
423   DIR* dir = opendir(script_path.c_str());
424   if(!dir){
425     regfree(&reg);
426     ERROR("PySems: script pre-loader (%s): %s\n",
427 	  script_path.c_str(),strerror(errno));
428     return -1;
429   }
430 
431   DBG("directory '%s' opened\n",script_path.c_str());
432 
433   set<string> unique_entries;
434   regmatch_t  pmatch[2];
435 
436   struct dirent* entry=0;
437   while((entry = readdir(dir)) != NULL){
438 
439     if(!regexec(&reg,entry->d_name,2,pmatch,0)){
440 
441       string name(entry->d_name + pmatch[1].rm_so,
442 		  pmatch[1].rm_eo - pmatch[1].rm_so);
443 
444       unique_entries.insert(name);
445     }
446   }
447   closedir(dir);
448   regfree(&reg);
449 
450   AmPlugIn* plugin = AmPlugIn::instance();
451   for(set<string>::iterator it = unique_entries.begin();
452       it != unique_entries.end(); it++) {
453 
454     if(loadScript(*it)){
455       bool res = plugin->registerFactory4App(*it,this);
456       if(res)
457 	INFO("Application script registered: %s.\n",
458 	     it->c_str());
459     }
460   }
461 
462   return 0; // don't stop sems from starting up
463 }
464 
465 /**
466  * Load a script using user name from URI.
467  * Note: there is no default script.
468  */
onInvite(const AmSipRequest & req)469 AmSession* PySemsFactory::onInvite(const AmSipRequest& req)
470 {
471   if(req.cmd != MOD_NAME)
472     return newDlg(req.cmd);
473   else
474     return newDlg(req.user);
475 }
476 
477 // PySemsDialogBase - base class for all possible PySemsDialog classes
PySemsDialogBase()478 PySemsDialogBase::PySemsDialogBase()
479   :  py_mod(NULL),
480      py_dlg(NULL)
481 {
482 }
483 
~PySemsDialogBase()484 PySemsDialogBase::~PySemsDialogBase() {
485   PYLOCK;
486   Py_XDECREF(py_dlg);
487 }
488 
setPyPtrs(PyObject * mod,PyObject * dlg)489 void PySemsDialogBase::setPyPtrs(PyObject *mod, PyObject *dlg)
490 {
491   PYLOCK;
492   Py_XDECREF(py_dlg);
493   py_dlg = dlg;
494 }
495 
callPyEventHandler(char * name,char * fmt,...)496 bool PySemsDialogBase::callPyEventHandler(char* name, char* fmt, ...)
497 {
498   bool ret=false;
499   va_list va;
500 
501   PYLOCK;
502 
503   va_start(va, fmt);
504   PyObject* o = PyObject_VaCallMethod(py_dlg,name,fmt,va);
505   va_end(va);
506 
507   if(!o) {
508 
509     if(PyErr_ExceptionMatches(PyExc_AttributeError)){
510 
511       DBG("method %s is not implemented, trying default one\n",name);
512       return true;
513     }
514 
515     PyErr_Print();
516   }
517   else {
518     if(o && PyBool_Check(o) && (o == Py_True)) {
519 
520       ret = true;
521     }
522 
523     Py_DECREF(o);
524   }
525 
526   return ret;
527 }
528