--- inn-1.7.2.orig/config/files.list
+++ inn-1.7.2/config/files.list
@@ -107,4 +107,5 @@
 ../syslog/syslog.c
 ../syslog/syslog.conf
 ../syslog/syslogd.c
+../actived/Makefile
 ;; Do not run subst on its own manpage!  ../doc/subst.1
--- inn-1.7.2.orig/include/paths.h
+++ inn-1.7.2/include/paths.h
@@ -210,3 +217,13 @@
 
 /* =()<#define _PATH_PERL_FILTER_NNRPD	"@<_PATH_PERL_FILTER_NNRPD>@">()= */
 #define _PATH_PERL_FILTER_NNRPD	"/usr/news/bin/control/filter_nnrpd.pl"
+
+/*
+**  18.  actived configuration
+*/
+
+/* =()<#define _PATH_ACTIVED		"@<_PATH_ACTIVED>@">()= */
+#define _PATH_ACTIVED		"/usr/lib/news/bin/actived"
+/* =()<#define _PATH_ACTIVEDPID		"@<_PATH_ACTIVEDPID>@">()= */
+#define _PATH_ACTIVEDPID		"/var/run/innd/actived.pid"
+
--- inn-1.7.2.orig/nnrpd/Makefile
+++ inn-1.7.2/nnrpd/Makefile
@@ -37,13 +37,15 @@
 LIBNEWS	= ../libinn.a
 LINTLIB	= ../llib-linn.ln
 
+CFLAGS+= -DOVERSCREAM
+
 SOURCES	= \
 	article.c group.c commands.c misc.c newnews.c nnrpd.c \
-	perl.c post.c loadave.c
+	perl.c post.c loadave.c udp.c
 
 OBJECTS	= \
 	article.o group.o commands.o misc.o newnews.o nnrpd.o \
-	perl.o post.o loadave.o
+	perl.o post.o loadave.o udp.o
 
 ALL	= nnrpd
 
--- inn-1.7.2.orig/nnrpd/group.c
+++ inn-1.7.2/nnrpd/group.c
@@ -8,6 +8,15 @@
 #include "clibrary.h"
 #include "nnrpd.h"
 #include "mydir.h"
+#include "../actived/protocol.h"
+#include <sys/time.h>
+#include <unistd.h>
+
+BOOL XGetGroupList();
+int write_udp(int s, char *buf, int len);
+int read_udp(int s, char *buf, int len);
+int create_udp_socket(int port);
+int connect_udp_socket(int s, char *address, int port);
 
 
 /*
@@ -30,13 +33,34 @@
 STATIC GROUPENTRY	*GRPentries;
 STATIC int		GRPbuckets;
 STATIC int		GRPsize;
+STATIC int		GRPactived = -1;
+int			GRPuselocalhash = 1; /*actived is disabled by default*/
+STATIC int		NRequestID;
+
+
+void
+NNewRequestID()
+{
+	static int pid = -1;
+	static int count = 123456789;
+	struct timeval tv;
+
+	if (pid < 0) {
+		pid = getpid();
+	}
+	gettimeofday(&tv, NULL);
+	count += pid;
+	NRequestID = tv.tv_sec ^ tv.tv_usec ^ pid ^ count;
+}
+
+
 
 
 /*
 **  See if a given newsgroup exists.
 */
 GROUPENTRY *
-GRPfind(group)
+XGRPfind(group)
     register char		*group;
 {
     register char		*p;
@@ -56,8 +80,129 @@
 }
 
 
+
+GROUPENTRY *
+NGRPfind(group)
+    register char		*group;
+{
+    int now = time(NULL);
+    int expireat = now + ACTIVED_TIMEOUT;
+    int last = now - 2;
+    static struct wireprotocol buffer;
+    fd_set fdset;
+    struct timeval timeout;
+    static GROUPENTRY	data;
+
+    /* Okay.  Let's ask the server for the data. */
+    NNewRequestID();
+    while (now < expireat) {
+	now = time(NULL);
+	if (last < now - 1) {
+	    last = now;
+	    buffer.RequestID = NRequestID;
+	    buffer.RequestType = REQ_FIND;
+	    strncpy(buffer.Name, group, sizeof(buffer.Name) - 1);
+	    buffer.Name[sizeof(buffer.Name) - 1] = '\0';
+
+	    if (write_udp(GRPactived, (char *)&buffer, sizeof(buffer)) != sizeof(buffer)) {
+		syslog(L_ERROR, "%s actived socket couldnt be written FIND %m",
+		    ClientHost);
+		sleep(1);
+		continue;
+	    }
+	}
+	FD_ZERO(&fdset);
+	FD_SET(GRPactived, &fdset);
+	timeout.tv_sec = 1;
+	timeout.tv_usec = 0;
+	if (select(GRPactived + 1, &fdset, NULL, NULL, &timeout) < 0) {
+	    syslog(L_ERROR, "%s actived socket failed select %m",
+		ClientHost);
+	    sleep(1);
+	    continue;
+	}
+	if (FD_ISSET(GRPactived, &fdset)) {
+	    if (read_udp(GRPactived, (char *)&buffer, sizeof(buffer)) != sizeof(buffer)) {
+	        syslog(L_ERROR, "%s actived socket couldnt be read FINDRESP %m",
+		    ClientHost);
+	        sleep(1);
+	        continue;
+	    }
+	    if (buffer.RequestID != NRequestID) {
+	        syslog(L_ERROR, "%s actived socket returned a different request-ID %d/%d",
+		    ClientHost, buffer.RequestID, NRequestID);
+	        sleep(1);
+	        continue;
+	    }
+	    if (buffer.RequestType != REQ_FINDRESP) {
+	        syslog(L_ERROR, "%s actived socket returned a non-FINDRESP %d",
+		    ClientHost, buffer.RequestType);
+	        sleep(1);
+	        continue;
+	    }
+
+	    /* Was the request successful?  Did we find the group? */
+	    if (! buffer.Success) {
+		return(NULL);
+	    }
+
+	    /* Looks good! Copy all the data to the static "data" struct */
+	    if (! buffer.NameNull) {
+	        data.Name = buffer.Name;
+	    } else {
+		data.Name = NULL;
+	    }
+	    data.High = buffer.High;
+	    data.Low = buffer.Low;
+	    data.Flag = buffer.Flag;
+	    if (! buffer.AliasNull) {
+	        data.Alias = buffer.Alias;
+	    } else {
+		data.Alias = NULL;
+	    }
+	    return(&data);
+        }
+    }
+
+    /* Something is very wrong.  Fall back to local access and whine like
+       hell.  GRPuselocalhash is explicitly checked by GRPfind, and will
+       handle initializing the database for us. */
+
+    syslog(L_ERROR, "%s NOT using actived", ClientHost);
+    GRPuselocalhash++;
+    return(NULL);
+}
+
+
+GROUPENTRY *
+GRPfind(group)
+    register char		*group;
+{
+    GROUPENTRY *rval;
+
+    /*
+     * If we are not flagged to use local hash, call NGRPfind.
+     * That could potentially fail (server no answer, etc) in which
+     * case we fall back to standard INN and scream bloody murder.
+     * NGRPfind will toggle GRPuselocalhash to TRUE if it has problems.
+     */
+
+    if (! GRPuselocalhash) {
+	rval = NGRPfind(group);
+	if (! GRPuselocalhash) {
+		return(rval);
+	}
+
+	/* Ow!  We are falling back to local access since NGRPfind failed! */
+	XGetGroupList();
+    }
+
+    return(XGRPfind(group));
+}
+
+
 STATIC void
-GRPhash()
+XGRPhash()
 {
     register char		*p;
     register int		i;
@@ -100,7 +245,7 @@
 **  newsgroups read in.  Return TRUE if okay, FALSE on error.
 */
 BOOL
-GetGroupList()
+XGetGroupList()
 {
     static char			*active;
     register char		*p;
@@ -166,10 +311,112 @@
     }
 
     GRPsize = i;
-    GRPhash();
+    XGRPhash();
     return TRUE;
 }
 
+BOOL
+NGetGroupList()
+{
+    int now = time(NULL);
+    int expireat = now + ACTIVED_TIMEOUT;
+    int last = now - 2;
+    int s;
+    struct wireprotocol buffer;
+    fd_set fdset;
+    struct timeval timeout;
+
+    if (GRPactived < 0) {
+        if ((s = create_udp_socket(0)) < 0) {
+	    syslog(L_ERROR, "%s actived socket couldnt be created %m",
+		ClientHost);
+            return(FALSE);
+        }
+        if (connect_udp_socket(s, "localhost", 1119) < 0) {
+	    syslog(L_ERROR, "%s actived socket couldnt be connected %m",
+		ClientHost);
+            return(FALSE);
+        }
+	if (fcntl(s, F_SETFL, O_NDELAY) < 0) {
+	    syslog(L_ERROR, "%s actived socket couldnt be fcntl O_NDELAY %m",
+		ClientHost);
+            return(FALSE);
+	}
+	GRPactived = s;
+    }
+
+    /* Okay.  Let's ask the server if it's there. */
+    NNewRequestID();
+    while (now < expireat) {
+	now = time(NULL);
+	if (last < now - 1) {
+	    last = now;
+	    buffer.RequestID = NRequestID;
+	    buffer.RequestType = REQ_AYT;
+
+	    if (write_udp(GRPactived, (char *)&buffer, sizeof(buffer)) != sizeof(buffer)) {
+		syslog(L_ERROR, "%s actived socket couldnt be written AYT %m",
+		    ClientHost);
+		sleep(1);
+		continue;
+	    }
+	}
+	FD_ZERO(&fdset);
+	FD_SET(GRPactived, &fdset);
+	timeout.tv_sec = 1;
+	timeout.tv_usec = 0;
+	if (select(GRPactived + 1, &fdset, NULL, NULL, &timeout) < 0) {
+	    syslog(L_ERROR, "%s actived socket failed select %m",
+		ClientHost);
+	    sleep(1);
+	    return(FALSE);
+	}
+	if (FD_ISSET(GRPactived, &fdset)) {
+	    if (read_udp(GRPactived, (char *)&buffer, sizeof(buffer)) != sizeof(buffer)) {
+	        syslog(L_ERROR, "%s actived socket couldnt be read AYTACK %m",
+		    ClientHost);
+	        sleep(1);
+	        continue;
+	    }
+	    if (buffer.RequestID != NRequestID) {
+	        syslog(L_ERROR, "%s actived socket returned a different request-ID %d/%d",
+		    ClientHost, buffer.RequestID, NRequestID);
+	        sleep(1);
+	        continue;
+	    }
+	    if (buffer.RequestType != REQ_AYTACK) {
+	        syslog(L_ERROR, "%s actived socket returned a non-AYTACK %d",
+		    ClientHost, buffer.RequestType);
+	        sleep(1);
+	        continue;
+	    }
+	    /* Looks good! */
+	    return(TRUE);
+        }
+    }
+    return(FALSE);
+}
+
+BOOL
+GetGroupList()
+{
+    /*
+     * If we are not flagged to use local hash, call NGetGroupList.
+     * That could potentially fail (server no answer, etc) in which
+     * case we fall back to standard INN and scream bloody murder.
+     */
+    if (! GRPuselocalhash) {
+	if (NGetGroupList() == FALSE) {
+		syslog(L_ERROR, "%s NOT using actived", ClientHost);
+		GRPuselocalhash++;
+		return(XGetGroupList());
+	}
+	return(TRUE);
+    } else {
+	return(XGetGroupList());
+    }
+}
+
 
 /*
 **  Sorting predicate to put newsgroup names into numeric order.
--- inn-1.7.2.orig/nnrpd/nnrpd.c
+++ inn-1.7.2/nnrpd/nnrpd.c
@@ -636,6 +637,14 @@
 	ExitWithStats(0);
     }
 
+    {
+	char *UseActived;
+	extern int GRPuselocalhash;
+	if ((UseActived = GetConfigValue(_CONF_USEACTIVED)) != NULL)
+	    if (EQ(UseActived, "true") || EQ(UseActived, "yes"))
+		GRPuselocalhash = 0;
+    }
+
     ARTreadschema();
     if (!GetGroupList()) {
 	/* This shouldn't really happen. */
--- inn-1.7.2.orig/nnrpd/udp.c
+++ inn-1.7.2/nnrpd/udp.c
@@ -0,0 +1,162 @@
+#include	<stdio.h>
+#include	<sys/types.h>
+#include	"clibrary.h"
+#include	<errno.h>
+#include	<sys/socket.h>
+#include	<netinet/in.h>
+#include	<arpa/inet.h>
+#include	<ctype.h>
+#include	<netdb.h>
+#include	"logging.h"
+
+#ifdef NEED_BZERO_ETC
+#include <fcntl.h>
+void
+bzero(b, length)
+    char *b;
+    int length;
+{
+    while (--length >= 0)
+        *b++ = '\0';
+}
+void
+bcopy(b1, b2, length)
+    char *b1;
+    char *b2;
+    int length;
+{
+    while (--length >= 0)
+        *b2++ = *b1++;
+}
+#endif
+
+int create_udp_socket(int port)
+{
+	int s;
+	struct sockaddr_in sin;
+
+	sin.sin_port = htons(port);
+	sin.sin_family = AF_INET;
+	sin.sin_addr.s_addr = INADDR_ANY;
+
+	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+		syslog(L_ERROR, "create_udp_socket: socket: %m");
+		return(-1);
+	}
+	if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		syslog(L_ERROR, "create_udp_socket: bind: %m");
+		return(-1);
+	}
+	return(s);
+}
+
+
+
+
+
+int make_udp_sockaddr(struct sockaddr_in *addr, char *ascii)
+{
+        struct hostent *host;
+        int dots = 0;
+        int numeric = 0;
+	char *str, *colon, *lastdot, *ptr;
+
+	if (! (str = malloc(strlen(ascii) + 1))) {
+		syslog(L_ERROR, "make_udp_sockaddr: malloc: %m");
+		return(-1);
+        }
+	strcpy(str, ascii);
+	colon = strrchr(str, ':');
+	lastdot = strrchr(str, '.');
+
+        addr->sin_family = AF_INET;
+
+        /* Count the number of dots in the address. */
+        for (ptr = str; *ptr; ptr++) {
+                if (*ptr == '.') {
+                        dots++;
+                }
+        }
+
+        /* Check if it seems to be numeric. */
+        numeric = isdigit(*str);
+
+        /* If numeric and four dots, we have a.b.c.d.6000 */
+        if (numeric && dots == 4) {
+                *lastdot = '\0';
+                addr->sin_port = htons(atoi(lastdot + 1));
+        }
+        /* If nonnumeric, check if the last part is a port */
+        if (! numeric && lastdot && isdigit(*(lastdot + 1))) {
+                *lastdot = '\0';
+                addr->sin_port = htons(atoi(lastdot + 1));
+        }
+        /* Now do we have a numeric address */
+        if (numeric) {
+                addr->sin_addr.s_addr = inet_addr(str);
+                free(str);
+                return(0);
+        }
+        /* Or a name */
+        if (! (host = gethostbyname(str))) {
+                free(str);
+                syslog(L_ERROR, "make_udp_sockaddr: gethostbyname: %m");
+		return(-1);
+        }
+        bcopy(host->h_addr_list[0], (char *)&addr->sin_addr.s_addr, sizeof(addr->sin_addr.s_addr));
+        free(str);
+        return(0);
+}
+
+
+
+
+
+int connect_udp_socket(int s, char *address, int port)
+{
+	struct sockaddr_in sin;
+
+	bzero(&sin, sizeof(sin));
+	sin.sin_port = htons(port);
+	sin.sin_family = AF_INET;
+	sin.sin_addr.s_addr = INADDR_ANY;
+
+	if (make_udp_sockaddr(&sin, address) < 0) {
+		return(-1);
+	}
+	if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		syslog(L_ERROR, "connect_udp_socket: connect: %m");
+		return(-1);
+	}
+	return(0);
+}
+
+
+
+
+
+int write_udp(int s, char *buf, int len)
+{
+	int rval;
+
+	if ((rval = send(s, buf, len, 0x0)) < 0) {
+		syslog(L_ERROR, "write_udp: send: %m");
+		return(-1);
+	}
+	return(rval);
+}
+
+
+
+
+
+int read_udp(int s, char *buf, int len)
+{
+	int rval;
+
+	if ((rval = recv(s, buf, len, 0x0)) < 0) {
+		syslog(L_ERROR, "read_udp: recv: %m");
+		return(-1);
+	}
+	return(rval);
+}
--- inn-1.7.2.orig/Makefile
+++ inn-1.7.2/Makefile
@@ -17,7 +17,7 @@
 RCSCOFLAGS	= -u
 
 ##  The first two directories must be config and lib.
-PROGS	= config lib frontends innd nnrpd backends expire doc
+PROGS	= config lib frontends innd nnrpd actived backends expire doc
 DIRS	= $(PROGS) site
 
 ##  We invoke an extra process and set this to be what to make.
--- inn-1.7.2.orig/actived/Makefile
+++ inn-1.7.2/actived/Makefile
@@ -0,0 +1,97 @@
+##  $Revision: 1.16 $
+SHELL	= /bin/sh
+MAKE	= make
+DESTDIR =
+D       = $(DESTDIR)
+##  =()<P	= @<P>@>()=
+P	= 
+
+##  =()<CC	= @<CC>@>()=
+CC	= gcc
+##  =()<DEFS	= @<DEFS>@>()=
+DEFS	= -I../include
+##  =()<CFLAGS	= @<CFLAGS>@>()=
+CFLAGS	= $(DEFS) -g
+##  =()<LDFLAGS	= @<LDFLAGS>@>()=
+LDFLAGS	= 
+##  =()<LINTFLAGS	= @<LINTFLAGS>@>()=
+LINTFLAGS	= -b -h -z $(DEFS)
+##  =()<LINTFILTER	= @<LINTFILTER>@>()=
+LINTFILTER	= | sed -n -f ../sedf.sun
+##  =()<CTAGS		= @<CTAGS>@>()=
+CTAGS		= ctags -t -w
+##  =()<PROF	= @<PROF>@>()=
+PROF	= -pg
+
+##  =()<ACTIVED	= @<_PATH_ACTIVED>@>()=
+ACTIVED	= /usr/lib/news/bin/actived
+## =()<OWNER	= -O @<NEWSUSER>@ -G @<NEWSGROUP>@>()=
+OWNER	= -O news -G news
+
+##  =()<LIBS	= @<LIBS>@>()=
+LIBS	= -lutil
+LIBNEWS	= ../libinn.a
+LINTLIB	= ../llib-linn.ln
+
+SOURCES	= \
+	actived.c group.c udp.c activedstats.c
+
+OBJECTS	= \
+	actived.o group.o udp.o activedstats.o
+
+ALL	= actived
+
+all:		$(ALL)
+
+install:	$D$(ACTIVED)
+
+##  Low-level install actions.
+$D$(ACTIVED):	actived
+	$(SHELL) ../installit.sh $(OWNER) -m 0555 -b .OLD $? $@
+
+clobber clean:
+	rm -f *.o $(ALL)
+	rm -f actived activedp profiled
+	rm -f all install lint
+
+tags ctags:	$(SOURCES)
+	$(CTAGS) $(SOURCES) ../lib/*.c actived.h ../include/*.h
+
+actived:		$(P) $(OBJECTS) $(LIBNEWS)
+	@rm -f $@
+	$(CC) $(LDFLAGS) -o $@ $(OBJECTS) $(LIBNEWS) $(LIBS)
+
+lint:		$(ALL)
+	@rm -f lint
+	lint $(LINTFLAGS) $(SOURCES) $(LINTLIB) $(LINTFILTER) >lint
+
+../include/dbz.h:
+	(cd ../lib ; $(MAKE) ../include/dbz.h)
+$(LIBNEWS) $(LINTLIB):
+	(cd ../lib ; $(MAKE) install )
+
+##  Profiling.  The rules are a bit brute-force, but good enough.
+profiled:	activedp
+	date >$@
+
+activedp:		$(SOURCES)
+	rm -f $(OBJECTS)
+	$(MAKE) actived CFLAGS="$(CFLAGS) $(PROF)" LIBNEWS=../libinn_p.a
+	mv actived activedp
+	rm -f $(OBJECTS)
+
+ccenter:	$(SOURCES)
+	#load $(CFLAGS) $(SOURCES) $(LIBNEWS)
+
+##  Dependencies.  Default list, below, is probably good enough.
+depend:		Makefile $(SOURCES)
+	makedepend $(DEFS) $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+$(OBJECTS):	actived.h \
+		../include/clibrary.h ../include/configdata.h \
+		../include/libinn.h ../include/logging.h \
+		../include/macros.h ../include/nntp.h \
+		../include/paths.h ../include/qio.h
+group.o:	../include/mydir.h
+misc.o:		../include/dbz.h
--- inn-1.7.2.orig/actived/actived.c
+++ inn-1.7.2/actived/actived.c
@@ -0,0 +1,193 @@
+/*  $Revision: 1.18 $
+**
+**  Active file server for readers (NNRP) for InterNetNews.
+*/
+
+#define		UPDATE_INTERVAL	30
+#define		MAINLINE
+#include	<stdio.h>
+#include	<time.h>
+#include	"actived.h"
+#include	"protocol.h"
+#include	"configdata.h"
+#include	"paths.h"
+
+BOOL GetGroupList();
+int create_udp_socket(int port);
+int loads(int reader_count, int startup_count);
+
+
+char	ACTIVE[] = _PATH_ACTIVE;
+char	PID[] = _PATH_ACTIVEDPID;
+char	INNDDIR[] = _PATH_INNDDIR;
+STATIC	UID_T	NewsUID;
+STATIC	GID_T	NewsGID;
+struct	stat	Sb;
+
+int
+handle(s)
+    int			s;
+{
+    GROUPENTRY *ptr;
+    struct sockaddr saddr;
+    struct sockaddr_in *sin = (struct sockaddr_in *)&saddr;
+    int saddrsiz = sizeof(saddr);
+    struct wireprotocol buffer;
+    int len = sizeof(buffer);
+    int rval;
+
+    if ((rval = recvfrom(s, (char *)&buffer, len, 0, &saddr, &saddrsiz)) < 0) {
+	syslog(L_ERROR, "cant recvfrom %m");
+	return(-1);
+    }
+
+    if (rval != sizeof(buffer)) {
+	syslog(L_ERROR, "message size wrong");
+	return(-1);
+    }
+
+    /* XXX If not 127.0.0.1, then reject and return */
+
+
+    switch (buffer.RequestType) {
+	case 	REQ_AYT:
+	    buffer.RequestType = REQ_AYTACK;
+	    if (sendto(s, (char *)&buffer, len, 0, &saddr, saddrsiz) < 0) {
+		syslog(L_ERROR, "cant sendto %m");
+		return(-1);
+	    }
+	    return(0);
+	case 	REQ_FIND:
+	    buffer.RequestType = REQ_FINDRESP;
+
+	    buffer.Name[sizeof(buffer.Name) - 1] = '\0';
+	    if (! (ptr = GRPfind(buffer.Name))) {
+		buffer.Success = 0;
+	    } else {
+		buffer.Success = 1;
+		if (ptr->Name) {
+		    buffer.NameNull = 0;
+		    strncpy(buffer.Name, ptr->Name, sizeof(buffer.Name) - 1);
+		    buffer.Name[sizeof(buffer.Name) - 1] = '\0';
+		} else {
+		    buffer.NameNull = 1;
+		}
+		buffer.High = ptr->High;
+		buffer.Low = ptr->Low;
+		buffer.Flag = ptr->Flag;
+		if (ptr->Alias) {
+		    buffer.AliasNull = 0;
+		    strncpy(buffer.Alias, ptr->Alias, sizeof(buffer.Alias) - 1);
+		    buffer.Alias[sizeof(buffer.Alias) - 1] = '\0';
+		} else {
+		    buffer.AliasNull = 1;
+		}
+	    }
+	    if (sendto(s, (char *)&buffer, len, 0, &saddr, saddrsiz) < 0) {
+		syslog(L_ERROR, "cant sendto %m");
+		return(-1);
+	    }
+	    return(0);
+    }
+    syslog(L_ERROR, "unknown requesttype %d", buffer.RequestType);
+    return(-1);
+}
+
+
+
+
+
+/* ARGSUSED0 */
+int
+main(argc, argv)
+    int			argc;
+    char		*argv[];
+{
+    int s, i;
+    time_t last_active_update = time(NULL), now;
+    fd_set fdset;
+    struct timeval tv;
+    FILE *F;
+    PID_T pid;
+    static char WHEN[] = "Pid file";
+    char LogName[] = "ACTIVED";
+    if (stat(INNDDIR, &Sb) < 0 || !S_ISDIR(Sb.st_mode)) {
+	syslog(L_FATAL, "inndstart cant stat %s %m", INNDDIR);
+	exit(1);
+    }
+    NewsUID = Sb.st_uid;
+    NewsGID = Sb.st_gid;
+
+    openlog("actived", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+
+    if ((s = create_udp_socket(1119)) < 0) {
+	syslog(L_ERROR, "cant createudpsocket %m");
+	exit(1);
+    }
+    if (fcntl(s, F_SETFL, O_NONBLOCK) < 0) {
+	syslog(L_ERROR, "cant fcntl O_NDELAY socket %m");
+	exit(1);
+    }
+    if (!GetGroupList()) {
+	/* This shouldn't really happen. */
+	syslog(L_ERROR, "cant getgrouplist %m");
+	exit(1);
+    }
+    i = fork();
+    if (i < 0)
+        syslog(L_ERROR, "daemon: cannot fork");
+    if (i != 0)
+        exit(0);
+
+    /* Change our UID and GID */
+    if (setgid(NewsGID) == -1)
+	syslog(L_ERROR, "actived cant setgid: %m");
+    if (setuid(NewsUID) == -1)
+	syslog(L_ERROR, "actived cant setuid: %m");
+
+    /* Record our PID. */
+    pid = getpid();
+    if ((F = fopen(PID, "w")) == NULL) {
+      i = errno;
+      syslog(L_ERROR, "%s cant fopen %s %m", LogName, PID);
+    }
+    else {
+      if (fprintf(F, "%ld\n", (long)pid) == EOF || ferror(F)) {
+            i = errno;
+            syslog(L_ERROR, "%s cant fprintf %s %m", LogName, PID);
+      }
+      if (fclose(F) == EOF) {
+            i = errno;
+            syslog(L_ERROR, "%s cant fclose %s %m", LogName, PID);
+      }
+      if (chmod(PID, 0664) < 0) {
+            i = errno;
+            syslog(L_ERROR, "%s cant chmod %s %m", LogName, PID);
+      }
+    }
+
+    for (;;) {
+	FD_ZERO(&fdset);
+	FD_SET(s, &fdset);
+	tv.tv_sec = 0;
+	tv.tv_usec = 300000;
+	if (select(s + 1, &fdset, NULL, NULL, &tv) < 0) {
+	    syslog(L_ERROR, "cant select %m");
+	    exit(1);
+	}
+	now = time(NULL);
+	if (now > last_active_update + UPDATE_INTERVAL) {
+	    last_active_update = now;
+	    if (!GetGroupList()) {
+		/* This shouldn't really happen. */
+		syslog(L_ERROR, "cant getgrouplist %m");
+		exit(1);
+	    }
+	}
+	if (FD_ISSET(s, &fdset)) {
+		handle(s);
+		loads(0, 1);
+	}
+    }
+    /* NOTREACHED */
+}
--- inn-1.7.2.orig/actived/actived.h
+++ inn-1.7.2/actived/actived.h
@@ -0,0 +1,51 @@
+/*  $Revision: 1.15 $
+**
+*/
+#include "configdata.h"
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/file.h>
+#if	defined(VAR_VARARGS)
+#include <varargs.h>
+#endif	/* defined(VAR_VARARGS) */
+#if	defined(VAR_STDARGS)
+#include <stdarg.h>
+#endif	/* defined(VAR_STDARGS) */
+#include <syslog.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include "paths.h"
+#include "nntp.h"
+#include "logging.h"
+#include "clibrary.h"
+#include "libinn.h"
+#include "qio.h"
+#include "macros.h"
+
+
+/*
+**  A group entry.
+*/
+typedef struct _GROUPENTRY {
+    char	*Name;
+    ARTNUM	High;
+    ARTNUM	Low;
+    char	Flag;
+    char	*Alias;
+} GROUPENTRY;
+
+
+#if	defined(MAINLINE)
+#define EXTERN	/* NULL */
+#else
+#define EXTERN	extern
+#endif	/* defined(MAINLINE) */
+
+extern char	ACTIVE[];
+extern GROUPENTRY	*GRPfind();
--- inn-1.7.2.orig/actived/activedstats.c
+++ inn-1.7.2/actived/activedstats.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 1996
+ *	Joe Greco.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by Joe Greco.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JOE GRECO AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL JOE GRECO OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+
+#include	<stdio.h>
+#include	<sys/types.h>
+#include	<sys/time.h>
+
+int dumploads();
+int fprintloads(FILE *fp);
+
+
+/* This breaks past 1024; see comments in sys/param.h - soln: make 64 bits */
+
+#define FSHIFT  11              /* bits to right of fixed binary point */
+#define FSCALE  (1<<FSHIFT)
+
+/* Load average structure. */
+struct loadavg {
+        u_long  ldavg[3];
+	long    fscale;
+};      
+		 
+struct loadavg avgreaders = { {0, 0, 0}, FSCALE};
+struct loadavg avgstartups = { {0, 0, 0}, FSCALE};
+
+/*
+ * Constants for averages over 1, 5, and 15 minutes
+ * when sampling at 5 second intervals.
+ */
+static u_long cexp[3] = {
+        0.9200444146293232 * FSCALE,    /* exp(-1/12) */
+        0.9834714538216174 * FSCALE,    /* exp(-1/60) */
+        0.9944598480048967 * FSCALE,    /* exp(-1/180) */
+};
+
+
+/*
+ * Compute a tenex style load average of a quantity on
+ * 1, 5 and 15 minute intervals.
+ */
+static void
+loadav(struct loadavg *avg, int nrun)
+{
+        register int i;
+        register struct proc *p;
+
+        for (i = 0; i < 3; i++)
+                avg->ldavg[i] = (cexp[i] * avg->ldavg[i] +
+                    nrun * FSCALE * (FSCALE - cexp[i])) >> FSHIFT;
+}
+
+/*
+ * Call this every half a second or so - every five seconds, it will 
+ * calculate loads.
+ * 
+ * Originally designed for readers.  We don't care for actived and
+ * instead we call with loads(0, 1) for requests, and loads (0, 0)
+ * for periodic calls.
+ *
+ * Formerly:
+ * When a reader connects:  loads(+1, 1)
+ * 		disconn's:  loads(-1, 0)
+ */
+
+int
+loads(int reader_count, int startup_count)
+{
+	static int readers = 0;
+	static int startups = 0;
+	struct timeval tv;
+	static int tv_next = 0;
+
+	readers += reader_count;
+	startups += startup_count;
+
+	gettimeofday(&tv, NULL);
+
+	if (! tv_next) {
+		tv_next = tv.tv_sec + 5;
+	}
+	if (tv.tv_sec >= tv_next) {
+		tv_next += 5;
+		loadav(&avgreaders, readers);
+		loadav(&avgstartups, startups);
+		startups = 0;
+		dumploads();
+	}
+	return(0);
+}
+
+int dumploads()
+{
+	FILE *fp;
+
+	if (! (fp = fopen("/var/log/news/activedloads", "w"))) {
+		return(-1);
+	}
+	fprintloads(fp);
+	fclose(fp);
+	return(0);
+}
+
+int
+fprintloads(FILE *fp)
+{
+	double avenrun[3];
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		avenrun[i] = (double) avgstartups.ldavg[i] / avgstartups.fscale;
+	}
+	fprintf(fp, "Average ACTIVED Loads: ");
+	for (i = 0; i < (sizeof(avenrun) / sizeof(avenrun[0])); i++) {
+		if (i > 0)
+			fprintf(fp, ",");
+		fprintf(fp, " %6.2f", avenrun[i]);
+	}
+	fprintf(fp, "\n");
+}
--- inn-1.7.2.orig/actived/group.c
+++ inn-1.7.2/actived/group.c
@@ -0,0 +1,166 @@
+/*  $Revision: 1.13 $
+**
+**  Newsgroups and the active file.
+*/
+#include "actived.h"
+
+
+/*
+**  Newsgroup hashing stuff.  See comments in innd/ng.c.
+*/
+
+#define GRP_HASH(Name, p, j)	\
+	for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
+#define GRP_SIZE	65536
+#define GRP_BUCKET(j)	&GRPtable[j & (GRP_SIZE - 1)]
+
+typedef struct _GRPHASH {
+    int		Size;
+    int		Used;
+    GROUPENTRY	**Groups;
+} GRPHASH;
+
+
+STATIC GRPHASH		GRPtable[GRP_SIZE];
+STATIC GROUPENTRY	*GRPentries;
+STATIC int		GRPbuckets;
+STATIC int		GRPsize;
+
+
+/*
+**  See if a given newsgroup exists.
+*/
+GROUPENTRY *
+GRPfind(group)
+    register char		*group;
+{
+    register char		*p;
+    register unsigned int	j;
+    register int		i;
+    register GROUPENTRY		**gpp;
+    GRPHASH			*htp;
+    char			c;
+
+    /* SUPPRESS 6 *//* Over/underflow from plus expression */
+    GRP_HASH(group, p, j);
+    htp = GRP_BUCKET(j);
+    for (c = *group, gpp = htp->Groups, i = htp->Used; --i >= 0; gpp++)
+	if (c == gpp[0]->Name[0] && EQ(group, gpp[0]->Name))
+	    return gpp[0];
+    return NULL;
+}
+
+
+STATIC void
+GRPhash()
+{
+    register char		*p;
+    register int		i;
+    register GROUPENTRY		*gp;
+    register unsigned int	j;
+    register GRPHASH		*htp;
+
+    /* Set up the default hash buckets. */
+    GRPbuckets = GRPsize / GRP_SIZE;
+    if (GRPbuckets == 0)
+	GRPbuckets = 1;
+    if (GRPtable[0].Groups)
+	for (i = GRP_SIZE, htp = GRPtable; --i >= 0; htp++)
+	    htp->Used = 0;
+    else
+	for (i = GRP_SIZE, htp = GRPtable; --i >= 0; htp++) {
+	    htp->Size = GRPbuckets;
+	    htp->Groups = NEW(GROUPENTRY*, htp->Size);
+	    htp->Used = 0;
+	}
+
+    /* Now put all groups into the hash table. */
+    for (i = GRPsize, gp = GRPentries; --i >= 0; gp++) {
+	/* SUPPRESS 6 *//* Over/underflow from plus expression */
+	GRP_HASH(gp->Name, p, j);
+	htp = GRP_BUCKET(j);
+	if (htp->Used >= htp->Size) {
+	    htp->Size += GRPbuckets;
+	    RENEW(htp->Groups, GROUPENTRY*, htp->Size);
+	}
+	htp->Groups[htp->Used++] = gp;
+    }
+
+    /* Note that we don't sort the buckets. */
+}
+
+
+/*
+**  Read the active file into memory, sort it, and set the number of
+**  newsgroups read in.  Return TRUE if okay, FALSE on error.
+*/
+BOOL
+GetGroupList()
+{
+    static char			*active;
+    register char		*p;
+    register char		*q;
+    register GROUPENTRY		*gp;
+    register int		i;
+
+    /* If re-scanning, free previous groups. */
+    if (active != NULL) {
+	DISPOSE(active);
+	DISPOSE(GRPentries);
+    }
+
+    /* Get the new file. */
+    active = ReadInFile(ACTIVE, (struct stat *)NULL);
+    if (active == NULL) {
+	syslog(L_ERROR, "cant read %s %m", ACTIVE);
+	return FALSE;
+    }
+
+    /* Count lines. */
+    for (p = active, i = 0; (p = strchr(p, '\n')) != NULL; p++, i++)
+	continue;
+
+    /* Fill in the group array. */
+    GRPentries = NEW(GROUPENTRY, i);
+    for (i = 0, gp = GRPentries, p = active; *p; i++, gp++, p = q + 1) {
+	gp->Name = p;
+	if ((p = strchr(p, ' ')) == NULL) {
+	    syslog(L_ERROR, "internal no_space1 \"%.20s...\"",
+		gp->Name);
+	    return FALSE;
+	}
+	*p++ = '\0';
+
+	/* Get the high mark. */
+	if ((q = strchr(p, ' ')) == NULL) {
+	    syslog(L_ERROR, "internal no_space2 \"%.20s...\"",
+		gp->Name);
+	    return FALSE;
+	}
+	*q++ = '\0';
+	gp->High = atol(p);
+
+	/* Get the low mark. */
+	if ((p = strchr(q, ' ')) == NULL) {
+	    syslog(L_ERROR, "internal no_space3 \"%.20s...\"",
+		gp->Name);
+	    return FALSE;
+	}
+	*p++ = '\0';
+	gp->Low = atol(q);
+
+	/* Kill the newline. */
+	if ((q = strchr(p, '\n')) == NULL) {
+	    syslog(L_ERROR, "internal newline \"%.20s...\"",
+		gp->Name);
+	    return FALSE;
+	}
+	*q = '\0';
+	gp->Flag = *p;
+	gp->Alias = gp->Flag == NF_FLAG_ALIAS ? p + 1 : NULL;
+    }
+
+    GRPsize = i;
+    GRPhash();
+    return TRUE;
+}
--- inn-1.7.2.orig/actived/protocol.h
+++ inn-1.7.2/actived/protocol.h
@@ -0,0 +1,19 @@
+#define	ACTIVED_TIMEOUT	10
+
+#define	REQ_AYT		1
+#define	REQ_AYTACK	2
+#define	REQ_FIND	3
+#define	REQ_FINDRESP	4
+
+struct wireprotocol {
+    int		RequestType;
+    int		RequestID;
+    int		Success;
+    int		NameNull;
+    char	Name[1024];
+    ARTNUM	High;
+    ARTNUM	Low;
+    char	Flag;
+    int		AliasNull;
+    char	Alias[1024];
+};
--- inn-1.7.2.orig/actived/query.c
+++ inn-1.7.2/actived/query.c
@@ -0,0 +1,48 @@
+#include	<stdio.h>
+#include	"actived.h"
+#include	"protocol.h"
+
+
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	int s, i;
+	int start = time(NULL) - 1;
+	struct wireprotocol buffer;
+
+	if ((s = create_udp_socket(0)) < 0) {
+		fprintf(stderr, "couldnt create socket\n");
+		exit(1);
+	}
+	if (connect_udp_socket(s, "localhost", 1119) < 0) {
+		fprintf(stderr, "couldnt connect socket\n");
+		exit(1);
+	}
+	i = 0;
+	buffer.RequestType = REQ_FIND;
+	buffer.RequestID = getpid();
+	sprintf(buffer.Name, argv[1]);
+
+	fprintf(stderr, "asking question: request-id %d, group \"%s\"\n\n", buffer.RequestID, buffer.Name);
+
+	if (write_udp(s, (char *)&buffer, sizeof(buffer)) < 0) {
+		fprintf(stderr, "couldnt write packet\n");
+		exit(1);
+	}
+	if (read_udp(s, (char *)&buffer, sizeof(buffer)) != sizeof(buffer)) {
+		fprintf(stderr, "couldnt read packet\n");
+		exit(1);
+	}
+	fprintf(stderr, "request-id ok?         %s (%d)\n", buffer.RequestID == getpid() ? "yes" : "no", buffer.RequestID);
+	fprintf(stderr, "response type correct? %s (%d)\n", buffer.RequestType == REQ_FINDRESP ? "yes" : "no", buffer.RequestType);
+	fprintf(stderr, "success?               %s\n", buffer.Success ? "yes" : "no");
+	fprintf(stderr, "name?                  %s\n", buffer.NameNull ? "<NULL>" : buffer.Name);
+	fprintf(stderr, "alias?                 %s\n", buffer.AliasNull ? "<NULL>" : buffer.Alias);
+	fprintf(stderr, "range?                 %d-%d\n", buffer.Low, buffer.High);
+	fprintf(stderr, "flag?                  %c\n", buffer.Flag);
+}
+
+
+
+
--- inn-1.7.2.orig/actived/test.c
+++ inn-1.7.2/actived/test.c
@@ -0,0 +1,43 @@
+#include	<stdio.h>
+#include	"actived.h"
+#include	"protocol.h"
+
+
+int main(argc, argv)
+int argc;
+char *argv[];
+{
+	int s, i;
+	int start = time(NULL) - 1;
+	struct wireprotocol buffer;
+
+	if ((s = create_udp_socket(0)) < 0) {
+		fprintf(stderr, "couldnt create socket\n");
+		exit(1);
+	}
+	if (connect_udp_socket(s, "localhost", 1119) < 0) {
+		fprintf(stderr, "couldnt connect socket\n");
+		exit(1);
+	}
+	i = 0;
+	fprintf(stderr, "%08d", 0);
+	for (;;) {
+		buffer.RequestType = REQ_FIND;
+		sprintf(buffer.Name, "alt.sex");
+		if (write_udp(s, (char *)&buffer, sizeof(buffer)) < 0) {
+			fprintf(stderr, "couldnt write packet\n");
+			exit(1);
+		}
+		if (read_udp(s, (char *)&buffer, sizeof(buffer)) != sizeof(buffer)) {
+			fprintf(stderr, "couldnt read packet\n");
+			exit(1);
+		}
+		if (! (++i % 100)) {
+			fprintf(stderr, "\r%08d %5.2f", i, (float)i / (time(NULL) - start));
+		}
+	}
+}
+
+
+
+
--- inn-1.7.2.orig/actived/udp.c
+++ inn-1.7.2/actived/udp.c
@@ -0,0 +1,162 @@
+#include	<stdio.h>
+#include	<sys/types.h>
+#include	"clibrary.h"
+#include	<errno.h>
+#include	<sys/socket.h>
+#include	<netinet/in.h>
+#include	<arpa/inet.h>
+#include	<ctype.h>
+#include	<netdb.h>
+#include	"logging.h"
+
+#ifdef NEED_BZERO_ETC
+#include <fcntl.h>
+void
+bzero(b, length)
+    char *b;
+    int length;
+{
+    while (--length >= 0)
+        *b++ = '\0';
+}
+void
+bcopy(b1, b2, length)
+    char *b1;
+    char *b2;
+    int length;
+{
+    while (--length >= 0)
+        *b2++ = *b1++;
+}
+#endif
+
+int create_udp_socket(int port)
+{
+	int s;
+	struct sockaddr_in sin;
+
+	sin.sin_port = htons(port);
+	sin.sin_family = AF_INET;
+	sin.sin_addr.s_addr = INADDR_ANY;
+
+	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+		syslog(L_ERROR, "create_udp_socket: socket: %m");
+		return(-1);
+	}
+	if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		syslog(L_ERROR, "create_udp_socket: bind: %m");
+		return(-1);
+	}
+	return(s);
+}
+
+
+
+
+
+int make_udp_sockaddr(struct sockaddr_in *addr, char *ascii)
+{
+        struct hostent *host;
+        int dots = 0;
+        int numeric = 0;
+	char *str, *colon, *lastdot, *ptr;
+
+	if (! (str = malloc(strlen(ascii) + 1))) {
+		syslog(L_ERROR, "make_udp_sockaddr: malloc: %m");
+		return(-1);
+        }
+	strcpy(str, ascii);
+	colon = strrchr(str, ':');
+	lastdot = strrchr(str, '.');
+
+        addr->sin_family = AF_INET;
+
+        /* Count the number of dots in the address. */
+        for (ptr = str; *ptr; ptr++) {
+                if (*ptr == '.') {
+                        dots++;
+                }
+        }
+
+        /* Check if it seems to be numeric. */
+        numeric = isdigit(*str);
+
+        /* If numeric and four dots, we have a.b.c.d.6000 */
+        if (numeric && dots == 4) {
+                *lastdot = '\0';
+                addr->sin_port = htons(atoi(lastdot + 1));
+        }
+        /* If nonnumeric, check if the last part is a port */
+        if (! numeric && lastdot && isdigit(*(lastdot + 1))) {
+                *lastdot = '\0';
+                addr->sin_port = htons(atoi(lastdot + 1));
+        }
+        /* Now do we have a numeric address */
+        if (numeric) {
+                addr->sin_addr.s_addr = inet_addr(str);
+                free(str);
+                return(0);
+        }
+        /* Or a name */
+        if (! (host = gethostbyname(str))) {
+                free(str);
+                syslog(L_ERROR, "make_udp_sockaddr: gethostbyname: %m");
+		return(-1);
+        }
+        bcopy(host->h_addr_list[0], (char *)&addr->sin_addr.s_addr, sizeof(addr->sin_addr.s_addr));
+        free(str);
+        return(0);
+}
+
+
+
+
+
+int connect_udp_socket(int s, char *address, int port)
+{
+	struct sockaddr_in sin;
+
+	bzero(&sin, sizeof(sin));
+	sin.sin_port = htons(port);
+	sin.sin_family = AF_INET;
+	sin.sin_addr.s_addr = INADDR_ANY;
+
+	if (make_udp_sockaddr(&sin, address) < 0) {
+		return(-1);
+	}
+	if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
+		syslog(L_ERROR, "connect_udp_socket: connect: %m");
+		return(-1);
+	}
+	return(0);
+}
+
+
+
+
+
+int write_udp(int s, char *buf, int len)
+{
+	int rval;
+
+	if ((rval = send(s, buf, len, 0x0)) < 0) {
+		syslog(L_ERROR, "write_udp: send: %m");
+		return(-1);
+	}
+	return(rval);
+}
+
+
+
+
+
+int read_udp(int s, char *buf, int len)
+{
+	int rval;
+
+	if ((rval = recv(s, buf, len, 0x0)) < 0) {
+		syslog(L_ERROR, "read_udp: recv: %m");
+		return(-1);
+	}
+	return(rval);
+}
--- inn-1.7.2.orig/actived/README.actived
+++ inn-1.7.2/actived/README.actived
@@ -0,0 +1,46 @@
+This is nnrpd-actived.
+
+I found that one of the huge bottlenecks in an nnrp-oriented INN system
+is creating new sessions.  I spent some time and tracked down one of the
+big time-consumers...  hashing the active file.  Even with sharedactive,
+each client would hash the contents into a local hash table, and with a
+very large active file, this took an appreciable amount of time.
+
+My CPU's could do it a few times a second, but during peak periods, I
+see 5+ new connections per second, which essentially floods the CPU.
+
+One fix would have been to have the nnrpd that updated the shared memory
+space also update a shared hash table.  I didn't like that since I still
+think the sharedactive thing is uccchy.  It used to occasionally snag
+a semaphore and hang a few hundred nnrpd's in limbo.  Sharing the active
+file via mmap had drawbacks as well.  
+
+I finally decided to go a different route.  I created an "active file
+server".  This server reads and rehashes the active file once every 30
+seconds, and will answer UDP requests about the contents of the file.
+
+This is able to handle thousands of transactions per second, even though
+I rarely even see hundreds in production use.  I can start up a hundred
+nnrp sessions a second without response time suffering noticeably.
+
+The system is designed to fall back to standard active file accesses in
+the case actived falls over, which it hasn't in several months of
+production use.  Hopefully my code is correct.  I would strongly advise
+anyone who runs it to periodically check to see if actived has died, and
+restart it.  Just because I've had luck with it,...  ;-) 
+
+To install:  patch your nnrpd with the enclosed "group.c.patch".
+Build the contents of this directory, install and run "actived", you
+can compile "query" and ask it a few questions (i.e. "query alt.sex"),
+and then install your new nnrpd if all looks good.
+
+JG970605
+
+--
+
+I've made several changes to Joe's code (with Joe's assistance--thanks) to
+make actived a bit more portable.  I've successfully compiled this version on
+Solaris 2.4, Solaris 2.5.1, SunOS 4.1.4, Linux 2.0.30, and OpenBSD 2.1, so
+I suspect it'll compile on just about anything now.
+
+-Andrew Smith <aos@insync.net> 970625
