4.4BSD/usr/src/contrib/news/trn3/nntp/nntp.patch

Here's my latest patch to modify NNTP 1.5.11 to support the transmission
of thread databases.  

This patch supports both mthread's .thread file format (XTHREAD) and News
OVerview's .overfile file format (XOVER).  It also adds the LISTGROUP
command for listing the numbers in a group, and the DATE command for
finding out the time on the server.  Finally, the NEWGROUPS command is
filtered based the user's access privileges.

Apply this patch from the root directory of your NNTP source.  While it
only affects your server (the clientlib is no longer modified), it does
modify things in the common directory.

To apply this:
	cd nntp-1.5.11
	patch -p < xthread.patch
  OR	unipatch < xthread.patch | patch -p

This patch changes the conf.h.dist file, so you will need to edit your
conf.h file to add the new defines.  For example, you could:

	cd common
	diff -c conf.h.dist.orig conf.h.dist | patch conf.h

and then edit conf.h to make sure the new stuff is configured properly.

Then you are ready to compile the new server with "make server" as usual.

Wayne Davison                                        davison@borland.com
---8<------8<------8<------8<---cut here--->8------>8------>8------>8---
Index:common/version.c
Prereq: "1.5.11
@@ -2,4 +2,4 @@
  * Provide the version number of this release.
  */
 
-char	nntp_version[] = "1.5.11 (10 February 1991)";
+char	nntp_version[] = "1.5.11t3 (10 February 1993)";
Index:CHANGES
@@ -2,6 +2,22 @@
 since the initial release.  Individuals who either reported the bug or
 inspired the bug fix are in square brackets.
 
+1.5.11+XOVER1   (Wayne Davison <davison@borland.com> 18 Jan 93)
+	Fixes, optimizations, and enhancements to the first patch.
+
+1.5.11+XOVER0	(Rich $alz <rsalz@uunet.uu.net> 23 dec 92)
+	This adds the XOVER command to the server.  The XOVER command
+	is used to retrieve data from the .overview file that is part
+	of Geoff Collyer's "nov" package (that package is not provided
+	here; the official archive for it is
+	world.std.com:pub/src/news/nov.dist.tar.Z).  This command
+	has the following syntax:
+		XOVER [first[-last]]
+	Where first and last are article numbers; the default is to
+	return data for the current article.  This command is only
+	valid after a GROUP command.  It returns a 224 code followed
+	by a multi-line response.
+
 1.5.11
 	Fixes to spawn.c and batch.c for those system that need
 	execle() to specifically call /bin/sh to exectute a sh script.
Index:common/README
@@ -340,6 +340,10 @@
 Authorization process. Read the file AUTHORIZATION in the root directory of
 the NNTP distribution for more information.
 
+XOVER		(defined)
+     Defines whether we want to include the XOVER command, described
+in the top-level README file of this distribution.
+
 SERVER_FILE	("/usr/local/lib/rn/server")
 
      This file contains the name of the machine which runs the
Index:common/conf.h.dist
@@ -94,6 +94,21 @@
 			/* the server more.  If your server is heavily */
 			/* loaded already, defining this may be a bad idea */
 
+/* XTHREAD defines:  if XTHREAD is defined, THREAD_DIR controls where the
+ * thread files will be read from.
+ */
+#define XTHREAD		/* Optional XTHREAD command.  This allows trn to
+			 * keep all data on the server. */
+
+/* Leave this undefined to indicate that thread files go in the spool
+ * directory.  However, if you want a separate hierarchy of thread files,
+ * define it here.
+ */
+/*#define THREAD_DIR	"/usr/spool/threads"		/* base directory */
+
+/* if LONG_THREAD_NAMES & THREAD_DIR are defined, create threads in one dir */
+#undef LONG_THREAD_NAMES		/* not for short-name systems */
+
 /* Things that vary in network implementations */
 #define	SUBNET		/* If you have 4.3 subnetting */
 #undef	DAMAGED_NETMASK	/* If your subnet mask is not a multiple of */
@@ -201,6 +216,10 @@
 /* Things that relate to authentication and access */
 /* Define AUTH to use the proposed NNTP Version 2 authentication protocol. */
 #define	AUTH	
+
+/* Various protocol extensions */
+#define XOVER		/* xover -- Return .overview data */
+
 /*
  * A file containing the name of the host which is running
  * the news server.  This will have to match what rrn thinks,
@@ -332,6 +351,24 @@
 #			endif
 #		endif
 #	endif
+#endif
+
+#ifdef XTHREAD
+# ifdef THREAD_DIR
+#   ifdef LONG_THREAD_NAMES
+#	undef SUFFIX
+#   else
+#     ifndef SUFFIX
+#	define SUFFIX ".th"
+#     endif
+#   endif
+# else
+#   define THREAD_DIR	SPOOLDIR
+#   ifndef SUFFIX
+#     define SUFFIX	"/.thread"
+#   endif
+#   undef LONG_THREAD_NAMES
+# endif
 #endif
 
 /*
Index:common/nntp.h
@@ -20,6 +20,7 @@
  *	x2x	Article selection
  *	x3x	Distribution
  *	x4x	Posting
+ *	x8x	Authorization
  */
 
 #define	CHAR_INF	'1'
@@ -29,6 +30,7 @@
 #define	CHAR_FATAL	'5'
 
 #define	INF_HELP	100	/* Help text on way */
+#define	INF_DATE	111	/* Date */
 #define	INF_AUTH	180	/* Authorization capabilities */
 #define	INF_DEBUG	199	/* Debug output */
 
@@ -42,6 +44,7 @@
 #define	OK_HEAD		221	/* Head follows */
 #define	OK_BODY		222	/* Body follows */
 #define	OK_NOTEXT	223	/* No text sent -- stat, next, last */
+#define	OK_OVER		224	/* Overview data follows */
 #define	OK_NEWNEWS	230	/* New articles by message-id follow */
 #define	OK_NEWGROUPS	231	/* New newsgroups follow */
 #define	OK_XFERED	235	/* Article transferred successfully */
@@ -48,6 +51,7 @@
 #define	OK_POSTED	240	/* Article posted successfully */
 #define	OK_AUTHSYS	280	/* Authorization system ok */
 #define	OK_AUTH		281	/* Authorization (user/pass) ok */
+#define	OK_BIN		282	/* binary data follows */
 
 #define	CONT_XFER	335	/* Continue to send article */
 #define	CONT_POST	340	/* Continue to post article */
Index:server/Makefile
@@ -6,13 +6,13 @@
 	ahbs.o globals.o group.o help.o ihave.o list.o misc.o netaux.o \
 	newgroups.o newnews.o nextlast.o ngmatch.o post.o parsit.o scandir.o \
 	slave.o spawn.o strcasecmp.o subnet.o time.o xhdr.o fakesyslog.o \
-	batch.o auth.o timer.o ../common/version.o
+	batch.o auth.o timer.o xthread.o ../common/version.o
 
 SRVRSRC = main.c serve.c access.c access_inet.c access_dnet.c active.c \
 	ahbs.c globals.c group.c help.c ihave.c list.c misc.c netaux.c \
 	newgroups.c newnews.c nextlast.c ngmatch.c post.c parsit.c scandir.c \
 	slave.c spawn.c strcasecmp.c subnet.c time.c xhdr.c fakesyslog.c \
-	batch.c auth.c timer.c ../common/version.c
+	batch.c auth.c timer.c xthread.c ../common/version.c
 
 SRVRINC = common.h ../common/conf.h ../common/nntp.h timer.h
 
Index:server/ahbs.c
@@ -56,8 +56,9 @@
 			(void) fclose(fp);
 			return;
 		}
-		printf("%d 0 %s Article retrieved, %s.\r\n",
-			OK_ARTICLE + method, argv[1], verbiage[method]);
+		printf("%d %ld %s Article retrieved, %s.\r\n",
+			OK_ARTICLE + method, group_artnum, argv[1],
+			verbiage[method]);
 		spew(fp, method);
 		(void) fclose(fp);
 #ifdef LOG
Index:server/batch.c
@@ -461,7 +461,7 @@
 #ifdef POSTER
 	sprintf(user, "USER=%s", POSTER);
 	sprintf(logname, "LOGNAME=%s", POSTER);
-	if ((home = (char *)malloc(strlen(home_poster)+5)) != NULL)
+	if ((home = (char *)malloc(strlen(home_poster)+5+1)) != NULL)
 		sprintf(home, "HOME=%s", home_poster);
 	envp[0] = user;
 	envp[1] = logname;
Index:server/common.h
@@ -164,11 +164,18 @@
 extern	char	inews[];
 extern	char	rnews[];
 
+#ifdef	XTHREAD
+extern	char	*threaddir;
+extern	char	*threadfile;
+#endif
+
 extern	char	**group_array;
 extern	char	*actbuf;
 extern	int	num_groups;
 extern	char	*homedir;
 extern	int	ingroup;
+extern	char	*group_name;
+extern	long	group_artnum;
 extern	int	maxgroups;
 #ifdef DYNAMIC_ART_ARRAY
 extern	int	*art_array;
Index:server/globals.c
@@ -26,6 +26,11 @@
 char	inews[] = INEWS;
 char	rnews[] = RNEWS;
 
+#ifdef	XTHREAD
+char	*threaddir = THREAD_DIR;
+char	*threadfile = NULL;
+#endif
+
 /*
  * Other random externals.
  */
@@ -34,6 +39,8 @@
 char 	*actbuf;
 int	num_groups;
 int	ingroup = 0;
+char	*group_name = NULL;
+long	group_artnum = 0;
 int	art_ptr;
 int	num_arts;
 #ifdef DYNAMIC_ART_ARRAY
Index:server/group.c
@@ -4,6 +4,12 @@
 
 #include "common.h"
 
+#ifdef	XTHREAD
+extern char *thread_name();
+#endif
+
+extern char *malloc();
+
 /*
  * GROUP newsgroup
  *
@@ -67,8 +73,15 @@
 		return;
 	}
 
+#ifdef XOVER
+	close_xover();
+#endif
 	close_crnt();
+	if (group_name)
+		free(group_name);
 	(void) chdir(spooldir);
+	if ((group_name = malloc(strlen(argv[1])+1)) != NULL)
+		strcpy(group_name, argv[1]);
 
 #ifdef LOG
 	syslog(LOG_INFO, "%s group %s", hostname, argv[1]);
@@ -97,6 +110,10 @@
 
 	ingroup = 1;
 
+#ifdef	XTHREAD
+	threadfile = thread_name(argv[1]);
+#endif
+
 	while ((cp = index(argv[1], '/')) != (char *) NULL)
 		*cp = '.';
 
@@ -108,3 +125,177 @@
 		argv[1]);
 	(void) fflush(stdout);
 }
+
+
+#ifdef XOVER
+static FILE *xover_fp;
+static int xover_num;
+
+doxover(argc, argv)
+	int		argc;
+	char		*argv[];
+{
+	register FILE	*fp;
+	register int	c, first, last;
+	int		artnum, artp;
+	char		*p;
+
+	if (!canread) {
+		printf("%d You only have permission to transfer, sorry.\r\n",
+			ERR_ACCESS);
+		(void) fflush(stdout);
+		return;
+	}
+
+	if (!ingroup) {
+		printf("%d You are not currently in a newsgroup.\r\n",
+			ERR_NCING);
+		(void) fflush(stdout);
+		return;
+	}
+	if (argc != 1 && argc != 2) {
+		printf("%d Usage: XOVER [first[-last]].\r\n", ERR_CMDSYN);
+		(void) fflush(stdout);
+		return;
+	}
+
+	if (xover_fp)
+		fp = xover_fp;
+	else {
+		fp = xover_fp = fopen(".overview", "r");
+		if (fp == NULL) {
+			printf("%d No overview available.\r\n", ERR_FAULT);
+			(void) fflush(stdout);
+#ifdef SYSLOG
+			syslog(LOG_ERR, "xover: fopen %s: %m", ".overview");
+#endif
+			return;
+		}
+		xover_num = 0;
+	}
+
+	if (argc == 1) {
+		if (art_ptr < 0 || art_ptr >= num_arts) {
+			printf("%d No article is currently selected.\r\n",
+				ERR_NOCRNT);
+			(void) fflush(stdout);
+			return;
+		}
+		first = last = art_array[art_ptr];
+	} else {
+		p = index(argv[1], '-');
+		if (p == NULL)
+			first = last = atoi(argv[1]);
+		else {
+			*p++ = '\0';
+			first = atoi(argv[1]);
+			last = atoi(p);
+		}
+		if (first < 1)
+		    first = 1;
+		if (last > art_array[num_arts-1])
+		    last = art_array[num_arts-1];
+	}
+	/* Return the desired data.  This is written carefully to avoid
+	 * over-long lines. */
+	printf("%d overview data follows\r\n", OK_OVER);
+	if (first < xover_num || !xover_num) {
+		fseek(fp, 0L, 0);
+		xover_num = 0;
+	}
+	if (xover_num) {
+		artnum = xover_num;
+		xover_num = 0;
+	} else
+		fscanf(fp, "%d", &artnum);
+	artp = 0;
+	while (!feof(fp)) {
+		if (artnum > last) {
+			xover_num = artnum;
+			break;
+		}
+		while (art_array[artp] < artnum)
+		    artp++;
+		if (artnum >= first && artnum == art_array[artp]) {
+			printf("%d", artnum);
+			while ((c = getc(fp)) != EOF && c != '\n')
+				putchar(c);
+			printf("\r\n");
+		} else
+			while ((c = getc(fp)) != EOF && c != '\n')
+				continue;
+		fscanf(fp, "%d", &artnum);
+	}
+	printf(".\r\n");
+	(void) fflush(stdout);
+}
+
+close_xover()
+{
+	if (xover_fp) {
+		fclose(xover_fp);
+		xover_fp = NULL;
+	}
+}
+#endif
+
+#ifdef LISTGROUP
+/*
+ * LISTGROUP [group]
+ *
+ * Lists all article numbers (filenames) in the given group. Used by
+ * newsreaders such as nn and trn for fast validation of a database.
+ * If a group name is given it becomes the current group.
+ *
+ * This command is an extention, and not included in RFC 977.
+ */
+
+listgroup(argc, argv)
+	int		argc;
+	char		*argv[];
+{
+	register int i;
+
+	if (argc == 2) {
+		ingroup = 0;
+		/* This will output a success or failure message */
+		group(argc, argv);
+		if (!ingroup) {
+			return;
+		}
+	} else if (argc > 2) {
+		printf("%d Usage: LISTGROUP [newsgroup].\r\n", ERR_CMDSYN);
+		(void) fflush(stdout);
+		return;
+	} else if (!ingroup) {
+		printf("%d You are not currently in a newsgroup.\r\n",
+			ERR_NCING);
+		(void) fflush(stdout);
+		return;
+	} else if (!canread) {
+		printf("%d You only have permission to transfer, sorry.\r\n",
+			ERR_ACCESS);
+		(void) fflush(stdout);
+		return;
+	} else {
+		/* output a success message when no group name is given */
+		printf("%d %d %d %d (current group)\r\n",
+			OK_GROUP,
+			num_arts,
+			(num_arts > 0 ? art_array[0] : 0),
+			(num_arts > 0 ? art_array[num_arts-1] : 0));
+	}
+
+#ifdef LOG
+	syslog(LOG_INFO, "%s listgroup", hostname);
+#endif
+	for (i = 0; i < num_arts; i++) {
+		printf("%d\r\n", art_array[i]);
+	}
+	putchar('.');
+	putchar('\r');
+	putchar('\n');
+	(void) fflush(stdout);
+}
+
+#endif /* LISTGROUP */
Index:server/help.c
@@ -21,8 +21,22 @@
 	printf("NEXT        POST         QUIT\r\n");
 	printf("STAT        NEWGROUPS    HELP\r\n");
 	printf("IHAVE       NEWNEWS      SLAVE\r\n");
-	printf("\r\nAdditionally, the following extention is supported:\r\n\r\n");
+	printf("DATE\r\n");
+#if defined(XHDR) || defined(XTHREAD) || defined(LISTGROUP) || defined(XOVER)
+	printf("\r\nAdditionally, the following extension(s) are supported:\r\n\r\n");
+# ifdef LISTGROUP
+	printf("LISTGROUP   Retrieve a list of valid article-numbers.\r\n");
+# endif
+# ifdef	XHDR
 	printf("XHDR        Retrieve a single header line from a range of articles.\r\n");
+# endif
+# ifdef	XOVER
+	printf("XOVER       Return news overview data\r\n");
+# endif
+# ifdef	XTHREAD
+	printf("XTHREAD     Retrieve trn thread file for the current group.\r\n");
+# endif
+#endif
 	printf("\r\n");
 	printf("Bugs to Stan Barber (Internet: nntp@tmc.edu; UUCP: ...!bcm!nntp)\r\n");
 	printf(".\r\n");
Index:server/misc.c
@@ -100,7 +100,7 @@
 	int		lookup;
 {
 	char		line[MAXBUFLEN];
-	char		*tmp;
+	char		*start, *end;
 	register char	*cp;
 	long		ltmp;
 	static char	path[MAXPATHLEN];
@@ -118,6 +118,7 @@
 #endif USGHIST
 	static FILE	*hfp = NULL;	/* history file, text version */
 
+	group_artnum = 0;
 #ifdef CNEWS
 	cp = rindex(msg_id,'@');	/* look for @ in message id */
 	if( cp != NULL)
@@ -234,17 +235,30 @@
 			ltmp, msg_id);
 #endif SYSLOG
 	if (cp == NULL) return(NULL); /* this article has expired */
-	tmp = cp+1;
 
-	if ((cp = index(tmp, ' ')) != NULL)
+	if (group_name) {
+		end = cp;
+		do {
+			if ((end = index(start = end+1, ' ')) != NULL)
+				*end = '\0';
+
+			if ((cp = index(start, '/')) != NULL) {
+				*cp = '\0';
+				if (!strcmp(start, group_name))
+					group_artnum = atol(cp+1);
+				*cp = '/';
+			}
+		} while (!group_artnum && end != NULL);
+	}
+	else if ((cp = index(start = cp+1, ' ')) != NULL)
 		*cp = '\0';
-	
-	while ((cp = index(tmp, '.')) != NULL)
+
+	while ((cp = index(start, '.')) != NULL)
 		*cp = '/';
 
 	(void) strcpy(path, spooldir);
 	(void) strcat(path, "/");
-	(void) strcat(path, tmp);
+	(void) strcat(path, start);
 #ifdef USGHIST
 	(void) fclose(hfp);
 #endif
@@ -481,9 +495,10 @@
 
 close_crnt()
 {
-	if (art_fp != NULL)
+	if (art_fp != NULL) {
 		(void) fclose(art_fp);
-	art_fp = NULL;
+		art_fp = NULL;
+	}
 }
 
 
Index:server/newgroups.c
@@ -23,6 +23,7 @@
 	int		i;
 	long		date;
 	register FILE	*date_fp;
+	char		*reqlist[2];
 
 	if (argc < 3) {
 printf("%d Usage: NEWGROUPS yymmdd hhmmss [\"GMT\"] [<distributions>].\r\n",
@@ -96,6 +97,22 @@
 #endif
 
 		if (distcount == 0) {
+#ifdef ACTIVE_TIMES_FILE
+			reqlist[0] = line;
+#else
+			reqlist[0] = cp + 1;
+#endif
+			reqlist[1] = NULL;
+
+			if (ngpermcount) {
+			    if (ngmatch(s1strneql, ALLBUT,
+			        ngpermlist, ngpermcount, reqlist, 1) == 0) {
+			         continue;
+			    }
+			} else if (ALLBUT == 0) {
+			    continue;
+			}
+
 #ifdef ACTIVE_TIMES_FILE
 			putline(line);
 #else
Index:server/serve.c
@@ -24,9 +24,9 @@
 #include "timer.h"
 #endif
 
-extern	int	ahbs(), group(), help(), ihave();
+extern	int	ahbs(), dodate(), group(), help(), ihave();
 extern	int	list(), newgroups(), newnews(), nextlast(), post();
-extern	int	slave(), stat(), xhdr();
+extern	int	slave(), stat(), listgroup(), xhdr(), doxover(), xthread();
 
 extern int errno;
 
@@ -33,6 +33,9 @@
 #ifdef AUTH
 extern	int	doauth();
 #endif AUTH
+#ifdef XOVER
+extern	int	doxover();
+#endif
 
 static struct cmdent {
 	char	*cmd_name;
@@ -46,6 +49,7 @@
 #endif AUTH
 	"article",	0,	ahbs,
 	"body",		0,	ahbs,
+	"date",		0,	dodate,
 	"group",	0,	group,
 	"head",		0,	ahbs,
 	"help",		0,	help,
@@ -52,6 +56,9 @@
 	"ihave",	1,	ihave,
 	"last",		0,	nextlast,
 	"list",		0,	list,
+#ifdef LISTGROUP
+	"listgroup",	0,	listgroup,
+#endif LISTGROUP
 	"newgroups",	0,	newgroups,
 	"newnews",	0,	newnews,
 	"next",		0,	nextlast,
@@ -61,6 +68,12 @@
 #ifdef XHDR
 	"xhdr",		0,	xhdr,
 #endif XHDR
+#ifdef XOVER
+	"xover",	0,	doxover,
+#endif
+#ifdef XTHREAD
+	"xthread",	0,	xthread,
+#endif
 };
 #define NUMCMDS (sizeof(cmdtbl) / sizeof(struct cmdent))
 
Index:server/time.c
@@ -14,6 +14,28 @@
 #include <sys/time.h>
 #endif not USG
 
+dodate(ac, av)
+	int	ac;
+	char	*av[];
+{
+	struct tm	*gmt;
+#ifdef USG
+	time_t		now;
+
+	(void) time(&now);
+	gmt = gmtime(&now);
+#else /* not USG */
+	struct timeval	now;
+
+	(void) gettimeofday(&now, (struct timezone *)NULL);
+	gmt = gmtime(&now.tv_sec);
+#endif /* not USG */
+	printf("%d %04.4d%02.2d%02.2d%02.2d%02.2d%02.2d\r\n", INF_DATE,
+		gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+		gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
+	(void) fflush(stdout);
+}
+
 /*
  * dtol -- convert date to long integer.  This is not implicitly
  * local time, or any other kind of time, for that matter.  If you
Index:server/xthread.c
@@ -0,0 +1,149 @@
+/* This file (and only this file - not the entire nntp distribution) is
+ * hereby placed in the public domain.  Use it as you see fit, but if you
+ * manage to find some wonderful use for this code elsewhere, it would be
+ * nice to receive mention for it.
+ *
+ * - Tim Iverson
+ *   iverson@xstor.com -/- uunet!xstor!iverson
+ *   3/28/91
+ *   modified by Wayne Davison (davison@borland.com) to work with trn 2.0
+ *   10/6/91
+ */
+
+#include "common.h"
+
+#ifdef XTHREAD
+
+# ifdef __GNUC__
+#  define alloca __builtin_alloca
+# endif
+
+char *thread_name();
+
+/* Usage: XTHREAD [DBINIT|THREAD]
+ *
+ * DBINIT	dump the contents of the db.init file to stdout
+ * THREAD	dump the contents of the thread file for the current
+ *		newsgroup to stdout (default if no argument).
+ *
+ * N.B. These two files are not ascii and no attempt is made at converting
+ *	native byte size to any type of standard whatsoever.  This leaves it
+ *	up to the receiver (i.e. trn) to translate the data based on db.init.
+ *
+ * This command is not documented in rfc977.
+ */
+
+void
+xthread(argc, argv)
+int	argc;
+char	*argv[];
+{
+	register FILE	*fp;
+	struct stat	s;
+	char		*buf, *file, *what;
+
+	/* can't transfer threads, only read 'em */
+	if (!canread)
+	{
+		printf("%d You only have permission to transfer, sorry.\r\n",
+			ERR_ACCESS);
+		(void) fflush(stdout);
+		return;
+	}
+
+	/* "parse" the argument */
+	if (argc == 2 && !strcasecmp(argv[1], "dbinit"))
+	{
+		file = thread_name("*******");
+		what = "db.init";
+		strcpy(index(file, '*'), what);
+	}
+	else if (argc == 1 || argc == 2 && !strcasecmp(argv[1], "thread"))
+	{
+		if (!threadfile)
+		{
+			printf("%d You are not currently in a newsgroup.\r\n",
+				ERR_NCING);
+			(void) fflush(stdout);
+			return;
+		}
+		file = threadfile;
+		what = "thread";
+	}
+	else
+	{
+		printf("%d Usage: XTHREAD [DBINIT|THREAD]\r\n", ERR_CMDSYN);
+		(void) fflush(stdout);
+		return;
+	}
+
+	/* try to open the file to be transfered */
+	if (!(fp = fopen(file, "r")))
+	{
+		printf("%d %s file is not available.\r\n", ERR_FAULT, what);
+		(void) fflush(stdout);
+#ifdef SYSLOG
+		if (!strcmp(what, "db.init"))
+		    syslog(LOG_ERR, "xthread: fopen %s: %m", file);
+#endif
+		return;
+	}
+
+	/* tell 'em how much binary data is coming down the pike */
+	fstat(fileno(fp), &s);
+	printf("%d %u bytes of %s file follows verbatim (binary!)\r\n",
+		OK_BIN, s.st_size, what);
+
+	/* copy the file verbatim to stdout */
+#ifdef __GNUC__
+	if (buf = alloca(s.st_size))
+	{
+		/* ah-so! got lotsa memoree */
+		read(fileno(fp), buf, s.st_size);
+		fwrite(buf, s.st_size, 1, stdout);
+	}
+	else
+#endif
+	{
+		int		bytes;
+		char		buf[BUFSIZ];
+
+		while (bytes = fread(buf, 1, sizeof buf, fp))
+			fwrite(buf, bytes, 1, stdout);
+	}
+
+	fputs("\r\n.\r\n", stdout);
+	fflush(stdout);
+	fclose(fp);
+}
+
+/* Change a newsgroup name into the name of the thread data file.  We
+** subsitute any '.'s in the group name into '/'s (unless LONG_THREAD_NAMES
+** is defined), prepend the path, and append the '/.thread' (or '.th') on to
+** the end.
+*/
+char *
+thread_name(group)
+char *group;
+{
+	static char name_buff[MAXPATHLEN];
+#ifdef LONG_THREAD_NAMES
+	char group_buff[512];
+	register char *ptr;
+
+	strcpy(group_buff, group);
+	ptr = group = group_buff;
+	while ((ptr = index(ptr, '/'))) {
+		*ptr = '.';
+	}
+#endif
+#ifdef SUFFIX
+	sprintf(name_buff, "%s/%s%s", threaddir, group, SUFFIX);
+#else
+	sprintf(name_buff, "%s/%s", threaddir, group);
+#endif
+
+	return name_buff;
+}
+
+#endif /* not XTHREAD */
---8<------8<------8<------8<---cut here--->8------>8------>8------>8---