PyMapiPlugin.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <Python.h>
  18. #include <kopano/platform.h>
  19. #include <memory>
  20. #include <new>
  21. #include <mapi.h>
  22. #include <mapix.h>
  23. #include <mapiutil.h>
  24. #include <mapidefs.h>
  25. #include <kopano/mapiext.h>
  26. #include <mapiguid.h>
  27. #include "PythonSWIGRuntime.h"
  28. #include "PyMapiPlugin.h"
  29. #include <kopano/stringutil.h>
  30. #include "frameobject.h"
  31. #define NEW_SWIG_INTERFACE_POINTER_OBJ(pyswigobj, objpointer, typeobj) {\
  32. if (objpointer) {\
  33. pyswigobj.reset(SWIG_NewPointerObj((void *)objpointer, typeobj, SWIG_POINTER_OWN | 0)); \
  34. PY_HANDLE_ERROR(m_lpLogger, pyswigobj) \
  35. \
  36. objpointer->AddRef();\
  37. } else {\
  38. pyswigobj.reset(Py_None); \
  39. Py_INCREF(Py_None);\
  40. }\
  41. }
  42. #define BUILD_SWIG_TYPE(pyswigobj, type) {\
  43. pyswigobj = SWIG_TypeQuery(type); \
  44. if (!pyswigobj) {\
  45. assert(false);\
  46. return S_FALSE;\
  47. }\
  48. }
  49. class kcpy_decref {
  50. public:
  51. void operator()(PyObject *obj) { Py_DECREF(obj); }
  52. };
  53. typedef KCHL::memory_ptr<PyObject, kcpy_decref> PyObjectAPtr;
  54. class PyMapiPlugin _kc_final : public pym_plugin_intf {
  55. public:
  56. PyMapiPlugin(void) = default;
  57. virtual ~PyMapiPlugin(void);
  58. HRESULT Init(ECLogger *lpLogger, PyObject *lpModMapiPlugin, const char* lpPluginManagerClassName, const char *lpPluginPath);
  59. virtual HRESULT MessageProcessing(const char *func, IMAPISession *, IAddrBook *, IMsgStore *, IMAPIFolder *, IMessage *, ULONG *result);
  60. virtual HRESULT RulesProcessing(const char *func, IMAPISession *, IAddrBook *, IMsgStore *, IExchangeModifyTable *emt_rules, ULONG *result);
  61. virtual HRESULT RequestCallExecution(const char *func, IMAPISession *, IAddrBook *, IMsgStore *, IMAPIFolder *, IMessage *, ULONG *do_callexe, ULONG *result);
  62. swig_type_info *type_p_ECLogger = nullptr, *type_p_IAddrBook = nullptr;
  63. swig_type_info *type_p_IMAPIFolder = nullptr;
  64. swig_type_info *type_p_IMAPISession = nullptr;
  65. swig_type_info *type_p_IMsgStore = nullptr;
  66. swig_type_info *type_p_IMessage = nullptr;
  67. swig_type_info *type_p_IExchangeModifyTable = nullptr;
  68. private:
  69. PyObjectAPtr m_ptrMapiPluginManager{nullptr};
  70. ECLogger *m_lpLogger = nullptr;
  71. /* Inhibit (accidental) copying */
  72. PyMapiPlugin(const PyMapiPlugin &) = delete;
  73. PyMapiPlugin &operator=(const PyMapiPlugin &) = delete;
  74. };
  75. struct pym_factory_priv {
  76. PyObjectAPtr m_ptrModMapiPlugin{nullptr};
  77. };
  78. /**
  79. * Handle the python errors
  80. *
  81. * note: The traceback doesn't work very well
  82. */
  83. static HRESULT PyHandleError(ECLogger *lpLogger, PyObject *pyobj)
  84. {
  85. HRESULT hr = hrSuccess;
  86. if (!pyobj)
  87. {
  88. PyObject *lpErr = PyErr_Occurred();
  89. if(lpErr) {
  90. PyObjectAPtr ptype, pvalue, ptraceback;
  91. PyErr_Fetch(&~ptype, &~pvalue, &~ptraceback);
  92. auto traceback = reinterpret_cast<PyTracebackObject *>(ptraceback.get());
  93. const char *pStrErrorMessage = "Unknown";
  94. const char *pStrType = "Unknown";
  95. if (pvalue != nullptr)
  96. pStrErrorMessage = PyString_AsString(pvalue.get());
  97. if (ptype != nullptr)
  98. pStrType = PyString_AsString(ptype.get());
  99. if (lpLogger)
  100. {
  101. lpLogger->Log(EC_LOGLEVEL_ERROR, " Python type: %s", pStrType);
  102. lpLogger->Log(EC_LOGLEVEL_ERROR, " Python error: %s", pStrErrorMessage);
  103. while (traceback && traceback->tb_next != NULL) {
  104. auto frame = traceback->tb_frame;
  105. if (frame) {
  106. int line = frame->f_lineno;
  107. const char *filename = PyString_AsString(frame->f_code->co_filename);
  108. const char *funcname = PyString_AsString(frame->f_code->co_name);
  109. lpLogger->Log(EC_LOGLEVEL_ERROR, " Python trace: %s(%d) %s", filename, line, funcname);
  110. } else {
  111. lpLogger->Log(EC_LOGLEVEL_ERROR, " Python trace: Unknown");
  112. }
  113. traceback = traceback->tb_next;
  114. }
  115. }
  116. PyErr_Clear();
  117. }
  118. assert(false);
  119. hr = S_FALSE;
  120. }
  121. return hr;
  122. }
  123. #define PY_HANDLE_ERROR(logger, pyobj) { \
  124. hr = PyHandleError(logger, pyobj);\
  125. if (hr != hrSuccess) \
  126. return hr; \
  127. }
  128. #define PY_CALL_METHOD(pluginmanager, functionname, returnmacro, format, ...) {\
  129. PyObjectAPtr ptrResult;\
  130. {\
  131. ptrResult.reset(PyObject_CallMethod(pluginmanager, const_cast<char *>(functionname), const_cast<char *>(format), __VA_ARGS__)); \
  132. PY_HANDLE_ERROR(m_lpLogger, ptrResult)\
  133. \
  134. returnmacro\
  135. }\
  136. }
  137. /**
  138. * Helper macro to parse the python return values which work together
  139. * with the macro PY_CALL_METHOD.
  140. *
  141. */
  142. #define PY_PARSE_TUPLE_HELPER(format, ...) {\
  143. if(!PyArg_ParseTuple(ptrResult, format, __VA_ARGS__)) { \
  144. m_lpLogger->Log(EC_LOGLEVEL_ERROR, " Wrong return value from the pluginmanager or plugin"); \
  145. PY_HANDLE_ERROR(m_lpLogger, NULL) \
  146. } \
  147. }
  148. PyMapiPlugin::~PyMapiPlugin(void)
  149. {
  150. if (m_lpLogger != nullptr)
  151. m_lpLogger->Release();
  152. }
  153. /**
  154. * Initialize the PyMapiPlugin.
  155. *
  156. * @param[in] lpConfig Pointer to the configuration class
  157. * @param[in] lpLogger Pointer to the logger
  158. * @param[in] lpPluginManagerClassName The class name of the plugin handler
  159. *
  160. * @return Standard mapi errorcodes
  161. */
  162. HRESULT PyMapiPlugin::Init(ECLogger *lpLogger, PyObject *lpModMapiPlugin, const char* lpPluginManagerClassName, const char *lpPluginPath)
  163. {
  164. HRESULT hr = S_OK;
  165. PyObjectAPtr ptrPyLogger;
  166. PyObjectAPtr ptrClass;
  167. PyObjectAPtr ptrArgs;
  168. std::string strEnvPython;
  169. if (!lpModMapiPlugin)
  170. return S_OK;
  171. m_lpLogger = lpLogger;
  172. if (m_lpLogger)
  173. m_lpLogger->AddRef();
  174. // Init MAPI-swig types
  175. BUILD_SWIG_TYPE(type_p_IMessage, "_p_IMessage");
  176. BUILD_SWIG_TYPE(type_p_IMAPISession, "_p_IMAPISession");
  177. BUILD_SWIG_TYPE(type_p_IMsgStore, "_p_IMsgStore");
  178. BUILD_SWIG_TYPE(type_p_IAddrBook, "_p_IAddrBook");
  179. BUILD_SWIG_TYPE(type_p_IMAPIFolder, "_p_IMAPIFolder");
  180. BUILD_SWIG_TYPE(type_p_ECLogger, "_p_ECLogger");
  181. BUILD_SWIG_TYPE(type_p_IExchangeModifyTable, "_p_IExchangeModifyTable");
  182. // Init logger swig object
  183. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyLogger, m_lpLogger, type_p_ECLogger);
  184. // Init plugin class
  185. ptrClass.reset(PyObject_GetAttrString(lpModMapiPlugin, /*char* */lpPluginManagerClassName));
  186. PY_HANDLE_ERROR(m_lpLogger, ptrClass);
  187. ptrArgs.reset(Py_BuildValue("(sO)", lpPluginPath, ptrPyLogger.get()));
  188. PY_HANDLE_ERROR(m_lpLogger, ptrArgs);
  189. m_ptrMapiPluginManager.reset(PyObject_CallObject(ptrClass, ptrArgs));
  190. PY_HANDLE_ERROR(m_lpLogger, m_ptrMapiPluginManager);
  191. return hr;
  192. }
  193. /**
  194. * Plugin python call between MAPI and python.
  195. *
  196. * @param[in] lpFunctionName Python function name to call in the plugin framework.
  197. * The function must be exist the lpPluginManagerClassName defined in the init function.
  198. * @param[in] lpMapiSession Pointer to a mapi session. Not NULL.
  199. * @param[in] lpAdrBook Pointer to a mapi Addressbook. Not NULL.
  200. * @param[in] lpMsgStore Pointer to a mapi mailbox. Can be NULL.
  201. * @param[in] lpInbox
  202. * @param[in] lpMessage Pointer to a mapi message.
  203. *
  204. * @return Default mapi error codes
  205. *
  206. * @todo something with exit codes
  207. */
  208. HRESULT PyMapiPlugin::MessageProcessing(const char *lpFunctionName, IMAPISession *lpMapiSession, IAddrBook *lpAdrBook, IMsgStore *lpMsgStore, IMAPIFolder *lpInbox, IMessage *lpMessage, ULONG *lpulResult)
  209. {
  210. HRESULT hr = hrSuccess;
  211. PyObjectAPtr ptrPySession, ptrPyAddrBook, ptrPyStore, ptrPyMessage, ptrPyFolderInbox;
  212. if (!m_ptrMapiPluginManager)
  213. return hrSuccess;
  214. if (!lpFunctionName || !lpMapiSession || !lpAdrBook)
  215. return MAPI_E_INVALID_PARAMETER;
  216. if (!m_ptrMapiPluginManager)
  217. return MAPI_E_CALL_FAILED;
  218. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPySession, lpMapiSession, type_p_IMAPISession)
  219. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyAddrBook, lpAdrBook, type_p_IAddrBook)
  220. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyStore, lpMsgStore, type_p_IMsgStore)
  221. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyFolderInbox, lpInbox, type_p_IMAPIFolder)
  222. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyMessage, lpMessage, type_p_IMessage)
  223. // Call the python function and get the (hr) return code back
  224. PY_CALL_METHOD(m_ptrMapiPluginManager, const_cast<char *>(lpFunctionName),
  225. PY_PARSE_TUPLE_HELPER("I", lpulResult), "OOOOO",
  226. ptrPySession.get(), ptrPyAddrBook.get(), ptrPyStore.get(),
  227. ptrPyFolderInbox.get(), ptrPyMessage.get());
  228. return hr;
  229. }
  230. /**
  231. * Hook for change the rules.
  232. *
  233. * @param[in] lpFunctionName Python function name to hook the rules in the plugin framework.
  234. * The function must be exist the lpPluginManagerClassName defined in the init function.
  235. * @param[in] lpEMTRules Pointer to a mapi IExchangeModifyTable object
  236. */
  237. HRESULT PyMapiPlugin::RulesProcessing(const char *lpFunctionName, IMAPISession *lpMapiSession, IAddrBook *lpAdrBook, IMsgStore *lpMsgStore, IExchangeModifyTable *lpEMTRules, ULONG *lpulResult)
  238. {
  239. HRESULT hr = hrSuccess;
  240. PyObjectAPtr ptrPySession, ptrPyAddrBook, ptrPyStore, ptrEMTIn;
  241. if (!m_ptrMapiPluginManager)
  242. return hrSuccess;
  243. if (!lpFunctionName || !lpMapiSession || !lpAdrBook || !lpEMTRules)
  244. return MAPI_E_INVALID_PARAMETER;
  245. if (!m_ptrMapiPluginManager)
  246. return MAPI_E_CALL_FAILED;
  247. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPySession, lpMapiSession, type_p_IMAPISession)
  248. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyAddrBook, lpAdrBook, type_p_IAddrBook)
  249. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyStore, lpMsgStore, type_p_IMsgStore)
  250. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrEMTIn, lpEMTRules, type_p_IExchangeModifyTable)
  251. PY_CALL_METHOD(m_ptrMapiPluginManager, const_cast<char *>(lpFunctionName),
  252. PY_PARSE_TUPLE_HELPER("I", lpulResult), "OOOO",
  253. ptrPySession.get(), ptrPyAddrBook.get(), ptrPyStore.get(),
  254. ptrEMTIn.get());
  255. return hr;
  256. }
  257. HRESULT PyMapiPlugin::RequestCallExecution(const char *lpFunctionName, IMAPISession *lpMapiSession, IAddrBook *lpAdrBook, IMsgStore *lpMsgStore, IMAPIFolder *lpFolder, IMessage *lpMessage, ULONG *lpulDoCallexe, ULONG *lpulResult)
  258. {
  259. HRESULT hr = hrSuccess;
  260. PyObjectAPtr ptrPySession, ptrPyAddrBook, ptrPyStore, ptrFolder, ptrMessage;
  261. if (!m_ptrMapiPluginManager)
  262. return hrSuccess;
  263. if (!lpFunctionName || !lpMapiSession || !lpAdrBook || !lpulDoCallexe)
  264. return MAPI_E_INVALID_PARAMETER;
  265. if (!m_ptrMapiPluginManager)
  266. return MAPI_E_CALL_FAILED;
  267. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPySession, lpMapiSession, type_p_IMAPISession)
  268. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyAddrBook, lpAdrBook, type_p_IAddrBook)
  269. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrPyStore, lpMsgStore, type_p_IMsgStore)
  270. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrFolder, lpFolder, type_p_IMAPIFolder)
  271. NEW_SWIG_INTERFACE_POINTER_OBJ(ptrMessage, lpMessage, type_p_IMessage)
  272. PY_CALL_METHOD(m_ptrMapiPluginManager, const_cast<char *>(lpFunctionName),
  273. PY_PARSE_TUPLE_HELPER("I|I", lpulResult, lpulDoCallexe), "OOOOO",
  274. ptrPySession.get(), ptrPyAddrBook.get(), ptrPyStore.get(),
  275. ptrFolder.get(), ptrMessage.get());
  276. return hr;
  277. }
  278. PyMapiPluginFactory::PyMapiPluginFactory() :
  279. m_priv(new struct pym_factory_priv)
  280. {
  281. }
  282. PyMapiPluginFactory::~PyMapiPluginFactory()
  283. {
  284. if (m_priv != nullptr && m_priv->m_ptrModMapiPlugin != nullptr) {
  285. m_priv->m_ptrModMapiPlugin = nullptr;
  286. Py_Finalize();
  287. }
  288. if (m_lpLogger != nullptr)
  289. m_lpLogger->Release();
  290. delete m_priv;
  291. }
  292. HRESULT PyMapiPluginFactory::create_plugin(ECConfig *lpConfig,
  293. ECLogger *lpLogger, const char *lpPluginManagerClassName,
  294. pym_plugin_intf **lppPlugin)
  295. {
  296. HRESULT hr = S_OK;
  297. std::string strEnvPython;
  298. char *lpEnvPython = NULL;
  299. PyObjectAPtr ptrName;
  300. PyObjectAPtr ptrModule;
  301. m_lpLogger = lpLogger;
  302. if (m_lpLogger)
  303. m_lpLogger->AddRef();
  304. m_bEnablePlugin = parseBool(lpConfig->GetSetting("plugin_enabled", NULL, "no"));
  305. if (m_bEnablePlugin) {
  306. m_strPluginPath = lpConfig->GetSetting("plugin_path");
  307. strEnvPython = lpConfig->GetSetting("plugin_manager_path");
  308. lpEnvPython = getenv("PYTHONPATH");
  309. if (lpEnvPython)
  310. strEnvPython += std::string(":") + lpEnvPython;
  311. setenv("PYTHONPATH", strEnvPython.c_str(), 1);
  312. lpLogger->Log(EC_LOGLEVEL_DEBUG, "PYTHONPATH = %s", strEnvPython.c_str());
  313. Py_Initialize();
  314. ptrModule.reset(PyImport_ImportModule("MAPI"));
  315. PY_HANDLE_ERROR(m_lpLogger, ptrModule);
  316. // Import python plugin framework
  317. // @todo error unable to find file xxx
  318. ptrName.reset(PyString_FromString("mapiplugin"));
  319. m_priv->m_ptrModMapiPlugin.reset(PyImport_Import(ptrName));
  320. PY_HANDLE_ERROR(m_lpLogger, m_priv->m_ptrModMapiPlugin);
  321. }
  322. std::unique_ptr<PyMapiPlugin> lpPlugin(new(std::nothrow) PyMapiPlugin);
  323. if (lpPlugin == nullptr)
  324. return MAPI_E_NOT_ENOUGH_MEMORY;
  325. hr = lpPlugin->Init(m_lpLogger, m_priv->m_ptrModMapiPlugin, lpPluginManagerClassName, m_strPluginPath.c_str());
  326. if (hr != S_OK)
  327. return hr;
  328. *lppPlugin = lpPlugin.release();
  329. return S_OK;
  330. }