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