*** configure.in% Sat Dec 4 19:31:19 1999 --- configure.in Fri Dec 17 11:47:26 1999 *************** *** 1162,1167 **** --- 1162,1168 ---- samples/innwatch.ctl samples/newsfeeds samples/nnrpd_auth.pl + samples/nnrpd_auth.py samples/startup.tcl scripts/inncheck scripts/innreport *** include/libinn.h% Fri Nov 26 19:51:20 1999 --- include/libinn.h Fri Dec 17 11:47:26 1999 *************** *** 156,161 **** --- 156,162 ---- int keyartlimit; /* Max size of an article for keyword generation */ int keymaxwords; /* Max count of interesting workd */ int nnrpperlauth; /* Use perl for nnrpd authentication */ + int nnrppythonauth; /* Use Python for nnrpd authentication */ int addnntppostinghost; /* Add NNTP-Posting-Host: header to posts */ int addnntppostingdate; /* Add NNTP-Posting-DATE: header to posts */ *** include/paths.h.in% Mon Nov 29 04:46:54 1999 --- include/paths.h.in Fri Dec 17 11:47:26 1999 *************** *** 31,36 **** --- 31,37 ---- #define _PATH_PERL_AUTH "nnrpd_auth.pl" #define _PATH_PYTHON_STARTUP "filter_innd.py" #define _PATH_PYTHON_STARTUP_M "filter_innd" + #define _PATH_PYTHON_AUTH_M "nnrpd_auth" /* Default prefix path is pathrun. */ #define _PATH_NNTPCONNECT "nntpin" *** include/innconf.h% Fri Nov 26 19:51:19 1999 --- include/innconf.h Fri Dec 17 12:35:01 1999 *************** *** 326,332 **** { _CONF_GROUPBASEEXPIRY, "", 2, 1 }, #define _CONF_WIPCHECK "wipcheck" #define CONF_VAR_WIPCHECK 102 ! { _CONF_WIPCHECK, "", 2, 1 } }; ! #define MAX_CONF_VAR 103 --- 326,335 ---- { _CONF_GROUPBASEEXPIRY, "", 2, 1 }, #define _CONF_WIPCHECK "wipcheck" #define CONF_VAR_WIPCHECK 102 ! { _CONF_WIPCHECK, "", 2, 1 }, ! #define _CONF_NNRPPYTHONAUTH "nnrppythonauth" ! #define CONF_VAR_NNRPPYTHONAUTH 103 ! { _CONF_NNRPPYTHONAUTH, "", 2, 1 } }; ! #define MAX_CONF_VAR 104 *** lib/getconfig.c% Fri Nov 26 19:55:27 1999 --- lib/getconfig.c Fri Dec 17 11:47:26 1999 *************** *** 218,223 **** --- 218,224 ---- innconf->nnrpdposthost = NULL; innconf->nnrpdpostport = NNTP_PORT; innconf->nnrpperlauth = FALSE; + innconf->nnrppythonauth = FALSE; innconf->addnntppostinghost = TRUE; innconf->addnntppostingdate = TRUE; *************** *** 689,694 **** --- 690,700 ---- TEST_CONFIG(CONF_VAR_NNRPPERLAUTH, bit); if (!bit && boolval != -1) innconf->nnrpperlauth = boolval; SET_CONFIG(CONF_VAR_NNRPPERLAUTH); + } else + if (EQ(ConfigBuff,_CONF_NNRPPYTHONAUTH)) { + TEST_CONFIG(CONF_VAR_NNRPPYTHONAUTH, bit); + if (!bit && boolval != -1) innconf->nnrppythonauth = boolval; + SET_CONFIG(CONF_VAR_NNRPPYTHONAUTH); } else if (EQ(ConfigBuff,_CONF_ADDNNTPPOSTINGHOST)) { TEST_CONFIG(CONF_VAR_ADDNNTPPOSTINGHOST, bit); *** nnrpd/Makefile% Fri Nov 26 07:29:39 1999 --- nnrpd/Makefile Fri Dec 17 11:47:26 1999 *************** *** 7,17 **** SOURCES = \ article.c group.c commands.c misc.c newnews.c nnrpd.c \ ! perl.c perm.c post.c loadave.c track.c OBJECTS = \ article.o group.o commands.o misc.o newnews.o \ ! perl.o perm.o post.o loadave.o track.o ALL = nnrpd --- 7,17 ---- SOURCES = \ article.c group.c commands.c misc.c newnews.c nnrpd.c \ ! perl.c perm.c post.c loadave.c track.c python.c OBJECTS = \ article.o group.o commands.o misc.o newnews.o \ ! perl.o perm.o post.o loadave.o track.o python.o ALL = nnrpd *************** *** 20,25 **** --- 20,28 ---- perl.o: perl.c $(CC) $(CFLAGS) $(PERLINC) -c perl.c + python.o: python.c + $(CC) $(CFLAGS) $(PYTHONINC) -c python.c -o $@ + install: $D$(NNRPD) ## Low-level install actions. *************** *** 37,45 **** nnrpd: $(P) nnrpd.o $(OBJECTS) $(LIBNEWS) @rm -f $@ ! # $(LIBTOOL) ./quantify $(CC) $(LDFLAGS) -o $@ $@.o $(OBJECTS) $(LIBNEWS) $(PERLLIB) $(LIBS) ! # $(LIBTOOL) purify $(CC) $(LDFLAGS) -o $@ $@.o $(OBJECTS) $(LIBNEWS) $(PERLLIB) $(LIBS) ! $(LIBTOOL) $(CC) $(LDFLAGS) -o $@ $@.o $(OBJECTS) $(LIBNEWS) $(PERLLIB) $(LIBS) $(LIBNEWS): (cd ../lib ; $(MAKE) ) --- 40,48 ---- nnrpd: $(P) nnrpd.o $(OBJECTS) $(LIBNEWS) @rm -f $@ ! # $(LIBTOOL) ./quantify $(CC) $(LDFLAGS) -o $@ $@.o $(OBJECTS) $(LIBNEWS) $(PERLLIB) $(PYTHONLIB) $(LIBS) ! # $(LIBTOOL) purify $(CC) $(LDFLAGS) -o $@ $@.o $(OBJECTS) $(LIBNEWS) $(PERLLIB) $(PYTHONLIB) $(LIBS) ! $(LIBTOOL) $(CC) $(LDFLAGS) -o $@ $@.o $(OBJECTS) $(LIBNEWS) $(PERLLIB) $(PYTHONLIB) $(LIBS) $(LIBNEWS): (cd ../lib ; $(MAKE) ) *** nnrpd/commands.c% Fri Nov 19 12:24:29 1999 --- nnrpd/commands.c Fri Dec 17 11:47:27 1999 *************** *** 303,308 **** --- 303,338 ---- } } else { #endif /* DO_PERL */ + + #ifdef DO_PYTHON + if (innconf->nnrppythonauth) { + if ((code = PY_authenticate(ClientHost, ClientIp, 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 */ + strcpy(PERMuser, User); + strcpy(PERMpass, Password); + PERMneedauth = FALSE; + PERMauthorized = TRUE; + return; + } else { + syslog(L_NOTICE, "%s bad_auth", ClientHost); + Reply("%d Authentication error\r\n", NNTP_ACCESS_VAL); + ExitWithStats(1); + } + } + } else { + #endif /* DO_PYTHON */ + if (EQ(User, PERMuser) && EQ(Password, PERMpass)) { syslog(L_NOTICE, "%s user %s", ClientHost, User); if (LLOGenable) { *************** *** 327,332 **** --- 357,365 ---- PERMauthorized = TRUE; return; } + #ifdef DO_PYTHON + } + #endif /* DO_PYTHON */ #ifdef DO_PERL } #endif /* DO_PERL */ *** nnrpd/group.c% Fri Nov 19 12:24:29 1999 --- nnrpd/group.c Fri Dec 17 11:47:27 1999 *************** *** 42,47 **** --- 42,64 ---- return; } + #ifdef DO_PYTHON + if (innconf->nnrppythonauth) { + char *reply; + + /* Authorize user at a Python authorization module */ + if (PY_authorize(ClientHost, ClientIp, ServerHost, PERMuser, group, FALSE, &reply) < 0) { + syslog(L_NOTICE, "PY_authorize(): authorization skipped due to no Python authorization 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); + Reply("%d %s\r\n", NNTP_ACCESS_VAL, reply); + return; + } + } + } + #endif /* DO_PYTHON */ + /* If permission is denied, pretend group doesn't exist. */ if (PERMspecified) { grplist[0] = group; *** nnrpd/misc.c% Fri Nov 19 12:24:30 1999 --- nnrpd/misc.c Fri Dec 17 11:47:27 1999 *************** *** 143,148 **** --- 143,164 ---- /* No newgroups or null entry. */ return 1; + #ifdef DO_PYTHON + if (innconf->nnrppythonauth) { + char *reply; + + /* Authorize user at a Python authorization module */ + if (PY_authorize(ClientHost, ClientIp, ServerHost, PERMuser, p, FALSE, &reply) < 0) { + syslog(L_NOTICE, "PY_authorize(): authorization skipped due to no Python authorization 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 1; + } + } + } + #endif /* DO_PYTHON */ + return PERMmatch(PERMreadlist, grplist); } *** nnrpd/nnrpd.c% Tue Dec 14 07:21:45 1999 --- nnrpd/nnrpd.c Fri Dec 17 11:47:27 1999 *************** *** 203,208 **** --- 203,216 ---- OVclose(); + #ifdef DO_PYTHON + if (innconf->nnrppythonauth) { + if (PY_close() < 0) { + syslog(L_NOTICE, "PY_close(): close method not invoked because it is not defined in Python authenticaton module."); + } + } + #endif /* DO_PYTHON */ + exit(x); } *************** *** 438,445 **** --- 446,473 ---- PERMpostlist = PERMreadlist; } else { #endif /* DO_PERL */ + + #ifdef DO_PYTHON + if (innconf->nnrppythonauth) { + if ((code = PY_authenticate(ClientHost, ClientIp, 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); + } + PERMspecified = NGgetlist(&PERMreadlist, accesslist); + PERMpostlist = PERMreadlist; + } + } else { + #endif /* DO_PYTHON */ PERMgetaccess(); PERMgetpermissions(); + #ifdef DO_PYTHON + } + #endif /* DO_PYTHON */ #ifdef DO_PERL } #endif /* DO_PERL */ *************** *** 552,557 **** --- 580,592 ---- PerlFilter(TRUE); } #endif /* defined(DO_PERL) */ + + #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()) { *** nnrpd/nnrpd.h% Fri Nov 19 12:24:30 1999 --- nnrpd/nnrpd.h Fri Dec 17 11:47:27 1999 *************** *** 174,176 **** --- 174,181 ---- int perlConnect(char *ClientHost, char *ClientIP, char *ServerHost, char *accesslist); int perlAuthenticate(char *ClientHost, char *ClientIP, char *ServerHost, char *user, char *passwd, char *accesslist); BOOL ARTinstorebytoken(TOKEN token); + + #ifdef DO_PYTHON + int PY_authenticate(char *ClientHost, char *ClientIP, char *ServerHost, char *Username, char *Password, char *accesslist); + int PY_authorize(char *ClientHost, char *ClientIP, char *ServerHost, char *Username, char *NewsGroup, int PostFlag, char **reply_message); + #endif /* DO_PYTHON */ *** nnrpd/post.c% Fri Nov 19 12:24:31 1999 --- nnrpd/post.c Fri Dec 17 11:47:28 1999 *************** *** 651,656 **** --- 651,672 ---- DDcheck(h, p); switch (flag) { case NF_FLAG_OK: + #ifdef DO_PYTHON + if (innconf->nnrppythonauth) { + char *reply; + + /* Authorize user at a Python authorization module */ + if (PY_authorize(ClientHost, ClientIp, ServerHost, PERMuser, p, TRUE, &reply) < 0) { + syslog(L_NOTICE, "PY_authorize(): authorization skipped due to no Python authorization 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); + (void)sprintf(Error, "%s\r\n", reply); + break; + } + } + } + #endif /* DO_PYTHON */ break; case NF_FLAG_MODERATED: if (!approved && !*modgroup) { *** samples/Makefile% Mon Nov 15 09:39:17 1999 --- samples/Makefile Fri Dec 17 11:47:28 1999 *************** *** 13,19 **** sendme sendsys senduuname version EXTRA = actsync.cfg checkgroups inn.conf innreport.conf \ ! innwatch.ctl newsfeeds nnrpd_auth.pl startup.tcl all: $(ALL) --- 13,20 ---- sendme sendsys senduuname version EXTRA = actsync.cfg checkgroups inn.conf innreport.conf \ ! innwatch.ctl newsfeeds nnrpd_auth.pl startup.tcl \ ! nnrpd_auth.py all: $(ALL) *** site/Makefile% Mon Nov 29 04:42:18 1999 --- site/Makefile Fri Dec 17 11:59:37 1999 *************** *** 21,26 **** --- 21,27 ---- PATH_PYTHON_FILTER_INND = ${PATHFILTER}/filter_innd.py PATH_PYTHON_INN_MODULE = ${PATHFILTER}/INN.py PATH_NNRPAUTH = ${PATHFILTER}/nnrpd_auth.pl + PATH_NNRPYAUTH = ${PATHFILTER}/nnrpd_auth.py PATH_PARSECTL = ${PATHBIN}/parsecontrol PATH_CONFIG = ${PATHETC}/inn.conf *************** *** 61,67 **** innfeed.conf startup_innd.pl filter_innd.pl filter_nnrpd.pl \ filter_innd.py INN.py \ startup.tcl filter.tcl nnrpd_auth.pl news2mail.cf readers.conf \ ! radius.conf ALL = $(MOST) $(REST) --- 62,68 ---- innfeed.conf startup_innd.pl filter_innd.pl filter_nnrpd.pl \ filter_innd.py INN.py \ startup.tcl filter.tcl nnrpd_auth.pl news2mail.cf readers.conf \ ! radius.conf nnrpd_auth.py ALL = $(MOST) $(REST) *************** *** 89,95 **** $D$(PATH_PYTHON_FILTER_INND) $D$(PATH_PYTHON_INN_MODULE) \ $D$(PATH_TCL_STARTUP) $D$(PATH_TCL_FILTER) \ $D$(PATH_NNRPAUTH) $D$(PATHETC)/news2mail.cf $D$(PATH_READERSCONF) \ ! $D$(PATH_RADIUS_CONF) ALL_INSTALLED = $(MOST_INSTALLED) $(REST_INSTALLED) --- 90,96 ---- $D$(PATH_PYTHON_FILTER_INND) $D$(PATH_PYTHON_INN_MODULE) \ $D$(PATH_TCL_STARTUP) $D$(PATH_TCL_FILTER) \ $D$(PATH_NNRPAUTH) $D$(PATHETC)/news2mail.cf $D$(PATH_READERSCONF) \ ! $D$(PATH_RADIUS_CONF) $D$(PATH_NNRPYAUTH) ALL_INSTALLED = $(MOST_INSTALLED) $(REST_INSTALLED) *************** *** 194,199 **** --- 195,201 ---- $D$(PATH_TCL_STARTUP): startup.tcl ; $(COPY_RPRI) $? $@ $D$(PATH_TCL_FILTER): filter.tcl ; $(COPY_RPRI) $? $@ $D$(PATH_NNRPAUTH): nnrpd_auth.pl ; $(COPY_RPRI) $? $@ + $D$(PATH_NNRPYAUTH): nnrpd_auth.py ; $(COPY_RPRI) $? $@ $D$(PATH_ACTSYNC_CFG): actsync.cfg ; $(COPY_RPRI) $? $@ $D$(PATH_ACTSYNC_IGN): actsync.ign ; $(COPY_RPRI) $? $@ $D$(PATH_MOTD): motd.news ; $(COPY_RPRI) $? $@ *************** *** 222,227 **** --- 224,230 ---- expire.ctl: ../samples/expire.ctl ; $(COPY) $? $@ filter.tcl: ../samples/filter.tcl ; $(COPY) $? $@ nnrpd_auth.pl: ../samples/nnrpd_auth.pl ; $(COPY) $? $@ + nnrpd_auth.py: ../samples/nnrpd_auth.py ; $(COPY) $? $@ filter_innd.pl: ../samples/filter_innd.pl ; $(COPY) $? $@ filter_nnrpd.pl: ../samples/filter_nnrpd.pl ; $(COPY) $? $@ filter_innd.py: ../samples/filter_innd.py ; $(COPY) $? $@ *** README.python_auth_hook% Fri Dec 17 12:05:22 1999 --- README.python_auth_hook Fri Dec 17 11:47:28 1999 *************** *** 0 **** --- 1,133 ---- + + PYTHON AUTHENTICATION AND AUTHORIZATION SUPPORT FOR NNRPD, version 1.0 + + This file documents nnrpd's built-in optional support for Python + reader authentication and authorization. It is based on Greg Andruk's + (nee Fluffy) Python interface to INN as well as on + TCL and Perl hooks develped by Bob Heiney and Christophe Wolfhugel. + + For details on Python care and feeding at INN, please, refer to Greg + Andruk's README.python_hook. + + Python authentication and authorization support in nnrpd along with + filtering support in innd may be compiled in by giving --with-python + command line flag to configure script. Python authentication and + authorization may be turned on by nnrppythonauth setting in inn.conf + configuration file. + + If nnrppythonauth in inn.conf is set to true, nnrpd will load Python + module as defined in include/paths.h and located in the directory + specified by pathfilter in inn.conf. Once the module is loaded, + nnrpd will authenticate and authorize readers by calling a Python methods + rather than reading 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 requested + operation. The authorization functionality makes sence when a list of + newsgroups in your access statements grows too long to maintain in + readers.conf or you need to have access control rules applying immediately + that is without having to restart all the nnrpd processes. Also, Python + authorization hooks perform access control on per newsgroup basis + while readers.conf does the same on per user basis. + + However, consider the authorization functionality as an option which is + reasonable in just a few cases (like those mentioned above). + + + WRITING A NNRPD AUTHENTICATION MODULE: + + You need to create a nnrpd_auth.py module in INN's filter + directory (see the pathfilter setting in inn.conf) where you should + define a class holding certain methods. + + The methods followed are known to nnrpd. It uses them if present: + + __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. + + close(self): + This method is invoked on nnrpd termination. You can use it + to save state information or close a database connection. + + authenticate(self, attributes): + Called when a reader connects or issues AUTHINFO command. + Connection attributes are passed in the "attributes" dictionary. + The following keys are initialized by nnrpd: + + type - "connect", "authinfo", "read" or "post" + values specify the authentication type. + hostname - resolved hostname (or IP address if + resolution fails) of connected reader; + ipaddress - IP address of connected reader; + interface - IP address of the interface at this + machine reader is connected to; + user - username as reader passed with AUTHINFO + command or None if not applicible; + pass - password as reader passed with AUTHINFO + command or None if not applicible; + newsgroup - name of the newsgroup reader requests read + or post access to or None if not applicible; + + All the above values are buffer objects. See README.python_hook + for comments on Python buffers. + + This method should return a tuple of four elements: + + 1) NNTP response code. Should be a valid NNTP response code + (see example for details); + 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. + + See explanation on applicible NNTP return codes in README.perl_hook + file which comes with INN distribution. + + 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. + + To register your methods with nnrpd, you need to create an instance + of your class, import the built-in nnrpd module, and pass the + instance to nnrpd.set_auth_hook(). For example: + + class AUTH: + def authenticate(self, attributes): + ... + + def authorize(self, attributes): + ... + + import nnrpd + myauth = AUTH() + nnrpd.set_auth_hook(myauth) + + There is also a nnrpd.py module there which is not actually used by nnrpd + but provides the same set of functions as built-in nnrpd module. This + stub module may be used when debugging your own module. + + Check Greg Andruk's tips and tricks regarding programming Python + INN filter (see README.python_hook). Almost everything there also applies + to the case of programming Python authentication and authorization + module. + + + FUNCTIONS SUPPLIED BY THE BUILT-IN NNRPD MODULE: + + As of this writing, nnrpd built-in module exports the following + functions: + + set_auth_hook() - used to pass a reference to the instance of + authentication and authorization class to + nnrpd; + syslog() - intended to be a replacement for a Python + native syslog. + + See README.python_hook for details. *** samples/nnrpd_auth.py.in% Fri Dec 17 12:05:46 1999 --- samples/nnrpd_auth.py.in Fri Dec 17 11:47:28 1999 *************** *** 0 **** --- 1,159 ---- + # + # + # This is a sample authentication and authorization module for nnrpd hook + # + # For details, see the file README.python_auth_hook 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. + # + # If there is a problem with return codes from any of these methods then nnrpd + # will die and syslog the exact reason. + # + # There are also a few Python functions defined in nnrpd: + # + # set_auth_hook() - Called by nnrpd as this module is loaded. + # It is used to pass a reference to an + # instance of authentication class to nnrpd. + # syslog() - An equivalent replacement for regular syslog. + # One consideration for using it is to + # uniform nnrpd logging. + + # + # Sample authentication and authorization class. It defines all methods known + # to nnrpd. + # + 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. + """ + # Create a list of NNTP codes to respond on connect + self.connectcodes = { 'READPOST':200, + 'READ':201, + 'AUTHNEEDED':480, + 'PERMDENIED':502 + } + + # Create a list of NNTP codes to respond on authentication + self.authcodes = { 'ALLOWED':281, + 'DENIED':502 + } + + 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""" + + # just for debugging purposes + syslog('debug', 'authenticate() invoked against type %s, hostname %s, ipaddress %s, interface %s, user %s' % (\ + attributes['type'], \ + 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'): + syslog('notice', 'authentication by username succeeded') + return ( self.authcodes['ALLOWED'], 1, 1, '*') + else: + syslog('notice', 'authentication type is not known: %s' % attributes['type']) + return ( self.authcodes['DENIED'], 0, 0, '!*') + + def authorize(self, attributes): + """Called when 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' % (\ + attributes['type'], \ + attributes['hostname'], \ + attributes['ipaddress'], \ + attributes['interface'], \ + attributes['user'], \ + attributes['newsgroup'])) + + # Allow reading of any newsgroup + if attributes['type'] == buffer('read'): + syslog('notice', 'authorization for read access succeeded') + 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" + + + # + # 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 = AUTH() + + # ...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]) + *** samples/nnrpd.py% Fri Dec 17 12:06:52 1999 --- samples/nnrpd.py Fri Dec 3 17:52:31 1999 *************** *** 0 **** --- 1,14 ---- + # This module supplies stub Python functions corresponding to the ones + # provided by nnrpd. It is not used by the server; it is only here so + # that you can test your auth and filter scripts before loading. + + from types import * + + def set_auth_hook(anObject): + if type(anObject) == InstanceType: + print "** set_auth_hook for " + repr(anObject) + else: + print "** " + + def syslog(level, message): + print "-- syslog level: %s message: %s" % (level, message) *** nnrpd/python.c% Fri Dec 17 12:06:52 1999 --- nnrpd/python.c Fri Dec 17 11:47:29 1999 *************** *** 0 **** --- 1,487 ---- + /* + ** + ** python.c: Embed Python in the style of nnrpd's TCL and Perl stuff + ** (authentication and authorization hooks only at this point). + ** + ** Written by Ilya Etingof , 1999. + ** + ** This code bases on Python work for innd filtering done by + ** G.J. Andruk . Also it borrows some ideas from + ** TCL/Perl work done by Bob Heiney and Christophe Wolfhugel. + */ + + #include + #include + #include + #include "configdata.h" + #include "clibrary.h" + #include "nnrpd.h" + + #if defined(DO_PYTHON) + + #include "Python.h" + + /* 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; + + /* Forward declaration */ + static PyObject *PY_set_auth_hook(PyObject *dummy, PyObject *args); + + /* These variable defined in other C modules */ + extern char accesslist[]; + + /* + ** Authenticate connecting host by IP address or username&password. + ** + ** Return NNTP reply code as returned by Python method or -1 if method + ** is not defined. + */ + int PY_authenticate(char *ClientHost, char *ClientIP, char *ServerHost, char *Username, char *Password, char *accesslist) { + PyObject *result, *item; + char *type; + int authnum; + int code, i; + + /* Return if authentication method is not defined */ + if (authenticate_method == 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++]); + + /* Client IP number */ + PYauthitem[authnum] = PyBuffer_FromMemory(ClientIP, strlen(ClientIP)); + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* Server interface the connection comes to */ + PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost)); + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* Username if known */ + if (Username == NULL) + PYauthitem[authnum] = Py_None; + else + PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username)); + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* Password if known */ + if (Password == NULL) + PYauthitem[authnum] = Py_None; + else + PYauthitem[authnum] = PyBuffer_FromMemory(Password, strlen(Password)); + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* Now invoke authenticate method and see if it likes this user */ + result = PyObject_CallFunction(authenticate_method, "O", PYauthinfo); + + /* Check the response */ + if (result == NULL || !PyTuple_Check(result)) + { + syslog(L_ERROR, "python authenticate_method (type %s) returned wrong result", type); + Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL); + ExitWithStats(1); + } + + /* Get the NNTP response code */ + item = PyTuple_GetItem(result, 0); + + /* Check the item */ + if (!PyInt_Check(item)) + { + syslog(L_ERROR, "python authenticate_method (type %s) returned bad NNTP response code", type); + Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL); + ExitWithStats(1); + } + + /* Store the code */ + code = PyInt_AS_LONG(item); + + /* Get the CanPost setting */ + item = PyTuple_GetItem(result, 1); + + /* Check the item */ + if (!PyInt_Check(item)) + { + syslog(L_ERROR, "python authenticate_method (type %s) returned bad CanPost setting", type); + Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL); + ExitWithStats(1); + } + + /* Store the setting */ + PERMcanpost = PyInt_AS_LONG(item); + + /* Get the CanRead setting */ + item = PyTuple_GetItem(result, 2); + + /* 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); + } + + /* Store the setting */ + PERMcanread = PyInt_AS_LONG(item); + + /* Get the access list */ + item = PyTuple_GetItem(result, 3); + + /* 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); + } + + /* Store access list*/ + strcpy(accesslist, PyString_AS_STRING(item)); + + /* Fix the NNTP response code */ + if ((code == NNTP_POSTOK_VAL) || (code == NNTP_NOPOSTOK_VAL)) + { + code = PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL; + } + + /* Initialize needauth flag */ + if (code == NNTP_AUTH_NEEDED_VAL) + PERMneedauth = TRUE; + + /* 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]); + } + } + + /* Log auth result */ + syslog(L_NOTICE, "python authenticate_method (type %s) succeeded, return code %d", type, code); + + /* Return response code */ + return code; + } + + + /* + ** Authorize user access to a 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. + */ + int PY_authorize(char *ClientHost, char *ClientIP, char *ServerHost, char *Username, char *NewsGroup, int PostFlag, char **reply_message) { + PyObject *result, *item; + char *string; + int authnum; + int i; + + /* Return if authorize_method is not defined */ + if (authorize_method == 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++]); + + /* Client IP number */ + PYauthitem[authnum] = PyBuffer_FromMemory(ClientIP, strlen(ClientIP)); + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* Server interface the connection comes to */ + PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost)); + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* Username */ + PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username)); + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* Password is not known */ + PYauthitem[authnum] = Py_None; + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* Newsgroup user tries to access */ + PYauthitem[authnum] = PyBuffer_FromMemory(NewsGroup, strlen(NewsGroup));; + PyDict_SetItem(PYauthinfo, PYauthkey[authnum], PYauthitem[authnum++]); + + /* + * Now invoke newsgroup access authorization method and see if + * it likes this user to access this newsgroup. + */ + result = PyObject_CallFunction(authorize_method, "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"); + Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL); + ExitWithStats(1); + } + + /* Get the response string */ + if (result == Py_None) + string = NULL; + else + string = PyString_AS_STRING(result); + + /* 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]); + } + } + + /* Log auth result */ + syslog(L_NOTICE, "python authorize_method (%s access) succeeded, refusion string: %s", PostFlag ? "post" : "read", string == NULL ? "" : string); + + /* Initialize reply string */ + if (reply_message != NULL) + *reply_message = string; + + /* Return result */ + return string == NULL ? 0 : 1; + } + + + /* + ** This runs when nnrpd shuts down. + */ + void + PY_close(void) + { + PyObject *result; + + if (close_method != NULL) { + result = PyObject_CallFunction(close_method, NULL); + Py_XDECREF(result); + } + } + + + /* + ** Python's syslog module isn't compiled in by default. It's easier + ** to do it this way, and the switch block looks pretty in a color + ** editor). + */ + static PyObject * + PY_syslog(self, args) + PyObject *self, *args; + { + char *loglevel; + int levellen; + char *logmsg; + int msglen; + int priority; + + /* Get loglevel and message */ + if (!PyArg_ParseTuple(args, "s#s#", &loglevel, &levellen, &logmsg, &msglen)) + return NULL; + + /* Assign syslog priority by abbreviated names */ + switch (*loglevel) { + default: priority = LOG_NOTICE ; + case 'd': case 'D': priority = LOG_DEBUG ; break; + case 'i': case 'I': priority = LOG_INFO ; break; + case 'n': case 'N': priority = LOG_NOTICE ; break; + case 'w': case 'W': priority = LOG_WARNING ; break; + case 'e': case 'E': priority = LOG_ERR ; break; + case 'c': case 'C': priority = LOG_CRIT ; break; + case 'a': case 'A': priority = LOG_ALERT ; break; + } + + /* Log the message */ + syslog(priority, "python: %s", logmsg); + + /* Return None */ + Py_INCREF(Py_None); + return Py_None; + } + + + /* + ** Make the internal nnrpd module's functions visible to Python. + */ + static PyMethodDef nnrpdPyMethods[] = { + {"set_auth_hook", PY_set_auth_hook, METH_VARARGS}, + {"syslog", PY_syslog, METH_VARARGS}, + {NULL, NULL} + }; + + + /* + ** Called by the external module so it can register itself with nnrpd. + */ + static PyObject * + PY_set_auth_hook(dummy, args) + PyObject *dummy, *args; + { + PyObject *result = NULL; + PyObject *temp; + + /* set_auth_hook method should return a pointer to nnrpd auth object */ + if (PyArg_ParseTuple(args, "O:set_auth_hook", &temp)) { + Py_XINCREF(temp); + Py_XDECREF(PYAuthObject); + PYAuthObject = temp; + Py_INCREF(Py_None); + result = Py_None; + } + + /* Return a pointer to nnrpd auth method */ + return result; + } + + + /* + ** 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); + + /* Get a pointer to given method */ + *methptr = PyObject_GetAttrString(PYAuthObject, methname); + + /* See if such method is defined */ + if (*methptr == NULL) + syslog(L_NOTICE, "python method %s not found", methname); + else { + /* See if it is callable */ + if (PyCallable_Check(*methptr) == 0) { + syslog(L_ERROR, "python object %s found but not a function", methname); + Py_DECREF(*methptr); + *methptr = NULL; + } + } + } + + + /* + ** Look up all the known authentication/authorization methods and set up + ** pointers to them so that we could call them from nnrpd. + */ + void + PYdefmethods(void) + { + /* Get a reference to authenticate() method */ + PYdefonemethod(&authenticate_method, "authenticate"); + + /* Get a reference to authorize() method */ + PYdefonemethod(&authorize_method, "authorize"); + + /* Get a reference to close() method */ + PYdefonemethod(&close_method, "close"); + } + + + /* + ** Called when nnrpd starts -- this gets the scripts hooked in. + */ + void + PY_setup(void) + { + int authnum; + + /* Export $PYTHONPATH to let Python find the scripts */ + 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 it Python initialized OK */ + if (!Py_IsInitialized ()) { + syslog(L_ERROR, "python interpreter NOT initialized"); + return; + } + syslog(L_NOTICE, "python interpreter initialized OK"); + + /* Build a module interface to certain nnrpd functions */ + (void) Py_InitModule("nnrpd", nnrpdPyMethods); + + /* Load up external nntpd auth module */ + PYAuthModule = PyImport_ImportModule(_PATH_PYTHON_AUTH_M); + if (PYAuthModule == NULL) + syslog(L_ERROR, "failed to import external python module"); + + /* 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."); + } + + /* + ** Grab space for authinfo dictionary so we aren't forever + ** recreating them. + */ + PYauthinfo = PyDict_New(); + PYauthitem = NEW(PyObject *, _PY_MAX_AUTH_ITEM); + PYauthkey = NEW(PyObject *, _PY_MAX_AUTH_ITEM); + + /* 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"); + } + #endif /* defined(DO_PYTHON) */