chiark / gitweb /
more manpage, remove -h option
[innduct.git] / nnrpd / python.c
1 /*  $Id: python.c 7893 2008-06-22 10:24:42Z iulius $
2 **
3 **  python.c: Embed Python in the style of nnrpd's TCL and Perl stuff
4 **            (authentication and authorization hooks 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 "config.h"
22 #include "clibrary.h"
23
24 #include "inn/innconf.h"
25 #include "nnrpd.h"
26 #include "inn/hashtab.h"
27
28 #if defined(DO_PYTHON)
29
30 #include "Python.h"
31
32 /* values relate name of hook to array index */
33 #define PYTHONauthen           1
34 #define PYTHONaccess           2
35 #define PYTHONdynamic          3
36
37 #define PYTHONtypes_max        4
38
39 /* values relate type of method to array index */
40 #define PYTHONmain             1
41 #define PYTHONinit             2
42 #define PYTHONclose            3
43
44 #define PYTHONmethods_max      4
45
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"
57
58 /* Max number of items in dictionary to pass to auth methods */
59 #define _PY_MAX_AUTH_ITEM       10
60
61
62 /* Pointers to external Python objects */
63 PyObject        *PYAuthObject = NULL;
64
65 /* Dictionary of params to pass to authentication methods */
66 PyObject        *PYauthinfo = NULL;
67 PyObject        **PYauthitem = NULL;
68
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);
77
78 bool   PythonLoaded = false;
79
80 /* structure for storage of attributes for a module file */
81 typedef struct PyFile {
82   char          *file;  
83   bool          loaded[PYTHONtypes_max];
84   PyObject      *procs[PYTHONtypes_max][PYTHONmethods_max];
85 } PyFile;
86
87 /* hash for storing files */
88 struct hash *files;
89
90 /* for passing the dynamic module filename from perm.c */
91 char*    dynamic_file;
92
93 /*
94 ** Authenticate connecting host by username&password.
95 **
96 ** Return NNTP reply code as returned by Python method or -1 if method
97 ** is not defined.
98 */
99 int PY_authenticate(char* file, char *Username, char *Password, char *errorstring, char *newUser) {
100     PyObject    *result, *item, *proc;
101     int         authnum;
102     int         code, i;
103     char        *temp;
104
105     PY_load_python();
106     proc = PY_setup(PYTHONauthen, PYTHONmain, file);
107
108     /* Return if authentication method is not defined */
109     if (proc == NULL)
110         return -1;
111
112     /* Initialize PythonAuthObject with connect method specific items */
113     authnum = 0;
114
115     /* Client hostname */
116     PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
117     PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
118
119     /* Client IP number */
120     PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
121     PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
122
123     /* Client port number */
124     PYauthitem[authnum] = PyInt_FromLong(ClientPort);
125     PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
126
127     /* Server interface the connection comes to */
128     PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
129     PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
130
131     /* Server IP number */
132     PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
133     PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
134
135     /* Server port number */
136     PYauthitem[authnum] = PyInt_FromLong(ServerPort);
137     PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
138
139     /* Username if known */
140     if (Username == NULL) {
141         PYauthitem[authnum] = Py_None;
142     } else {
143         PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
144     }
145     PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
146
147     /* Password if known */
148     if (Password == NULL) {
149         PYauthitem[authnum] = Py_None;
150     } else {
151         PYauthitem[authnum] = PyBuffer_FromMemory(Password, strlen(Password));
152     }
153     PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
154
155     /* Now invoke authenticate method and see if it likes this user */
156     result = PyObject_CallFunction(proc, "O", PYauthinfo);
157
158     /* Check the response */
159     if (result == NULL || !PyTuple_Check(result) 
160         || ((PyTuple_Size(result) != 2) && (PyTuple_Size(result) != 3)))
161     {
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);
165     }
166
167     /* Get the NNTP response code */
168     item = PyTuple_GetItem(result, 0);
169
170     /* Check the item */
171     if (!PyInt_Check(item))
172     {
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);
176     }
177
178     /* Store the code */
179     code = PyInt_AS_LONG(item);
180
181     /* Get the error string */
182     item = PyTuple_GetItem(result, 1);
183
184     /* Check the item */
185     if (!PyString_Check(item))
186     {
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);
190     }
191
192     /* Store error string */
193     temp = PyString_AS_STRING(item);
194     errorstring = xstrdup(temp);
195     
196     if (PyTuple_Size(result) == 3) {
197         
198         /* Get the username string */
199         item = PyTuple_GetItem(result, 2);
200         
201         /* Check the item */
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);
206         }
207
208         /* Store error string */
209         temp = PyString_AS_STRING(item);
210         newUser = xstrdup(temp);
211     }
212
213     /* Clean up the dictionary object */
214     PyDict_Clear(PYauthinfo);
215
216     /* Clean up dictionary items */
217     for (i = 0; i < authnum; i++) {
218         if (PYauthitem[i] != Py_None) {
219             Py_DECREF(PYauthitem[i]);
220         }
221     }
222
223     /* Log auth result */
224     syslog(L_NOTICE, "python authenticate method succeeded, return code %d, error string %s", code, errorstring);
225
226     /* Return response code */
227     return code;
228 }
229
230 /*
231 ** Create an access group based on the values returned by the script in file
232 **
233 */
234 void PY_access(char* file, struct vector *access_vec, char *Username) {
235     PyObject    *result, *key, *value, *proc;
236     char        *buffer;
237     int         authnum;
238     int         i;
239
240     PY_load_python();
241     proc = PY_setup(PYTHONaccess, PYTHONmain, file);
242
243     /* Exit if access method is not defined */
244     if (proc == NULL) {
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);
248      }
249  
250     /* Initialize PythonAuthObject with group method specific items */
251     authnum = 0;
252
253     /* Client hostname */
254     PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
255     PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
256
257     /* Client IP number */
258     PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
259     PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
260
261     /* Client port number */
262     PYauthitem[authnum] = PyInt_FromLong(ClientPort);
263     PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
264
265     /* Server interface the connection comes to */
266     PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
267     PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
268
269     /* Server IP number */
270     PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
271     PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
272
273     /* Server port number */
274     PYauthitem[authnum] = PyInt_FromLong(ServerPort);
275     PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
276
277     /* Username */
278     PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
279     PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
280  
281     /* Password is not known */
282     PYauthitem[authnum] = Py_None;
283     PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
284
285     /*
286      * Now invoke newsgroup access method
287      */
288     result = PyObject_CallFunction(proc, "O", PYauthinfo);
289
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);
295      }
296  
297     /* resize vector to dictionary length */
298     vector_resize(access_vec, PyDict_Size(result) - 1);
299
300     /* store dict values in proper format in access vector */
301     i = 0;
302     buffer = xmalloc(BIG_BUFFER);
303
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);
309         }
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);
314         }
315
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);
320
321         vector_add(access_vec, xstrdup(buffer));
322     }
323
324     free(buffer);
325
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]);
332         }
333     }
334
335     /* Log auth result */
336     syslog(L_NOTICE, "python access method succeeded");
337 }
338
339 /*
340 ** Initialize dynamic access control code
341 */
342
343 void PY_dynamic_init (char* file) {
344   dynamic_file = xstrdup(file);
345   PY_use_dynamic = true;
346 }
347
348
349 /*
350 ** Determine dynamic user access rights to a given newsgroup.
351 **
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.
355 */
356 int PY_dynamic(char *Username, char *NewsGroup, int PostFlag, char **reply_message) {
357     PyObject    *result, *item, *proc;
358     char        *string, *temp;
359     int         authnum;
360     int         i;
361
362     PY_load_python();
363     proc = PY_setup(PYTHONdynamic, PYTHONmain, dynamic_file);
364
365     /* Return if dynamic method is not defined */
366     if (proc == NULL)
367         return -1;
368
369     /* Initialize PythonAuthObject with group method specific items */
370     authnum = 0;
371
372     /* Client hostname */
373     PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
374     PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
375
376     /* Client IP number */
377     PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
378     PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
379
380     /* Client port number */
381     PYauthitem[authnum] = PyInt_FromLong(ClientPort);
382     PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
383
384     /* Server interface the connection comes to */
385     PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
386     PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
387
388     /* Server IP number */
389     PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
390     PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
391
392     /* Server port number */
393     PYauthitem[authnum] = PyInt_FromLong(ServerPort);
394     PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
395     
396     /* Username */
397     PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
398     PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
399     
400     /* Password is not known */
401     PYauthitem[authnum] = Py_None;
402     PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
403
404     /* Assign authentication type */
405     PYauthitem[authnum] = PyBuffer_FromMemory(PostFlag ? "post" : "read", 4);
406     PyDict_SetItemString(PYauthinfo, PYTHONtype, PYauthitem[authnum++]);
407  
408     /* Newsgroup user tries to access */
409     PYauthitem[authnum] = PyBuffer_FromMemory(NewsGroup, strlen(NewsGroup));
410     PyDict_SetItemString(PYauthinfo, PYTHONnewsgroup,  PYauthitem[authnum++]);
411     
412     /*
413      * Now invoke newsgroup dynamic access method and see if
414      * it likes this user to access this newsgroup.
415      */
416     result = PyObject_CallFunction(proc, "O", PYauthinfo);
417
418     /* Check the response */
419     if (result == NULL || (result != Py_None && !PyString_Check(result)))
420     {
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);
424     }
425
426     /* Get the response string */
427     if (result == Py_None) {
428         string = NULL;
429     } else {
430         temp = PyString_AS_STRING(result);
431         string = xstrdup(temp);
432     }
433     /* Clean up the dictionary object */
434     PyDict_Clear(PYauthinfo);
435
436     /* Clean up dictionary items */
437     for (i = 0; i < authnum; i++) {
438         if (PYauthitem[i] != Py_None) {
439             Py_DECREF(PYauthitem[i]);
440         }
441     }
442
443     /* Log auth result */
444     syslog(L_NOTICE, "python dynamic method (%s access) succeeded, refusion string: %s", PostFlag ? "post" : "read", string == NULL ? "<empty>" : string);
445
446     /* Initialize reply string */
447     if (reply_message != NULL)
448         *reply_message = string;
449     
450     /* Return result */
451     return string == NULL ? 0 : 1;
452 }
453
454
455 /*
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.
459 */
460 void
461 PY_close_python(void)
462 {
463     if (files != NULL) {
464         hash_traverse(files, file_trav, NULL);
465         hash_free(files);
466         files = NULL;
467     }
468     if (dynamic_file != NULL) {
469         free(dynamic_file);
470         dynamic_file = NULL;
471     }
472 }
473
474 /*
475 ** Traversal function for PY_close_python
476 */
477 void
478 file_trav(void *data, void* null UNUSED)
479 {
480     PyFile *fp = data;
481     int j;
482     PyObject    *result, *func;
483
484     for (j = 1; j < PYTHONtypes_max; j++) {
485         if (fp->loaded[j] != false) {
486             func = fp->procs[j][PYTHONclose];
487               if (func != NULL) {
488                   result = PyObject_CallFunction(func, NULL);
489                   Py_XDECREF(result);
490               }
491         }
492     }
493 }
494
495 /*
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
498 **  editor).
499 */
500 static PyObject *
501 PY_syslog(PyObject *self UNUSED, PyObject *args)
502 {
503     char        *loglevel;
504     int         levellen;
505     char        *logmsg;
506     int         msglen;
507     int         priority;
508
509     /* Get loglevel and message */
510     if (!PyArg_ParseTuple(args, "s#s#", &loglevel, &levellen, &logmsg, &msglen))
511         return NULL;
512
513     /* Assign syslog priority by abbreviated names */
514     switch (*loglevel) {
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;
523     }
524
525     /* Log the message */
526     syslog(priority, "python: %s", logmsg);
527
528     /* Return None */
529     Py_INCREF(Py_None);
530     return Py_None;
531 }
532
533
534 /*
535 **  Make the internal nnrpd module's functions visible to Python.
536 */
537 static PyMethodDef nnrpdPyMethods[] = {
538     {"set_auth_hook",   PY_set_auth_hook,       METH_VARARGS},
539     {"syslog",          PY_syslog,              METH_VARARGS},
540     {NULL,              NULL}
541 };
542
543
544 /*
545 **  Called by the external module so it can register itself with nnrpd.
546 */
547 static PyObject *
548 PY_set_auth_hook(PyObject *dummy UNUSED, PyObject *args)
549 {
550     PyObject    *result = NULL;
551     PyObject    *temp;
552
553     /* set_auth_hook method should return a pointer to nnrpd auth object */
554     if (PyArg_ParseTuple(args, "O:set_auth_hook", &temp)) {
555         Py_XINCREF(temp);
556         Py_XDECREF(PYAuthObject);
557         PYAuthObject = temp;
558         Py_INCREF(Py_None);
559         result = Py_None;
560     }
561
562     /* Return a pointer to nnrpd auth method */
563     return result;
564 }
565
566 /*
567 ** Load the Python interpreter
568 */
569 void PY_load_python() {
570     if (!PythonLoaded) {
571         /* add path for nnrpd module */    
572         setenv("PYTHONPATH", innconf->pathfilter, 1);
573
574         /* Load up the interpreter ;-O */
575         Py_Initialize();
576     
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')");
580    
581         /* See if Python initialized OK */
582         if (!Py_IsInitialized ()) {
583             syslog(L_ERROR, "python interpreter NOT initialized");
584             return;
585         }
586
587
588         /* Build a module interface to certain nnrpd functions */
589         (void) Py_InitModule("nnrpd", nnrpdPyMethods);
590
591         /*
592         ** Grab space for authinfo dictionary so we aren't forever
593         ** recreating them.
594         */
595         PYauthinfo = PyDict_New();
596         PYauthitem = xcalloc(_PY_MAX_AUTH_ITEM, sizeof(PyObject *));
597
598         /* create hash to store file attributes */
599         
600         files = hash_create(4, hash_string, file_key,
601                             file_equal, file_free);
602
603         PythonLoaded = true;
604
605         syslog(L_NOTICE, "python interpreter initialized OK");
606     }
607 }
608
609 /*
610 **  Check that a method exists and is callable.  Set up a pointer to
611 **  the corresponding PyObject, or NULL if not found.
612 */
613 void
614 PYdefonemethod(PyFile *fp, int type, int method, char *methname, int realtype) {
615     PyObject **methptr;
616
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) {
620         /*
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.
624         */
625         if (PyObject_HasAttrString(PYAuthObject, (char *) methname) == 1) {
626             /* Get a pointer to given method. */
627             *methptr = PyObject_GetAttrString(PYAuthObject, (char *) methname);
628         } else {
629             *methptr = NULL;
630         }
631
632         /* See if such method is defined */
633         if (*methptr == NULL)
634             syslog(L_NOTICE, "python method %s not found", methname);
635         else {
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);
639                 Py_DECREF(*methptr);
640                 *methptr = NULL;
641             }
642         }
643     } else {
644         *methptr = NULL;
645     }
646 }
647
648
649 /*
650 **  Look up all the known python methods and set up
651 **  pointers to them so that we could call them from nnrpd.
652 */
653 void
654 PYdefmethods(PyFile *fp, int realtype)
655 {
656     /* Get a reference to authenticate() method */
657     PYdefonemethod(fp, PYTHONauthen, PYTHONmain, "authenticate", realtype);
658
659     /* Get a reference to authen_init() method */
660     PYdefonemethod(fp, PYTHONauthen, PYTHONinit, "authen_init", realtype);
661     
662     /* Get a reference to authen_close() method */
663     PYdefonemethod(fp, PYTHONauthen, PYTHONclose, "authen_close", realtype);
664
665     /* Get a reference to access() method */
666     PYdefonemethod(fp, PYTHONaccess, PYTHONmain, "access", realtype);
667     
668     /* Get a reference to access_init() method */
669     PYdefonemethod(fp, PYTHONaccess, PYTHONinit, "access_init", realtype);
670     
671     /* Get a reference to access_close() method */
672     PYdefonemethod(fp, PYTHONaccess, PYTHONclose, "access_close", realtype);
673     
674     /* Get a reference to dynamic() method */
675     PYdefonemethod(fp, PYTHONdynamic, PYTHONmain, "dynamic", realtype);
676     
677     /* Get a reference to dynamic_init() method */
678     PYdefonemethod(fp, PYTHONdynamic, PYTHONinit, "dynamic_init", realtype);
679     
680     /* Get a reference to dynamic_close() method */
681     PYdefonemethod(fp, PYTHONdynamic, PYTHONclose, "dynamic_close", realtype);
682 }
683
684
685 /*
686 **  Called when a python hook is needed -- this gets the scripts hooked in.
687 */
688 PyObject*
689 PY_setup(int type, int method, char *file)
690 {
691     int  i;
692     PyFile *fp;
693     PyObject    *result;
694
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);
699
700         for (i = 1; i < PYTHONtypes_max; i++) {
701             fp->loaded[i] = false;
702         }
703         
704         /* Load up external module */
705         (void) PyImport_ImportModule(file);
706
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);
711             PY_close_python();
712             ExitWithStats(1, false);
713         } else {
714             /* Set up pointers to known Python methods */
715             PYdefmethods(fp, type);
716         }
717         hash_insert(files, file, fp);
718
719         if ((!fp->loaded[type]) && (fp->procs[type][PYTHONinit] != NULL)) {
720             result = PyObject_CallFunction(fp->procs[type][PYTHONinit], NULL);
721             if (result != NULL) {
722                 Py_XDECREF(result);
723             }
724             fp->loaded[type] = true;
725         }
726         return fp->procs[type][method];
727     }
728     return NULL;
729 }
730
731 /*
732 **  Return the key (filename) from a file struct, used by the hash table.
733 */
734 static const void *
735 file_key(const void *p)
736 {
737     const struct PyFile *f = p;
738
739     return f->file;
740 }
741
742 /*
743 **  Check to see if a provided key matches the key of a PyFile struct,
744 **  used by the hash table.
745 */
746 static bool
747 file_equal(const void *k, const void *p)
748 {
749     const char *key = k;
750     const struct PyFile *f = p;
751
752     return strcmp(key, f->file) == 0;
753 }
754
755 /*
756 **  Free a file, used by the hash table.
757 */
758 static void
759 file_free(void *p)
760 {
761     struct PyFile *fp = p;
762     int i, j;
763
764     free(fp->file);
765
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]);
770             }
771         }
772     }
773
774     free(fp);
775 }
776
777 #endif /* defined(DO_PYTHON) */