1 /*
2 **  python.c: Embed Python in the style of nnrpd's Perl stuff
3 **            (authentication, authorization and dynamic hooks
4 **            only at this point).
5 **
6 **  Written by Ilya Etingof <ilya@glas.net>, 1999.
7 **
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.
11 **
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).
19 */
20 
21 #include "portable/system.h"
22 
23 #ifdef DO_PYTHON
24 
25 /*  Python redefines _POSIX_C_SOURCE, so undef it to suppress warnings. */
26 #    undef _POSIX_C_SOURCE
27 
28 /*  Make "s#" use Py_ssize_t rather than int. */
29 #    define PY_SSIZE_T_CLEAN
30 
31 /*  Python.h must be included after having defined PY_SSIZE_T_CLEAN,
32  *  and before any standard headers are included (because Python may
33  *  define some pre-processor definitions which affect the standard
34  *  headers on some systems). */
35 #    pragma GCC diagnostic ignored "-Wredundant-decls"
36 #    include "Python.h"
37 #    pragma GCC diagnostic warning "-Wredundant-decls"
38 
39 /*  Define Py_ssize_t when it does not exist (Python < 2.5.0). */
40 #    if PY_VERSION_HEX < 0x02050000
41 typedef int Py_ssize_t;
42 #    endif
43 
44 #    if PY_MAJOR_VERSION >= 3
45 #        define PyInt_AS_LONG      PyLong_AS_LONG
46 #        define PyInt_Check        PyLong_Check
47 #        define PyInt_FromLong     PyLong_FromLong
48 #        define PyString_AS_STRING PyUnicode_AsUTF8
49 #        define PyString_AsString  PyUnicode_AsUTF8
50 #        define PyString_Check     PyUnicode_Check
51 #        define PYBUFF_FROMMEMORY(str, len) \
52             PyMemoryView_FromMemory((str), (len), PyBUF_WRITE)
53 #    else
54 #        define PYBUFF_FROMMEMORY(str, len) PyBuffer_FromMemory((str), (len))
55 #    endif
56 
57 #    include "inn/hashtab.h"
58 #    include "inn/innconf.h"
59 #    include "nnrpd.h"
60 
61 /*  Values relate name of hook to array index. */
62 #    define PYTHONauthen  1
63 #    define PYTHONaccess  2
64 #    define PYTHONdynamic 3
65 
66 #    define PYTHONtypes_max 4
67 
68 /*  Values relate type of method to array index. */
69 #    define PYTHONmain  1
70 #    define PYTHONinit  2
71 #    define PYTHONclose 3
72 
73 #    define PYTHONmethods_max 4
74 
75 /*  Key names for attributes dictionary. */
76 #    define PYTHONhostname  "hostname"
77 #    define PYTHONipaddress "ipaddress"
78 #    define PYTHONport      "port"
79 #    define PYTHONinterface "interface"
80 #    define PYTHONintipaddr "intipaddr"
81 #    define PYTHONintport   "intport"
82 #    define PYTHONuser      "user"
83 #    define PYTHONpass      "pass"
84 #    define PYTHONtype      "type"
85 #    define PYTHONnewsgroup "newsgroup"
86 
87 /*  Max number of items in dictionary to pass to auth methods. */
88 #    define _PY_MAX_AUTH_ITEM 10
89 
90 
91 /*  Pointers to external Python objects. */
92 static PyObject *PYAuthObject = NULL;
93 
94 /*  Dictionary of params to pass to authentication methods. */
95 static PyObject *PYauthinfo = NULL;
96 static PyObject **PYauthitem = NULL;
97 
98 /*  Forward declaration. */
99 static PyObject *PY_set_auth_hook(PyObject *dummy, PyObject *args);
100 void PY_load_python(void);
101 PyObject *PY_setup(int type, int method, char *file);
102 static const void *file_key(const void *p);
103 static bool file_equal(const void *k, const void *p);
104 static void file_free(void *p);
105 static void file_trav(void *data, void *null);
106 
107 static bool PythonLoaded = false;
108 
109 /*  Structure for storage of attributes for a module file. */
110 typedef struct PyFile {
111     char *file;
112     bool loaded[PYTHONtypes_max];
113     PyObject *procs[PYTHONtypes_max][PYTHONmethods_max];
114 } PyFile;
115 
116 /*  Hash for storing files. */
117 static struct hash *files;
118 
119 /*  For passing the dynamic module filename from perm.c. */
120 static char *dynamic_file;
121 
122 
123 /*
124 **  Authenticate connecting host by username&password.
125 **
126 **  code contains the NNTP reply code as returned by Python method
127 **  or -1 if method is not defined.
128 */
129 void
PY_authenticate(char * file,char * User,char * Password,int * code,char * errorstring,char * newUser)130 PY_authenticate(char *file, char *User, char *Password, int *code,
131                 char *errorstring, char *newUser)
132 {
133     PyObject *result, *item, *proc;
134     int authnum;
135     int i;
136     const char *temp;
137 
138     PY_load_python();
139     proc = PY_setup(PYTHONauthen, PYTHONmain, file);
140 
141     /* Return if authentication method is not defined. */
142     if (proc == NULL)
143         *code = -1;
144 
145     /* Initialize PythonAuthObject with connect method specific items. */
146     authnum = 0;
147 
148     /* Client hostname. */
149     PYauthitem[authnum] = PYBUFF_FROMMEMORY(Client.host, strlen(Client.host));
150     PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
151 
152     /* Client IP number. */
153     PYauthitem[authnum] = PYBUFF_FROMMEMORY(Client.ip, strlen(Client.ip));
154     PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
155 
156     /* Client port number. */
157     PYauthitem[authnum] = PyInt_FromLong(Client.port);
158     PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
159 
160     /* Server interface the connection comes to. */
161     PYauthitem[authnum] =
162         PYBUFF_FROMMEMORY(Client.serverhost, strlen(Client.serverhost));
163     PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
164 
165     /* Server IP number. */
166     PYauthitem[authnum] =
167         PYBUFF_FROMMEMORY(Client.serverip, strlen(Client.serverip));
168     PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
169 
170     /* Server port number. */
171     PYauthitem[authnum] = PyInt_FromLong(Client.serverport);
172     PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
173 
174     /* Username if known. */
175     if (User == NULL) {
176         PYauthitem[authnum] = Py_None;
177     } else {
178         PYauthitem[authnum] = PYBUFF_FROMMEMORY(User, strlen(User));
179     }
180     PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
181 
182     /* Password if known. */
183     if (Password == NULL) {
184         PYauthitem[authnum] = Py_None;
185     } else {
186         PYauthitem[authnum] = PYBUFF_FROMMEMORY(Password, strlen(Password));
187     }
188     PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
189 
190     /*
191     ** Now invoke authenticate method and see if it likes this user.
192     */
193     result = PyObject_CallFunction(proc, (char *) "O", PYauthinfo);
194 
195     /* Check the response. */
196     if (result == NULL || !PyTuple_Check(result)
197         || ((PyTuple_Size(result) != 2) && (PyTuple_Size(result) != 3))) {
198         syslog(L_ERROR, "python authenticate method returned wrong result");
199         Reply("%d Internal error (2).  Goodbye!\r\n", NNTP_FAIL_TERMINATING);
200         ExitWithStats(1, true);
201     }
202 
203     /* Get the NNTP response code. */
204     item = PyTuple_GetItem(result, 0);
205 
206     /* Check the item. */
207     if (!PyInt_Check(item)) {
208         syslog(L_ERROR,
209                "python authenticate method returned bad NNTP response code");
210         Reply("%d Internal error (2).  Goodbye!\r\n", NNTP_FAIL_TERMINATING);
211         ExitWithStats(1, true);
212     }
213 
214     /* Store the code. */
215     *code = PyInt_AS_LONG(item);
216 
217     /* Get the error string. */
218     item = PyTuple_GetItem(result, 1);
219 
220     /* Check the item. */
221     if (!PyString_Check(item)) {
222         syslog(L_ERROR,
223                "python authenticate method returned bad error string");
224         Reply("%d Internal error (2).  Goodbye!\r\n", NNTP_FAIL_TERMINATING);
225         ExitWithStats(1, true);
226     }
227 
228     /* Store error string. */
229     temp = PyString_AS_STRING(item);
230     strlcpy(errorstring, temp, BIG_BUFFER);
231 
232     if (PyTuple_Size(result) == 3) {
233 
234         /* Get the username string. */
235         item = PyTuple_GetItem(result, 2);
236 
237         /* Check the item. */
238         if (!PyString_Check(item)) {
239             syslog(L_ERROR,
240                    "python authenticate method returned bad username string");
241             Reply("%d Internal error (2).  Goodbye!\r\n",
242                   NNTP_FAIL_TERMINATING);
243             ExitWithStats(1, true);
244         }
245 
246         /* Store user string. */
247         temp = PyString_AS_STRING(item);
248         strlcpy(newUser, temp, BIG_BUFFER);
249     }
250 
251     /* Clean up the dictionary object. */
252     PyDict_Clear(PYauthinfo);
253 
254     /* Clean up dictionary items. */
255     for (i = 0; i < authnum; i++) {
256         if (PYauthitem[i] != Py_None) {
257             Py_DECREF(PYauthitem[i]);
258         }
259     }
260 
261     /* Log auth result. */
262     syslog(L_NOTICE,
263            "python authenticate method succeeded, return code %d, error "
264            "string %s",
265            *code, errorstring);
266 }
267 
268 
269 /*
270 **  Create an access group based on the values returned by the script in file.
271 **
272 **  The access_vec vector is set.
273 **  If the access group cannot be generated, the connection is closed.
274 */
275 void
PY_access(char * file,struct vector * access_vec,char * User)276 PY_access(char *file, struct vector *access_vec, char *User)
277 {
278     PyObject *result, *key, *value, *proc;
279     char *buffer;
280     int authnum;
281     int i;
282     Py_ssize_t pos;
283 
284     PY_load_python();
285     proc = PY_setup(PYTHONaccess, PYTHONmain, file);
286 
287     /* Exit if access method is not defined. */
288     if (proc == NULL) {
289         syslog(L_ERROR, "python access method not defined");
290         Reply("%d Internal error (3).  Goodbye!\r\n", NNTP_FAIL_TERMINATING);
291         ExitWithStats(1, true);
292     }
293 
294     /* Initialize PythonAuthObject with group method specific items. */
295     authnum = 0;
296 
297     /* Client hostname. */
298     PYauthitem[authnum] = PYBUFF_FROMMEMORY(Client.host, strlen(Client.host));
299     PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
300 
301     /* Client IP number. */
302     PYauthitem[authnum] = PYBUFF_FROMMEMORY(Client.ip, strlen(Client.ip));
303     PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
304 
305     /* Client port number. */
306     PYauthitem[authnum] = PyInt_FromLong(Client.port);
307     PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
308 
309     /* Server interface the connection comes to. */
310     PYauthitem[authnum] =
311         PYBUFF_FROMMEMORY(Client.serverhost, strlen(Client.serverhost));
312     PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
313 
314     /* Server IP number. */
315     PYauthitem[authnum] =
316         PYBUFF_FROMMEMORY(Client.serverip, strlen(Client.serverip));
317     PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
318 
319     /* Server port number. */
320     PYauthitem[authnum] = PyInt_FromLong(Client.serverport);
321     PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
322 
323     /* Username. */
324     PYauthitem[authnum] = PYBUFF_FROMMEMORY(User, strlen(User));
325     PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
326 
327     /* Password is not known. */
328     PYauthitem[authnum] = Py_None;
329     PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
330 
331     /*
332     ** Now invoke newsgroup access method.
333     */
334     result = PyObject_CallFunction(proc, (char *) "O", PYauthinfo);
335 
336     /* Check the response. */
337     if (result == NULL || result == Py_None || !PyDict_Check(result)) {
338         syslog(L_ERROR, "python access method returned wrong result -- "
339                         "expected a dictionary");
340         Reply("%d Internal error (2).  Goodbye!\r\n", NNTP_FAIL_TERMINATING);
341         ExitWithStats(1, true);
342     }
343 
344     /* Resize vector to dictionary length. */
345     vector_resize(access_vec, PyDict_Size(result) - 1);
346 
347     /* Store dict values in proper format in access vector. */
348     pos = 0;
349     buffer = xmalloc(BIG_BUFFER);
350 
351     while (PyDict_Next(result, &pos, &key, &value)) {
352         if (!PyString_Check(key)) {
353             syslog(
354                 L_ERROR,
355                 "python access method return dictionary key %ld not a string",
356                 (long int) pos);
357             Reply("%d Internal error (2).  Goodbye!\r\n",
358                   NNTP_FAIL_TERMINATING);
359             ExitWithStats(1, false);
360         }
361         if (!PyString_Check(value)) {
362             syslog(L_ERROR,
363                    "python access method return dictionary value %ld not a "
364                    "string",
365                    (long int) pos);
366             Reply("%d Internal error (2).  Goodbye!\r\n",
367                   NNTP_FAIL_TERMINATING);
368             ExitWithStats(1, false);
369         }
370 
371         strlcpy(buffer, PyString_AsString(key), BIG_BUFFER);
372         strlcat(buffer, ": \"", BIG_BUFFER);
373         strlcat(buffer, PyString_AsString(value), BIG_BUFFER);
374         strlcat(buffer, "\"\n", BIG_BUFFER);
375 
376         vector_add(access_vec, buffer);
377     }
378 
379     free(buffer);
380 
381     /* Clean up the dictionary object. */
382     PyDict_Clear(PYauthinfo);
383 
384     /* Clean up dictionary items. */
385     for (i = 0; i < authnum; i++) {
386         if (PYauthitem[i] != Py_None) {
387             Py_DECREF(PYauthitem[i]);
388         }
389     }
390 
391     /* Log auth result. */
392     syslog(L_NOTICE, "python access method succeeded");
393 }
394 
395 
396 /*
397 **  Initialize dynamic access control code.
398 */
399 void
PY_dynamic_init(char * file)400 PY_dynamic_init(char *file)
401 {
402     if (!access(file, R_OK)) {
403         syslog(L_NOTICE, "pyfilter %s not installed", file);
404         return;
405     }
406 
407     dynamic_file = xstrdup(file);
408     PY_use_dynamic = true;
409 }
410 
411 
412 /*
413 **  Determine dynamic user access rights to a given newsgroup.
414 **
415 **  Return 0 if the requested privilege is granted or a positive value
416 **  and a reply_message pointer initialized with reply message.
417 **  Return negative value if dynamic method is not defined.
418 */
419 int
PY_dynamic(char * User,char * NewsGroup,int PostFlag,char ** reply_message)420 PY_dynamic(char *User, char *NewsGroup, int PostFlag, char **reply_message)
421 {
422     PyObject *result, *proc;
423     char *string;
424     const char *temp;
425     int authnum;
426     int i;
427 
428     PY_load_python();
429     proc = PY_setup(PYTHONdynamic, PYTHONmain, dynamic_file);
430 
431     /* Return if dynamic method is not defined. */
432     if (proc == NULL)
433         return -1;
434 
435     /* Initialize PythonAuthObject with group method specific items. */
436     authnum = 0;
437 
438     /* Client hostname. */
439     PYauthitem[authnum] = PYBUFF_FROMMEMORY(Client.host, strlen(Client.host));
440     PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
441 
442     /* Client IP number. */
443     PYauthitem[authnum] = PYBUFF_FROMMEMORY(Client.ip, strlen(Client.ip));
444     PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
445 
446     /* Client port number. */
447     PYauthitem[authnum] = PyInt_FromLong(Client.port);
448     PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
449 
450     /* Server interface the connection comes to. */
451     PYauthitem[authnum] =
452         PYBUFF_FROMMEMORY(Client.serverhost, strlen(Client.serverhost));
453     PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
454 
455     /* Server IP number. */
456     PYauthitem[authnum] =
457         PYBUFF_FROMMEMORY(Client.serverip, strlen(Client.serverip));
458     PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
459 
460     /* Server port number. */
461     PYauthitem[authnum] = PyInt_FromLong(Client.serverport);
462     PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
463 
464     /* Username. */
465     PYauthitem[authnum] = PYBUFF_FROMMEMORY(User, strlen(User));
466     PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
467 
468     /* Password is not known. */
469     PYauthitem[authnum] = Py_None;
470     PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
471 
472     /* Assign authentication type. */
473     PYauthitem[authnum] =
474         PYBUFF_FROMMEMORY((char *) (PostFlag ? "post" : "read"), 4);
475     PyDict_SetItemString(PYauthinfo, PYTHONtype, PYauthitem[authnum++]);
476 
477     /* Newsgroup user tries to access. */
478     PYauthitem[authnum] = PYBUFF_FROMMEMORY(NewsGroup, strlen(NewsGroup));
479     PyDict_SetItemString(PYauthinfo, PYTHONnewsgroup, PYauthitem[authnum++]);
480 
481     /*
482     ** Now invoke newsgroup dynamic access method and see if
483     ** it likes this user to access this newsgroup.
484     */
485     result = PyObject_CallFunction(proc, (char *) "O", PYauthinfo);
486 
487     /* Check the response. */
488     if (result == NULL || (result != Py_None && !PyString_Check(result))) {
489         syslog(L_ERROR,
490                "python dynamic method (%s access) returned wrong result",
491                PostFlag ? "post" : "read");
492         Reply("%d Internal error (2).  Goodbye!\r\n", NNTP_FAIL_TERMINATING);
493         ExitWithStats(1, false);
494     }
495 
496     /* Get the response string. */
497     if (result == Py_None) {
498         string = NULL;
499     } else {
500         temp = PyString_AS_STRING(result);
501         string = xstrdup(temp);
502     }
503 
504     /* Clean up the dictionary object. */
505     PyDict_Clear(PYauthinfo);
506 
507     /* Clean up dictionary items. */
508     for (i = 0; i < authnum; i++) {
509         if (PYauthitem[i] != Py_None) {
510             Py_DECREF(PYauthitem[i]);
511         }
512     }
513 
514     /* Log auth result. */
515     syslog(L_NOTICE,
516            "python dynamic method (%s access) succeeded, refuse string: %s",
517            PostFlag ? "post" : "read", string == NULL ? "<empty>" : string);
518 
519     /* Initialize reply string. */
520     if (reply_message != NULL)
521         *reply_message = string;
522 
523     /* Return result. */
524     return string == NULL ? 0 : 1;
525 }
526 
527 
528 /*
529 **  This runs when nnrpd shuts down.  If Python is closed and reopened
530 **  in the same process, files and dynamic_file are reused so they
531 **  must point to NULL.
532 */
533 void
PY_close_python(void)534 PY_close_python(void)
535 {
536     if (files != NULL) {
537         hash_traverse(files, file_trav, NULL);
538         hash_free(files);
539         files = NULL;
540     }
541     if (dynamic_file != NULL) {
542         free(dynamic_file);
543         dynamic_file = NULL;
544     }
545 }
546 
547 
548 /*
549 **  Traversal function for PY_close_python.
550 */
551 void
file_trav(void * data,void * null UNUSED)552 file_trav(void *data, void *null UNUSED)
553 {
554     PyFile *fp = data;
555     int j;
556     PyObject *result, *func;
557 
558     for (j = 1; j < PYTHONtypes_max; j++) {
559         if (fp->loaded[j] != false) {
560             func = fp->procs[j][PYTHONclose];
561             if (func != NULL) {
562                 result = PyObject_CallFunction(func, NULL);
563                 Py_XDECREF(result);
564             }
565         }
566     }
567 }
568 
569 
570 /*
571 **  Python's syslog module isn't compiled in by default.  It's easier
572 **  to do it this way, and the switch block looks pretty in a color
573 **  editor).
574 */
575 static PyObject *
PY_syslog(PyObject * self UNUSED,PyObject * args)576 PY_syslog(PyObject *self UNUSED, PyObject *args)
577 {
578     char *loglevel;
579     Py_ssize_t levellen;
580     char *logmsg;
581     Py_ssize_t msglen;
582     int priority;
583 
584     /* Get loglevel and message. */
585     if (!PyArg_ParseTuple(args, (char *) "s#s#", &loglevel, &levellen, &logmsg,
586                           &msglen))
587         return NULL;
588 
589     /* Assign syslog priority by abbreviated names. */
590     /* clang-format off */
591     switch (*loglevel) {
592     case 'd': case 'D': priority = LOG_DEBUG;   break;
593     case 'i': case 'I': priority = LOG_INFO;    break;
594     case 'n': case 'N': priority = LOG_NOTICE;  break;
595     case 'w': case 'W': priority = LOG_WARNING; break;
596     case 'e': case 'E': priority = LOG_ERR;     break;
597     case 'c': case 'C': priority = LOG_CRIT;    break;
598     case 'a': case 'A': priority = LOG_ALERT;   break;
599     default:            priority = LOG_NOTICE;
600     }
601     /* clang-format on */
602 
603     /* Log the message. */
604     syslog(priority, "python: %s", logmsg);
605 
606     /* Return None. */
607     Py_INCREF(Py_None);
608     return Py_None;
609 }
610 
611 
612 /*
613 **  Make the internal nnrpd module's functions visible to Python.  Python
614 **  annoyingly doesn't use const where appropriate in its structure
615 **  definitions, so we have to add casts for all of the string parameters that
616 **  we're initializing with constant strings.
617 */
618 #    define METHOD(name, func, flags, help)                   \
619         {                                                     \
620             (char *) (name), (func), (flags), (char *) (help) \
621         }
622 
623 /* clang-format off */
624 static PyMethodDef nnrpdPyMethods[] = {
625     METHOD("set_auth_hook", PY_set_auth_hook, METH_VARARGS, ""),
626     METHOD("syslog", PY_syslog, METH_VARARGS, ""),
627     METHOD(NULL, NULL, 0, "")};
628 /* clang-format on */
629 
630 #    if PY_MAJOR_VERSION >= 3
631 static struct PyModuleDef nnrpdPyModule = {
632     PyModuleDef_HEAD_INIT,               /* m_base */
633     (char *) "nnrpd",                    /* m_name */
634     (char *) "nnrpd Python filter hook", /* m_doc */
635     -1,                                  /* m_size */
636     nnrpdPyMethods,                      /* m_methods */
637     NULL,                                /* m_slots */
638     NULL,                                /* m_traverse */
639     NULL,                                /* m_clear */
640     NULL,                                /* m_free */
641 };
642 
643 PyMODINIT_FUNC
PyInit_nnrpd(void)644 PyInit_nnrpd(void)
645 {
646     PyObject *module = PyModule_Create(&nnrpdPyModule);
647 
648     if (module == NULL)
649         syslog(L_ERROR, "failed to create nnrpd python module");
650 
651     return module;
652 }
653 #    else
654 void
PyInit_nnrpd(void)655 PyInit_nnrpd(void)
656 {
657     if (Py_InitModule3((char *) "nnrpd", nnrpdPyMethods,
658                        (char *) "nnrpd Python filter hook")
659         == NULL)
660         syslog(L_ERROR, "failed to initialize nnrpd python module");
661 }
662 #    endif
663 
664 
665 /*
666 **  Called by the external module so it can register itself with nnrpd.
667 */
668 static PyObject *
PY_set_auth_hook(PyObject * dummy UNUSED,PyObject * args)669 PY_set_auth_hook(PyObject *dummy UNUSED, PyObject *args)
670 {
671     PyObject *result = NULL;
672     PyObject *temp;
673 
674     /* set_auth_hook method should return a pointer to nnrpd auth object. */
675     if (PyArg_ParseTuple(args, (char *) "O:set_auth_hook", &temp)) {
676         Py_XINCREF(temp);
677         Py_XDECREF(PYAuthObject);
678         PYAuthObject = temp;
679         Py_INCREF(Py_None);
680         result = Py_None;
681     }
682 
683     /* Return a pointer to nnrpd auth method. */
684     return result;
685 }
686 
687 
688 /*
689 **  Load the Python interpreter.
690 */
691 void
PY_load_python(void)692 PY_load_python(void)
693 {
694     if (!PythonLoaded) {
695         /*
696         ** Add path for nnrpd module.  The environment variable PYTHONPATH
697         ** does it; one can also append innconf->pathfilter to sys.path once
698         ** Python has been initialized.
699         */
700         setenv("PYTHONPATH", innconf->pathfilter, 1);
701 
702         /* Build a module interface to certain nnrpd functions. */
703         PyImport_AppendInittab("nnrpd", &PyInit_nnrpd);
704 
705         /* Load up the interpreter ;-O */
706         Py_Initialize();
707 
708         /* It makes Python sad when its stdout or stderr are closed. */
709         if ((fileno(stdout) == -1) || (fileno(stderr) == -1))
710             PyRun_SimpleString(
711                 "import sys; sys.stdout=sys.stderr=open('/dev/null', 'a')");
712 
713         /* See if Python initialized OK. */
714         if (!Py_IsInitialized()) {
715             syslog(L_ERROR, "python interpreter NOT initialized");
716             return;
717         }
718 
719         /*
720         ** Grab space for authinfo dictionary so we aren't forever
721         ** recreating them.
722         */
723         PYauthinfo = PyDict_New();
724         PYauthitem = xcalloc(_PY_MAX_AUTH_ITEM, sizeof(PyObject *));
725 
726         /* Create hash to store file attributes. */
727         files = hash_create(4, hash_string, file_key, file_equal, file_free);
728 
729         PythonLoaded = true;
730 
731         syslog(L_NOTICE, "python interpreter initialized OK");
732     }
733 }
734 
735 
736 /*
737 **  Check that a method exists and is callable.  Set up a pointer to
738 **  the corresponding PyObject, or NULL if not found.
739 */
740 static void
PYdefonemethod(PyFile * fp,int type,int method,const char * methname,int realtype)741 PYdefonemethod(PyFile *fp, int type, int method, const char *methname,
742                int realtype)
743 {
744     PyObject **methptr;
745 
746     methptr = &fp->procs[type][method];
747 
748     /* There is no need to check the existence of methods useless for our
749      * realtype. */
750     if (type == realtype) {
751         /*
752         ** We check with HasAttrString() the existence of the method because
753         ** otherwise, in case it does not exist, an exception is raised by
754         *Python,
755         ** although the result of the function is NULL.
756         */
757         if (PyObject_HasAttrString(PYAuthObject, (char *) methname) == 1) {
758             /* Get a pointer to given method. */
759             *methptr = PyObject_GetAttrString(PYAuthObject, (char *) methname);
760         } else {
761             *methptr = NULL;
762         }
763 
764         /* See if such method is defined. */
765         if (*methptr == NULL)
766             syslog(L_NOTICE, "python method %s not found", methname);
767         else {
768             /* See if it is callable. */
769             if (PyCallable_Check(*methptr) == 0) {
770                 syslog(L_ERROR, "python object %s found but not a function",
771                        methname);
772                 Py_DECREF(*methptr);
773                 *methptr = NULL;
774             }
775         }
776     } else {
777         *methptr = NULL;
778     }
779 }
780 
781 
782 /*
783 **  Look up all the known python methods and set up
784 **  pointers to them so that we could call them from nnrpd.
785 */
786 static void
PYdefmethods(PyFile * fp,int realtype)787 PYdefmethods(PyFile *fp, int realtype)
788 {
789     /* Get a reference to authenticate() method. */
790     PYdefonemethod(fp, PYTHONauthen, PYTHONmain, "authenticate", realtype);
791 
792     /* Get a reference to authen_init() method. */
793     PYdefonemethod(fp, PYTHONauthen, PYTHONinit, "authen_init", realtype);
794 
795     /* Get a reference to authen_close() method. */
796     PYdefonemethod(fp, PYTHONauthen, PYTHONclose, "authen_close", realtype);
797 
798     /* Get a reference to access() method. */
799     PYdefonemethod(fp, PYTHONaccess, PYTHONmain, "access", realtype);
800 
801     /* Get a reference to access_init() method. */
802     PYdefonemethod(fp, PYTHONaccess, PYTHONinit, "access_init", realtype);
803 
804     /* Get a reference to access_close() method. */
805     PYdefonemethod(fp, PYTHONaccess, PYTHONclose, "access_close", realtype);
806 
807     /* Get a reference to dynamic() method. */
808     PYdefonemethod(fp, PYTHONdynamic, PYTHONmain, "dynamic", realtype);
809 
810     /* Get a reference to dynamic_init() method. */
811     PYdefonemethod(fp, PYTHONdynamic, PYTHONinit, "dynamic_init", realtype);
812 
813     /* Get a reference to dynamic_close() method. */
814     PYdefonemethod(fp, PYTHONdynamic, PYTHONclose, "dynamic_close", realtype);
815 }
816 
817 
818 /*
819 **  Called when a python hook is needed -- this gets the scripts hooked in.
820 */
821 PyObject *
PY_setup(int type,int method,char * file)822 PY_setup(int type, int method, char *file)
823 {
824     int i;
825     PyFile *fp;
826     PyObject *result;
827 
828     /* Check to see if this file is in files. */
829     if ((fp = hash_lookup(files, file)) == NULL) {
830         fp = xmalloc(sizeof(PyFile));
831         fp->file = xstrdup(file);
832 
833         for (i = 1; i < PYTHONtypes_max; i++) {
834             fp->loaded[i] = false;
835         }
836 
837         /* Load up external module. */
838         (void) PyImport_ImportModule(file);
839 
840         /* See if nnrpd auth object is defined in auth module. */
841         if (PYAuthObject == NULL) {
842             syslog(L_ERROR, "python auth object is not defined");
843             Reply("%d Internal error (3).  Goodbye!\r\n",
844                   NNTP_FAIL_TERMINATING);
845             PY_close_python();
846             ExitWithStats(1, false);
847         } else {
848             /* Set up pointers to known Python methods. */
849             PYdefmethods(fp, type);
850         }
851         hash_insert(files, file, fp);
852 
853         if ((!fp->loaded[type]) && (fp->procs[type][PYTHONinit] != NULL)) {
854             result = PyObject_CallFunction(fp->procs[type][PYTHONinit], NULL);
855             if (result != NULL) {
856                 Py_XDECREF(result);
857             }
858             fp->loaded[type] = true;
859         }
860     }
861     return fp->procs[type][method];
862 }
863 
864 
865 /*
866 **  Return the key (filename) from a file struct, used by the hash table.
867 */
868 static const void *
file_key(const void * p)869 file_key(const void *p)
870 {
871     const struct PyFile *f = p;
872 
873     return f->file;
874 }
875 
876 
877 /*
878 **  Check to see if a provided key matches the key of a PyFile struct,
879 **  used by the hash table.
880 */
881 static bool
file_equal(const void * k,const void * p)882 file_equal(const void *k, const void *p)
883 {
884     const char *key = k;
885     const struct PyFile *f = p;
886 
887     return strcmp(key, f->file) == 0;
888 }
889 
890 
891 /*
892 **  Free a file, used by the hash table.
893 */
894 static void
file_free(void * p)895 file_free(void *p)
896 {
897     struct PyFile *fp = p;
898     int i, j;
899 
900     free(fp->file);
901 
902     for (i = 1; i < PYTHONtypes_max; i++) {
903         for (j = 1; j < PYTHONmethods_max; j++) {
904             if (fp->procs[i][j] != NULL) {
905                 Py_DECREF(fp->procs[i][j]);
906             }
907         }
908     }
909 
910     free(fp);
911 }
912 
913 #endif /* defined(DO_PYTHON) */
914