1 /* $Id: python.c 7891 2008-06-22 09:59:11Z iulius $
3 ** python.c: Embed Python in the style of innd's TCL and Perl stuff.
5 ** Written by G.J. Andruk <meowing@banet.net> patterned after
6 ** TCL/Perl work by Bob Heiney and Christophe Wolfhugel and a whole
7 ** bunch of other people mentioned in the docs and sources for the
10 ** The astute reader may notice the commission of blatant atrocities
11 ** against Python's OO model here. Don't tell Guido.
13 ** A quick note regarding Python exceptions: functions like
14 ** PyObject_GetAttrString(PyObject *o, const char *attr_name)
15 ** raise an exception when they fail, even though they return NULL.
16 ** And as exceptions accumulate from caller to caller and so on,
17 ** it generates weird issues with Python scripts afterwards. So such
18 ** uses should be checked before. For instance with:
19 ** PyObject_HasAttrString(PyObject *o, const char *attr_name).
25 #include "inn/innconf.h"
29 #if defined(DO_PYTHON)
34 bool PythonFilterActive;
35 char *filterPath; /* this gets set in art.c */
36 PyObject *PYFilterObject = NULL;
37 PyObject *PYFilterModule = NULL;
39 /* article filter bits and pieces */
40 PyObject *PYheaders = NULL;
41 PyObject **PYheaditem;
43 PyObject *PYpathkey, *PYlineskey, *PYbodykey;
45 /* external functions */
46 PyObject *msgid_method = NULL;
47 PyObject *art_method = NULL;
48 PyObject *mode_method = NULL;
49 PyObject *pre_reload_method = NULL;
50 PyObject *close_method = NULL;
55 ** Turn filtering on or off.
61 PythonFilterActive = value;
62 syslog(L_NOTICE, "%s Python filtering %s", LogName,
63 PythonFilterActive ? "enabled" : "disabled");
69 ** Front end for PYfilter()
75 extern bool PythonFilterActive;
81 if (PythonFilterActive)
82 return "1 Python filter already enabled";
83 else if (PYFilterObject == NULL)
84 return "1 Python filter not defined" ;
88 if (!PythonFilterActive)
89 return "1 Python filter already disabled";
100 ** Reject articles we don't like.
103 PYartfilter(const ARTDATA *data, char *artBody, long artLen, int lines)
106 const HDRCONTENT *hc = data->HdrContent;
110 static char buf[256];
113 if (!PythonFilterActive || PYFilterObject == NULL || art_method == NULL)
116 /* Add headers to the dictionary... */
118 for (i = 0 ; i < MAX_ARTHEADER ; i++) {
121 PYheaditem[hdrnum] = PyBuffer_FromMemory(HDR(i), HDR_LEN(i));
123 PYheaditem[hdrnum] = Py_None;
124 PyDict_SetItem(PYheaders, PYheadkey[hdrnum], PYheaditem[hdrnum]);
128 /* ...then the body... */
129 if (artLen && artBody != NULL)
130 PYheaditem[hdrnum] = PyBuffer_FromMemory(artBody, --artLen);
132 PYheaditem[hdrnum] = Py_None;
133 PyDict_SetItem(PYheaders, PYbodykey, PYheaditem[hdrnum++]);
135 /* ...and finally, the line count. */
136 PYheaditem[hdrnum] = PyInt_FromLong((long) lines);
137 PyDict_SetItem(PYheaders, PYlineskey, PYheaditem[hdrnum++]);
139 /* Now see if the filter likes it. */
140 result = PyObject_CallFunction(art_method, "O", PYheaders);
141 if ((result != NULL) && PyObject_IsTrue(result))
142 strlcpy(buf, PyString_AS_STRING(result), sizeof(buf));
147 /* Clean up after ourselves */
148 PyDict_Clear(PYheaders);
149 for (i = 0; i < hdrnum; i++)
150 if (PYheaditem[i] != Py_None)
151 Py_DECREF(PYheaditem[i]);
161 ** Refuse message IDs offered thru CHECK or IHAVE that we don't like.
164 PYmidfilter(messageID, msglen)
168 static char buf[256];
171 if (!PythonFilterActive || PYFilterObject == NULL || msgid_method == NULL)
174 result = PyObject_CallFunction(msgid_method, "s#", messageID, msglen);
175 if ((result != NULL) && PyObject_IsTrue(result))
176 strlcpy(buf, PyString_AS_STRING(result), sizeof(buf));
189 ** Tell the external module about innd's state.
192 PYmode(Mode, NewMode, reason)
193 OPERATINGMODE Mode, NewMode;
197 char oldmode[10], newmode[10];
199 if (!PythonFilterActive || PYFilterObject == NULL || mode_method == NULL)
203 default: strlcpy(oldmode, "unknown", 10); break;
204 case OMrunning: strlcpy(oldmode, "running", 10); break;
205 case OMpaused: strlcpy(oldmode, "paused", 10); break;
206 case OMthrottled: strlcpy(oldmode, "throttled", 10); break;
210 default: strlcpy(newmode, "unknown", 10); break;
211 case OMrunning: strlcpy(newmode, "running", 10); break;
212 case OMpaused: strlcpy(newmode, "paused", 10); break;
213 case OMthrottled: strlcpy(newmode, "throttled", 10); break;
216 result = PyObject_CallFunction(mode_method, "sss",
217 oldmode, newmode, reason);
224 ** Called by the external module so it can register itself with innd.
227 PY_set_filter_hook(dummy, args)
228 PyObject *dummy, *args;
230 PyObject *result = NULL;
233 if (PyArg_ParseTuple(args, "O:set_filter_hook", &temp)) {
235 Py_XDECREF(PYFilterObject);
236 PYFilterObject = temp;
246 ** Allow external module to ask innd if an ID is in history.
249 PY_havehist(self, args)
250 PyObject *self, *args;
255 if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
258 if (HIScheck(History, msgid))
259 return PyInt_FromLong(1);
260 return PyInt_FromLong(0);
266 ** Allow external module to locally delete an article.
269 PY_cancel(self, args)
270 PyObject *self, *args;
276 if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
282 if (!CCcancel(parambuf))
283 return PyInt_FromLong(1);
284 return PyInt_FromLong(0);
290 ** Stuff an ID into history so that it will be refused later.
293 PY_addhist(self, args)
294 PyObject *self, *args;
298 char *articlepaths = "";
302 if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
305 snprintf(tbuff, sizeof(tbuff), "%d", time(NULL));
308 parambuf[1] = parambuf[2] = parambuf[3] = tbuff;
309 parambuf[4] = articlepaths;
312 if (!CCaddhist(parambuf))
313 return PyInt_FromLong(1);
314 return PyInt_FromLong(0);
320 ** Get a newsgroup's status flag (j, m, n, x, y, =other.group)
323 PY_newsgroup(self, args)
324 PyObject *self, *args;
333 if (!PyArg_ParseTuple(args, "s#", &newsgroup, &nglen))
336 ngp = NGfind(newsgroup);
338 return PyString_FromStringAndSize(NULL, 0);
340 /* ngp->Rest is newline-terminated; find the end. */
341 end = strchr(ngp->Rest, '\n');
343 size = strlen(ngp->Rest);
345 size = end - ngp->Rest;
347 /* If an alias is longer than this, active is probably broken. */
348 if (size > MAXHEADERSIZE) {
349 syslog(L_ERROR, "too-long flag field in active for %s", newsgroup);
350 size = MAXHEADERSIZE;
353 return PyString_FromStringAndSize(ngp->Rest, size);
359 ** Return an article header to the external module as a string. We
360 ** don't use a buffer object here because that would make it harder,
361 ** for example, to compare two on-spool articles.
365 PyObject *self, *args;
375 if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
378 if (! HISlookup(History, msgid, NULL, NULL, NULL, &token))
379 return Py_BuildValue("s", "");
380 if ((art = SMretrieve(token, RETR_HEAD)) == NULL)
381 return Py_BuildValue("s", "");
382 p = FromWireFmt(art->data, art->len, &headerlen);
384 header = PyString_FromStringAndSize(p, headerlen);
393 ** Return a whole article to the external module as a string.
396 PY_article(self, args)
397 PyObject *self, *args;
407 if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
410 if (! HISlookup(History, msgid, NULL, NULL, NULL, &token))
411 return Py_BuildValue("s", "");
412 if ((arth = SMretrieve(token, RETR_ALL)) == NULL)
413 return Py_BuildValue("s", "");
414 p = FromWireFmt(arth->data, arth->len, &artlen);
416 art = PyString_FromStringAndSize(p, artlen);
425 ** Python's syslog module isn't compiled in by default. It's easier
426 ** to do it this way, and the switch block looks pretty in a color
430 PY_syslog(self, args)
431 PyObject *self, *args;
439 if (!PyArg_ParseTuple(args, "s#s#",
440 &loglevel, &levellen, &logmsg, &msglen))
444 default: priority = LOG_NOTICE ;
445 case 'd': case 'D': priority = LOG_DEBUG ; break;
446 case 'i': case 'I': priority = LOG_INFO ; break;
447 case 'n': case 'N': priority = LOG_NOTICE ; break;
448 case 'w': case 'W': priority = LOG_WARNING ; break;
449 case 'e': case 'E': priority = LOG_ERR ; break;
450 case 'c': case 'C': priority = LOG_CRIT ; break;
451 case 'a': case 'A': priority = LOG_ALERT ; break;
454 syslog(priority, "python: %s", logmsg);
463 ** Compute a hash digest for a string.
466 PY_hashstring(self, args)
467 PyObject *self, *args;
469 char *instring, *wpos, *p, *q;
470 char *workstring = NULL;
471 int insize, worksize, newsize, i, wasspace;
475 if (!PyArg_ParseTuple(args, "s#|i", &instring, &insize, &lines))
478 /* If a linecount is provided, munge before hashing. */
482 /* chop leading whitespace */
483 for (p=instring ; worksize>0 && isspace(*p) ; p++) {
491 for (p=&wpos[worksize] ; worksize>0 && isspace(*p) ; p--) {
497 /* chop last 3 lines if we have >= 5. From above chop the
498 * last line has no CR so we use 1 less here. */
500 for (i=0, p=wpos+worksize ; i<2 ; p--)
506 /* Compress out multiple whitespace in the trimmed string. We
507 * do a copy because this is probably an original art
509 workstring = memcpy(xmalloc(worksize), wpos, worksize);
510 newsize = wasspace = 0;
513 for (i=0 ; i<worksize ; i++) {
525 worksize = q - workstring;
526 myhash = Hash(workstring, worksize);
530 myhash = Hash(instring, insize);
532 return PyString_FromStringAndSize((const char *)&myhash, sizeof(myhash));
538 ** Make the internal INN module's functions visible to Python.
540 static PyMethodDef INNPyMethods[] = {
541 {"set_filter_hook", PY_set_filter_hook, METH_VARARGS},
542 {"havehist", PY_havehist, METH_VARARGS},
543 {"addhist", PY_addhist, METH_VARARGS},
544 {"cancel", PY_cancel, METH_VARARGS},
545 {"newsgroup", PY_newsgroup, METH_VARARGS},
546 {"head", PY_head, METH_VARARGS},
547 {"article", PY_article, METH_VARARGS},
548 {"syslog", PY_syslog, METH_VARARGS},
549 {"hashstring", PY_hashstring, METH_VARARGS},
556 ** This runs when innd shuts down.
563 if (close_method != NULL) {
564 result = PyObject_CallFunction(close_method, NULL);
572 ** Check that a method exists and is callable. Set a pointer to
573 ** the corresponding PyObject, or NULL if not found.
576 PYdefonemethod(methptr, methname)
580 Py_XDECREF(*methptr);
582 /* We check with HasAttrString() the existence of the method because
583 * otherwise, in case it does not exist, an exception is raised by Python,
584 * although the result of the function is NULL. */
585 if (PyObject_HasAttrString(PYFilterObject, (char *) methname) == 1) {
586 /* Get a pointer to given method. */
587 *methptr = PyObject_GetAttrString(PYFilterObject, (char *) methname);
592 if (*methptr == NULL)
593 syslog(L_NOTICE, "python method %s not found", methname);
594 else if (PyCallable_Check(*methptr) == 0) {
595 syslog(L_ERROR, "python object %s found but not a function", methname);
604 ** Look up the filter methods, so we will know what's available when
605 ** innd wants to call them.
610 PYdefonemethod(&msgid_method, "filter_messageid");
611 PYdefonemethod(&art_method, "filter_art");
612 PYdefonemethod(&mode_method, "filter_mode");
613 PYdefonemethod(&pre_reload_method, "filter_before_reload");
614 PYdefonemethod(&close_method, "filter_close");
620 ** Used by "ctlinnd reload filter.python 'reason'".
625 PyObject *newmodule = NULL;
628 if (!Py_IsInitialized()) {
629 syslog(L_ERROR, "python is not initialized");
633 /* If there is a filter running, let it clean up first. */
634 if (pre_reload_method != NULL) {
635 result = PyObject_CallFunction(pre_reload_method, NULL);
639 /* We need to reimport the module before reloading it because otherwise,
640 * it might not be taken into account by Python.
641 * See Python API documentation:
642 * If a module is syntactically correct but its initialization fails,
643 * the first import statement for it does not bind its name locally,
644 * but does store a (partially initialized) module object in
645 * sys.modules. To reload the module, you must first import it again
646 * (this will bind the name to the partially initialized module object)
647 * before you can reload() it.
649 PYFilterModule = PyImport_ImportModule((char *) _PATH_PYTHON_STARTUP_M);
650 if (PYFilterModule == NULL) {
651 syslog(L_ERROR, "failed to reimport external python module");
654 if ((newmodule = PyImport_ReloadModule(PYFilterModule)) == NULL) {
655 syslog(L_ERROR, "cant reload python filter module");
660 Py_XDECREF(PYFilterModule);
661 PYFilterModule = newmodule;
663 if (PYFilterObject == NULL) {
664 syslog(L_ERROR, "python reload error, filter object not defined");
678 ** Called when innd first starts -- this gets the filters hooked in.
687 setenv("PYTHONPATH", innconf->pathfilter, 1);
690 /* It makes Python sad when its stdout and stderr are closed. */
691 if ((fileno(stdout) == -1) || (fileno(stderr) == -1))
693 ("import sys; sys.stdout=sys.stderr=open('/dev/null', 'a')");
695 if (!Py_IsInitialized ()) {
696 syslog(L_ERROR, "python interpreter NOT initialized");
699 syslog(L_NOTICE, "python interpreter initialized OK");
701 Py_InitModule("INN", INNPyMethods);
703 PYFilterModule = PyImport_ImportModule(_PATH_PYTHON_STARTUP_M);
704 if (PYFilterModule == NULL)
705 syslog(L_ERROR, "failed to import external python module");
707 if (PYFilterObject == NULL) {
708 syslog(L_ERROR, "python filter object is not defined");
713 syslog(L_NOTICE, "defined python methods");
716 /* Grab space for these so we aren't forever recreating them. We also
717 put the body and the line count into PYheaditem, so it needs to be
718 two elements longer than the total number of headers. */
719 PYheaders = PyDict_New();
720 hdrcount = ARRAY_END(ARTheaders) - ARTheaders;
721 PYheaditem = xmalloc((hdrcount + 2) * sizeof(PyObject *));
722 PYheadkey = xmalloc(hdrcount * sizeof(PyObject *));
724 /* Preallocate keys for the article dictionary */
725 for (hp = ARTheaders; hp < ARRAY_END(ARTheaders); hp++)
726 PYheadkey[hp - ARTheaders] = PyString_InternFromString(hp->Name);
727 PYpathkey = PyString_InternFromString("Path");
728 PYlineskey = PyString_InternFromString("__LINES__");
729 PYbodykey = PyString_InternFromString("__BODY__");
732 #endif /* defined(DO_PYTHON) */