/* $Id: python.c 6650 2004-01-25 22:22:14Z rra $
**
** 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.
*/
#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_DECREF(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);
*methptr = PyObject_GetAttrString(PYFilterObject, methname);
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'
*/
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);
}
if ((newmodule = PyImport_ReloadModule(PYFilterModule)) == NULL) {
syslog(L_ERROR, "cant reload python filter module");
PYfilter(false);
return 0;
}
Py_DECREF(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) */
syntax highlighted by Code2HTML, v. 0.9.1