Patch for the integration of Python hooks and readers.conf

Erik Klavon erik at eriq.org
Tue Jan 14 06:43:32 UTC 2003



Greetings

Enclosed are materials for the integration of the nnrpd python hooks
and readers.conf. Three new files have been added to samples; all are
wrapper scripts provided to make migration to the new hooks easier. A
diff of changes made to existing files (both code and documentation)
is also included.

Erik

**************** new files *************************

*** begin samples/nnrpd_access_wrapper.py ***

# Example wrapper nnrpd_access_wrapper.py for support of old python
# authentication scripts, by Erik Klavon. 

# This file contains a sample python script which can be used to
# duplicate the behavior of the old nnrppythonauth functionality. This
# script only supports access control.

# How to use this wrapper:
# - insert your authentication class into this file.
# - rename your authentication class OLDAUTH
#
# Old AUTH class
# Insert your old auth class here
# do not include the code which sets the hook



#
# Wrapper AUTH class. It creates an instance of the old class and
# calls its methods. Arguments and return values are munged as
# needed to fit the new way of doing things.
#

class MYAUTH:
    """Provide access callbacks to nnrpd."""
    def access_init(self):
        self.old = OLDAUTH()

    def access(self, attributes):
        attributes['type'] = buffer('connect')
        perm = (self.old).authenticate(attributes)
        result = dict([('users','*')])        
        if perm[1] == 1:
                result['read'] = perm[3]
        if perm[2] == 1:
                result['post'] = perm[3]
        return result

    def access_close(self):
        (self.old).close()

#
# The rest is used to hook up the auth module on nnrpd. It is unlikely
# you will ever need to modify this.
#

# Import functions exposed by nnrpd. This import must succeed, or nothing
# will work!
from nnrpd import *

# Create a class instance
myauth = MYAUTH()

# ...and try to hook up on nnrpd. This would make auth object methods visible
# to nnrpd.
try:
    set_auth_hook(myauth)
    syslog('notice', "authentication module successfully hooked into nnrpd")
except Exception, errmsg:
    syslog('error', "Cannot obtain nnrpd hook for authentication method: %s" % errmsg[0])

*** end samples/nnrpd_access_wrapper.py ***

*** begin samples/nnrpd_auth_wrapper.py ***

# Example wrapper nnrpd_auth_wrapper.py for support of old python
# authentication scripts, by Erik Klavon. 

# This file contains a sample python script which can be used to
# duplicate the behavior of the old nnrppythonauth functionality. This
# script only supports authentication.

# How to use this wrapper:
# - insert your authentication class into this file.
# - rename your authentication class OLDAUTH
#

# Old AUTH class
# Insert your old auth class here
# do not include the code which sets the hook



#
# Wrapper AUTH class. It creates an instance of the old class and
# calls its methods. Arguments and return values are munged as
# needed to fit the new way of doing things.
#

class MYAUTH:
    """Provide auth callbacks to nnrpd."""
    def authen_init(self):
        self.old = OLDAUTH()

    def authenticate(self, attributes):
        attributes['type'] = buffer('authinfo')
        perm = (self.old).authenticate(attributes)
        err_str = "No error"
        if perm[0] == 502:
                err_str = "Python authentication error!"        
        return (perm[0],err_str)                

    def authen_close(self):
        (self.old).close()

#
# The rest is used to hook up the auth module on nnrpd. It is unlikely
# you will ever need to modify this.
#

# Import functions exposed by nnrpd. This import must succeed, or nothing
# will work!
from nnrpd import *

# Create a class instance
myauth = MYAUTH()

# ...and try to hook up on nnrpd. This would make auth object methods visible
# to nnrpd.
try:
    set_auth_hook(myauth)
    syslog('notice', "authentication module successfully hooked into nnrpd")
except Exception, errmsg:
    syslog('error', "Cannot obtain nnrpd hook for authentication method: %s" % errmsg[0])

*** end samples/nnrpd_auth_wrapper.py ***

*** begin samples/nnrpd_dynamic_wrapper.py ***

# Example wrapper nnrpd_dynamic_wrapper.py for support of old python
# authentication scripts, by Erik Klavon. 

# This file contains a sample python script which can be used to
# duplicate the behavior of the old nnrppythonauth functionality. This
# script only supports dynamic access control by group.

# How to use this wrapper:
# - insert your authentication class into this file.
# - rename your authentication class OLDAUTH
#

# Old AUTH class
# Insert your old auth class here
# do not include the code which sets the hook



#
# Wrapper AUTH class. It creates an instance of the old class and
# calls its methods. Arguments and return values are munged as
# needed to fit the new way of doing things.
#

class MYAUTH:
    """Provide dynamic access callbacks to nnrpd."""
    def dynamic_init(self):
        self.old = OLDAUTH()

    def dynamic(self, attributes):
        return (self.old).authorize(attributes)

    def dynamic_close(self):
        (self.old).close()

#
# The rest is used to hook up the auth module on nnrpd. It is unlikely
# you will ever need to modify this.
#

# Import functions exposed by nnrpd. This import must succeed, or nothing
# will work!
from nnrpd import *

# Create a class instance
myauth = MYAUTH()

# ...and try to hook up on nnrpd. This would make auth object methods visible
# to nnrpd.
try:
    set_auth_hook(myauth)
    syslog('notice', "authentication module successfully hooked into nnrpd")
except Exception, errmsg:
    syslog('error', "Cannot obtain nnrpd hook for authentication method: %s" % errmsg[0])

*** end samples/nnrpd_auth_wrapper.py ***

**************** diff *************************

diff -ur inn/doc/pod/hook-python.pod inn_python/doc/pod/hook-python.pod
--- inn/doc/pod/hook-python.pod	Mon Dec  2 21:17:17 2002
+++ inn_python/doc/pod/hook-python.pod	Mon Jan 13 16:22:53 2003
@@ -260,65 +260,240 @@
     else:
         moderated = "something else"
 
-=head1 PYTHON AUTHENTICATION AND AUTHORIZATION SUPPORT FOR NNRPD
+=head1 CHANGES TO PYTHON AUTHENTICATION AND ACCESS CONTROL SUPPORT FOR
+NNRPD
 
-Python authentication and authorization support in nnrpd along with
-filtering support in innd may be compiled in by passing C<--with-python>
-C<configure>.  Python authentication and authorization may be enabled with
-the nnrppythonauth setting in F<inn.conf>.
-
-If nnrppythonauth in F<inn.conf> is set to true, nnrpd will load the
-Python module defined in include/paths.h and located in the directory
-specified by pathfilter in F<inn.conf>.  Once that module is loaded, nnrpd
-will authenticate and authorize readers by calling a Python methods rather
-than reading F<readers.conf> and using the normal authentication
-mechanism.
-
-Every time an authenticated reader asks nnrpd to read or post an article,
-Python authorization hooks are invoked before proceeding with the
-requested operation.  The authorization functionality makes sense when a
-list of newsgroups in your access statements grows too long to maintain in
-F<readers.conf> or you need to have access control rules applied
-immediately without having to restart all the nnrpd processes.  Also,
-Python authorization hooks perform access control on per newsgroup basis
-while F<readers.conf> does the same on per user basis.
+The old authentication and access control functionality has been
+combined with the new readers.conf mechanism by Erik Klavon
+<erik at eriq.org>; bug reports should however go to inn-bugs at isc.org,
+not Erik.
+
+The remainder of this section is an introduction to the new mechanism
+(which uses the python_auth: python_access: and python_dynamic:
+f<readers.conf> parameters) with porting/migration suggestions for
+people familiar with the old mechanism (identifiable by the now
+deprecated nnrpperlauth: parameter in F<inn.conf>).
+
+Other people should skip this section.
+
+The python_auth parameter allows the use of Python to authenticate a
+user. Authentication scripts (like those from the old mechanism) are
+listed in F<readers.conf> using python_auth in the same manner other
+authenticators are using auth:
+
+        python_auth: "auth1.py"
+
+Scripts should be placed as before in the filter directory (see the
+pathfilter setting in F<inn.conf>). The new hook method authen_init
+takes no arguments and its return value is ignored; its purpose is to
+provide a means for authentication specific initialization. The hook
+method authen_close is the more specific analogue to the old close
+method. These method hooks are not required.
+
+The argument dictionary passed to authenticate remains the same,
+except for the removal of the "type" entry which is no longer needed
+in this modification. The return tuple now only contains either two or
+three elements, the first of which is the NNTP response code. The
+second is an error string which is passed to the client if the
+response code indicates that the authentication attempt has
+failed. This allows a specific error message to be generated by the
+Python script in place of the generic message "Authentication
+failed". An optional third return element if present will be used to
+match the connection with the user: parameter in access groups and
+will also be the username logged. If this element is absent, the
+username supplied by the client during authentication will be used as
+was the previous behavior.
+
+The python_access parameter (described below) is new; it allows the
+dynamic generation of an access group of an incoming connection using
+a Python script. If a connection matches an auth group which has a
+python_access parameter, all access groups in readers.conf are
+ignored; instead the procedure described below is used to generate an
+access group. This concept is due to Jeffery M. Vinocur.
+
+In the old implementation, the authorization method allowed for access
+control on a per-group basis. That functionality is preserved in the
+new implementation by the inclusion of the python_dynamic parameter in
+F<readers.conf>. The only change is the corresponding method name of
+dynamic as opposed to authorize; domain and range are the same as
+before. Additionally, the associated optional housekeeping methods
+dynamic_init and dynamic_close may be implemented if needed.
+
+This new implementation should provide all of the previous
+capabilities of the Python hooks, in combination with the flexibility
+of readers.conf and the use of other authentication and resolving
+programs (including the Perl hooks!). To use Python code that predates
+the new mechanism, you would need to modify the code slightly (see
+below for the new specification) and supply a simple readers.conf
+file. If you don't want to modify your code, the sample directory has
+F<nnrpd_auth_wrapper.py>, F<nnrpd_access_wrapper.py> and
+F<nnrpd_dynamic_wrapper.py> which should allow you to use your old
+code without needing to change it.
+
+However, before trying to use your old Python code, you may want to
+consider replacing it entirely with non-Python authentication. (With
+readers.conf and the regular authenticator and resolver programs, much
+of what once required Perl can be done directly.)  Even if the
+functionality is not available directly, you may wish to write a new
+authenticator or resolver (which can be done in whatever language you
+prefer).
+
+=head1 PYTHON AUTHENTICATION SUPPORT FOR NNRPD
+
+Python authentication, dynamic access group generation and dynamic
+access control support in nnrpd along with filtering support in innd
+may be compiled in by passing C<--with-python> C<configure>.
+
+Support for authentication via Python is provided in nnrpd by the
+inclusion of a python_auth: parameter in a F<readers.conf> auth
+group. python_auth: works exactly like the auth: parameter in
+F<readers.conf>, except that it calls the script given as argument
+using the Python hook rather then treating it as an external
+program. Multiple, mixed use of python_auth: with other auth:
+statements including perl_auth: is permitted. Each auth: statement
+will be tried in the order they appear in the auth group until either
+one succeeds or all are exhausted.
+
+If the processing of readers.conf requires that a python_auth:
+statement be used for authentication, Python is loaded (if it has yet
+to be) and the file given as argument to the python_auth: parameter is
+loaded as well. If a Python object with a method authen_init is hooked in
+during the loading of that file, then that method is called
+immediately after the file is loaded. If no errors have occurred, the
+method authenticate is called. Depending on the NNTP response code
+returned by authenticate, the authentication hook either succeeds or
+fails, after which the processing of the auth group continues as
+usual. When the connection with the client is closed, the method
+authen_close is called if it exists.
+
+=head1 DYNAMIC GENERATION OF ACCESS GROUPS
+
+A Python script may be used to dynamically generate an access group
+which is then used to determine the access rights of the client. This
+occurs whenever the python_access: parameter is specified in an auth group
+which has successfully matched the client. Only one python_access:
+statement is allowed in an auth group. This parameter should not be
+mixed with a perl_access: statement in the same auth group.
+
+When a python_access: parameter is encountered, Python is loaded (if
+it has yet to be) and the file given as argument is loaded as well. If
+a Python object with a method access_init is hooked in during the
+loading of that file, then that method is called immediately after the
+file is loaded. If no errors have occurred, the method access is
+called. The dictionary returned by access is used to generate an
+access group which is then used to determine the access rights of the
+client. When the connection with the client is closed, the method
+access_close is called if it exists.
+
+While you may include the users: parameter in a dynamically generated
+access group, some care should be taken (unless your pattern is just
+* which is equivalent to leaving the parameter out). The group created
+with the values returned from the Python script is the only one
+considered when nnrpd attempts to find an access group matching the
+connection. If a users: parameter is included and it doesn't match the
+connection, then the client will be denied access since there are no
+other access groups which could match the connection.
+
+=head1 DYNAMIC ACCESS CONTROL
+
+If you need to have access control rules applied immediately without
+having to restart all the nnrpd processes, you may apply access
+control on a per newsgroup basis using the Python dynamic hooks (as
+opposed to F<readers.conf> which does the same on per user
+basis). These hooks are activated through the inclusion of the
+python_dynamic: parameter in a F<readers.conf> auth group. Only one
+python_dynamic: statement is allowed in an auth group.
+
+When a python_dynamic: parameter is encountered, Python is loaded (if
+it has yet to be) and the file given as argument is loaded as well. If
+a Python object with a method dynamic_init is hooked in during the
+loading of that file, then that method is called immediately after the
+file is loaded. Every time a reader asks nnrpd to read or post an
+article, the Python method dynamic is invoked before proceeding with
+the requested operation. Based on the value returned by dynamic, the
+operation is either permitted or denied. When the connection with the
+client is closed, the method access_close is called if it exists.
 
-However, consider the authorization functionality as an option which is
-reasonable in just a few cases (like those mentioned above).
-
-=head1 WRITING A NNRPD AUTHENTICATION MODULE
+=head1 WRITING A PYTHON NNRPD AUTHENTICATION MODULE
 
 You need to create a F<nnrpd_auth.py> module in INN's filter directory
 (see the pathfilter setting in F<inn.conf>) where you should define a
-class holding certain methods.
+class holding certain methods depending on which hook(s) you want to
+make use of.
 
-The following methods are known to nnrpd.  It uses them if present:
+The following methods are known to nnrpd:
 
 =over 4
 
 =item __init__(self)
 
 Not explicitly called by nnrpd, but will run whenever the auth module is
-loaded.  This is a good place to initialize constants or establish a
-database connection.
+loaded. Use this method to initialize any general variables or open
+a common database connection. This method may be omitted.
+
+=item authen_init(self)
+
+Initialization function specific to authentication. This method may be
+omitted.
+
+=item authenticate(self, attributes)
 
-=item close(self)
+Called when a python_auth statement is reached in the processing of
+readers.conf. Connection attributes are passed in the "attributes"
+dictionary. Returns a response code, an error string and an optional
+string to appear in the logs as the username and is used to match the
+connection with an access group.
+
+=item authen_close(self)
 
 This method is invoked on nnrpd termination.  You can use it to save state
 information or close a database connection.
 
-=item authenticate(self, attributes)
+=item access_init(self)
+
+Initialization function specific to generation of an access
+group. This method may be omitted.
+
+=item access(self, attributes)
+
+Called when a python_access statement is reached in the processing of
+readers.conf. Connection attributes are passed in the "attributes"
+dictionary. Returns a dictionary of values representing statements to
+be included in an access group.
+
+=item access_close(self)
+
+This method is invoked on nnrpd termination.  You can use it to save state
+information or close a database connection.
+
+=item dynamic_init(self)
+
+Initialization function specific to dynamic access control. This
+method may be omitted.
+
+=item dynamic(self, attributes)
+
+Called when a client requests a newsgroup, an atricle or attempts to
+post. Connection attributes are passed in the "attributes"
+dictionary. Returns None to grant access, or a non-empty string (which
+will be reported back to the client) otherwise.
+
+=item dynamic_close(self)
 
-Called when a reader connects or issues AUTHINFO command.  Connection
-attributes are passed in the "attributes" dictionary.  The following keys
-are initialized by nnrpd:
+This method is invoked on nnrpd termination.  You can use it to save state
+information or close a database connection.
+
+=item attributes dictionary
+
+The keys and associated values of the attributes dictionary are
+described below.
 
 =over 4
 
 =item type
 
-"connect", "authinfo", "read" or "post" values specify the authentication
-type.
+"read" or "post" values specify the authentication
+type. Only valid for dynamic method.
 
 =item hostname
 
@@ -342,49 +517,13 @@
 
 =item newsgroup
 
-name of the newsgroup reader requests read or post access to or None if
-not applicable
+name of the newsgroup reader requests read or post access to. Only
+valid for dynamic method.
 
 =back
 
 All the above values are buffer objects (see the notes above on what
 buffer objects are).
-
-This method should return a tuple of four elements:
-
-=over 3
-
-=item 1)
-
-NNTP response code.  Should be a valid NNTP response code (see example for
-details).
-
-=item 2)
-
-Whether reading is allowed.  Should be a boolean value.
-
-=item 3)
-
-Whether posting is allowed.  Should be a boolean value.
-
-=item 4)
-
-Wildmat expression that says what groups to provide access to.
-
-=back
-
-See the explanation of applicable NNTP return codes in F<hook-perl> in the
-INN documentation.
-
-=item authorize(self, attributes)
-
-Called when a reader requests either read or post permission.  The
-"attributes" dictionary is passed to group() method (see above for
-details).
-
-This method should return None to grant requested permission to requested
-newsgroup or non-empty string otherwise.  The rejection string will be
-shown to reader.
 
 =back
 
diff -ur inn/doc/pod/inn.conf.pod inn_python/doc/pod/inn.conf.pod
--- inn/doc/pod/inn.conf.pod	Mon Dec 16 02:48:17 2002
+++ inn_python/doc/pod/inn.conf.pod	Mon Jan 13 16:22:53 2003
@@ -572,10 +572,7 @@
 
 =item I<nnrppythonauth>
 
-Whether to use the Python hook in nnrpd(8) to authenticate readers.  If
-this is enabled, normal readers.conf(5) authentication will not be used,
-and instead the python hook will be called to authenticate connections.
-This is a boolean value and the default is false.
+This parameter is now obsolete; see "Changes to Python Authentication and Access Control Support for nnrpd" in F<doc/hook-python>.
 
 =item I<noreader>
 
diff -ur inn/doc/pod/readers.conf.pod inn_python/doc/pod/readers.conf.pod
--- inn/doc/pod/readers.conf.pod	Sun Dec  8 11:33:34 2002
+++ inn_python/doc/pod/readers.conf.pod	Mon Jan 13 16:22:53 2003
@@ -96,13 +96,13 @@
 <res-program> only returns a username, <defdomain> is used as the
 domain.
 
-If the user later authenticates via the AUTHINFO USER/PASS commands, the
-provided username and password are passed to each <auth-program>
-(multiple auth: or perl_auth: lines may be present in a block; they are
-run in sequence until one succeeds), if any.  If one succeeds and
-returns a different identity than the one assigned at the time of the
-connection, it is matched against the available access groups again and
-the actions the user is authorized to do may change.
+If the user later authenticates via the AUTHINFO USER/PASS commands,
+the provided username and password are passed to each <auth-program>
+(multiple auth: perl_auth: or python_auth: lines may be present in a
+block; they are run in sequence until one succeeds), if any.  If one
+succeeds and returns a different identity than the one assigned at the
+time of the connection, it is matched against the available access
+groups again and the actions the user is authorized to do may change.
 
 When matching auth groups, the last auth group in the file that matches a
 given connection and username/password combination is used.
@@ -135,10 +135,10 @@
 Just like with auth groups, when matching access groups the last matching
 one in the file is used to determine the user's permissions.  There is
 an exception to this rule: if the auth group which matched the client
-contains the perl_access: parameter then the perl script given as
-argument is used to dynamically generate an access group.  This new
-access group is then used to determine the access rights of the
-client; the access groups in the file are ignored.
+contains the perl_access: or python_access: parameter then the script
+given as argument is used to dynamically generate an access group.
+This new access group is then used to determine the access rights of
+the client; the access groups in the file are ignored.
 
 There is one additional special case to be aware of.  When forming
 particularly complex authentication and authorization rules, it is
@@ -242,13 +242,26 @@
 A path to a perl script for authentication.  The perl_auth: parameter
 works exactly like auth:, except that it calls the named script using
 the perl hook rather then an external program.  Multiple/mixed use of
-auth: and perl_auth: is permitted within any auth group; each method is
-tried in order listed in F<readers.conf>.  perl_auth: has more power
-than auth: in that it provides the authentication program with
-additional information about the client and the ability to return an
-error string.  This parameter is only valid if INN is compiled with Perl
-support (B<--with-perl> passed to configure).  More information may be
-found in F<doc/hook-perl>.
+auth: perl_auth: and python_auth: is permitted within any auth group;
+each method is tried in order listed in F<readers.conf>.  perl_auth:
+has more power than auth: in that it provides the authentication
+program with additional information about the client and the ability
+to return an error string and a username.  This parameter is only
+valid if INN is compiled with Perl support (B<--with-perl> passed to
+configure).  More information may be found in F<doc/hook-perl>.
+
+=item B<python_auth:>
+
+A python script for authentication.  The python_auth: parameter works
+exactly like auth:, except that it calls the named script using the
+python hook rather then an external program.  Multiple/mixed use of
+auth: perl_auth: and python_auth: is permitted within any auth group;
+each method is tried in order listed in F<readers.conf>.  python_auth:
+has more power than auth: in that it provides the authentication
+program with additional information about the client and the ability
+to return an error string and a username.  This parameter is only
+valid if INN is compiled with Python support (B<--with-python> passed
+to configure).  More information may be found in F<doc/hook-python>.
 
 =item B<default:>
 
@@ -293,6 +306,31 @@
 support (B<--with-perl> passed to configure).  More information may be
 found in the file F<doc/hook-perl>.
 
+=item B<python_access:>
+
+A python script for dynamically generating an access group.  If
+an auth group matches successfully and contains a python_access parameter,
+then the argument script will be used to create an access group.
+This group will then determine the access rights of the client,
+overriding any access groups in F<readers.conf>.  If and only if a
+successful auth group contains the python_access parameter, F<readers.conf>
+access groups are ignored and the client's rights are instead determined
+dynamically.  This parameter is only valid if INN is compiled with Python
+support (B<--with-python> passed to configure).  More information may be
+found in the file F<doc/hook-python>.
+
+=item B<python_dynamic:>
+
+A python script for applying access control dynamically on a per
+newsgroup basis.  If an auth group matches successfully and contains a
+python_dynamic parameter, then the argument script will be used to
+determine the clients rights each time the user attempts to view a
+newsgroup, read or post an article. Access rights as determined by
+python_dynamic override the values of access group parameters such as
+newsgroups: read: and post:. This parameter is only valid if INN is
+compiled with Python support (B<--with-python> passed to configure).
+More information may be found in the file F<doc/hook-python>.
+
 =back
 
 =head1 ACCESS GROUP PARAMETERS
@@ -485,27 +523,28 @@
 =item *
 
 When the user authenticates, the auth groups are rescanned, and only the
-matching ones which contain at least one auth: or perl_auth: line are
-considered.  These auth groups are scanned from the last to the first,
-running auth: programs and perl_auth: scripts.  The first auth group
-(starting from the bottom) to return a valid user is kept as the active
-auth group.
+matching ones which contain at least one auth: perl_auth: or
+python_auth: line are considered.  These auth groups are scanned from
+the last to the first, running auth: programs and perl_auth: or
+python_auth: scripts. The first auth group (starting from the bottom)
+to return a valid user is kept as the active auth group.
 
 =item *
 
 Regardless of how an auth group is established, as soon as one is, that
 auth group is used to assign a user identity by taking the result of the
-successful res:, auth, or perl_auth: line (or the default: if necessary),
-and appending the default-domain: if necessary.  (If the perl_access:
-parameter is present, see below.)
+successful res:, auth, perl_auth: or python_auth: line (or the
+default: if necessary), and appending the default-domain: if
+necessary.  (If the perl_access: or python_access: parameter is
+present, see below.)
 
 =item *
 
 Finally, an access group is selected by scanning the access groups from
 bottom up and finding the first match.  (If the established auth group
-contained a perl_access: line, the dynamically generated access group
-returned by the Perl script is used instead.)  User permissions are granted
-based on the established access group.
+contained a perl_access: or python_access line, the dynamically
+generated access group returned by the script is used instead.)
+User permissions are granted based on the established access group.
 
 =back
 
diff -ur inn/include/inn/innconf.h inn_python/include/inn/innconf.h
--- inn/include/inn/innconf.h	Mon Dec 16 02:48:19 2002
+++ inn_python/include/inn/innconf.h	Mon Jan 13 16:22:51 2003
@@ -77,8 +77,6 @@
     long nfsreaderdelay;        /* Delay applied to article arrival */
     bool nnrpdcheckart;         /* Check article existence before returning? */
     long nnrpdloadlimit;	/* Maximum getloadvg() we allow */
-    bool nnrpperlauth;          /* Use Perl for nnrpd authentication */
-    bool nnrppythonauth;        /* Use Python for nnrpd authentication */
     bool noreader;              /* Refuse to fork nnrpd for readers? */
     bool readerswhenstopped;    /* Allow nnrpd when server is paused */
     bool readertrack;           /* Use the reader tracking system? */
diff -ur inn/lib/innconf.c inn_python/lib/innconf.c
--- inn/lib/innconf.c	Thu Dec 26 20:48:04 2002
+++ inn_python/lib/innconf.c	Mon Jan 13 16:22:51 2003
@@ -207,7 +207,6 @@
     { K(nnrpdauthsender),       BOOL    (false) },
     { K(nnrpdloadlimit),        NUMBER  (16) },
     { K(nnrpdoverstats),        BOOL    (false) },
-    { K(nnrppythonauth),        BOOL    (false) },
     { K(organization),          STRING  (NULL) },
     { K(readertrack),           BOOL    (false) },
     { K(spoolfirst),            BOOL    (false) },
diff -ur inn/nnrpd/commands.c inn_python/nnrpd/commands.c
--- inn/nnrpd/commands.c	Sun Jan 12 23:59:35 2003
+++ inn_python/nnrpd/commands.c	Mon Jan 13 16:22:51 2003
@@ -225,9 +225,6 @@
     static char	Password[SMBUF];
     char	accesslist[BIG_BUFFER];
     char        errorstr[BIG_BUFFER];
-#ifdef DO_PYTHON
-    int         code;
-#endif
 
     if (caseEQ(av[1], "generic")) {
 	char *logrec = Glom(av);
@@ -290,37 +287,6 @@
 	    Password[sizeof Password - 1] = 0;
 	}
 
-#ifdef DO_PYTHON
-	    if (innconf->nnrppythonauth) {
-	        if ((code = PY_authenticate(ClientHost, ClientIpString, ServerHost, User, Password, accesslist)) < 0) {
-		    syslog(L_NOTICE, "PY_authenticate(): authentication skipped due to no Python authentication method defined.");
-		} else {
-		    if (code == NNTP_AUTH_OK_VAL) {
-		        PERMspecified = NGgetlist(&PERMreadlist, accesslist);
-			PERMpostlist = PERMreadlist;
-			syslog(L_NOTICE, "%s user %s", ClientHost, User);
-			if (LLOGenable) {
-			    fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, User);
-			    fflush(locallog);
-			}
-			Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
-			/* save these values in case you need them later */
-			strncpy(PERMuser, User, sizeof(PERMuser) - 1);
-                        PERMuser[sizeof(PERMuser) - 1] = '\0';
-			strncpy(PERMpass, Password, sizeof(PERMpass) - 1);
-                        PERMpass[sizeof(PERMpass) - 1] = '\0';
-			PERMneedauth = FALSE;
-			PERMauthorized = TRUE;
-			return;
-		    } else {
-		        syslog(L_NOTICE, "%s bad_auth", ClientHost);
-			Reply("%d Authentication error\r\n", NNTP_ACCESS_VAL);
-			ExitWithStats(1, FALSE);
-		    }
-		}
-	    } else {
-#endif /* DO_PYTHON */
-
 	    if (EQ(User, PERMuser) && EQ(Password, PERMpass)) {
 		syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
 		if (LLOGenable) {
@@ -348,13 +314,10 @@
 		PERMauthorized = TRUE;
 		return;
 	    }
-#ifdef	DO_PYTHON
-	}
-#endif	/* DO_PYTHON */
 
 	syslog(L_NOTICE, "%s bad_auth", ClientHost);
         if (errorstr[0] != '\0') {
-            syslog(L_NOTICE, "%s perl error str: %s", ClientHost, errorstr);
+            syslog(L_NOTICE, "%s script error str: %s", ClientHost, errorstr);
             Reply("%d %s\r\n", NNTP_ACCESS_VAL, errorstr);
         } else {
             Reply("%d Authentication error\r\n", NNTP_ACCESS_VAL);
diff -ur inn/nnrpd/group.c inn_python/nnrpd/group.c
--- inn/nnrpd/group.c	Sun Jan 12 22:44:23 2003
+++ inn_python/nnrpd/group.c	Mon Jan 13 16:27:48 2003
@@ -24,8 +24,13 @@
     TOKEN               token;
     int                 count;
     bool		boolval;
+    bool                hookpresent = FALSE;
 
-    if (!PERMcanread) {
+#ifdef DO_PYTHON
+    hookpresent = PY_use_dynamic;
+#endif /* DO_PYTHON */
+
+    if (!hookpresent && !PERMcanread) {
 	Reply("%s\r\n", NOACCESS);
 	return;
     }
@@ -49,17 +54,18 @@
     }
 
 #ifdef DO_PYTHON
-    if (innconf->nnrppythonauth) {
+    if (PY_use_dynamic) {
         char    *reply;
 
-	/* Authorize user at a Python authorization module */
-	if (PY_authorize(ClientHost, ClientIpString, ServerHost, PERMuser, group, FALSE, &reply) < 0) {
-	    syslog(L_NOTICE, "PY_authorize(): authorization skipped due to no Python authorization method defined.");
+	/* Authorize user using Python module method dynamic */
+	if (PY_dynamic(ClientHost, ClientIpString, ServerHost, PERMuser, group, FALSE, &reply) < 0) {
+	    syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
 	} else {
 	    if (reply != NULL) {
-	        syslog(L_TRACE, "PY_authorize() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, group, reply);
+	        syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, group, reply);
 		Reply("%d %s\r\n", NNTP_ACCESS_VAL, reply);
 		DISPOSE(group);
+		DISPOSE(reply);
 		return;
 	    }
 	}
@@ -67,18 +73,20 @@
 #endif /* DO_PYTHON */
 
     /* If permission is denied, pretend group doesn't exist. */
-    if (PERMspecified) {
-	grplist[0] = group;
-	grplist[1] = NULL;
-	if (!PERMmatch(PERMreadlist, grplist)) {
-	    Reply("%s %s\r\n", NOSUCHGROUP, group);
-	    DISPOSE(group);
-	    return;
-	}
-    } else {
-	Reply("%s %s\r\n", NOSUCHGROUP, group);
-	DISPOSE(group);
-	return;
+    if (!hookpresent) {
+        if (PERMspecified) {
+            grplist[0] = group;
+            grplist[1] = NULL;
+            if (!PERMmatch(PERMreadlist, grplist)) {
+                Reply("%s %s\r\n", NOSUCHGROUP, group);
+                DISPOSE(group);
+                return;
+            }
+        } else {
+            Reply("%s %s\r\n", NOSUCHGROUP, group);
+            DISPOSE(group);
+            return;
+        }
     }
 
     /* Close out any existing article, report group stats. */
diff -ur inn/nnrpd/misc.c inn_python/nnrpd/misc.c
--- inn/nnrpd/misc.c	Sun Jan 12 23:59:35 2003
+++ inn_python/nnrpd/misc.c	Mon Jan 13 16:22:51 2003
@@ -164,17 +164,19 @@
     }
 
 #ifdef DO_PYTHON
-    if (innconf->nnrppythonauth) {
+    if (PY_use_dynamic) {
         char    *reply;
 
 	/* Authorize user at a Python authorization module */
-	if (PY_authorize(ClientHost, ClientIpString, ServerHost, PERMuser, p, FALSE, &reply) < 0) {
-	    syslog(L_NOTICE, "PY_authorize(): authorization skipped due to no Python authorization method defined.");
+	if (PY_dynamic(ClientHost, ClientIpString, ServerHost, PERMuser, p, FALSE, &reply) < 0) {
+	    syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
 	} else {
 	    if (reply != NULL) {
-	        syslog(L_TRACE, "PY_authorize() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
-		return TRUE;
+	        syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
+		DISPOSE(reply);
+		return FALSE;
 	    }
+            return TRUE;
 	}
     }
 #endif /* DO_PYTHON */
diff -ur inn/nnrpd/nnrpd.c inn_python/nnrpd/nnrpd.c
--- inn/nnrpd/nnrpd.c	Sun Jan 12 23:59:35 2003
+++ inn_python/nnrpd/nnrpd.c	Mon Jan 13 16:22:51 2003
@@ -102,6 +102,10 @@
 bool   PerlLoaded = FALSE;
 #endif /* DO_PERL */
 
+#ifdef DO_PYTHON
+bool   PY_use_dynamic = FALSE;
+#endif /* DO_PYTHON */
+
 bool LLOGenable;
 
 static char	CMDfetchhelp[] = "[MessageID|Number]";
@@ -237,9 +241,8 @@
     SMshutdown();
 
 #ifdef DO_PYTHON
-    if (innconf->nnrppythonauth)
-        PY_close();
-#endif
+    PY_close_python();
+#endif	/* DO_PYTHON */
 
     if (History)
 	HISclose(History);
@@ -481,11 +484,6 @@
 {
     struct sockaddr_storage	ssc, sss;
     socklen_t		length;
-#ifdef DO_PYTHON
-    char		accesslist[BIG_BUFFER];
-    int                 code;
-    static ACCESSGROUP	*authconf;
-#endif
     const char		*default_host_error = "unknown error";
 
     ClientIpAddr = 0L;
@@ -590,31 +588,9 @@
     LogName[sizeof(LogName) - 1] = '\0';
 
     syslog(L_NOTICE, "%s (%s) connect", ClientHost, ClientIpString);
-#ifdef DO_PYTHON
-    if (innconf->nnrppythonauth) {
-        if ((code = PY_authenticate(ClientHost, ClientIpString, ServerHost, NULL, NULL, accesslist)) < 0) {
-	    syslog(L_NOTICE, "PY_authenticate(): authentication skipped due to no Python authentication method defined.");
-	} else {
-	    if (code == 502) {
-	        syslog(L_NOTICE, "%s no_access", ClientHost);
-		Printf("%d You are not in my access file. Goodbye.\r\n",
-		       NNTP_ACCESS_VAL);
-		ExitWithStats(1, TRUE);
-	    }
-	    PERMspecified = NGgetlist(&PERMreadlist, accesslist);
-	    PERMpostlist = PERMreadlist;
-	}
-	if (!authconf)
-	    authconf = NEW(ACCESSGROUP, 1);
-	PERMaccessconf = authconf;
-	SetDefaultAccess(PERMaccessconf);
-    } else {
-#endif	/* DO_PYTHON */
+    
 	PERMgetaccess(NNRPACCESS);
 	PERMgetpermissions();
-#ifdef DO_PYTHON
-    }
-#endif /* DO_PYTHON */
 }
 
 
@@ -782,13 +758,6 @@
 static void SetupDaemon(void) {
     bool                val;
     time_t statinterval;
-
-#ifdef	DO_PYTHON
-    /* Load the Python code */
-    if (innconf->nnrppythonauth) {
-        PY_setup();
-    }
-#endif /* defined(DO_PYTHON) */
 
     val = TRUE;
     if (SMsetup(SM_PREOPEN, (void *)&val) && !SMinit()) {
diff -ur inn/nnrpd/nnrpd.h inn_python/nnrpd/nnrpd.h
--- inn/nnrpd/nnrpd.h	Mon Dec 16 02:16:48 2002
+++ inn_python/nnrpd/nnrpd.h	Mon Jan 13 16:22:51 2003
@@ -275,10 +275,12 @@
 #endif /* DO_PERL */
 
 #ifdef	DO_PYTHON
-int PY_authenticate(char *clientHost, char *clientIpString, char *serverHost, char *username, char *password, char *accesslist);
-int PY_authorize(char *clientHost, char *clientIpString, char *serverHost, char *username, char *NewsGroup, int PostFlag, char **reply_message);
-void PY_close(void);
-void PY_setup(void);
+extern bool             PY_use_dynamic;
+
+int PY_authenticate(char *path, char *clientHost, char *clientIpString, char *serverHost, char *Username, char *Password, char *errorstring, char *newUser);
+void PY_access(char* path, struct vector *access_vec, char *clientHost, char *clientIpString, char *serverHost, char *Username);
+int PY_dynamic(char *clientHost, char *clientIpString, char *ServerHost, char *Username, char *NewsGroup, int PostFlag, char **reply_message);
+void PY_dynamic_init (char* file);
 #endif	/* DO_PYTHON */
 
 void line_free(struct line *);
diff -ur inn/nnrpd/perl.c inn_python/nnrpd/perl.c
--- inn/nnrpd/perl.c	Sun Jan 12 22:44:24 2003
+++ inn_python/nnrpd/perl.c	Mon Jan 13 16:22:51 2003
@@ -16,7 +16,7 @@
 #include "config.h"
 
 /* Skip this entire file if DO_PERL (./configure --with-perl) isn't set. */
-#if DO_PERL
+#ifdef DO_PERL
 
 #include "clibrary.h"
 
diff -ur inn/nnrpd/perm.c inn_python/nnrpd/perm.c
--- inn/nnrpd/perm.c	Fri Nov 22 23:29:58 2002
+++ inn_python/nnrpd/perm.c	Mon Jan 13 16:22:51 2003
@@ -29,7 +29,7 @@
 typedef struct _METHOD {
     char *name;
     char *program;
-    int  type;          /* for seperating perl_auth from auth */
+    int  type;          /* type of auth (perl, python or external) */
     char *users;	/* only used for auth_methods, not for res_methods. */
     char **extra_headers;
     char **extra_logs;
@@ -47,7 +47,10 @@
     char *default_user;
     char *default_domain;
     char *localaddress;
-    char *perl_access;
+    char *access_script;
+    int  access_type; /* type of access (perl or python) */
+    char *dynamic_script;
+    int  dynamic_type; /* type of dynamic authorization (python only) */
 } AUTHGROUP;
 
 typedef struct _GROUP {
@@ -150,12 +153,15 @@
 #define PERMrejectwith		54
 #define PERMmaxbytespersecond	55
 #define PERMperl_auth           56
-#define PERMperl_access         57
+#define PERMpython_auth         57
+#define PERMperl_access         58
+#define PERMpython_access       59
+#define PERMpython_dynamic      60
 #ifdef HAVE_SSL
-#define PERMrequire_ssl		58
-#define PERMMAX			59
+#define PERMrequire_ssl		61
+#define PERMMAX			62
 #else
-#define PERMMAX			58
+#define PERMMAX			61
 #endif
 
 #define TEST_CONFIG(a, b) \
@@ -237,7 +243,10 @@
   { PERMrejectwith, "reject_with:" },
   { PERMmaxbytespersecond, "max_rate:" },
   { PERMperl_auth, "perl_auth:" },
+  { PERMpython_auth, "python_auth:" },
   { PERMperl_access, "perl_access:" },
+  { PERMpython_access, "python_access:" },
+  { PERMpython_dynamic, "python_dynamic:" },
 #ifdef HAVE_SSL
   { PERMrequire_ssl, "require_ssl:" },
 #endif
@@ -375,10 +384,25 @@
     else
 	ret->localaddress = 0;
 
-    if (orig->perl_access)
-        ret->perl_access = COPY(orig->perl_access);
+    if (orig->access_script)
+        ret->access_script = COPY(orig->access_script);
     else
-        ret->perl_access = 0;
+        ret->access_script = 0;
+
+    if (orig->access_type)
+        ret->access_type = orig->access_type;
+    else
+        ret->access_type = 0;
+
+    if (orig->dynamic_script)
+        ret->dynamic_script = COPY(orig->dynamic_script);
+    else
+        ret->dynamic_script = 0;
+
+    if (orig->dynamic_type)
+        ret->dynamic_type = orig->dynamic_type;
+    else
+        ret->dynamic_type = 0;
 
     return(ret);
 }
@@ -514,8 +538,10 @@
 	DISPOSE(del->default_domain);
     if (del->localaddress)
 	DISPOSE(del->localaddress);
-    if (del->perl_access)
-        DISPOSE(del->perl_access);
+    if (del->access_script)
+        DISPOSE(del->access_script);
+    if (del->dynamic_script)
+        DISPOSE(del->dynamic_script);
     DISPOSE(del);
 }
 
@@ -691,6 +717,7 @@
 	break;
       case PERMauth:
       case PERMperl_auth:
+      case PERMpython_auth:
       case PERMauthprog:
 	m = NEW(METHOD, 1);
 	memset(m, 0, sizeof(METHOD));
@@ -706,6 +733,13 @@
 #else
             ReportError(f, "perl_auth can not be used in readers.conf: inn not compiled with perl support enabled.");
 #endif
+        } else if (oldtype == PERMpython_auth) {
+#ifdef DO_PYTHON
+	    m->type = PERMpython_auth;
+	    m->program = COPY(tok->name);
+#else
+	    ReportError(f, "python_auth can not be used in readers.conf: inn not compiled with python support enabled.");
+#endif
         } else {
 	    m->name = COPY(tok->name);
 	    tok = CONFgettoken(PERMtoks, f);
@@ -728,11 +762,28 @@
 	break;
       case PERMperl_access:
 #ifdef DO_PERL
-        curauth->perl_access = COPY(tok->name);
+        curauth->access_script = COPY(tok->name);
+	curauth->access_type = PERMperl_access;
 #else
         ReportError(f, "perl_access can not be used in readers.conf: inn not compiled with perl support enabled.");
 #endif
         break;
+      case PERMpython_access:
+#ifdef DO_PYTHON
+	curauth->access_script = COPY(tok->name);
+	curauth->access_type = PERMpython_access;
+#else
+	ReportError(f, "python_access can not be used in readers.conf: inn not compiled with python support enabled.");
+#endif
+	break;
+      case PERMpython_dynamic:
+#ifdef DO_PYTHON
+	curauth->dynamic_script = COPY(tok->name);
+	curauth->dynamic_type = PERMpython_dynamic;
+#else
+	ReportError(f, "python_dynamic can not be used in readers.conf: inn not compiled with python support enabled.");
+#endif
+	break;
       case PERMlocaladdress:
 	curauth->localaddress = COPY(tok->name);
 	CompressList(curauth->localaddress);
@@ -1477,7 +1528,7 @@
     char *user[2];
     static ACCESSGROUP *noaccessconf;
     char *uname;
-    char *cpp, *perl_path;
+    char *cpp, *script_path;
     char **args;
     struct vector *access_vec;
 
@@ -1498,18 +1549,18 @@
 	SetDefaultAccess(PERMaccessconf);
 	return;
 #ifdef DO_PERL
-    } else if (success_auth->perl_access != NULL) {
+    } else if ((success_auth->access_script != NULL) && (success_auth->access_type == PERMperl_access)) {
       i = 0;
-      cpp = COPY(success_auth->perl_access);
+      cpp = COPY(success_auth->access_script);
       args = 0;
       Argify(cpp, &args);
-      perl_path = concat(args[0], (char *) 0);
-      if ((perl_path != NULL) && (strlen(perl_path) > 0)) {
+      script_path = concat(args[0], (char *) 0);
+      if ((script_path != NULL) && (strlen(script_path) > 0)) {
         if(!PerlLoaded) {
           loadPerl();
         }
-        PERLsetup(NULL, perl_path, "access");
-        free(perl_path);
+        PERLsetup(NULL, script_path, "access");
+        free(script_path);
 
         uname = COPY(PERMuser);
         
@@ -1521,17 +1572,45 @@
         access_realms[0] = NEW(ACCESSGROUP, 1);
         memset(access_realms[0], 0, sizeof(ACCESSGROUP));
 
-        PERMvectortoaccess(access_realms[0], "perl-dyanmic", access_vec);
+        PERMvectortoaccess(access_realms[0], "perl-dynamic", access_vec);
 
         vector_free(access_vec);
       } else {
-        syslog(L_ERROR, "No script specified in auth method.\n");
+        syslog(L_ERROR, "No script specified in perl_access method.\n");
         Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
         ExitWithStats(1, TRUE);
       }
       DISPOSE(cpp);
-      DISPOSE(args);
 #endif /* DO_PERL */
+#ifdef DO_PYTHON
+    } else if ((success_auth->access_script != NULL) && (success_auth->access_type == PERMpython_access)) {
+      i = 0;
+      cpp = COPY(success_auth->access_script);
+      args = 0;
+      Argify(cpp, &args);
+      script_path = concat(args[0], (char *) 0);
+      if ((script_path != NULL) && (strlen(script_path) > 0)) {
+	uname = COPY(PERMuser);
+	access_vec = vector_new();
+	
+	PY_access(script_path, access_vec, ClientHost, ClientIpString, ServerHost, uname);
+	free(script_path);
+	DISPOSE(uname);
+	DISPOSE(args);
+
+	access_realms[0] = NEW(ACCESSGROUP, 1);
+	memset(access_realms[0], 0, sizeof(ACCESSGROUP));
+
+	PERMvectortoaccess(access_realms[0], "python-dynamic", access_vec);
+
+	vector_free(access_vec);
+      } else {
+	syslog(L_ERROR, "No script specified in python_access method.\n");
+	Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+	ExitWithStats(1, TRUE);
+      }
+      DISPOSE(cpp);
+#endif /* DO_PYTHON */
     } else {
       for (i = 0; access_realms[i]; i++)
         ;
@@ -1626,6 +1705,12 @@
 	SetDefaultAccess(PERMaccessconf);
 	syslog(L_TRACE, "%s no_access_realm", ClientHost);
     }
+    /* check if dynamic access control is enabled, if so init it */
+#ifdef DO_PYTHON
+    if ((success_auth->dynamic_type == PERMpython_dynamic) && success_auth->dynamic_script) {
+      PY_dynamic_init(success_auth->dynamic_script);
+    }
+#endif /* DO_PYTHON */
 }
 
 /* strip blanks out of a string */
@@ -2146,7 +2231,7 @@
     char *arg0;
     char *resdir;
     char *tmp;
-    char *perl_path;
+    char *script_path;
     char newUser[BIG_BUFFER];
     EXECSTUFF *foo;
     int done	    = 0;
@@ -2163,18 +2248,18 @@
     ubuf[0] = '\0';
     newUser[0] = '\0';
     for (i = 0; auth->auth_methods[i]; i++) {
-#ifdef DO_PERL
       if (auth->auth_methods[i]->type == PERMperl_auth) {
+#ifdef DO_PERL
             cp = COPY(auth->auth_methods[i]->program);
             args = 0;
             Argify(cp, &args);
-            perl_path = concat(args[0], (char *) 0);
-            if ((perl_path != NULL) && (strlen(perl_path) > 0)) {
+            script_path = concat(args[0], (char *) 0);
+            if ((script_path != NULL) && (strlen(script_path) > 0)) {
                 if(!PerlLoaded) {
                     loadPerl();
                 }
-                PERLsetup(NULL, perl_path, "authenticate");
-                free(perl_path);
+                PERLsetup(NULL, script_path, "authenticate");
+                free(script_path);
                 perlAuthInit();
           
                 code = perlAuthenticate(ClientHost, ClientIpString, ServerHost, username, password, errorstr, newUser);
@@ -2198,8 +2283,41 @@
             } else {
               syslog(L_ERROR, "No script specified in auth method.\n");
             }
-      } else if (auth->auth_methods[i]->type == PERMauthprog) {
 #endif	/* DO_PERL */    
+      } else if (auth->auth_methods[i]->type == PERMpython_auth) {
+#ifdef DO_PYTHON
+	cp = COPY(auth->auth_methods[i]->program);
+	args = 0;
+	Argify(cp, &args);
+	script_path = concat(args[0], (char *) 0);
+	if ((script_path != NULL) && (strlen(script_path) > 0)) {
+	  code = PY_authenticate(script_path, ClientHost, ClientIpString, ServerHost, username, password, errorstr, newUser);
+	  free(script_path);
+	  if (code < 0) {
+	    syslog(L_NOTICE, "PY_authenticate(): authentication skipped due to no Python authentication method defined.");
+	  } else {
+	    if (code == NNTP_AUTH_OK_VAL) {
+              /* Set the value of ubuf to the right username */
+              if (newUser[0] != '\0') {
+                strcpy(ubuf, newUser);
+              } else {
+                strcpy(ubuf, username);
+              }
+	      syslog(L_NOTICE, "%s user %s", ClientHost, ubuf);
+	      if (LLOGenable) {
+		fprintf(locallog, "%s user %s\n", ClientHost, ubuf);
+		fflush(locallog);
+	      }
+	      break;
+	    } else {
+	      syslog(L_NOTICE, "%s bad_auth", ClientHost);
+	    }
+	  }
+	} else {
+	  syslog(L_ERROR, "No script specified in auth method.\n");
+	}
+#endif /* DO_PYTHON */
+      } else {
 	if (auth->auth_methods[i]->users &&
 	  !MatchUser(auth->auth_methods[i]->users, username))
 	    continue;
@@ -2247,9 +2365,7 @@
 	if (done)
 	    /* this authenticator succeeded */
 	    break;
-#ifdef DO_PERL
       }
-#endif /* DO_PERL */
     }
     DISPOSE(resdir);
     if (ubuf[0])
diff -ur inn/nnrpd/post.c inn_python/nnrpd/post.c
--- inn/nnrpd/post.c	Sun Jan 12 23:59:35 2003
+++ inn_python/nnrpd/post.c	Mon Jan 13 16:22:51 2003
@@ -673,6 +673,11 @@
     bool		IsNewgroup;
     bool		FoundOne;
     int                 flag;
+    bool                hookpresent = FALSE;
+
+#ifdef DO_PYTHON
+    hookpresent = TRUE;
+#endif /* DO_PYTHON */
 
     p = HDR(HDR__CONTROL);
     IsNewgroup = p && EQn(p, "newgroup", 8);
@@ -689,7 +694,7 @@
     do {
 	if (innconf->mergetogroups && p[0] == 't' && p[1] == 'o' && p[2] == '.')
 	    p = "to";
-        if (PERMspecified) {
+        if (!hookpresent && PERMspecified) {
 	    grplist[0] = p;
 	    grplist[1] = NULL;
 	    if (!PERMmatch(PERMpostlist, grplist)) {
@@ -704,16 +709,17 @@
 	switch (flag) {
 	case NF_FLAG_OK:
 #ifdef DO_PYTHON
-	if (innconf->nnrppythonauth) {
+	if (PY_use_dynamic) {
 	    char    *reply;
 
-	    /* Authorize user at a Python authorization module */
-	    if (PY_authorize(ClientHost, ClientIpString, ServerHost, PERMuser, p, TRUE, &reply) < 0) {
-	        syslog(L_NOTICE, "PY_authorize(): authorization skipped due to no Python authorization method defined.");
+	    /* Authorize user using a Python module method dynamic */
+	    if (PY_dynamic(ClientHost, ClientIpString, ServerHost, PERMuser, p, TRUE, &reply) < 0) {
+	        syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
 	    } else {
 	        if (reply != NULL) {
-		    syslog(L_TRACE, "PY_authorize() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
-		    snprintf(Error, sizeof(Error), "%s\r\n", reply);
+		    syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
+		    (void)sprintf(Error, "%s\r\n", reply);
+		    DISPOSE(reply);
 		    break;
 		}
 	    }
diff -ur inn/nnrpd/python.c inn_python/nnrpd/python.c
--- inn/nnrpd/python.c	Sun Jan 12 23:59:36 2003
+++ inn_python/nnrpd/python.c	Mon Jan 13 16:22:51 2003
@@ -15,96 +15,128 @@
 
 #include "inn/innconf.h"
 #include "nnrpd.h"
+#include "inn/hashtab.h"
 
 #if defined(DO_PYTHON)
 
 #include "Python.h"
 
+/* values relate name of hook to array index */
+#define PYTHONauthen           1
+#define PYTHONaccess           2
+#define PYTHONdynamic          3
+
+#define PYTHONtypes_max        4
+
+/* values relate type of method to array index */
+#define PYTHONmain             1
+#define PYTHONinit             2
+#define PYTHONclose            3
+
+#define PYTHONmethods_max      4
+
+/* key names for attributes dictionary */
+#define PYTHONhostname         "hostname"
+#define PYTHONipaddress        "ipaddress"
+#define PYTHONinterface        "interface"
+#define PYTHONuser             "user"
+#define PYTHONpass             "pass"
+#define PYTHONtype             "type"
+#define PYTHONnewsgroup        "newsgroup"
+
+/* Max number of items in dictionary to pass to auth methods */
+#define	_PY_MAX_AUTH_ITEM	7
+
 /* Pointers to external Python objects */
 PyObject	*PYAuthObject = NULL;
-PyObject	*PYAuthModule = NULL;
 
 /* Dictionary of params to pass to authentication methods */
 PyObject	*PYauthinfo = NULL;
 PyObject	**PYauthitem = NULL;
-PyObject	**PYauthkey = NULL;
-
-/* Max number of items in dictionary to pass to auth methods */
-#define	_PY_MAX_AUTH_ITEM	10
 
 /* These are pointers to Python methods specified in auth module */
-PyObject	*authenticate_method = NULL;
-PyObject	*authorize_method = NULL;
-PyObject	*close_method = NULL;
+static PyMethodDef nnrpdPyMethods[];
 
 /* Forward declaration */
 static PyObject *PY_set_auth_hook(PyObject *dummy, PyObject *args);
+void PY_load_python(void);
+PyObject* PY_setup(int type, int method, char *file);
+static const void *file_key(const void *p);
+static bool file_equal(const void *k, const void *p);
+static void file_free(void *p);
+static void file_trav(void *data, void* null);
+
+bool   PythonLoaded = FALSE;
+
+/* structure for storage of attributes for a module file */
+typedef struct PyFile {
+  char          *file;  
+  bool          loaded[PYTHONtypes_max];
+  PyObject	*procs[PYTHONtypes_max][PYTHONmethods_max];
+} PyFile;
 
-/* These variable defined in other C modules */
-extern char accesslist[];
+/* hash for storing files */
+struct hash *files;
+
+/* for passing the dynamic module filename from perm.c */
+char*    dynamic_file;
 
 /*
-** Authenticate connecting host by IP address or username&password.
+** Authenticate connecting host by username&password.
 **
 ** Return NNTP reply code as returned by Python method or -1 if method
 ** is not defined.
 */
-int PY_authenticate(char *clientHost, char *clientIpString, char *serverHost, char *Username, char *Password, char *accesslist) {
-    PyObject    *result, *item;
-    char        *type;
+int PY_authenticate(char* file, char *clientHost, char *clientIpString, char *serverHost, char *Username, char *Password, char *errorstring, char *newUser) {
+    PyObject    *result, *item, *proc;
     int         authnum;
     int         code, i;
+    char        *temp;
+
+    PY_load_python();
+    proc = PY_setup(PYTHONauthen, PYTHONmain, file);
 
     /* Return if authentication method is not defined */
-    if (authenticate_method == NULL)
+    if (proc == NULL)
         return -1;
 
-    /* Figure out authentication type */
-    if (Username == NULL)
-        type = "connect";
-    else
-        type = "authinfo";
-
     /* Initialize PythonAuthObject with connect method specific items */
     authnum = 0;
 
-    /* Authentication type */
-    PYauthitem[authnum] = PyBuffer_FromMemory(type, strlen(type));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
-
     /* Client hostname */
     PYauthitem[authnum] = PyBuffer_FromMemory(clientHost, strlen(clientHost));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
 
     /* Client IP number */
     PYauthitem[authnum] = PyBuffer_FromMemory(clientIpString, strlen(clientIpString));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
 
     /* Server interface the connection comes to */
     PYauthitem[authnum] = PyBuffer_FromMemory(serverHost, strlen(serverHost));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
 
     /* Username if known */
     if (Username == NULL)
         PYauthitem[authnum] = Py_None;
     else
         PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
 
     /* Password if known */
     if (Password == NULL)
         PYauthitem[authnum] = Py_None;
     else
         PYauthitem[authnum] = PyBuffer_FromMemory(Password, strlen(Password));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
 
     /* Now invoke authenticate method and see if it likes this user */
-    result = PyObject_CallFunction(authenticate_method, "O", PYauthinfo);
+    result = PyObject_CallFunction(proc, "O", PYauthinfo);
 
     /* Check the response */
-    if (result == NULL || !PyTuple_Check(result))
+    if (result == NULL || !PyTuple_Check(result) 
+        || ((PyTuple_Size(result) != 2) && (PyTuple_Size(result) != 3)))
     {
-        syslog(L_ERROR, "python authenticate_method (type %s) returned wrong result", type);
+        syslog(L_ERROR, "python authenticate method returned wrong result");
 	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
 	ExitWithStats(1, TRUE);
     }
@@ -115,7 +147,7 @@
     /* Check the item */
     if (!PyInt_Check(item))
     {
-        syslog(L_ERROR, "python authenticate_method (type %s) returned bad NNTP response code", type);
+        syslog(L_ERROR, "python authenticate method returned bad NNTP response code");
 	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
 	ExitWithStats(1, TRUE);
     }
@@ -123,57 +155,146 @@
     /* Store the code */
     code = PyInt_AS_LONG(item);
 
-    /* Get the CanPost setting */
+    /* Get the error string */
     item = PyTuple_GetItem(result, 1);
 
     /* Check the item */
-    if (!PyInt_Check(item))
+    if (!PyString_Check(item))
     {
-        syslog(L_ERROR, "python authenticate_method (type %s) returned bad CanPost setting", type);
+        syslog(L_ERROR, "python authenticate method returned bad error string");
 	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
 	ExitWithStats(1, TRUE);
     }
 
-    /* Store the setting */
-    PERMcanpost = PyInt_AS_LONG(item);
+    /* Store error string */
+    temp = PyString_AS_STRING(item);
+    strcpy(errorstring, temp);
 
-    /* Get the CanRead setting */
+    if (PyTuple_Size(result) == 3)
+    {
+
+      /* Get the username string */
     item = PyTuple_GetItem(result, 2);
 
     /* Check the item */
-    if (!PyInt_Check(item))
+      if (!PyString_Check(item))
     {
-        syslog(L_ERROR, "python authenticate_method (type %s) returned bad CanRead setting", type);
+        syslog(L_ERROR, "python authenticate method returned bad username string");
 	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
 	ExitWithStats(1, TRUE);
     }
 
-    /* Store the setting */
-    PERMcanread = PyInt_AS_LONG(item);
+      /* Store error string */
+      temp = PyString_AS_STRING(item);
+      strcpy(newUser, temp);
+    }
 
-    /* Get the access list */
-    item = PyTuple_GetItem(result, 3);
+    /* Clean up the dictionary object */
+    PyDict_Clear(PYauthinfo);
 
-    /* Check the item */
-    if (!PyString_Check(item))
+    /* Clean up dictionary items */
+    for (i = 0; i < authnum; i++)
+    {
+        if (PYauthitem[i] != Py_None)
     {
-        syslog(L_ERROR, "python authenticate_method (type %s) returned bad access list value", type);
+	    Py_DECREF(PYauthitem[i]);
+	}
+    }
+
+    /* Log auth result */
+    syslog(L_NOTICE, "python authenticate method succeeded, return code %d, error string %s", code, errorstring);
+
+    /* Return response code */
+    return code;
+}
+
+/*
+** Create an access group based on the values returned by the script in file
+**
+*/
+void PY_access(char* file, struct vector *access_vec, char *clientHost, char *clientIpString, char *serverHost, char *Username) {
+    PyObject	*result, *key, *value, *proc;
+    char	*skey, *svalue, *temp;
+    int		authnum;
+    int		i;
+
+    PY_load_python();
+    proc = PY_setup(PYTHONaccess, PYTHONmain, file);
+
+    /* Exit if access method is not defined */
+    if (proc == NULL) {
+      syslog(L_ERROR, "python access method not defined");
 	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
 	ExitWithStats(1, TRUE);
     }
 
-    /* Store access list*/
-    strcpy(accesslist, PyString_AS_STRING(item));
+    /* Initialize PythonAuthObject with group method specific items */
+    authnum = 0;
+
+    /* Client hostname */
+    PYauthitem[authnum] = PyBuffer_FromMemory(clientHost, strlen(clientHost));
+    PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
+
+    /* Client IP number */
+    PYauthitem[authnum] = PyBuffer_FromMemory(clientIpString, strlen(clientIpString));
+    PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
+
+    /* Server interface the connection comes to */
+    PYauthitem[authnum] = PyBuffer_FromMemory(serverHost, strlen(serverHost));
+    PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
+
+    /* Username */
+    PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
+    PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
 
-    /* Fix the NNTP response code */
-    if ((code == NNTP_POSTOK_VAL) || (code == NNTP_NOPOSTOK_VAL))
+    /* Password is not known */
+    PYauthitem[authnum] = Py_None;
+    PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
+
+    /*
+     * Now invoke newsgroup access method
+     */
+    result = PyObject_CallFunction(proc, "O", PYauthinfo);
+
+    /* Check the response */
+    if (result == NULL || result == Py_None || !PyDict_Check(result))
     {
-      code = PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL;
+        syslog(L_ERROR, "python access method returned wrong result - expected a dictionary");
+	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
+	ExitWithStats(1, TRUE);
     }
 
-    /* Initialize needauth flag */
-    if (code == NNTP_AUTH_NEEDED_VAL) 
-        PERMneedauth = TRUE;
+    /* resize vector to dictionary length */
+    vector_resize(access_vec, PyDict_Size(result) - 1);
+
+    /* store dict values in proper format in access vector */
+    i = 0;
+    while(PyDict_Next(result, &i, &key, &value)) {
+      if (!PyString_Check(key)) {
+        syslog(L_ERROR, "python access method return dictionary key %i not a string", i);
+        Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
+        ExitWithStats(1, FALSE);
+    }
+      if (!PyString_Check(value)) {
+        syslog(L_ERROR, "python access method return dictionary value %i not a string", i);
+        Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
+        ExitWithStats(1, FALSE);
+      }
+      
+      temp = PyString_AsString(key);
+      skey = COPY(temp);
+
+      temp = PyString_AsString(value);
+      svalue = COPY(temp);
+
+      skey = strcat(skey, ": \"");
+      skey = strcat(skey, svalue);
+      skey = strcat(skey, "\"\n");
+      vector_add(access_vec, skey);
+
+      free(skey);
+      free(svalue);
+    }
 
     /* Clean up the dictionary object */
     PyDict_Clear(PYauthinfo);
@@ -188,80 +309,93 @@
     }
 
     /* Log auth result */
-    syslog(L_NOTICE, "python authenticate_method (type %s) succeeded, return code %d", type, code);
-
-    /* Return response code */
-    return code;
+    syslog(L_NOTICE, "python access method succeeded");
 }
 
+/*
+** Initialize dynamic access control code
+*/
+
+void PY_dynamic_init (char* file) {
+  dynamic_file = malloc(strlen(file));
+  dynamic_file = COPY(file);
+  PY_use_dynamic = TRUE;
+}
 
 /*
-** Authorize user access to a newsgroup.
+** Determine dynamic user access rights to a given newsgroup.
 **
 ** Return 0 if requested privelege is granted or positive value
 ** and a reply_message pointer initialized with reply message.
-** Return negative value if authorize method is not defined.
+** Return negative value if dynamic method is not defined.
 */
-int PY_authorize(char *clientHost, char *clientIpString, char *serverHost, char *Username, char *NewsGroup, int PostFlag, char **reply_message) {
-    PyObject	*result, *item;
-    char	*string;
+int PY_dynamic(char *clientHost, char *clientIpString, char *serverHost, char *Username, char *NewsGroup, int PostFlag, char **reply_message) {
+    PyObject	*result, *item, *proc;
+    char	*string, *temp;
     int		authnum;
     int		i;
 
-    /* Return if authorize_method is not defined */
-    if (authorize_method == NULL)
+    PY_load_python();
+    proc = PY_setup(PYTHONdynamic, PYTHONmain, dynamic_file);
+
+    /* Return if dyanmic method is not defined */
+    if (proc == NULL) {
         return -1;
+    }
 
     /* Initialize PythonAuthObject with group method specific items */
     authnum = 0;
 
-    /* Assign authentication type */
-    PYauthitem[authnum] = PyBuffer_FromMemory(PostFlag ? "post" : "read", 4);
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
-
     /* Client hostname */
     PYauthitem[authnum] = PyBuffer_FromMemory(clientHost, strlen(clientHost));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
 
     /* Client IP number */
     PYauthitem[authnum] = PyBuffer_FromMemory(clientIpString, strlen(clientIpString));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
 
     /* Server interface the connection comes to */
     PYauthitem[authnum] = PyBuffer_FromMemory(serverHost, strlen(serverHost));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
 
     /* Username */
     PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
 
     /* Password is not known */
     PYauthitem[authnum] = Py_None;
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
+
+    /* Assign authentication type */
+    PYauthitem[authnum] = PyBuffer_FromMemory(PostFlag ? "post" : "read", 4);
+    PyDict_SetItemString(PYauthinfo, PYTHONtype, PYauthitem[authnum++]);
 
     /* Newsgroup user tries to access */
-    PYauthitem[authnum] = PyBuffer_FromMemory(NewsGroup, strlen(NewsGroup));;
-    PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]);
+    PYauthitem[authnum] = PyBuffer_FromMemory(NewsGroup, strlen(NewsGroup));
+    PyDict_SetItemString(PYauthinfo, PYTHONnewsgroup,  PYauthitem[authnum++]);
 
     /*
-     * Now invoke newsgroup access authorization method and see if
+     * Now invoke newsgroup dynamic access method and see if
      * it likes this user to access this newsgroup.
      */
-    result = PyObject_CallFunction(authorize_method, "O", PYauthinfo);
+
+    result = PyObject_CallFunction(proc, "O", PYauthinfo);
 
     /* Check the response */
     if (result == NULL || result != Py_None && !PyString_Check(result))
     {
-        syslog(L_ERROR, "python authorize_method (%s access) returned wrong result", PostFlag ? "post" : "read");
+        syslog(L_ERROR, "python dyanmic method (%s access) returned wrong result: %s", PostFlag ? "post" : "read", result);
 	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
 	ExitWithStats(1, FALSE);
     }
 
     /* Get the response string */
-    if (result == Py_None)
+    if (result == Py_None) {
         string = NULL;
-    else
-        string = PyString_AS_STRING(result);
+    } else {
+        temp = PyString_AS_STRING(result);
+	string = COPY(temp);
+    }
 
     /* Clean up the dictionary object */
     PyDict_Clear(PYauthinfo);
@@ -276,7 +410,7 @@
     }
 
     /* Log auth result */
-    syslog(L_NOTICE, "python authorize_method (%s access) succeeded, refusion string: %s", PostFlag ? "post" : "read", string == NULL ? "<empty>" : string);
+    syslog(L_NOTICE, "python dynamic method (%s access) succeeded, refusion string: %s", PostFlag ? "post" : "read", string == NULL ? "<empty>" : string);
 
     /* Initialize reply string */
     if (reply_message != NULL)
@@ -291,14 +425,34 @@
 **  This runs when nnrpd shuts down.
 */
 void
-PY_close(void)
+PY_close_python(void)
 {
-    PyObject	*result;
+  hash_traverse(files, file_trav, NULL);
+
+  hash_free(files);
 
-    if (close_method != NULL) {
-        result = PyObject_CallFunction(close_method, NULL);
+  free(dynamic_file);
+}
+
+/*
+** Traversal function for PY_close_python
+*/
+void
+file_trav(void *data, void* null)
+{
+  PyFile *fp = data;
+  int j;
+  PyObject	*result, *func;
+
+  for (j = 1; j < PYTHONtypes_max; j++) {
+    if (fp->loaded[j] != FALSE) {
+      func = fp->procs[j][PYTHONclose];
+      if (func != NULL) {
+	result = PyObject_CallFunction(func, NULL);
         Py_XDECREF(result);
     }
+    }
+  }
 }
 
 
@@ -375,17 +529,63 @@
     return result;
 }
 
+/*
+** Load the Python interpreter
+*/
+
+void PY_load_python() {
+  int i, authnum;
+  
+  if (!PythonLoaded) {
+      /* add path for nnrpd module */    
+      setenv("PYTHONPATH", innconf->pathfilter, 1);
+
+      /* Load up the interpreter ;-O */
+      Py_Initialize();
+    
+      /* It makes Python sad when its stdout and stderr are closed. */
+      if (feof(stdout) || feof(stderr))
+          PyRun_SimpleString
+            ("import sys; sys.stdout=sys.stderr=open('/dev/null', 'a')");
+    
+      /* See if Python initialized OK */
+      if (!Py_IsInitialized ()) {
+          syslog(L_ERROR, "python interpreter NOT initialized");
+          return;
+      }
+
+
+      /* Build a module interface to certain nnrpd functions */
+      (void) Py_InitModule("nnrpd", nnrpdPyMethods);
+
+      /*
+      ** Grab space for authinfo dictionary so we aren't forever
+      ** recreating them.
+      */
+      PYauthinfo = PyDict_New();
+      PYauthitem = NEW(PyObject *, _PY_MAX_AUTH_ITEM);
+
+      /* create hash to store file attributes */
+
+      files = hash_create(4, hash_string, file_key,
+                                file_equal, file_free);
+
+      PythonLoaded = TRUE;
+
+      syslog(L_NOTICE, "python interpreter initialized OK");
+  }
+}
 
 /*
 **  Check that a method exists and is callable.	 Set up a pointer to
 **  the corresponding PyObject, or NULL if not found.
 */
 void
-PYdefonemethod(methptr, methname)
-    PyObject    **methptr;
-    char        *methname;
+PYdefonemethod(PyFile *fp, int type, int method, char *methname)
 {
-    Py_XDECREF(*methptr);
+    PyObject **methptr;
+
+    methptr = &fp->procs[type][method];
 
     /* Get a pointer to given method */
     *methptr = PyObject_GetAttrString(PYAuthObject, methname);
@@ -405,82 +605,133 @@
 
 
 /*
-**  Look up all the known authentication/authorization methods and set up
+**  Look up all the known python methods and set up
 **  pointers to them so that we could call them from nnrpd.
 */
 void
-PYdefmethods(void)
+PYdefmethods(PyFile *fp)
 {
     /* Get a reference to authenticate() method */
-    PYdefonemethod(&authenticate_method, "authenticate");
+  PYdefonemethod(fp, PYTHONauthen, PYTHONmain, "authenticate");
 
-    /* Get a reference to authorize() method */
-    PYdefonemethod(&authorize_method, "authorize");
+  /* Get a reference to authen_init() method */
+  PYdefonemethod(fp, PYTHONauthen, PYTHONinit, "authen_init");
+
+  /* Get a reference to authen_close() method */
+  PYdefonemethod(fp, PYTHONauthen, PYTHONclose, "authen_close");
 
-    /* Get a reference to close() method */
-    PYdefonemethod(&close_method, "close");
-}
 
+  /* Get a reference to access() method */
+  PYdefonemethod(fp, PYTHONaccess, PYTHONmain, "access");
+  
+  /* Get a reference to access_init() method */
+  PYdefonemethod(fp, PYTHONaccess, PYTHONinit, "access_init");
+  
+  /* Get a reference to access_close() method */
+  PYdefonemethod(fp, PYTHONaccess, PYTHONclose, "access_close");
+
+  /* Get a reference to dynamic() method */
+  PYdefonemethod(fp, PYTHONdynamic, PYTHONmain, "dynamic");
+
+  /* Get a reference to dynamic_init() method */
+  PYdefonemethod(fp, PYTHONdynamic, PYTHONinit, "dynamic_init");
+  
+  /* Get a reference to dynamic_close() method */
+  PYdefonemethod(fp, PYTHONdynamic, PYTHONclose, "dynamic_close");
+}
 
 /*
-**  Called when nnrpd starts -- this gets the scripts hooked in.
+**  Called when a python hook is needed -- this gets the scripts hooked in.
 */
-void
-PY_setup(void)
+PyObject*
+PY_setup(int type, int method, char *file)
 {
-    int  authnum;
-
-    /* Export $PYTHONPATH to let Python find the scripts */
-    setenv("PYTHONPATH", innconf->pathfilter, 1);
+    int  i;
+    PyFile *fp;
+    char *temp;
+    PyObject    *result;
 
-    /* Load up the interpreter ;-O */
-    Py_Initialize();
+    /* check to see if this file is in files */
+    if (!(fp = hash_lookup(files, file))) {
+      fp = malloc(sizeof(PyFile));
 
-    /* It makes Python sad when its stdout and stderr are closed. */
-    if (feof(stdout) || feof(stderr))
-        PyRun_SimpleString
-	  ("import sys; sys.stdout=sys.stderr=open('/dev/null', 'a')");
+      fp->file = malloc(strlen(file));
+      fp->file = COPY(file);
 
-    /* See it Python initialized OK */
-    if (!Py_IsInitialized ()) {
-        syslog(L_ERROR, "python interpreter NOT initialized");
-	return;
+      for (i = 1; i < PYTHONtypes_max; i++) {
+	fp->loaded[i] = FALSE;
     }
-    syslog(L_NOTICE, "python interpreter initialized OK");
 
-    /* Build a module interface to certain nnrpd functions */
-    Py_InitModule("nnrpd", nnrpdPyMethods);
-
-    /* Load up external nntpd auth module */
-    PYAuthModule = PyImport_ImportModule(_PATH_PYTHON_AUTH_M);
-    if (PYAuthModule == NULL)
-        syslog(L_ERROR, "failed to import external python module");
+      /* Load up external module */
+      (void) PyImport_ImportModule(file);
 
     /* See if nnrpd auth object is defined in auth module */
-    if (PYAuthObject == NULL)
+      if (PYAuthObject == NULL) {
         syslog(L_ERROR, "python auth object is not defined");
-    else {
+	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
+	PY_close_python();
+	ExitWithStats(1, FALSE);
+      } else {
         /* Set up pointers to known Python methods */
-        PYdefmethods();
-        syslog(L_NOTICE, "some python methods defined. good.");
+	PYdefmethods(fp);
+      }
+      hash_insert(files, file, fp);
     }
 
-    /*
-    ** Grab space for authinfo dictionary so we aren't forever
-    ** recreating them.
-    */
-    PYauthinfo = PyDict_New();
-    PYauthitem = NEW(PyObject *, _PY_MAX_AUTH_ITEM);
-    PYauthkey = NEW(PyObject *, _PY_MAX_AUTH_ITEM);
+    if ((!fp->loaded[type]) && (fp->procs[type][PYTHONinit] != NULL)) {
+      result = PyObject_CallFunction(fp->procs[type][PYTHONinit], NULL);
+      if (result != NULL) {
+	Py_XDECREF(result);
+      }
+      fp->loaded[type] = TRUE;
+    }
+    return fp->procs[type][method];
+}
 
-    /* Preallocate keys for the authinfo dictionary (up to PY_MAX_AUTH_ITEM) */
-    authnum = 0;
-    PYauthkey[authnum++] = PyString_InternFromString("type");
-    PYauthkey[authnum++] = PyString_InternFromString("hostname");
-    PYauthkey[authnum++] = PyString_InternFromString("ipaddress");
-    PYauthkey[authnum++] = PyString_InternFromString("interface");
-    PYauthkey[authnum++] = PyString_InternFromString("user");
-    PYauthkey[authnum++] = PyString_InternFromString("pass");
-    PYauthkey[authnum++] = PyString_InternFromString("newsgroup");
+/*
+**  Return the key (filename) from a file struct, used by the hash table.
+*/
+static const void *
+file_key(const void *p)
+{
+    const struct PyFile *f = p;
+
+    return f->file;
+}
+
+/*
+**  Check to see if a provided key matches the key of a PyFile struct,
+**  used by the hash table.
+*/
+static bool
+file_equal(const void *k, const void *p)
+{
+    const char *key = k;
+    const struct PyFile *f = p;
+
+    return EQ(key, f->file);
+}
+
+/*
+**  Free a file, used by the hash table.
+*/
+static void
+file_free(void *p)
+{
+    struct PyFile *fp = p;
+    int i, j;
+
+    free(fp->file);
+
+    for (i = 1; i < PYTHONtypes_max; i++) {
+      for (j = 1; j < PYTHONmethods_max; j++) {
+	if (fp->procs[i][j] != NULL) {
+	  Py_DECREF(fp->procs[i][j]);
+	}
+      }
+    }
+
+    free(fp);
 }
+        
 #endif /* defined(DO_PYTHON) */
diff -ur inn/samples/inn.conf.in inn_python/samples/inn.conf.in
--- inn/samples/inn.conf.in	Mon Dec 23 21:01:36 2002
+++ inn_python/samples/inn.conf.in	Mon Jan 13 16:22:51 2003
@@ -77,7 +77,6 @@
 initialtimeout:         10
 msgidcachesize:         10000
 nnrpdcheckart:          true
-nnrppythonauth:         false
 noreader:               false
 readerswhenstopped:     false
 readertrack:            false
diff -ur inn/samples/nnrpd_auth.py inn_python/samples/nnrpd_auth.py
--- inn/samples/nnrpd_auth.py	Mon Jan 15 05:31:35 2001
+++ inn_python/samples/nnrpd_auth.py	Mon Jan 13 16:33:04 2003
@@ -1,44 +1,53 @@
 #
 #
-# This is a sample authentication and authorization module for nnrpd hook
+# This is a sample authentication and authorization module for python
+# nnrpd hook
 #
 # For details, see the file doc/hook-python that came with INN.
 #
 
 #
-# This file is loaded when nnrpd starts up. An instance of AUTH class
-# is passed to nnrpd via set_auth_hook() function imported from nnrpd. The
-# following methods of that class are known to nnrpd:
-#
-#   __init__()                  - Called on nnrpd startup. Use this method
-#                                 to initilalize your variables or open
-#                                 a database connection.
-#   close()                     - Called on nnrpd termination. Save your state
-#                                 variables or close a database connection.
-#   authenticate()              - Called whenever a reader connects or uses
-#                                 AUTHINFO command. A "type" entry of 
-#                                 "attributes" dictionary is passed to this
-#                                 method to figure out the type of
-#                                 authentication happening.
-#   authorize()                 - Called whenever a reader requests either
-#                                 read or post access to a newsgroup.
-#
-# Attributes about the connection are passed to the program in the
-# "attributes" global dictinary variable.
-#
-# The authenticate() method should return a tuple of four elements:
-#
-# 1) NNTP response code.  Should be one of the codes from
-#    NNRPD_AUTH.connectcodes{} or NNRPD_AUTH.authcodes{}
-# 2) Reading Allowed. Should be a boolean value.
-# 3) Posting Allowed. Should be a boolean value.
-# 4) Wildmat expression that says what groups to provide access to.
-#
-# All four of these are required.
-#
-# The authorize() method should return None to grant requested
-# priveleges or a non-empty string (which will be reported back to reader)
-# otherwise.
+# This file is loaded when one of the python_* readers.conf parameters
+# is encountered. An instance of AUTH class is passed to nnrpd via
+# set_auth_hook() function imported from nnrpd. The following methods
+# of that class are known to nnrpd:
+#
+#   __init__()                  - Use this method to initilalize your
+#                                 general variables or open a common
+#                                 database connection. May be omitted.
+#   access_init()               - Init function specific to access
+#                                 control. May be omitted
+#   access(attributes)          - Called when a python_access
+#                                 statement is reached in the
+#                                 processing of readers.conf. Returns
+#                                 a dictionary of values representing
+#                                 statements to be included in an
+#                                 access group. 
+#   access_close()              - Called on nnrpd termination. Save
+#                                 your state variables or close a
+#                                 database connection. May be omitted
+#   authen_init()               - Init function specific to
+#                                 authentication. May be omitted
+#   authenticate(attributes)    - Called when a python_auth statement
+#                                 is reached in the processing of
+#                                 readers.conf. Returns a response
+#                                 code, an error string and an
+#                                 optional string to appear in the
+#                                 logs as the username.
+#   authen_close()              - Called on nnrpd termination. Save
+#                                 your state variables or close a database
+#                                 connection. May be omitted
+#   dynamic_init()              - Init function specific to
+#                                 authentication. May be omitted
+#   dynamic(attributes)         - Called whenever a reader requests either
+#                                 read or post access to a
+#                                 newsgroup. Returns None to grant
+#                                 access, or a non-empty string (which
+#                                 will be reported back to reader)
+#                                 otherwise. 
+#   dynamic_close()             - Called on nnrpd termination. Save
+#                                 your state variables or close a database
+#                                 connection. May be omitted
 #
 # If there is a problem with return codes from any of these methods then nnrpd
 # will die and syslog the exact reason.
@@ -59,8 +68,8 @@
 class AUTH:
     """Provide authentication and authorization callbacks to nnrpd."""
     def __init__(self):
-        """This runs on nnrpd startup. It is a good place to initialize
-           variables or open a database connection.
+        """This is a good place to initialize variables or open a
+           database connection. 
         """
         # Create a list of NNTP codes to respond on connect
         self.connectcodes = {   'READPOST':200,
@@ -76,65 +85,66 @@
 
         syslog('notice', 'nnrpd authentication class instance created')
 
-    def close(self):
-        """Runs when nnrpd exits. You can use this method to save state
-           information to be restored by the __init__() method or close
-           a database connection.
-        """
-        syslog('notice', "close method running, bye!")
-
     def authenticate(self, attributes):
-        """Called when a reader connects or authenticates"""
+        """Called when python_auth is encountered in readers.conf"""
 
 	# just for debugging purposes
-	syslog('debug', 'authenticate() invoked against type %s, hostname %s, ipaddress %s, interface %s, user %s' % (\
-		attributes['type'], \
+	syslog('notice', 'n_a authenticate() invoked: hostname %s, ipaddress %s, interface %s, user %s' % (\
 		attributes['hostname'], \
 		attributes['ipaddress'], \
 		attributes['interface'], \
 		attributes['user']))
 
-	# allow newsreading from specific host only
-        if attributes['type'] == buffer('connect'):
-            if attributes['ipaddress'] == buffer('127.0.0.1'):
-                syslog('notice', 'authentication by IP address succeeded')
-                return ( self.connectcodes['READPOST'], 1, 1, '*' )
-            else:
-                syslog('notice', 'authentication by IP address failed')
-                return ( self.connectcodes['PERMDENIED'], 0, 0, '!*' )
-                
-	# do not do any username authentication
-        elif attributes['type'] == buffer('authinfo'):
+	# do username passworld authentication
+        if 'foo' == str(attributes['user'])  \
+           and 'foo' == str(attributes['pass']):
             syslog('notice', 'authentication by username succeeded')
-            return ( self.authcodes['ALLOWED'], 1, 1, '*')
+            return ( self.authcodes['ALLOWED'], 'No error', 'default_user')
         else:
-            syslog('notice', 'authentication type is not known: %s' % attributes['type'])
-            return ( self.authcodes['DENIED'], 0, 0, '!*')
+            syslog('notice', 'authentication by username failed')
+            return ( self.authcodes['DENIED'], 'Access Denied!')
 
-    def authorize(self, attributes):
-        """Called when a reader requests either read or post permission
-           for particular newsgroup.
+    def access(self, attributes):
+        """Called when python_access is encountered in readers.conf"""
+
+	# just for debugging purposes
+	syslog('notice', 'n_a access() invoked: hostname %s, ipaddress %s, interface %s, user %s' % (\
+		attributes['hostname'], \
+		attributes['ipaddress'], \
+		attributes['interface'], \
+		attributes['user']))
+
+	# allow newsreading from specific host only
+        if '127.0.0.1' == str(attributes['ipaddress']):
+            syslog('notice', 'authentication by IP address succeeded')
+            return {'read':'*','post':'*'}
+        else:
+            syslog('notice', 'authentication by IP address failed')
+            return {'read':'!*','post':'!*'}
+
+    def dynamic(self, attributes):
+        """Called when python_dynamic was reached in the processing of
+           readers.conf and a reader requests either read or post
+           permission for particular newsgroup.
         """
 	# just for debugging purposes
-	syslog('debug', 'authorize() invoked against type %s, hostname %s, ipaddress %s, interface %s, user %s, newsgroup %s' % (\
+	syslog('notice', 'n_a dyanmic() invoked against type %s, hostname %s, ipaddress %s, interface %s, user %s' % (\
 		attributes['type'], \
 		attributes['hostname'], \
 		attributes['ipaddress'], \
 		attributes['interface'], \
-		attributes['user'], \
-		attributes['newsgroup']))
+		attributes['user']))
 
-	# Allow reading of any newsgroup
-        if attributes['type'] == buffer('read'):
-            syslog('notice', 'authorization for read access succeeded')
+	# Allow reading of any newsgroup but not posting
+        if 'post' == str(attributes['type']):
+            syslog('notice', 'authorization for post access denied')
+            return "no posting for you"
+        elif 'read' == str(attributes['type']):
+            syslog('notice', 'authorization for read access granted')
             return None
-	# ..but disallow postings
-        elif attributes['type'] == buffer('post'):
-            syslog('notice', 'authorization for post access failed')
-            return "Posting disallowed"
         else:
             syslog('notice', 'authorization type is not known: %s' % attributes['type'])
-            return "Internal error"
+            return "Internal error";
 

-- 
erik         | "It is idle to think that, by means of words, | Maurice
  kl at von     | any real communication can ever pass | Maeterlinck
    eriq.org | from one [human] to another." | Silence


More information about the inn-patches mailing list