1 /* $Id: python.c 7893 2008-06-22 10:24:42Z iulius $
3 ** python.c: Embed Python in the style of nnrpd's TCL and Perl stuff
4 ** (authentication and authorization hooks only at this point).
6 ** Written by Ilya Etingof <ilya@glas.net>, 1999.
8 ** This code bases on Python work for innd filtering done by
9 ** G.J. Andruk <meowing@banet.net>. Also it borrows some ideas from
10 ** TCL/Perl work done by Bob Heiney and Christophe Wolfhugel.
12 ** A quick note regarding Python exceptions: functions like
13 ** PyObject_GetAttrString(PyObject *o, const char *attr_name)
14 ** raise an exception when they fail, even though they return NULL.
15 ** And as exceptions accumulate from caller to caller and so on,
16 ** it generates weird issues with Python scripts afterwards. So such
17 ** uses should be checked before. For instance with:
18 ** PyObject_HasAttrString(PyObject *o, const char *attr_name).
24 #include "inn/innconf.h"
26 #include "inn/hashtab.h"
28 #if defined(DO_PYTHON)
32 /* values relate name of hook to array index */
33 #define PYTHONauthen 1
34 #define PYTHONaccess 2
35 #define PYTHONdynamic 3
37 #define PYTHONtypes_max 4
39 /* values relate type of method to array index */
44 #define PYTHONmethods_max 4
46 /* key names for attributes dictionary */
47 #define PYTHONhostname "hostname"
48 #define PYTHONipaddress "ipaddress"
49 #define PYTHONport "port"
50 #define PYTHONinterface "interface"
51 #define PYTHONintipaddr "intipaddr"
52 #define PYTHONintport "intport"
53 #define PYTHONuser "user"
54 #define PYTHONpass "pass"
55 #define PYTHONtype "type"
56 #define PYTHONnewsgroup "newsgroup"
58 /* Max number of items in dictionary to pass to auth methods */
59 #define _PY_MAX_AUTH_ITEM 10
62 /* Pointers to external Python objects */
63 PyObject *PYAuthObject = NULL;
65 /* Dictionary of params to pass to authentication methods */
66 PyObject *PYauthinfo = NULL;
67 PyObject **PYauthitem = NULL;
69 /* Forward declaration */
70 static PyObject *PY_set_auth_hook(PyObject *dummy, PyObject *args);
71 void PY_load_python(void);
72 PyObject* PY_setup(int type, int method, char *file);
73 static const void *file_key(const void *p);
74 static bool file_equal(const void *k, const void *p);
75 static void file_free(void *p);
76 static void file_trav(void *data, void* null);
78 bool PythonLoaded = false;
80 /* structure for storage of attributes for a module file */
81 typedef struct PyFile {
83 bool loaded[PYTHONtypes_max];
84 PyObject *procs[PYTHONtypes_max][PYTHONmethods_max];
87 /* hash for storing files */
90 /* for passing the dynamic module filename from perm.c */
94 ** Authenticate connecting host by username&password.
96 ** Return NNTP reply code as returned by Python method or -1 if method
99 int PY_authenticate(char* file, char *Username, char *Password, char *errorstring, char *newUser) {
100 PyObject *result, *item, *proc;
106 proc = PY_setup(PYTHONauthen, PYTHONmain, file);
108 /* Return if authentication method is not defined */
112 /* Initialize PythonAuthObject with connect method specific items */
115 /* Client hostname */
116 PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
117 PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
119 /* Client IP number */
120 PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
121 PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
123 /* Client port number */
124 PYauthitem[authnum] = PyInt_FromLong(ClientPort);
125 PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
127 /* Server interface the connection comes to */
128 PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
129 PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
131 /* Server IP number */
132 PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
133 PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
135 /* Server port number */
136 PYauthitem[authnum] = PyInt_FromLong(ServerPort);
137 PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
139 /* Username if known */
140 if (Username == NULL) {
141 PYauthitem[authnum] = Py_None;
143 PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
145 PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
147 /* Password if known */
148 if (Password == NULL) {
149 PYauthitem[authnum] = Py_None;
151 PYauthitem[authnum] = PyBuffer_FromMemory(Password, strlen(Password));
153 PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
155 /* Now invoke authenticate method and see if it likes this user */
156 result = PyObject_CallFunction(proc, "O", PYauthinfo);
158 /* Check the response */
159 if (result == NULL || !PyTuple_Check(result)
160 || ((PyTuple_Size(result) != 2) && (PyTuple_Size(result) != 3)))
162 syslog(L_ERROR, "python authenticate method returned wrong result");
163 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
164 ExitWithStats(1, true);
167 /* Get the NNTP response code */
168 item = PyTuple_GetItem(result, 0);
171 if (!PyInt_Check(item))
173 syslog(L_ERROR, "python authenticate method returned bad NNTP response code");
174 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
175 ExitWithStats(1, true);
179 code = PyInt_AS_LONG(item);
181 /* Get the error string */
182 item = PyTuple_GetItem(result, 1);
185 if (!PyString_Check(item))
187 syslog(L_ERROR, "python authenticate method returned bad error string");
188 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
189 ExitWithStats(1, true);
192 /* Store error string */
193 temp = PyString_AS_STRING(item);
194 errorstring = xstrdup(temp);
196 if (PyTuple_Size(result) == 3) {
198 /* Get the username string */
199 item = PyTuple_GetItem(result, 2);
202 if (!PyString_Check(item)) {
203 syslog(L_ERROR, "python authenticate method returned bad username string");
204 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
205 ExitWithStats(1, true);
208 /* Store error string */
209 temp = PyString_AS_STRING(item);
210 newUser = xstrdup(temp);
213 /* Clean up the dictionary object */
214 PyDict_Clear(PYauthinfo);
216 /* Clean up dictionary items */
217 for (i = 0; i < authnum; i++) {
218 if (PYauthitem[i] != Py_None) {
219 Py_DECREF(PYauthitem[i]);
223 /* Log auth result */
224 syslog(L_NOTICE, "python authenticate method succeeded, return code %d, error string %s", code, errorstring);
226 /* Return response code */
231 ** Create an access group based on the values returned by the script in file
234 void PY_access(char* file, struct vector *access_vec, char *Username) {
235 PyObject *result, *key, *value, *proc;
241 proc = PY_setup(PYTHONaccess, PYTHONmain, file);
243 /* Exit if access method is not defined */
245 syslog(L_ERROR, "python access method not defined");
246 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
247 ExitWithStats(1, true);
250 /* Initialize PythonAuthObject with group method specific items */
253 /* Client hostname */
254 PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
255 PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
257 /* Client IP number */
258 PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
259 PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
261 /* Client port number */
262 PYauthitem[authnum] = PyInt_FromLong(ClientPort);
263 PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
265 /* Server interface the connection comes to */
266 PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
267 PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
269 /* Server IP number */
270 PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
271 PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
273 /* Server port number */
274 PYauthitem[authnum] = PyInt_FromLong(ServerPort);
275 PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
278 PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
279 PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
281 /* Password is not known */
282 PYauthitem[authnum] = Py_None;
283 PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
286 * Now invoke newsgroup access method
288 result = PyObject_CallFunction(proc, "O", PYauthinfo);
290 /* Check the response */
291 if (result == NULL || result == Py_None || !PyDict_Check(result)) {
292 syslog(L_ERROR, "python access method returned wrong result -- expected a dictionary");
293 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
294 ExitWithStats(1, true);
297 /* resize vector to dictionary length */
298 vector_resize(access_vec, PyDict_Size(result) - 1);
300 /* store dict values in proper format in access vector */
302 buffer = xmalloc(BIG_BUFFER);
304 while(PyDict_Next(result, &i, &key, &value)) {
305 if (!PyString_Check(key)) {
306 syslog(L_ERROR, "python access method return dictionary key %i not a string", i);
307 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
308 ExitWithStats(1, false);
310 if (!PyString_Check(value)) {
311 syslog(L_ERROR, "python access method return dictionary value %i not a string", i);
312 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
313 ExitWithStats(1, false);
316 strlcpy(buffer, PyString_AsString(key), BIG_BUFFER);
317 strlcat(buffer, ": \"", BIG_BUFFER);
318 strlcat(buffer, PyString_AsString(value), BIG_BUFFER);
319 strlcat(buffer, "\"\n", BIG_BUFFER);
321 vector_add(access_vec, xstrdup(buffer));
326 /* Clean up the dictionary object */
327 PyDict_Clear(PYauthinfo);
328 /* Clean up dictionary items */
329 for (i = 0; i < authnum; i++) {
330 if (PYauthitem[i] != Py_None) {
331 Py_DECREF(PYauthitem[i]);
335 /* Log auth result */
336 syslog(L_NOTICE, "python access method succeeded");
340 ** Initialize dynamic access control code
343 void PY_dynamic_init (char* file) {
344 dynamic_file = xstrdup(file);
345 PY_use_dynamic = true;
350 ** Determine dynamic user access rights to a given newsgroup.
352 ** Return 0 if requested privelege is granted or positive value
353 ** and a reply_message pointer initialized with reply message.
354 ** Return negative value if dynamic method is not defined.
356 int PY_dynamic(char *Username, char *NewsGroup, int PostFlag, char **reply_message) {
357 PyObject *result, *item, *proc;
363 proc = PY_setup(PYTHONdynamic, PYTHONmain, dynamic_file);
365 /* Return if dynamic method is not defined */
369 /* Initialize PythonAuthObject with group method specific items */
372 /* Client hostname */
373 PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
374 PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
376 /* Client IP number */
377 PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
378 PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
380 /* Client port number */
381 PYauthitem[authnum] = PyInt_FromLong(ClientPort);
382 PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
384 /* Server interface the connection comes to */
385 PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
386 PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
388 /* Server IP number */
389 PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
390 PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
392 /* Server port number */
393 PYauthitem[authnum] = PyInt_FromLong(ServerPort);
394 PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
397 PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
398 PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
400 /* Password is not known */
401 PYauthitem[authnum] = Py_None;
402 PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
404 /* Assign authentication type */
405 PYauthitem[authnum] = PyBuffer_FromMemory(PostFlag ? "post" : "read", 4);
406 PyDict_SetItemString(PYauthinfo, PYTHONtype, PYauthitem[authnum++]);
408 /* Newsgroup user tries to access */
409 PYauthitem[authnum] = PyBuffer_FromMemory(NewsGroup, strlen(NewsGroup));
410 PyDict_SetItemString(PYauthinfo, PYTHONnewsgroup, PYauthitem[authnum++]);
413 * Now invoke newsgroup dynamic access method and see if
414 * it likes this user to access this newsgroup.
416 result = PyObject_CallFunction(proc, "O", PYauthinfo);
418 /* Check the response */
419 if (result == NULL || (result != Py_None && !PyString_Check(result)))
421 syslog(L_ERROR, "python dynamic method (%s access) returned wrong result: %s", PostFlag ? "post" : "read", result);
422 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
423 ExitWithStats(1, false);
426 /* Get the response string */
427 if (result == Py_None) {
430 temp = PyString_AS_STRING(result);
431 string = xstrdup(temp);
433 /* Clean up the dictionary object */
434 PyDict_Clear(PYauthinfo);
436 /* Clean up dictionary items */
437 for (i = 0; i < authnum; i++) {
438 if (PYauthitem[i] != Py_None) {
439 Py_DECREF(PYauthitem[i]);
443 /* Log auth result */
444 syslog(L_NOTICE, "python dynamic method (%s access) succeeded, refusion string: %s", PostFlag ? "post" : "read", string == NULL ? "<empty>" : string);
446 /* Initialize reply string */
447 if (reply_message != NULL)
448 *reply_message = string;
451 return string == NULL ? 0 : 1;
456 ** This runs when nnrpd shuts down. If Python is closed and reopened
457 ** in the same process, files and dynamic_file are reused so they
458 ** must point to NULL.
461 PY_close_python(void)
464 hash_traverse(files, file_trav, NULL);
468 if (dynamic_file != NULL) {
475 ** Traversal function for PY_close_python
478 file_trav(void *data, void* null UNUSED)
482 PyObject *result, *func;
484 for (j = 1; j < PYTHONtypes_max; j++) {
485 if (fp->loaded[j] != false) {
486 func = fp->procs[j][PYTHONclose];
488 result = PyObject_CallFunction(func, NULL);
496 ** Python's syslog module isn't compiled in by default. It's easier
497 ** to do it this way, and the switch block looks pretty in a color
501 PY_syslog(PyObject *self UNUSED, PyObject *args)
509 /* Get loglevel and message */
510 if (!PyArg_ParseTuple(args, "s#s#", &loglevel, &levellen, &logmsg, &msglen))
513 /* Assign syslog priority by abbreviated names */
515 default: priority = LOG_NOTICE ;
516 case 'd': case 'D': priority = LOG_DEBUG ; break;
517 case 'i': case 'I': priority = LOG_INFO ; break;
518 case 'n': case 'N': priority = LOG_NOTICE ; break;
519 case 'w': case 'W': priority = LOG_WARNING ; break;
520 case 'e': case 'E': priority = LOG_ERR ; break;
521 case 'c': case 'C': priority = LOG_CRIT ; break;
522 case 'a': case 'A': priority = LOG_ALERT ; break;
525 /* Log the message */
526 syslog(priority, "python: %s", logmsg);
535 ** Make the internal nnrpd module's functions visible to Python.
537 static PyMethodDef nnrpdPyMethods[] = {
538 {"set_auth_hook", PY_set_auth_hook, METH_VARARGS},
539 {"syslog", PY_syslog, METH_VARARGS},
545 ** Called by the external module so it can register itself with nnrpd.
548 PY_set_auth_hook(PyObject *dummy UNUSED, PyObject *args)
550 PyObject *result = NULL;
553 /* set_auth_hook method should return a pointer to nnrpd auth object */
554 if (PyArg_ParseTuple(args, "O:set_auth_hook", &temp)) {
556 Py_XDECREF(PYAuthObject);
562 /* Return a pointer to nnrpd auth method */
567 ** Load the Python interpreter
569 void PY_load_python() {
571 /* add path for nnrpd module */
572 setenv("PYTHONPATH", innconf->pathfilter, 1);
574 /* Load up the interpreter ;-O */
577 /* It makes Python sad when its stdout and stderr are closed. */
578 if ((fileno(stdout) == -1) || (fileno(stderr) == -1))
579 PyRun_SimpleString("import sys; sys.stdout=sys.stderr=open('/dev/null', 'a')");
581 /* See if Python initialized OK */
582 if (!Py_IsInitialized ()) {
583 syslog(L_ERROR, "python interpreter NOT initialized");
588 /* Build a module interface to certain nnrpd functions */
589 (void) Py_InitModule("nnrpd", nnrpdPyMethods);
592 ** Grab space for authinfo dictionary so we aren't forever
595 PYauthinfo = PyDict_New();
596 PYauthitem = xcalloc(_PY_MAX_AUTH_ITEM, sizeof(PyObject *));
598 /* create hash to store file attributes */
600 files = hash_create(4, hash_string, file_key,
601 file_equal, file_free);
605 syslog(L_NOTICE, "python interpreter initialized OK");
610 ** Check that a method exists and is callable. Set up a pointer to
611 ** the corresponding PyObject, or NULL if not found.
614 PYdefonemethod(PyFile *fp, int type, int method, char *methname, int realtype) {
617 methptr = &fp->procs[type][method];
618 /* There is no need to check the existence of methods useless for our realtype. */
619 if (type == realtype) {
621 ** We check with HasAttrString() the existence of the method because
622 ** otherwise, in case it does not exist, an exception is raised by Python,
623 ** although the result of the function is NULL.
625 if (PyObject_HasAttrString(PYAuthObject, (char *) methname) == 1) {
626 /* Get a pointer to given method. */
627 *methptr = PyObject_GetAttrString(PYAuthObject, (char *) methname);
632 /* See if such method is defined */
633 if (*methptr == NULL)
634 syslog(L_NOTICE, "python method %s not found", methname);
636 /* See if it is callable */
637 if (PyCallable_Check(*methptr) == 0) {
638 syslog(L_ERROR, "python object %s found but not a function", methname);
650 ** Look up all the known python methods and set up
651 ** pointers to them so that we could call them from nnrpd.
654 PYdefmethods(PyFile *fp, int realtype)
656 /* Get a reference to authenticate() method */
657 PYdefonemethod(fp, PYTHONauthen, PYTHONmain, "authenticate", realtype);
659 /* Get a reference to authen_init() method */
660 PYdefonemethod(fp, PYTHONauthen, PYTHONinit, "authen_init", realtype);
662 /* Get a reference to authen_close() method */
663 PYdefonemethod(fp, PYTHONauthen, PYTHONclose, "authen_close", realtype);
665 /* Get a reference to access() method */
666 PYdefonemethod(fp, PYTHONaccess, PYTHONmain, "access", realtype);
668 /* Get a reference to access_init() method */
669 PYdefonemethod(fp, PYTHONaccess, PYTHONinit, "access_init", realtype);
671 /* Get a reference to access_close() method */
672 PYdefonemethod(fp, PYTHONaccess, PYTHONclose, "access_close", realtype);
674 /* Get a reference to dynamic() method */
675 PYdefonemethod(fp, PYTHONdynamic, PYTHONmain, "dynamic", realtype);
677 /* Get a reference to dynamic_init() method */
678 PYdefonemethod(fp, PYTHONdynamic, PYTHONinit, "dynamic_init", realtype);
680 /* Get a reference to dynamic_close() method */
681 PYdefonemethod(fp, PYTHONdynamic, PYTHONclose, "dynamic_close", realtype);
686 ** Called when a python hook is needed -- this gets the scripts hooked in.
689 PY_setup(int type, int method, char *file)
695 /* check to see if this file is in files */
696 if (!(hash_lookup(files, file))) {
697 fp = xmalloc(sizeof(PyFile));
698 fp->file = xstrdup(file);
700 for (i = 1; i < PYTHONtypes_max; i++) {
701 fp->loaded[i] = false;
704 /* Load up external module */
705 (void) PyImport_ImportModule(file);
707 /* See if nnrpd auth object is defined in auth module */
708 if (PYAuthObject == NULL) {
709 syslog(L_ERROR, "python auth object is not defined");
710 Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
712 ExitWithStats(1, false);
714 /* Set up pointers to known Python methods */
715 PYdefmethods(fp, type);
717 hash_insert(files, file, fp);
719 if ((!fp->loaded[type]) && (fp->procs[type][PYTHONinit] != NULL)) {
720 result = PyObject_CallFunction(fp->procs[type][PYTHONinit], NULL);
721 if (result != NULL) {
724 fp->loaded[type] = true;
726 return fp->procs[type][method];
732 ** Return the key (filename) from a file struct, used by the hash table.
735 file_key(const void *p)
737 const struct PyFile *f = p;
743 ** Check to see if a provided key matches the key of a PyFile struct,
744 ** used by the hash table.
747 file_equal(const void *k, const void *p)
750 const struct PyFile *f = p;
752 return strcmp(key, f->file) == 0;
756 ** Free a file, used by the hash table.
761 struct PyFile *fp = p;
766 for (i = 1; i < PYTHONtypes_max; i++) {
767 for (j = 1; j < PYTHONmethods_max; j++) {
768 if (fp->procs[i][j] != NULL) {
769 Py_DECREF(fp->procs[i][j]);
777 #endif /* defined(DO_PYTHON) */