+++ /dev/null
-/* $Id: python.c 7891 2008-06-22 09:59:11Z iulius $
-**
-** python.c: Embed Python in the style of innd's TCL and Perl stuff.
-**
-** Written by G.J. Andruk <meowing@banet.net> patterned after
-** TCL/Perl work by Bob Heiney and Christophe Wolfhugel and a whole
-** bunch of other people mentioned in the docs and sources for the
-** other filters.
-**
-** The astute reader may notice the commission of blatant atrocities
-** against Python's OO model here. Don't tell Guido.
-**
-** A quick note regarding Python exceptions: functions like
-** PyObject_GetAttrString(PyObject *o, const char *attr_name)
-** raise an exception when they fail, even though they return NULL.
-** And as exceptions accumulate from caller to caller and so on,
-** it generates weird issues with Python scripts afterwards. So such
-** uses should be checked before. For instance with:
-** PyObject_HasAttrString(PyObject *o, const char *attr_name).
-*/
-
-#include "config.h"
-#include "clibrary.h"
-
-#include "inn/innconf.h"
-#include "innd.h"
-
-
-#if defined(DO_PYTHON)
-
-#include "Python.h"
-
-
-bool PythonFilterActive;
-char *filterPath; /* this gets set in art.c */
-PyObject *PYFilterObject = NULL;
-PyObject *PYFilterModule = NULL;
-
-/* article filter bits and pieces */
-PyObject *PYheaders = NULL;
-PyObject **PYheaditem;
-PyObject **PYheadkey;
-PyObject *PYpathkey, *PYlineskey, *PYbodykey;
-
-/* external functions */
-PyObject *msgid_method = NULL;
-PyObject *art_method = NULL;
-PyObject *mode_method = NULL;
-PyObject *pre_reload_method = NULL;
-PyObject *close_method = NULL;
-
-
-
-/*
-** Turn filtering on or off.
-*/
-void
-PYfilter(value)
- bool value;
-{
- PythonFilterActive = value;
- syslog(L_NOTICE, "%s Python filtering %s", LogName,
- PythonFilterActive ? "enabled" : "disabled");
-}
-
-
-
-/*
-** Front end for PYfilter()
-*/
-const char *
-PYcontrol(char **av)
-{
- char *p;
- extern bool PythonFilterActive;
-
- switch (av[0][0]) {
- default:
- return "1 Bad flag";
- case 'y':
- if (PythonFilterActive)
- return "1 Python filter already enabled";
- else if (PYFilterObject == NULL)
- return "1 Python filter not defined" ;
- PYfilter(true);
- break;
- case 'n':
- if (!PythonFilterActive)
- return "1 Python filter already disabled";
- PYfilter(false);
- break;
- }
- return NULL;
-}
-
-
-
-
-/*
-** Reject articles we don't like.
-*/
-char *
-PYartfilter(const ARTDATA *data, char *artBody, long artLen, int lines)
-{
- const ARTHEADER *hp;
- const HDRCONTENT *hc = data->HdrContent;
- int hdrnum;
- int i;
- char *p, save;
- static char buf[256];
- PyObject *result;
-
- if (!PythonFilterActive || PYFilterObject == NULL || art_method == NULL)
- return NULL;
-
- /* Add headers to the dictionary... */
- hdrnum = 0;
- for (i = 0 ; i < MAX_ARTHEADER ; i++) {
- if (HDR_FOUND(i)) {
- hp = &ARTheaders[i];
- PYheaditem[hdrnum] = PyBuffer_FromMemory(HDR(i), HDR_LEN(i));
- } else
- PYheaditem[hdrnum] = Py_None;
- PyDict_SetItem(PYheaders, PYheadkey[hdrnum], PYheaditem[hdrnum]);
- hdrnum++;
- }
-
- /* ...then the body... */
- if (artLen && artBody != NULL)
- PYheaditem[hdrnum] = PyBuffer_FromMemory(artBody, --artLen);
- else
- PYheaditem[hdrnum] = Py_None;
- PyDict_SetItem(PYheaders, PYbodykey, PYheaditem[hdrnum++]);
-
- /* ...and finally, the line count. */
- PYheaditem[hdrnum] = PyInt_FromLong((long) lines);
- PyDict_SetItem(PYheaders, PYlineskey, PYheaditem[hdrnum++]);
-
- /* Now see if the filter likes it. */
- result = PyObject_CallFunction(art_method, "O", PYheaders);
- if ((result != NULL) && PyObject_IsTrue(result))
- strlcpy(buf, PyString_AS_STRING(result), sizeof(buf));
- else
- *buf = '\0';
- Py_XDECREF(result);
-
- /* Clean up after ourselves */
- PyDict_Clear(PYheaders);
- for (i = 0; i < hdrnum; i++)
- if (PYheaditem[i] != Py_None)
- Py_DECREF(PYheaditem[i]);
-
- if (*buf != '\0')
- return buf;
- return NULL;
-}
-
-
-
-/*
-** Refuse message IDs offered thru CHECK or IHAVE that we don't like.
-*/
-char *
-PYmidfilter(messageID, msglen)
- char *messageID;
- int msglen;
-{
- static char buf[256];
- PyObject *result;
-
- if (!PythonFilterActive || PYFilterObject == NULL || msgid_method == NULL)
- return NULL;
-
- result = PyObject_CallFunction(msgid_method, "s#", messageID, msglen);
- if ((result != NULL) && PyObject_IsTrue(result))
- strlcpy(buf, PyString_AS_STRING(result), sizeof(buf));
- else
- *buf = '\0';
- Py_XDECREF(result);
-
- if (*buf != '\0')
- return buf;
- return NULL;
-}
-
-
-
-/*
-** Tell the external module about innd's state.
-*/
-void
-PYmode(Mode, NewMode, reason)
- OPERATINGMODE Mode, NewMode;
- char *reason;
-{
- PyObject *result;
- char oldmode[10], newmode[10];
-
- if (!PythonFilterActive || PYFilterObject == NULL || mode_method == NULL)
- return;
-
- switch (Mode) {
- default: strlcpy(oldmode, "unknown", 10); break;
- case OMrunning: strlcpy(oldmode, "running", 10); break;
- case OMpaused: strlcpy(oldmode, "paused", 10); break;
- case OMthrottled: strlcpy(oldmode, "throttled", 10); break;
- }
-
- switch (NewMode) {
- default: strlcpy(newmode, "unknown", 10); break;
- case OMrunning: strlcpy(newmode, "running", 10); break;
- case OMpaused: strlcpy(newmode, "paused", 10); break;
- case OMthrottled: strlcpy(newmode, "throttled", 10); break;
- }
-
- result = PyObject_CallFunction(mode_method, "sss",
- oldmode, newmode, reason);
- Py_XDECREF(result);
-}
-
-
-
-/*
-** Called by the external module so it can register itself with innd.
-*/
-static PyObject *
-PY_set_filter_hook(dummy, args)
- PyObject *dummy, *args;
-{
- PyObject *result = NULL;
- PyObject *temp;
-
- if (PyArg_ParseTuple(args, "O:set_filter_hook", &temp)) {
- Py_XINCREF(temp);
- Py_XDECREF(PYFilterObject);
- PYFilterObject = temp;
- Py_INCREF(Py_None);
- result = Py_None;
- }
- return result;
-}
-
-
-
-/*
-** Allow external module to ask innd if an ID is in history.
-*/
-static PyObject *
-PY_havehist(self, args)
- PyObject *self, *args;
-{
- char *msgid;
- int msgidlen;
-
- if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
- return NULL;
-
- if (HIScheck(History, msgid))
- return PyInt_FromLong(1);
- return PyInt_FromLong(0);
-}
-
-
-
-/*
-** Allow external module to locally delete an article.
-*/
-static PyObject *
-PY_cancel(self, args)
- PyObject *self, *args;
-{
- char *msgid;
- int msgidlen;
- char *parambuf[2];
-
- if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
- return NULL;
-
- parambuf[0]= msgid;
- parambuf[1]= 0;
-
- if (!CCcancel(parambuf))
- return PyInt_FromLong(1);
- return PyInt_FromLong(0);
-}
-
-
-
-/*
-** Stuff an ID into history so that it will be refused later.
-*/
-static PyObject *
-PY_addhist(self, args)
- PyObject *self, *args;
-{
- char *msgid;
- int msgidlen;
- char *articlepaths = "";
- char tbuff[12];
- char *parambuf[6];
-
- if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
- return NULL;
-
- snprintf(tbuff, sizeof(tbuff), "%d", time(NULL));
-
- parambuf[0] = msgid;
- parambuf[1] = parambuf[2] = parambuf[3] = tbuff;
- parambuf[4] = articlepaths;
- parambuf[5] = 0;
-
- if (!CCaddhist(parambuf))
- return PyInt_FromLong(1);
- return PyInt_FromLong(0);
-}
-
-
-
-/*
-** Get a newsgroup's status flag (j, m, n, x, y, =other.group)
-*/
-static PyObject *
-PY_newsgroup(self, args)
- PyObject *self, *args;
-{
- char *newsgroup;
- int nglen;
- NEWSGROUP *ngp;
- char *end;
- char *rest;
- int size;
-
- if (!PyArg_ParseTuple(args, "s#", &newsgroup, &nglen))
- return NULL;
-
- ngp = NGfind(newsgroup);
- if (ngp == NULL)
- return PyString_FromStringAndSize(NULL, 0);
-
- /* ngp->Rest is newline-terminated; find the end. */
- end = strchr(ngp->Rest, '\n');
- if (end == NULL)
- size = strlen(ngp->Rest);
- else
- size = end - ngp->Rest;
-
- /* If an alias is longer than this, active is probably broken. */
- if (size > MAXHEADERSIZE) {
- syslog(L_ERROR, "too-long flag field in active for %s", newsgroup);
- size = MAXHEADERSIZE;
- }
-
- return PyString_FromStringAndSize(ngp->Rest, size);
-}
-
-
-
-/*
-** Return an article header to the external module as a string. We
-** don't use a buffer object here because that would make it harder,
-** for example, to compare two on-spool articles.
-*/
-static PyObject *
-PY_head(self, args)
- PyObject *self, *args;
-{
- char *msgid;
- int msgidlen;
- char *p;
- TOKEN token;
- ARTHANDLE *art;
- PyObject *header;
- int headerlen;
-
- if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
- return NULL;
-
- if (! HISlookup(History, msgid, NULL, NULL, NULL, &token))
- return Py_BuildValue("s", "");
- if ((art = SMretrieve(token, RETR_HEAD)) == NULL)
- return Py_BuildValue("s", "");
- p = FromWireFmt(art->data, art->len, &headerlen);
- SMfreearticle(art);
- header = PyString_FromStringAndSize(p, headerlen);
- free(p);
-
- return header;
-}
-
-
-
-/*
-** Return a whole article to the external module as a string.
-*/
-static PyObject *
-PY_article(self, args)
- PyObject *self, *args;
-{
- char *msgid;
- int msgidlen;
- char *p;
- TOKEN token;
- ARTHANDLE *arth;
- PyObject *art;
- int artlen;
-
- if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
- return NULL;
-
- if (! HISlookup(History, msgid, NULL, NULL, NULL, &token))
- return Py_BuildValue("s", "");
- if ((arth = SMretrieve(token, RETR_ALL)) == NULL)
- return Py_BuildValue("s", "");
- p = FromWireFmt(arth->data, arth->len, &artlen);
- SMfreearticle(arth);
- art = PyString_FromStringAndSize(p, artlen);
- free(p);
-
- return art;
-}
-
-
-
-/*
-** Python's syslog module isn't compiled in by default. It's easier
-** to do it this way, and the switch block looks pretty in a color
-** editor).
-*/
-static PyObject *
-PY_syslog(self, args)
- PyObject *self, *args;
-{
- char *loglevel;
- int levellen;
- char *logmsg;
- int msglen;
- int priority;
-
- if (!PyArg_ParseTuple(args, "s#s#",
- &loglevel, &levellen, &logmsg, &msglen))
- return NULL;
-
- switch (*loglevel) {
- default: priority = LOG_NOTICE ;
- case 'd': case 'D': priority = LOG_DEBUG ; break;
- case 'i': case 'I': priority = LOG_INFO ; break;
- case 'n': case 'N': priority = LOG_NOTICE ; break;
- case 'w': case 'W': priority = LOG_WARNING ; break;
- case 'e': case 'E': priority = LOG_ERR ; break;
- case 'c': case 'C': priority = LOG_CRIT ; break;
- case 'a': case 'A': priority = LOG_ALERT ; break;
- }
-
- syslog(priority, "python: %s", logmsg);
-
- Py_INCREF(Py_None);
- return Py_None;
-}
-
-
-
-/*
-** Compute a hash digest for a string.
-*/
-static PyObject *
-PY_hashstring(self, args)
- PyObject *self, *args;
-{
- char *instring, *wpos, *p, *q;
- char *workstring = NULL;
- int insize, worksize, newsize, i, wasspace;
- int lines = 0;
- HASH myhash;
-
- if (!PyArg_ParseTuple(args, "s#|i", &instring, &insize, &lines))
- return NULL;
-
- /* If a linecount is provided, munge before hashing. */
- if (lines > 0) {
- worksize = insize;
-
- /* chop leading whitespace */
- for (p=instring ; worksize>0 && isspace(*p) ; p++) {
- if (*p == '\n')
- lines--;
- worksize--;
- }
- wpos = p;
-
- /* and trailing */
- for (p=&wpos[worksize] ; worksize>0 && isspace(*p) ; p--) {
- if (*p == '\n')
- lines--;
- worksize--;
- }
-
- /* chop last 3 lines if we have >= 5. From above chop the
- * last line has no CR so we use 1 less here. */
- if (lines >= 4) {
- for (i=0, p=wpos+worksize ; i<2 ; p--)
- if (*p == '\n')
- i++;
- worksize = p - wpos;
- }
-
- /* Compress out multiple whitespace in the trimmed string. We
- * do a copy because this is probably an original art
- * buffer. */
- workstring = memcpy(xmalloc(worksize), wpos, worksize);
- newsize = wasspace = 0;
- p = wpos;
- q = workstring;
- for (i=0 ; i<worksize ; i++) {
- if (isspace(*p)) {
- if (!wasspace)
- *q++ = ' ';
- wasspace = 1;
- }
- else {
- *q++ = tolower(*p);
- wasspace = 0;
- }
- p++;
- }
- worksize = q - workstring;
- myhash = Hash(workstring, worksize);
- free(workstring);
- }
- else
- myhash = Hash(instring, insize);
-
- return PyString_FromStringAndSize((const char *)&myhash, sizeof(myhash));
-}
-
-
-
-/*
-** Make the internal INN module's functions visible to Python.
-*/
-static PyMethodDef INNPyMethods[] = {
- {"set_filter_hook", PY_set_filter_hook, METH_VARARGS},
- {"havehist", PY_havehist, METH_VARARGS},
- {"addhist", PY_addhist, METH_VARARGS},
- {"cancel", PY_cancel, METH_VARARGS},
- {"newsgroup", PY_newsgroup, METH_VARARGS},
- {"head", PY_head, METH_VARARGS},
- {"article", PY_article, METH_VARARGS},
- {"syslog", PY_syslog, METH_VARARGS},
- {"hashstring", PY_hashstring, METH_VARARGS},
- {NULL, NULL}
-};
-
-
-
-/*
-** This runs when innd shuts down.
-*/
-void
-PYclose(void)
-{
- PyObject *result;
-
- if (close_method != NULL) {
- result = PyObject_CallFunction(close_method, NULL);
- Py_XDECREF(result);
- }
-}
-
-
-
-/*
-** Check that a method exists and is callable. Set a pointer to
-** the corresponding PyObject, or NULL if not found.
-*/
-void
-PYdefonemethod(methptr, methname)
- PyObject **methptr;
- char *methname;
-{
- Py_XDECREF(*methptr);
-
- /* We check with HasAttrString() the existence of the method because
- * otherwise, in case it does not exist, an exception is raised by Python,
- * although the result of the function is NULL. */
- if (PyObject_HasAttrString(PYFilterObject, (char *) methname) == 1) {
- /* Get a pointer to given method. */
- *methptr = PyObject_GetAttrString(PYFilterObject, (char *) methname);
- } else {
- *methptr = NULL;
- }
-
- if (*methptr == NULL)
- syslog(L_NOTICE, "python method %s not found", methname);
- else if (PyCallable_Check(*methptr) == 0) {
- syslog(L_ERROR, "python object %s found but not a function", methname);
- Py_DECREF(*methptr);
- *methptr = NULL;
- }
-}
-
-
-
-/*
-** Look up the filter methods, so we will know what's available when
-** innd wants to call them.
-*/
-void
-PYdefmethods(void)
-{
- PYdefonemethod(&msgid_method, "filter_messageid");
- PYdefonemethod(&art_method, "filter_art");
- PYdefonemethod(&mode_method, "filter_mode");
- PYdefonemethod(&pre_reload_method, "filter_before_reload");
- PYdefonemethod(&close_method, "filter_close");
-}
-
-
-
-/*
-** Used by "ctlinnd reload filter.python 'reason'".
-*/
-int
-PYreadfilter(void)
-{
- PyObject *newmodule = NULL;
- PyObject *result;
-
- if (!Py_IsInitialized()) {
- syslog(L_ERROR, "python is not initialized");
- return 0;
- }
-
- /* If there is a filter running, let it clean up first. */
- if (pre_reload_method != NULL) {
- result = PyObject_CallFunction(pre_reload_method, NULL);
- Py_XDECREF(result);
- }
-
- /* We need to reimport the module before reloading it because otherwise,
- * it might not be taken into account by Python.
- * See Python API documentation:
- * If a module is syntactically correct but its initialization fails,
- * the first import statement for it does not bind its name locally,
- * but does store a (partially initialized) module object in
- * sys.modules. To reload the module, you must first import it again
- * (this will bind the name to the partially initialized module object)
- * before you can reload() it.
- */
- PYFilterModule = PyImport_ImportModule((char *) _PATH_PYTHON_STARTUP_M);
- if (PYFilterModule == NULL) {
- syslog(L_ERROR, "failed to reimport external python module");
- }
-
- if ((newmodule = PyImport_ReloadModule(PYFilterModule)) == NULL) {
- syslog(L_ERROR, "cant reload python filter module");
- PYfilter(false);
- return 0;
- }
-
- Py_XDECREF(PYFilterModule);
- PYFilterModule = newmodule;
-
- if (PYFilterObject == NULL) {
- syslog(L_ERROR, "python reload error, filter object not defined");
- PYfilter(false);
- return 0;
- }
-
- PYfilter(true);
- PYdefmethods();
-
- return 1;
-}
-
-
-
-/*
-** Called when innd first starts -- this gets the filters hooked in.
-*/
-void
-PYsetup(void)
-{
- const ARTHEADER *hp;
- int hdrindex;
- size_t hdrcount;
-
- setenv("PYTHONPATH", innconf->pathfilter, 1);
- Py_Initialize();
-
- /* It makes Python sad when its stdout and stderr are closed. */
- if ((fileno(stdout) == -1) || (fileno(stderr) == -1))
- PyRun_SimpleString
- ("import sys; sys.stdout=sys.stderr=open('/dev/null', 'a')");
-
- if (!Py_IsInitialized ()) {
- syslog(L_ERROR, "python interpreter NOT initialized");
- return;
- }
- syslog(L_NOTICE, "python interpreter initialized OK");
-
- Py_InitModule("INN", INNPyMethods);
-
- PYFilterModule = PyImport_ImportModule(_PATH_PYTHON_STARTUP_M);
- if (PYFilterModule == NULL)
- syslog(L_ERROR, "failed to import external python module");
-
- if (PYFilterObject == NULL) {
- syslog(L_ERROR, "python filter object is not defined");
- PYfilter(false);
- } else {
- PYfilter(true);
- PYdefmethods();
- syslog(L_NOTICE, "defined python methods");
- }
-
- /* Grab space for these so we aren't forever recreating them. We also
- put the body and the line count into PYheaditem, so it needs to be
- two elements longer than the total number of headers. */
- PYheaders = PyDict_New();
- hdrcount = ARRAY_END(ARTheaders) - ARTheaders;
- PYheaditem = xmalloc((hdrcount + 2) * sizeof(PyObject *));
- PYheadkey = xmalloc(hdrcount * sizeof(PyObject *));
-
- /* Preallocate keys for the article dictionary */
- for (hp = ARTheaders; hp < ARRAY_END(ARTheaders); hp++)
- PYheadkey[hp - ARTheaders] = PyString_InternFromString(hp->Name);
- PYpathkey = PyString_InternFromString("Path");
- PYlineskey = PyString_InternFromString("__LINES__");
- PYbodykey = PyString_InternFromString("__BODY__");
-}
-
-#endif /* defined(DO_PYTHON) */