mirror of https://github.com/znc/znc
Mirror of the official znc repository
https://znc.in
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
513 lines
17 KiB
513 lines
17 KiB
/* |
|
* Copyright (C) 2004-2022 ZNC, see the NOTICE file for details. |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
#define PY_SSIZE_T_CLEAN |
|
#include <Python.h> |
|
|
|
#include <znc/Chan.h> |
|
#include <znc/FileUtils.h> |
|
#include <znc/IRCSock.h> |
|
#include <znc/Modules.h> |
|
#include <znc/Nick.h> |
|
#include <znc/User.h> |
|
#include <znc/znc.h> |
|
|
|
#include "modpython/swigpyrun.h" |
|
#include "modpython/module.h" |
|
#include "modpython/ret.h" |
|
|
|
using std::vector; |
|
using std::set; |
|
|
|
class CModPython : public CModule { |
|
PyObject* m_PyZNCModule; |
|
PyObject* m_PyFormatException; |
|
vector<PyObject*> m_vpObject; |
|
|
|
public: |
|
CString GetPyExceptionStr() { |
|
PyObject* ptype; |
|
PyObject* pvalue; |
|
PyObject* ptraceback; |
|
PyErr_Fetch(&ptype, &pvalue, &ptraceback); |
|
CString result; |
|
if (!pvalue) { |
|
Py_INCREF(Py_None); |
|
pvalue = Py_None; |
|
} |
|
if (!ptraceback) { |
|
Py_INCREF(Py_None); |
|
ptraceback = Py_None; |
|
} |
|
PyErr_NormalizeException(&ptype, &pvalue, &ptraceback); |
|
PyObject* strlist = PyObject_CallFunctionObjArgs( |
|
m_PyFormatException, ptype, pvalue, ptraceback, nullptr); |
|
Py_CLEAR(ptype); |
|
Py_CLEAR(pvalue); |
|
Py_CLEAR(ptraceback); |
|
if (!strlist) { |
|
return "Couldn't get exact error message"; |
|
} |
|
|
|
if (PySequence_Check(strlist)) { |
|
PyObject* strlist_fast = |
|
PySequence_Fast(strlist, "Shouldn't happen (1)"); |
|
PyObject** items = PySequence_Fast_ITEMS(strlist_fast); |
|
Py_ssize_t L = PySequence_Fast_GET_SIZE(strlist_fast); |
|
for (Py_ssize_t i = 0; i < L; ++i) { |
|
PyObject* utf8 = PyUnicode_AsUTF8String(items[i]); |
|
result += PyBytes_AsString(utf8); |
|
Py_CLEAR(utf8); |
|
} |
|
Py_CLEAR(strlist_fast); |
|
} else { |
|
result = "Can't get exact error message"; |
|
} |
|
|
|
Py_CLEAR(strlist); |
|
|
|
return result; |
|
} |
|
|
|
MODCONSTRUCTOR(CModPython) { |
|
CZNC::Get().ForceEncoding(); |
|
Py_Initialize(); |
|
m_PyFormatException = nullptr; |
|
m_PyZNCModule = nullptr; |
|
} |
|
|
|
bool OnLoad(const CString& sArgsi, CString& sMessage) override { |
|
CString sModPath, sTmp; |
|
#ifdef __CYGWIN__ |
|
CString sDllPath = "modpython/_znc_core.dll"; |
|
#else |
|
CString sDllPath = "modpython/_znc_core.so"; |
|
#endif |
|
if (!CModules::FindModPath(sDllPath, sModPath, sTmp)) { |
|
sMessage = sDllPath + " not found."; |
|
return false; |
|
} |
|
sTmp = CDir::ChangeDir(sModPath, ".."); |
|
|
|
PyObject* pyModuleTraceback = PyImport_ImportModule("traceback"); |
|
if (!pyModuleTraceback) { |
|
sMessage = "Couldn't import python module traceback"; |
|
return false; |
|
} |
|
m_PyFormatException = |
|
PyObject_GetAttrString(pyModuleTraceback, "format_exception"); |
|
if (!m_PyFormatException) { |
|
sMessage = "Couldn't get traceback.format_exception"; |
|
Py_CLEAR(pyModuleTraceback); |
|
return false; |
|
} |
|
Py_CLEAR(pyModuleTraceback); |
|
|
|
PyObject* pySysModule = PyImport_ImportModule("sys"); |
|
if (!pySysModule) { |
|
sMessage = GetPyExceptionStr(); |
|
return false; |
|
} |
|
PyObject* pySysPath = PyObject_GetAttrString(pySysModule, "path"); |
|
if (!pySysPath) { |
|
sMessage = GetPyExceptionStr(); |
|
Py_CLEAR(pySysModule); |
|
return false; |
|
} |
|
Py_CLEAR(pySysModule); |
|
PyObject* pyIgnored = |
|
PyObject_CallMethod(pySysPath, const_cast<char*>("append"), |
|
const_cast<char*>("s"), sTmp.c_str()); |
|
if (!pyIgnored) { |
|
sMessage = GetPyExceptionStr(); |
|
Py_CLEAR(pyIgnored); |
|
return false; |
|
} |
|
Py_CLEAR(pyIgnored); |
|
Py_CLEAR(pySysPath); |
|
|
|
m_PyZNCModule = PyImport_ImportModule("znc"); |
|
if (!m_PyZNCModule) { |
|
sMessage = GetPyExceptionStr(); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
EModRet OnModuleLoading(const CString& sModName, const CString& sArgs, |
|
CModInfo::EModuleType eType, bool& bSuccess, |
|
CString& sRetMsg) override { |
|
PyObject* pyFunc = PyObject_GetAttrString(m_PyZNCModule, "load_module"); |
|
if (!pyFunc) { |
|
sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython: " << sRetMsg); |
|
bSuccess = false; |
|
return HALT; |
|
} |
|
PyObject* pyRes = PyObject_CallFunction( |
|
pyFunc, const_cast<char*>("ssiNNNN"), sModName.c_str(), |
|
sArgs.c_str(), (int)eType, |
|
(eType == CModInfo::GlobalModule |
|
? Py_None |
|
: SWIG_NewInstanceObj(GetUser(), SWIG_TypeQuery("CUser*"), 0)), |
|
(eType == CModInfo::NetworkModule |
|
? SWIG_NewInstanceObj(GetNetwork(), |
|
SWIG_TypeQuery("CIRCNetwork*"), 0) |
|
: Py_None), |
|
CPyRetString::wrap(sRetMsg), |
|
SWIG_NewInstanceObj(reinterpret_cast<CModule*>(this), |
|
SWIG_TypeQuery("CModPython*"), 0)); |
|
if (!pyRes) { |
|
sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython: " << sRetMsg); |
|
bSuccess = false; |
|
Py_CLEAR(pyFunc); |
|
return HALT; |
|
} |
|
Py_CLEAR(pyFunc); |
|
long int ret = PyLong_AsLong(pyRes); |
|
if (PyErr_Occurred()) { |
|
sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython: " << sRetMsg); |
|
Py_CLEAR(pyRes); |
|
return HALT; |
|
} |
|
Py_CLEAR(pyRes); |
|
switch (ret) { |
|
case 0: |
|
// Not found |
|
return CONTINUE; |
|
case 1: |
|
// Error |
|
bSuccess = false; |
|
return HALT; |
|
case 2: |
|
// Success |
|
bSuccess = true; |
|
return HALT; |
|
} |
|
bSuccess = false; |
|
sRetMsg += " unknown value returned by modpython.load_module"; |
|
return HALT; |
|
} |
|
|
|
EModRet OnModuleUnloading(CModule* pModule, bool& bSuccess, |
|
CString& sRetMsg) override { |
|
CPyModule* pMod = AsPyModule(pModule); |
|
if (pMod) { |
|
CString sModName = pMod->GetModName(); |
|
PyObject* pyFunc = |
|
PyObject_GetAttrString(m_PyZNCModule, "unload_module"); |
|
if (!pyFunc) { |
|
sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython: " << sRetMsg); |
|
bSuccess = false; |
|
return HALT; |
|
} |
|
PyObject* pyRes = |
|
PyObject_CallFunctionObjArgs(pyFunc, pMod->GetPyObj(), nullptr); |
|
if (!pyRes) { |
|
sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython: " << sRetMsg); |
|
bSuccess = false; |
|
Py_CLEAR(pyFunc); |
|
return HALT; |
|
} |
|
if (!PyObject_IsTrue(pyRes)) { |
|
// python module, but not handled by modpython itself. |
|
// some module-provider written on python loaded it? |
|
return CONTINUE; |
|
} |
|
Py_CLEAR(pyFunc); |
|
Py_CLEAR(pyRes); |
|
bSuccess = true; |
|
sRetMsg = "Module [" + sModName + "] unloaded"; |
|
return HALT; |
|
} |
|
return CONTINUE; |
|
} |
|
|
|
EModRet OnGetModInfo(CModInfo& ModInfo, const CString& sModule, |
|
bool& bSuccess, CString& sRetMsg) override { |
|
PyObject* pyFunc = |
|
PyObject_GetAttrString(m_PyZNCModule, "get_mod_info"); |
|
if (!pyFunc) { |
|
sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython: " << sRetMsg); |
|
bSuccess = false; |
|
return HALT; |
|
} |
|
PyObject* pyRes = PyObject_CallFunction( |
|
pyFunc, const_cast<char*>("sNN"), sModule.c_str(), |
|
CPyRetString::wrap(sRetMsg), |
|
SWIG_NewInstanceObj(&ModInfo, SWIG_TypeQuery("CModInfo*"), 0)); |
|
if (!pyRes) { |
|
sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython: " << sRetMsg); |
|
bSuccess = false; |
|
Py_CLEAR(pyFunc); |
|
return HALT; |
|
} |
|
Py_CLEAR(pyFunc); |
|
long int x = PyLong_AsLong(pyRes); |
|
if (PyErr_Occurred()) { |
|
sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython: " << sRetMsg); |
|
bSuccess = false; |
|
Py_CLEAR(pyRes); |
|
return HALT; |
|
} |
|
Py_CLEAR(pyRes); |
|
switch (x) { |
|
case 0: |
|
return CONTINUE; |
|
case 1: |
|
bSuccess = false; |
|
return HALT; |
|
case 2: |
|
bSuccess = true; |
|
return HALT; |
|
} |
|
bSuccess = false; |
|
sRetMsg = CString("Shouldn't happen. ") + __PRETTY_FUNCTION__ + " on " + |
|
__FILE__ + ":" + CString(__LINE__); |
|
DEBUG(sRetMsg); |
|
return HALT; |
|
} |
|
|
|
void TryAddModInfo(const CString& sPath, const CString& sName, |
|
set<CModInfo>& ssMods, set<CString>& ssAlready, |
|
CModInfo::EModuleType eType) { |
|
if (ssAlready.count(sName)) { |
|
return; |
|
} |
|
PyObject* pyFunc = |
|
PyObject_GetAttrString(m_PyZNCModule, "get_mod_info_path"); |
|
if (!pyFunc) { |
|
CString sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython tried to get info about [" |
|
<< sPath << "] (1) but: " << sRetMsg); |
|
return; |
|
} |
|
CModInfo ModInfo; |
|
PyObject* pyRes = PyObject_CallFunction( |
|
pyFunc, const_cast<char*>("ssN"), sPath.c_str(), sName.c_str(), |
|
SWIG_NewInstanceObj(&ModInfo, SWIG_TypeQuery("CModInfo*"), 0)); |
|
if (!pyRes) { |
|
CString sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython tried to get info about [" |
|
<< sPath << "] (2) but: " << sRetMsg); |
|
Py_CLEAR(pyFunc); |
|
return; |
|
} |
|
Py_CLEAR(pyFunc); |
|
long int x = PyLong_AsLong(pyRes); |
|
if (PyErr_Occurred()) { |
|
CString sRetMsg = GetPyExceptionStr(); |
|
DEBUG("modpython tried to get info about [" |
|
<< sPath << "] (3) but: " << sRetMsg); |
|
Py_CLEAR(pyRes); |
|
return; |
|
} |
|
Py_CLEAR(pyRes); |
|
if (x && ModInfo.SupportsType(eType)) { |
|
ssMods.insert(ModInfo); |
|
ssAlready.insert(sName); |
|
} |
|
} |
|
|
|
void OnGetAvailableMods(set<CModInfo>& ssMods, |
|
CModInfo::EModuleType eType) override { |
|
CDir Dir; |
|
CModules::ModDirList dirs = CModules::GetModDirs(); |
|
|
|
while (!dirs.empty()) { |
|
set<CString> already; |
|
|
|
Dir.Fill(dirs.front().first); |
|
for (unsigned int a = 0; a < Dir.size(); a++) { |
|
CFile& File = *Dir[a]; |
|
CString sName = File.GetShortName(); |
|
CString sPath = File.GetLongName(); |
|
sPath.TrimSuffix(sName); |
|
|
|
if (!File.IsDir()) { |
|
if (sName.WildCmp("*.pyc")) { |
|
sName.RightChomp(4); |
|
} else if (sName.WildCmp("*.py") || sName.WildCmp("*.so")) { |
|
sName.RightChomp(3); |
|
} else { |
|
continue; |
|
} |
|
} |
|
|
|
TryAddModInfo(sPath, sName, ssMods, already, eType); |
|
} |
|
|
|
dirs.pop(); |
|
} |
|
} |
|
|
|
~CModPython() override { |
|
if (!m_PyZNCModule) { |
|
DEBUG( |
|
"~CModPython(): seems like CModPython::OnLoad() didn't " |
|
"initialize python"); |
|
return; |
|
} |
|
PyObject* pyFunc = PyObject_GetAttrString(m_PyZNCModule, "unload_all"); |
|
if (!pyFunc) { |
|
CString sRetMsg = GetPyExceptionStr(); |
|
DEBUG("~CModPython(): couldn't find unload_all: " << sRetMsg); |
|
return; |
|
} |
|
PyObject* pyRes = PyObject_CallFunctionObjArgs(pyFunc, nullptr); |
|
if (!pyRes) { |
|
CString sRetMsg = GetPyExceptionStr(); |
|
DEBUG( |
|
"modpython tried to unload all modules in its destructor, but: " |
|
<< sRetMsg); |
|
} |
|
Py_CLEAR(pyRes); |
|
Py_CLEAR(pyFunc); |
|
|
|
Py_CLEAR(m_PyFormatException); |
|
Py_CLEAR(m_PyZNCModule); |
|
Py_Finalize(); |
|
CZNC::Get().UnforceEncoding(); |
|
} |
|
}; |
|
|
|
CString CPyModule::GetPyExceptionStr() { |
|
return m_pModPython->GetPyExceptionStr(); |
|
} |
|
|
|
#include "modpython/pyfunctions.cpp" |
|
|
|
VWebSubPages& CPyModule::GetSubPages() { |
|
VWebSubPages* result = _GetSubPages(); |
|
if (!result) { |
|
return CModule::GetSubPages(); |
|
} |
|
return *result; |
|
} |
|
|
|
void CPyTimer::RunJob() { |
|
CPyModule* pMod = AsPyModule(GetModule()); |
|
if (pMod) { |
|
PyObject* pyRes = PyObject_CallMethod( |
|
m_pyObj, const_cast<char*>("RunJob"), const_cast<char*>("")); |
|
if (!pyRes) { |
|
CString sRetMsg = m_pModPython->GetPyExceptionStr(); |
|
DEBUG("python timer failed: " << sRetMsg); |
|
Stop(); |
|
} |
|
Py_CLEAR(pyRes); |
|
} |
|
} |
|
|
|
CPyTimer::~CPyTimer() { |
|
CPyModule* pMod = AsPyModule(GetModule()); |
|
if (pMod) { |
|
PyObject* pyRes = PyObject_CallMethod( |
|
m_pyObj, const_cast<char*>("OnShutdown"), const_cast<char*>("")); |
|
if (!pyRes) { |
|
CString sRetMsg = m_pModPython->GetPyExceptionStr(); |
|
DEBUG("python timer shutdown failed: " << sRetMsg); |
|
} |
|
Py_CLEAR(pyRes); |
|
Py_CLEAR(m_pyObj); |
|
} |
|
} |
|
|
|
#define CHECKCLEARSOCK(Func) \ |
|
if (!pyRes) { \ |
|
CString sRetMsg = m_pModPython->GetPyExceptionStr(); \ |
|
DEBUG("python socket failed in " Func ": " << sRetMsg); \ |
|
Close(); \ |
|
} \ |
|
Py_CLEAR(pyRes) |
|
|
|
#define CBSOCK(Func) \ |
|
void CPySocket::Func() { \ |
|
PyObject* pyRes = PyObject_CallMethod( \ |
|
m_pyObj, const_cast<char*>("On" #Func), const_cast<char*>("")); \ |
|
CHECKCLEARSOCK(#Func); \ |
|
} |
|
CBSOCK(Connected); |
|
CBSOCK(Disconnected); |
|
CBSOCK(Timeout); |
|
CBSOCK(ConnectionRefused); |
|
|
|
void CPySocket::ReadData(const char* data, size_t len) { |
|
PyObject* pyRes = |
|
PyObject_CallMethod(m_pyObj, const_cast<char*>("OnReadData"), |
|
const_cast<char*>("y#"), data, (Py_ssize_t)len); |
|
CHECKCLEARSOCK("OnReadData"); |
|
} |
|
|
|
void CPySocket::ReadLine(const CString& sLine) { |
|
PyObject* pyRes = |
|
PyObject_CallMethod(m_pyObj, const_cast<char*>("OnReadLine"), |
|
const_cast<char*>("s"), sLine.c_str()); |
|
CHECKCLEARSOCK("OnReadLine"); |
|
} |
|
|
|
Csock* CPySocket::GetSockObj(const CString& sHost, unsigned short uPort) { |
|
CPySocket* result = nullptr; |
|
PyObject* pyRes = |
|
PyObject_CallMethod(m_pyObj, const_cast<char*>("_Accepted"), |
|
const_cast<char*>("sH"), sHost.c_str(), uPort); |
|
if (!pyRes) { |
|
CString sRetMsg = m_pModPython->GetPyExceptionStr(); |
|
DEBUG("python socket failed in OnAccepted: " << sRetMsg); |
|
Close(); |
|
} |
|
int res = SWIG_ConvertPtr(pyRes, (void**)&result, |
|
SWIG_TypeQuery("CPySocket*"), 0); |
|
if (!SWIG_IsOK(res)) { |
|
DEBUG( |
|
"python socket was expected to return new socket from OnAccepted, " |
|
"but error=" |
|
<< res); |
|
Close(); |
|
result = nullptr; |
|
} |
|
if (!result) { |
|
DEBUG("modpython: OnAccepted didn't return new socket"); |
|
} |
|
Py_CLEAR(pyRes); |
|
return result; |
|
} |
|
|
|
CPySocket::~CPySocket() { |
|
PyObject* pyRes = PyObject_CallMethod( |
|
m_pyObj, const_cast<char*>("OnShutdown"), const_cast<char*>("")); |
|
if (!pyRes) { |
|
CString sRetMsg = m_pModPython->GetPyExceptionStr(); |
|
DEBUG("python socket failed in OnShutdown: " << sRetMsg); |
|
} |
|
Py_CLEAR(pyRes); |
|
Py_CLEAR(m_pyObj); |
|
} |
|
|
|
template <> |
|
void TModInfo<CModPython>(CModInfo& Info) { |
|
Info.SetWikiPage("modpython"); |
|
} |
|
|
|
GLOBALMODULEDEFS(CModPython, t_s("Loads python scripts as ZNC modules"))
|
|
|