Patch for the integration of python hooks and readers.conf

Erik Klavon erik at eriq.org
Sun Feb 2 18:41:24 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. The diff is against current as of 1000 PST today.

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 ***

**************** end new files ****************



**************** 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	Sat Feb  1 11:03:22 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	Sat Feb  1 11:03:22 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 Jan 19 14:10:57 2003
+++ inn_python/doc/pod/readers.conf.pod	Sat Feb  1 11:03:22 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
@@ -489,27 +527,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	Sat Feb  1 11:05:57 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	Sat Jan 18 17:15:34 2003
+++ inn_python/lib/innconf.c	Sat Feb  1 11:06:36 2003
@@ -206,7 +206,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	Sat Jan 18 20:13:47 2003
+++ inn_python/nnrpd/commands.c	Sat Feb  1 11:14:18 2003
@@ -224,9 +224,6 @@
     static char	Password[SMBUF];
     char	accesslist[BIG_BUFFER];
     char        errorstr[BIG_BUFFER];
-#ifdef DO_PYTHON
-    int         code;
-#endif
 
     if (strcasecmp(av[1], "generic") == 0) {
 	char *logrec = Glom(av);
@@ -283,69 +280,37 @@
 	    strlcpy(Password, av[2], sizeof(Password));
 	}
 
-#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 */
-			strlcpy(PERMuser, User, sizeof(PERMuser));
-			strlcpy(PERMpass, Password, sizeof(PERMpass));
-			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 (strcmp(User, PERMuser) == 0 && strcmp(Password, PERMpass) == 0) {
-		syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
-		if (LLOGenable) {
-			fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
-			fflush(locallog);
-		}
-		Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
-		PERMneedauth = false;
-		PERMauthorized = true;
-		return;
-	    }
-
-            errorstr[0] = '\0';
-
-            PERMlogin(User, Password, errorstr);
-	    PERMgetpermissions();
-	    if (!PERMneedauth) {
-		syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
-		if (LLOGenable) {
-			fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
-			fflush(locallog);
-		}
-		Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
-		PERMneedauth = false;
-		PERMauthorized = true;
-		return;
-	    }
-#ifdef	DO_PYTHON
-	}
-#endif	/* DO_PYTHON */
+        if (strcmp(User, PERMuser) == 0 && strcmp(Password, PERMpass) == 0) {
+            syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
+            if (LLOGenable) {
+                fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
+                fflush(locallog);
+            }
+            Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
+            PERMneedauth = false;
+            PERMauthorized = true;
+            return;
+        }
+        
+        errorstr[0] = '\0';
+        
+        PERMlogin(User, Password, errorstr);
+        PERMgetpermissions();
+        if (!PERMneedauth) {
+            syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
+            if (LLOGenable) {
+                fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
+                fflush(locallog);
+            }
+            Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
+            PERMneedauth = false;
+            PERMauthorized = true;
+            return;
+        }
 
 	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	Sat Jan 18 20:13:47 2003
+++ inn_python/nnrpd/group.c	Sun Feb  2 09:54:16 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);
 		free(group);
+                free(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);
-	    free(group);
-	    return;
-	}
-    } else {
-	Reply("%s %s\r\n", NOSUCHGROUP, group);
-	free(group);
-	return;
+    if (!hookpresent) {
+        if (PERMspecified) {
+            grplist[0] = group;
+            grplist[1] = NULL;
+            if (!PERMmatch(PERMreadlist, grplist)) {
+	         Reply("%s %s\r\n", NOSUCHGROUP, group);
+                 free(group);
+                 return;
+            }
+        } else {
+            Reply("%s %s\r\n", NOSUCHGROUP, group);
+            free(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	Tue Jan 21 17:22:41 2003
+++ inn_python/nnrpd/misc.c	Sun Feb  2 09:54:44 2003
@@ -161,17 +161,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);
+                free(reply);
+		return false;
 	    }
+            return true;
 	}
     }
 #endif /* DO_PYTHON */
diff -ur inn/nnrpd/nnrpd.c inn_python/nnrpd/nnrpd.c
--- inn/nnrpd/nnrpd.c	Mon Jan 20 22:39:58 2003
+++ inn_python/nnrpd/nnrpd.c	Sat Feb  1 11:27:59 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]";
@@ -235,9 +239,8 @@
     SMshutdown();
 
 #ifdef DO_PYTHON
-    if (innconf->nnrppythonauth)
-        PY_close();
-#endif
+        PY_close_python();
+#endif /* DO_PYTHON */
 
     if (History)
 	HISclose(History);
@@ -476,11 +479,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;
@@ -581,31 +579,9 @@
     strlcpy(LogName, ClientHost, sizeof(LogName));
 
     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 = xmalloc(sizeof(ACCESSGROUP));
-	PERMaccessconf = authconf;
-	SetDefaultAccess(PERMaccessconf);
-    } else {
-#endif	/* DO_PYTHON */
-	PERMgetaccess(NNRPACCESS);
-	PERMgetpermissions();
-#ifdef DO_PYTHON
-    }
-#endif /* DO_PYTHON */
+
+    PERMgetaccess(NNRPACCESS);
+    PERMgetpermissions();
 }
 
 
@@ -773,13 +749,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	Sat Jan 18 17:15:35 2003
+++ inn_python/nnrpd/nnrpd.h	Sun Feb  2 09:53:54 2003
@@ -274,10 +274,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	Sat Jan 18 20:13:48 2003
+++ inn_python/nnrpd/perl.c	Sat Feb  1 11:30:02 2003
@@ -24,7 +24,7 @@
 #include "nntp.h"
 
 /* Skip this entire file if DO_PERL (./configure --with-perl) isn't set. */
-#if DO_PERL
+#ifdef DO_PERL
 
 #include <EXTERN.h>
 #include <perl.h>
diff -ur inn/nnrpd/perm.c inn_python/nnrpd/perm.c
--- inn/nnrpd/perm.c	Sun Jan 19 12:58:03 2003
+++ inn_python/nnrpd/perm.c	Sun Feb  2 09:56:54 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 = xstrdup(orig->perl_access);
+    if (orig->access_script)
+        ret->access_script = xstrdup(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 = xstrdup(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 @@
 	free(del->default_domain);
     if (del->localaddress)
 	free(del->localaddress);
-    if (del->perl_access)
-        free(del->perl_access);
+    if (del->access_script)
+        free(del->access_script);
+    if (del->dynamic_script)
+        free(del->dynamic_script);
     free(del);
 }
 
@@ -694,6 +720,7 @@
 	break;
       case PERMauth:
       case PERMperl_auth:
+      case PERMpython_auth:
       case PERMauthprog:
         m = xcalloc(1, sizeof(METHOD));
 	memset(ConfigBit, '\0', ConfigBitsize);
@@ -708,6 +735,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 = xstrdup(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 = xstrdup(tok->name);
 	    tok = CONFgettoken(PERMtoks, f);
@@ -730,11 +764,28 @@
 	break;
       case PERMperl_access:
 #ifdef DO_PERL
-        curauth->perl_access = xstrdup(tok->name);
+        curauth->access_script = xstrdup(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 = xstrdup(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 = xstrdup(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 = xstrdup(tok->name);
 	CompressList(curauth->localaddress);
@@ -1474,7 +1525,7 @@
     char *user[2];
     static ACCESSGROUP *noaccessconf;
     char *uname;
-    char *cpp, *perl_path;
+    char *cpp, *script_path;
     char **args;
     struct vector *access_vec;
 
@@ -1494,18 +1545,18 @@
 	SetDefaultAccess(PERMaccessconf);
 	return;
 #ifdef DO_PERL
-    } else if (success_auth->perl_access != NULL) {
+    } else if (success_auth->access_script != NULL) {
       i = 0;
-      cpp = xstrdup(success_auth->perl_access);
+      cpp = xstrdup(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 = xstrdup(PERMuser);
         
@@ -1516,17 +1567,46 @@
 
         access_realms[0] = xcalloc(1, 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);
       }
       free(cpp);
       free(args);
 #endif /* DO_PERL */
+#ifdef DO_PYTHON
+    } else if ((success_auth->access_script != NULL) && (success_auth->access_type == PERMpython_access)) {
+        i = 0;
+        cpp = xstrdup(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 = xstrdup(PERMuser);
+            access_vec = vector_new();
+
+            PY_access(script_path, access_vec, ClientHost, ClientIpString, ServerHost, uname);
+            free(script_path);
+            free(uname);
+            free(args);
+            
+            access_realms[0] = xcalloc(1, sizeof(ACCESSGROUP));
+            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);
+        }
+        free(cpp);
+#endif /* DO_PYTHON */
     } else {
       for (i = 0; access_realms[i]; i++)
         ;
@@ -1621,6 +1701,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 */
@@ -2131,7 +2217,7 @@
     char *arg0;
     char *resdir;
     char *tmp;
-    char *perl_path;
+    char *script_path;
     char newUser[BIG_BUFFER];
     EXECSTUFF *foo;
     int done	    = 0;
@@ -2148,18 +2234,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 = xstrdup(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);
@@ -2183,8 +2269,42 @@
             } 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 = xstrdup(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') {
+                  strlcpy(ubuf, newUser, sizeof(ubuf));
+              } else {
+                  strlcpy(ubuf, username, sizeof(ubuf));
+              }
+              
+	      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;
@@ -2232,9 +2352,7 @@
 	if (done)
 	    /* this authenticator succeeded */
 	    break;
-#ifdef DO_PERL
       }
-#endif /* DO_PERL */
     }
     free(resdir);
     if (ubuf[0])
diff -ur inn/nnrpd/post.c inn_python/nnrpd/post.c
--- inn/nnrpd/post.c	Sat Feb  1 13:23:46 2003
+++ inn_python/nnrpd/post.c	Sun Feb  2 09:57:46 2003
@@ -677,6 +677,11 @@
     bool		IsNewgroup;
     bool		FoundOne;
     int                 flag;
+    bool                hookpresent = false;
+
+#ifdef DO_PYTHON
+    hookpresent = PY_use_dynamic;
+#endif /* DO_PYTHON */
 
     p = HDR(HDR__CONTROL);
     IsNewgroup = (p && strncmp(p, "newgroup", 8) == 0);
@@ -692,7 +698,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)) {
@@ -707,16 +713,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 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);
+		    syslog(L_TRACE, "PY_dynamic() 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);
+                    free(reply);
 		    break;
 		}
 	    }
diff -ur inn/nnrpd/python.c inn_python/nnrpd/python.c
--- inn/nnrpd/python.c	Mon Jan 13 22:03:25 2003
+++ inn_python/nnrpd/python.c	Sun Feb  2 10:06:38 2003
@@ -15,96 +15,131 @@
 
 #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;
+int PY_authenticate(char* file, char *clientHost, char *clientIpString, char *serverHost, char *Username, char *Password, char *errorstring, char *newUser) {
+    PyObject    *result, *item, *proc;
     char        *type;
     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)
+    if (Username == NULL) {
         PYauthitem[authnum] = Py_None;
-    else
+    } 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)
+    if (Password == NULL) {
         PYauthitem[authnum] = Py_None;
-    else
+    } 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 +150,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,165 +158,254 @@
     /* 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);
+    errorstring = xstrdup(temp);
+    
+    if (PyTuple_Size(result) == 3) {
+        
+        /* Get the username string */
+        item = PyTuple_GetItem(result, 2);
+        
+        /* Check the item */
+        if (!PyString_Check(item)) {
+            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 error string */
+        temp = PyString_AS_STRING(item);
+        newUser = xstrdup(temp);
+    }
 
-    /* Get the CanRead setting */
-    item = PyTuple_GetItem(result, 2);
+    /* Clean up the dictionary object */
+    PyDict_Clear(PYauthinfo);
 
-    /* Check the item */
-    if (!PyInt_Check(item))
-    {
-        syslog(L_ERROR, "python authenticate_method (type %s) returned bad CanRead setting", type);
-	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
-	ExitWithStats(1, true);
+    /* Clean up dictionary items */
+    for (i = 0; i < authnum; i++) {
+        if (PYauthitem[i] != Py_None) {
+            Py_DECREF(PYauthitem[i]);
+        }
     }
 
-    /* Store the setting */
-    PERMcanread = PyInt_AS_LONG(item);
+    /* Log auth result */
+    syslog(L_NOTICE, "python authenticate method succeeded, return code %d, error string %s", code, errorstring);
 
-    /* Get the access list */
-    item = PyTuple_GetItem(result, 3);
+    /* Return response code */
+    return code;
+}
 
-    /* Check the item */
-    if (!PyString_Check(item))
-    {
-        syslog(L_ERROR, "python authenticate_method (type %s) returned bad access list value", type);
-	Reply("%d Internal Error (7).  Goodbye\r\n", NNTP_ACCESS_VAL);
-	ExitWithStats(1, true);
-    }
+/*
+** 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;
 
-    /* Store access list*/
-    strcpy(accesslist, PyString_AS_STRING(item));
+    PY_load_python();
+    proc = PY_setup(PYTHONaccess, PYTHONmain, file);
 
-    /* Fix the NNTP response code */
-    if ((code == NNTP_POSTOK_VAL) || (code == NNTP_NOPOSTOK_VAL))
-    {
-      code = PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL;
-    }
+    /* 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);
+     }
+ 
+    /* 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++]);
 
-    /* Initialize needauth flag */
-    if (code == NNTP_AUTH_NEEDED_VAL) 
-        PERMneedauth = true;
+    /* 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++]);
+ 
+    /* 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)) {
+        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);
+     }
+ 
+    /* 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 = xstrdup(temp);
+        
+        temp = PyString_AsString(value);
+        svalue = xstrdup(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);
-
     /* Clean up dictionary items */
-    for (i = 0; i < authnum; i++)
-    {
-        if (PYauthitem[i] != Py_None)
-	{
-	    Py_DECREF(PYauthitem[i]);
+    for (i = 0; i < authnum; i++) {
+        if (PYauthitem[i] != Py_None) {
+            Py_DECREF(PYauthitem[i]);
 	}
     }
 
     /* Log auth result */
-    syslog(L_NOTICE, "python authenticate_method (type %s) succeeded, return code %d", type, code);
+    syslog(L_NOTICE, "python access method succeeded");
+}
 
-    /* Return response code */
-    return code;
+/*
+** Initialize dynamic access control code
+*/
+
+void PY_dynamic_init (char* file) {
+  dynamic_file = xstrdup(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 dynamic 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 = xstrdup(temp);
+    }
     /* Clean up the dictionary object */
     PyDict_Clear(PYauthinfo);
 
     /* Clean up dictionary items */
-    for (i = 0; i < authnum; i++)
-    {
-        if (PYauthitem[i] != Py_None)
-	{
-	    Py_DECREF(PYauthitem[i]);
-	}
+    for (i = 0; i < authnum; i++) {
+        if (PYauthitem[i] != Py_None) {
+            Py_DECREF(PYauthitem[i]);
+        }
     }
 
     /* 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)
         *reply_message = string;
-
+    
     /* Return result */
     return string == NULL ? 0 : 1;
 }
@@ -291,16 +415,35 @@
 **  This runs when nnrpd shuts down.
 */
 void
-PY_close(void)
+PY_close_python(void)
 {
-    PyObject	*result;
+    hash_traverse(files, file_trav, NULL);
 
-    if (close_method != NULL) {
-        result = PyObject_CallFunction(close_method, NULL);
-        Py_XDECREF(result);
-    }
+    hash_free(files);
+    
+    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);
+              }
+        }
+    }
+}
 
 /*
 **  Python's syslog module isn't compiled in by default.  It's easier
@@ -375,17 +518,60 @@
     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 = xcalloc(_PY_MAX_AUTH_ITEM, sizeof(PyObject *));
+
+        /* 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;
-{
-    Py_XDECREF(*methptr);
+PYdefonemethod(PyFile *fp, int type, int method, char *methname) {
+    PyObject **methptr;
+
+    methptr = &fp->procs[type][method];
 
     /* Get a pointer to given method */
     *methptr = PyObject_GetAttrString(PYAuthObject, methname);
@@ -405,82 +591,131 @@
 
 
 /*
-**  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");
-
-    /* Get a reference to authorize() method */
-    PYdefonemethod(&authorize_method, "authorize");
+    PYdefonemethod(fp, PYTHONauthen, PYTHONmain, "authenticate");
 
-    /* Get a reference to close() method */
-    PYdefonemethod(&close_method, "close");
+    /* 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 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;
+    int  i;
+    PyFile *fp;
+    char *temp;
+    PyObject    *result;
+
+    /* check to see if this file is in files */
+    if (!(hash_lookup(files, file))) {
+        fp = xmalloc(sizeof(PyFile));
+        fp->file = xstrdup(file);
+
+        for (i = 1; i < PYTHONtypes_max; i++) {
+            fp->loaded[i] = false;
+        }
+        
+        /* Load up external module */
+        (void) PyImport_ImportModule(file);
+
+        /* See if nnrpd auth object is defined in auth module */
+        if (PYAuthObject == NULL) {
+            syslog(L_ERROR, "python auth object is not defined");
+            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(fp);
+        }
+        hash_insert(files, file, fp);
+
+        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];
+    }
+}
 
-    /* Export $PYTHONPATH to let Python find the scripts */
-    setenv("PYTHONPATH", innconf->pathfilter, 1);
+/*
+**  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;
 
-    /* Load up the interpreter ;-O */
-    Py_Initialize();
+    return f->file;
+}
 
-    /* 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')");
+/*
+**  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;
 
-    /* See it Python initialized OK */
-    if (!Py_IsInitialized ()) {
-        syslog(L_ERROR, "python interpreter NOT initialized");
-	return;
-    }
-    syslog(L_NOTICE, "python interpreter initialized OK");
+    return strcmp(key, f->file) == 0;
+}
 
-    /* Build a module interface to certain nnrpd functions */
-    Py_InitModule("nnrpd", nnrpdPyMethods);
+/*
+**  Free a file, used by the hash table.
+*/
+static void
+file_free(void *p)
+{
+    struct PyFile *fp = p;
+    int i, j;
 
-    /* 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");
+    free(fp->file);
 
-    /* See if nnrpd auth object is defined in auth module */
-    if (PYAuthObject == NULL)
-        syslog(L_ERROR, "python auth object is not defined");
-    else {
-        /* Set up pointers to known Python methods */
-        PYdefmethods();
-        syslog(L_NOTICE, "some python methods defined. good.");
+    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]);
+            }
+        }
     }
 
-    /*
-    ** Grab space for authinfo dictionary so we aren't forever
-    ** recreating them.
-    */
-    PYauthinfo = PyDict_New();
-    PYauthitem = xmalloc(_PY_MAX_AUTH_ITEM * sizeof(PyObject *));
-    PYauthkey = xmalloc(_PY_MAX_AUTH_ITEM * sizeof(PyObject *));
-
-    /* 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");
+    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	Sat Feb  1 11:08:15 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	Sat Feb  1 11:10:20 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";
 

********************* end diff ***********************
-- 
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-workers mailing list